Skip to content

Commit 29ffbe9

Browse files
committed
feat(web): redesign smartquote website
- Add hero section with decorative floating quote marks - Add install command with copy button and GitHub link - Add smart quotes explanation with comparison table - Add usage examples for Vercel AI SDK, TransformStream, AsyncIterable, and ESLint - Implement warm typography-focused design with CSS variables - Add responsive styles for mobile devices
1 parent aa83a3c commit 29ffbe9

8 files changed

Lines changed: 709 additions & 41 deletions

File tree

apps/web/eslint.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { plugin as smartQuotesPlugin } from 'smartquote/eslint';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default [
5+
...tseslint.configs.recommended,
6+
{
7+
files: ['**/*.tsx', '**/*.jsx'],
8+
plugins: {
9+
smartquote: smartQuotesPlugin,
10+
},
11+
rules: {
12+
'smartquote/smart-quotes': 'error',
13+
},
14+
},
15+
];

apps/web/package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"dev": "next dev",
7+
"dev": "next dev --turbopack",
88
"build": "next build",
9-
"start": "next start"
9+
"start": "next start",
10+
"lint": "eslint src/"
1011
},
1112
"dependencies": {
1213
"next": "^15.3.6",
1314
"react": "^19.1.0",
1415
"react-dom": "^19.1.0",
15-
"smartquote": "^0.1.0"
16+
"smartquote": "workspace:*"
1617
},
1718
"devDependencies": {
1819
"@types/node": "^22.0.0",
1920
"@types/react": "^19.1.8",
2021
"@types/react-dom": "^19.1.6",
21-
"typescript": "^5.9.3"
22+
"eslint": "^9.0.0",
23+
"typescript": "^5.9.3",
24+
"typescript-eslint": "^8.0.0"
2225
}
2326
}

apps/web/src/app/globals.css

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/* ═══════════════════════════════════════════════════════════════════════════
2+
smartquote - Design System
3+
Typography-focused + Playful
4+
═══════════════════════════════════════════════════════════════════════════ */
5+
6+
/*
7+
Josh's Custom CSS Reset
8+
https://www.joshwcomeau.com/css/custom-css-reset/
9+
*/
10+
*,
11+
*::before,
12+
*::after {
13+
box-sizing: border-box;
14+
}
15+
16+
*:not(dialog) {
17+
margin: 0;
18+
}
19+
20+
@media (prefers-reduced-motion: no-preference) {
21+
html {
22+
interpolate-size: allow-keywords;
23+
}
24+
}
25+
26+
img,
27+
picture,
28+
video,
29+
canvas,
30+
svg {
31+
display: block;
32+
max-width: 100%;
33+
}
34+
35+
input,
36+
button,
37+
textarea,
38+
select {
39+
font: inherit;
40+
}
41+
42+
p,
43+
h1,
44+
h2,
45+
h3,
46+
h4,
47+
h5,
48+
h6 {
49+
overflow-wrap: break-word;
50+
}
51+
52+
p {
53+
text-wrap: pretty;
54+
}
55+
56+
h1,
57+
h2,
58+
h3,
59+
h4,
60+
h5,
61+
h6 {
62+
text-wrap: balance;
63+
}
64+
65+
#root,
66+
#__next {
67+
isolation: isolate;
68+
}
69+
70+
/* Design Tokens */
71+
:root {
72+
/* Colors - Warm, ink-inspired palette */
73+
--color-ink: #1a1a1a;
74+
--color-ink-soft: #3d3d3d;
75+
--color-ink-muted: #6b6b6b;
76+
--color-paper: #faf8f5;
77+
--color-paper-dark: #f0ede8;
78+
--color-accent: #c45d4a; /* Warm terracotta */
79+
--color-accent-hover: #a84d3d;
80+
81+
/* Typography */
82+
--font-display: 'Fraunces', Georgia, serif;
83+
--font-body: 'Source Serif 4', Georgia, serif;
84+
--font-mono: 'IBM Plex Mono', 'Fira Code', monospace;
85+
86+
/* Font sizes - fluid typography */
87+
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
88+
--text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
89+
--text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
90+
--text-lg: clamp(1.125rem, 1rem + 0.6vw, 1.375rem);
91+
--text-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
92+
--text-2xl: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
93+
--text-hero: clamp(3rem, 2rem + 5vw, 6rem);
94+
95+
/* Spacing */
96+
--space-xs: 0.5rem;
97+
--space-sm: 1rem;
98+
--space-md: 1.5rem;
99+
--space-lg: 2.5rem;
100+
--space-xl: 4rem;
101+
--space-2xl: 6rem;
102+
103+
/* Layout */
104+
--max-width: 48rem;
105+
106+
/* Transitions */
107+
--transition-fast: 150ms ease;
108+
--transition-base: 250ms ease;
109+
--transition-slow: 400ms ease;
110+
111+
/* Shadows */
112+
--shadow-sm: 0 1px 2px rgba(26, 26, 26, 0.05);
113+
--shadow-md: 0 4px 12px rgba(26, 26, 26, 0.08);
114+
}
115+
116+
/* Base styles */
117+
html {
118+
font-size: 16px;
119+
}
120+
121+
body {
122+
font-family: var(--font-body);
123+
font-size: var(--text-base);
124+
line-height: 1.5;
125+
-webkit-font-smoothing: antialiased;
126+
color: var(--color-ink);
127+
background-color: var(--color-paper);
128+
min-height: 100vh;
129+
}
130+
131+
/* Selection styling */
132+
::selection {
133+
background-color: var(--color-accent);
134+
color: var(--color-paper);
135+
}
136+
137+
/* Focus styles */
138+
:focus-visible {
139+
outline: 2px solid var(--color-accent);
140+
outline-offset: 2px;
141+
}
142+
143+
/* Links */
144+
a {
145+
color: var(--color-accent);
146+
text-decoration: none;
147+
transition: color var(--transition-fast);
148+
}
149+
150+
a:hover {
151+
color: var(--color-accent-hover);
152+
}
153+
154+
/* Code */
155+
code {
156+
font-family: var(--font-mono);
157+
font-size: 0.9em;
158+
}
159+
160+
/* Keyframe Animations */
161+
@keyframes float {
162+
0%, 100% {
163+
transform: translateY(0) rotate(0deg);
164+
}
165+
50% {
166+
transform: translateY(-8px) rotate(2deg);
167+
}
168+
}
169+
170+
@keyframes float-reverse {
171+
0%, 100% {
172+
transform: translateY(0) rotate(0deg);
173+
}
174+
50% {
175+
transform: translateY(-8px) rotate(-2deg);
176+
}
177+
}
178+
179+
@keyframes fade-in {
180+
from {
181+
opacity: 0;
182+
transform: translateY(10px);
183+
}
184+
to {
185+
opacity: 1;
186+
transform: translateY(0);
187+
}
188+
}
189+
190+
@keyframes pulse-subtle {
191+
0%, 100% {
192+
opacity: 1;
193+
}
194+
50% {
195+
opacity: 0.7;
196+
}
197+
}

apps/web/src/app/layout.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
11
import type { Metadata } from 'next';
2+
import { Fraunces, Source_Serif_4, IBM_Plex_Mono } from 'next/font/google';
3+
import './globals.css';
4+
5+
const fraunces = Fraunces({
6+
subsets: ['latin'],
7+
display: 'swap',
8+
variable: '--font-display',
9+
weight: ['400', '500', '600', '700'],
10+
});
11+
12+
const sourceSerif = Source_Serif_4({
13+
subsets: ['latin'],
14+
display: 'swap',
15+
variable: '--font-body',
16+
weight: ['400', '500', '600'],
17+
});
18+
19+
const ibmPlexMono = IBM_Plex_Mono({
20+
subsets: ['latin'],
21+
display: 'swap',
22+
variable: '--font-mono',
23+
weight: ['400', '500'],
24+
});
225

326
export const metadata: Metadata = {
4-
title: 'Smart Quotes',
5-
description: 'Smart quote conversion utilities for typographically correct quotes',
27+
title: 'smartquote',
28+
description:
29+
'Smart quote conversion utilities for typographically correct quotes. ESLint plugin, streaming API, and Vercel AI SDK integration.',
30+
keywords: [
31+
'smart quotes',
32+
'typography',
33+
'eslint',
34+
'quotes',
35+
'curly quotes',
36+
'typographic quotes',
37+
],
38+
authors: [{ name: 'Andy Bons' }],
39+
openGraph: {
40+
title: 'smartquote',
41+
description: 'Typographically correct quotes, everywhere.',
42+
type: 'website',
43+
},
644
};
745

846
export default function RootLayout({
@@ -11,7 +49,10 @@ export default function RootLayout({
1149
children: React.ReactNode;
1250
}) {
1351
return (
14-
<html lang="en">
52+
<html
53+
lang="en"
54+
className={`${fraunces.variable} ${sourceSerif.variable} ${ibmPlexMono.variable}`}
55+
>
1556
<body>{children}</body>
1657
</html>
1758
);

0 commit comments

Comments
 (0)