From 71d9a14fe7d8c3763b7a881c0c5e534b5dd133ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Jul 2026 02:28:43 +0000 Subject: [PATCH] Make CodeTidyup cooler with a studio redesign Refresh the visual system with Syne/Source Sans typography, a misty atmospheric background, brand-first hero, selectable export themes, and subtle entrance motion while keeping format/compare/export intact. Co-authored-by: Jack --- app/globals.css | 144 +++++++++++++++++++++++++-- app/layout.tsx | 16 ++- app/page.tsx | 188 ++++++++++++++++++++++++----------- components/icons.tsx | 2 +- components/ui/Button.tsx | 20 ++-- components/ui/Popover.tsx | 2 +- components/ui/Typography.tsx | 9 +- tailwind.config.ts | 20 ++-- 8 files changed, 315 insertions(+), 86 deletions(-) diff --git a/app/globals.css b/app/globals.css index 174f8cf..a49d0ba 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,15 +3,22 @@ @tailwind utilities; :root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; + --ink: #12151a; + --ink-soft: #2a3038; + --fog: #e8eef2; + --mist: #f3f6f8; + --foam: #ffffff; + --accent: #0d9488; + --accent-deep: #0f766e; + --muted: rgba(18, 21, 26, 0.55); + --line: rgba(18, 21, 26, 0.1); + --grain: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E"); } body { - font-family: 'Inter', sans-serif; - color: rgb(var(--foreground-rgb)); - background: #f4f4f5; + color: var(--ink); + background: var(--mist); + min-height: 100vh; } @layer utilities { @@ -19,3 +26,128 @@ body { text-wrap: balance; } } + +/* Soft studio atmosphere — cool mist, not flat gray */ +.page-atmosphere { + position: relative; + isolation: isolate; + background: + radial-gradient(ellipse 90% 60% at 10% -10%, rgba(13, 148, 136, 0.14), transparent 55%), + radial-gradient(ellipse 70% 50% at 95% 5%, rgba(14, 116, 144, 0.12), transparent 50%), + radial-gradient(ellipse 60% 40% at 50% 100%, rgba(245, 158, 11, 0.08), transparent 55%), + linear-gradient(180deg, #eef3f6 0%, #f5f7f9 45%, #eef2f4 100%); +} + +.page-atmosphere::before { + content: ''; + pointer-events: none; + position: absolute; + inset: 0; + z-index: -1; + opacity: 0.35; + background-image: var(--grain); + background-size: 180px 180px; + mix-blend-mode: multiply; +} + +@keyframes rise-in { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes canvas-in { + from { + opacity: 0; + transform: translateY(28px) scale(0.985); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes swatch-pop { + from { + opacity: 0; + transform: scale(0.7); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.animate-rise { + animation: rise-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +.animate-rise-delay { + animation: rise-in 0.75s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both; +} + +.animate-rise-delay-2 { + animation: rise-in 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.16s both; +} + +.animate-canvas { + animation: canvas-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.22s both; +} + +.theme-swatch { + animation: swatch-pop 0.45s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +.theme-swatch:nth-child(1) { + animation-delay: 0.28s; +} +.theme-swatch:nth-child(2) { + animation-delay: 0.34s; +} +.theme-swatch:nth-child(3) { + animation-delay: 0.4s; +} +.theme-swatch:nth-child(4) { + animation-delay: 0.46s; +} +.theme-swatch:nth-child(5) { + animation-delay: 0.52s; +} + +/* Code window depth without heavy multi-shadow stacks */ +.code-frame { + box-shadow: + 0 1px 0 rgba(255, 255, 255, 0.35) inset, + 0 18px 40px -24px rgba(18, 21, 26, 0.45); + transition: background 0.45s ease; +} + +.code-chrome { + box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.55); +} + +.cm-editor { + background: transparent !important; +} + +.cm-gutters { + background: transparent !important; + border: none !important; +} + +@media (prefers-reduced-motion: reduce) { + .animate-rise, + .animate-rise-delay, + .animate-rise-delay-2, + .animate-canvas, + .theme-swatch { + animation: none; + opacity: 1; + transform: none; + } +} diff --git a/app/layout.tsx b/app/layout.tsx index 0196714..0162e45 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,9 +1,19 @@ import type { Metadata } from 'next' -import { Inter } from 'next/font/google' +import { Syne, Source_Sans_3 } from 'next/font/google' import './globals.css' import PlausibleProvider from 'next-plausible' -const inter = Inter({ subsets: ['latin'] }) +const display = Syne({ + subsets: ['latin'], + variable: '--font-display', + weight: ['600', '700', '800'] +}) + +const sans = Source_Sans_3({ + subsets: ['latin'], + variable: '--font-sans', + weight: ['400', '500', '600', '700'] +}) const APP_NAME = 'CodeTidyup' const APP_URL = 'https://codetidyup.com' @@ -56,7 +66,7 @@ export default function RootLayout({ enabled={true} /> - {children} + {children} ) } diff --git a/app/page.tsx b/app/page.tsx index 05768f6..48a7df2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -33,21 +33,59 @@ export function EasterEgg() { ) }` +const THEMES = [ + { + id: 'tide', + label: 'Tide', + className: 'bg-gradient-to-br from-teal-300 via-cyan-300 to-sky-400' + }, + { + id: 'ember', + label: 'Ember', + className: 'bg-gradient-to-br from-orange-300 via-amber-300 to-rose-300' + }, + { + id: 'moss', + label: 'Moss', + className: 'bg-gradient-to-br from-lime-300 via-emerald-300 to-teal-400' + }, + { + id: 'ink', + label: 'Ink', + className: 'bg-gradient-to-br from-slate-600 via-slate-700 to-zinc-900' + }, + { + id: 'dawn', + label: 'Dawn', + className: 'bg-gradient-to-br from-rose-200 via-orange-200 to-amber-200' + } +] as const + +type ThemeId = (typeof THEMES)[number]['id'] + export default function Home() { const [code, setCode] = useState(SAMPLE_CODE) const [compareCode, setCompareCode] = useState('') const [compareMode, setCompareMode] = useState(false) + const [themeId, setThemeId] = useState('tide') + const [isFormatting, setIsFormatting] = useState(false) + + const activeTheme = THEMES.find((theme) => theme.id === themeId) ?? THEMES[0] useEffect(() => { handleFormat() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const handleFormat = async () => { + setIsFormatting(true) try { - const formatCode = async (code: string) => { + const formatCode = async (source: string) => { + if (!source.trim()) return source + let formattedCode try { - formattedCode = await prettier.format(code, { + formattedCode = await prettier.format(source, { parser: 'flow', plugins: [flow, esTree, html, ts], trailingComma: 'none', @@ -57,8 +95,8 @@ export default function Home() { bracketSameLine: true, semi: false }) - } catch (flowError) { - formattedCode = await prettier.format(code, { + } catch { + formattedCode = await prettier.format(source, { parser: 'typescript', plugins: [ts, esTree], trailingComma: 'none', @@ -80,6 +118,8 @@ export default function Home() { } catch (error) { alert(`Error formatting code: ${error}`) console.error('Error formatting code:', error) + } finally { + setIsFormatting(false) } } @@ -88,42 +128,48 @@ export default function Home() { if (screenshotDiv) { domtoimage .toPng(screenshotDiv) - .then((dataUrl: any) => { + .then((dataUrl: string) => { const link = document.createElement('a') link.href = dataUrl link.download = 'code_screenshot.png' link.click() }) - .catch((error: any) => { + .catch((error: Error) => { console.error('Error capturing screenshot:', error) }) } } return ( -
-
-
- - Beautiful images of your code +
+
+
+ + CodeTidyup -

- Format and share beautiful images of your code. Type or paste your code below to get started. +

+ Format. Frame. Share. +

+

+ Paste messy code, tidy it instantly, and export a share-ready image.

-
-
-
-
-
-
- - +
+
} options={[ @@ -144,54 +190,82 @@ export default function Home() { } ]} /> +
+ {THEMES.map((theme) => { + const selected = theme.id === themeId + return ( +
-
- -
+
-
-
-
+ +
+
+
-
-
-
+
+
+
-
+
{compareMode && }
-
-
- - - Created by{' '} - - Jack Stevens - {' '} - and open sourced on{' '} - - Github - - {'.'} - + +
+ + + Created by{' '} + + Jack Stevens + {' '} + and open sourced on{' '} + + Github + + {'.'} + +
diff --git a/components/icons.tsx b/components/icons.tsx index 4fe8823..8254f70 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -1,7 +1,7 @@ export function AppIcon({ ...other }) { return ( - + , HTMLButtonElement> { fullWidth?: boolean - variant?: 'default' | 'outlined' | 'text' + variant?: 'default' | 'outlined' | 'text' | 'accent' size?: 'none' | 'small' | 'medium' | 'large' className?: string onClick?: (e: React.MouseEvent) => void @@ -20,20 +20,24 @@ export const Button = ({ disabled, ...other }: PropsWithChildren) => { - const baseClasses = 'font-medium focus:outline-none transition ease-in duration-100 whitespace-nowrap' + const baseClasses = + 'font-medium focus:outline-none transition duration-200 ease-out whitespace-nowrap disabled:opacity-50 disabled:pointer-events-none' const variantClasses = { - default: `bg-black text-white hover:bg-black/80 rounded-full `, - outlined: `rounded-full border border-black hover:bg-black/5`, - text: `rounded-full text-textPrimary hover:text-black/80` + default: 'bg-ink text-white hover:bg-ink/85 rounded-lg', + accent: 'bg-accent text-white hover:bg-accent-deep rounded-lg', + outlined: 'rounded-lg border border-ink/20 bg-white/60 backdrop-blur-sm hover:bg-white hover:border-ink/35', + text: 'rounded-lg text-textPrimary hover:bg-hover' } const sizeClasses = { none: 'text-xs', small: 'text-xs px-3 py-1', - medium: 'text-sm px-3 py-1.5', - large: 'text-base px-5 py-2' + medium: 'text-sm px-3.5 py-2', + large: 'text-base px-5 py-2.5' } - const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}` + const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${ + fullWidth ? 'w-full' : '' + } ${className}` return (
{options.map((option, index) => ( diff --git a/components/ui/Typography.tsx b/components/ui/Typography.tsx index 8b7f1bb..6beea54 100644 --- a/components/ui/Typography.tsx +++ b/components/ui/Typography.tsx @@ -3,7 +3,7 @@ import { type PropsWithChildren } from 'react' interface TypographyProps { color?: 'textPrimary' | 'textSecondary' | 'textDisabled' className?: string - variant?: 'h1' | 'h2' | 'caption' | 'body1' | 'body2' | 'subtitle1' | 'subtitle2' | 'button' + variant?: 'h1' | 'h2' | 'caption' | 'body1' | 'body2' | 'subtitle1' | 'subtitle2' | 'button' | 'brand' } export const Typography = ({ @@ -19,8 +19,9 @@ export const Typography = ({ } const variants = { - h1: 'text-5xl lg:text-7xl font-black', - h2: 'text-4xl lg:text-5xl font-black', + brand: 'font-display text-5xl sm:text-6xl lg:text-7xl font-extrabold tracking-tight', + h1: 'font-display text-5xl lg:text-7xl font-extrabold tracking-tight', + h2: 'font-display text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight', subtitle1: 'text-base font-semibold', subtitle2: 'text-sm font-semibold', body1: 'text-base', @@ -30,6 +31,8 @@ export const Typography = ({ } switch (variant) { + case 'brand': + return

{children}

case 'h1': return

{children}

case 'h2': diff --git a/tailwind.config.ts b/tailwind.config.ts index 67ad978..066c560 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,8 +9,8 @@ const config: Config = { theme: { extend: { fontFamily: { - sans: ['Inter', 'ui-sans-serif', 'system-ui'], - serif: ['ui-serif', 'Georgia'], + display: ['var(--font-display)', 'ui-sans-serif', 'system-ui'], + sans: ['var(--font-sans)', 'ui-sans-serif', 'system-ui'], mono: ['ui-monospace', 'SFMono-Regular'] }, backgroundImage: { @@ -18,11 +18,17 @@ const config: Config = { 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' }, colors: { - textPrimary: 'rgba(0, 0, 0, 0.87)', - textSecondary: 'rgba(0, 0, 0, 0.6)', - textDisabled: 'rgba(0, 0, 0, 0.38)', - divider: 'rgba(0, 0, 0, 0.08)', - hover: 'rgba(0, 0, 0, 0.04)' + ink: '#12151a', + mist: '#f3f6f8', + accent: { + DEFAULT: '#0d9488', + deep: '#0f766e' + }, + textPrimary: 'rgba(18, 21, 26, 0.92)', + textSecondary: 'rgba(18, 21, 26, 0.55)', + textDisabled: 'rgba(18, 21, 26, 0.35)', + divider: 'rgba(18, 21, 26, 0.1)', + hover: 'rgba(18, 21, 26, 0.04)' } } },