Repository navigation
Expand file tree
/
Copy pathglobals.css
More file actions
462 lines (449 loc) · 13.2 KB
/
Copy pathglobals.css
File metadata and controls
462 lines (449 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-heading: var(--font-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-link: var(--link);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--radius-xl: 0;
--radius-2xl: 0;
--radius-3xl: 0;
--radius-4xl: 0;
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(0.985 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.145 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.96 0 0);
--secondary-foreground: oklch(0.145 0 0);
--muted: oklch(0.96 0 0);
--muted-foreground: oklch(0.45 0 0);
--accent: oklch(0.96 0 0);
--accent-foreground: oklch(0.145 0 0);
--destructive: oklch(0.577 0.245 27.325);
/* Inline text links — PALETTE.slate. The one place chrome gets colour:
links must read as links (AA contrast on --background). */
--link: #4d7691;
--border: oklch(0.145 0 0);
--input: oklch(0.8 0 0);
--ring: oklch(0.145 0 0);
--chart-1: oklch(0.145 0 0);
--chart-2: oklch(0.4 0 0);
--chart-3: oklch(0.55 0 0);
--chart-4: oklch(0.7 0 0);
--chart-5: oklch(0.85 0 0);
--radius: 0;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.145 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.96 0 0);
--sidebar-accent-foreground: oklch(0.145 0 0);
--sidebar-border: oklch(0.145 0 0);
--sidebar-ring: oklch(0.145 0 0);
}
.dark {
--background: oklch(0.1 0 0);
--foreground: oklch(0.93 0 0);
--card: oklch(0.15 0 0);
--card-foreground: oklch(0.93 0 0);
--popover: oklch(0.15 0 0);
--popover-foreground: oklch(0.93 0 0);
--primary: oklch(0.93 0 0);
--primary-foreground: oklch(0.1 0 0);
--secondary: oklch(0.2 0 0);
--secondary-foreground: oklch(0.93 0 0);
--muted: oklch(0.2 0 0);
--muted-foreground: oklch(0.6 0 0);
--accent: oklch(0.2 0 0);
--accent-foreground: oklch(0.93 0 0);
--destructive: oklch(0.704 0.191 22.216);
/* PALETTE.dustyblue — the light-mode slate is too dim on a dark background. */
--link: #a6c0cd;
--border: oklch(0.93 0 0);
--input: oklch(0.3 0 0);
--ring: oklch(0.93 0 0);
--chart-1: oklch(0.93 0 0);
--chart-2: oklch(0.7 0 0);
--chart-3: oklch(0.55 0 0);
--chart-4: oklch(0.4 0 0);
--chart-5: oklch(0.25 0 0);
--sidebar: oklch(0.15 0 0);
--sidebar-foreground: oklch(0.93 0 0);
--sidebar-primary: oklch(0.93 0 0);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.2 0 0);
--sidebar-accent-foreground: oklch(0.93 0 0);
--sidebar-border: oklch(0.93 0 0);
--sidebar-ring: oklch(0.93 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
/* App-shell height. The shell is a flex column (top bar / scrolling main /
bottom tab bar); `main` scrolls internally, so the shell needs a DEFINITE
height. Plain `height: 100%` mis-sizes on iOS standalone PWAs with
viewport-fit=cover — the flex column overshoots and the bottom nav drifts,
letting content sit under the tab bar. The dynamic viewport unit tracks the
real standalone viewport; `100%` is the fallback for engines without `dvh`
(desktop is unaffected — dvh == vh == 100% there). Keep these on the
elements, NOT via `.h-full` utilities: a utility class would out-specify
this rule and reintroduce `height: 100%`. */
html {
@apply font-sans;
-webkit-tap-highlight-color: transparent;
height: 100%;
height: 100dvh;
}
body {
@apply bg-background text-foreground;
height: 100%;
height: 100dvh;
}
/* Brand text selection — sage (PALETTE.sage, lib/colors.ts) instead of the
OS default blue. Espresso text keeps contrast on the light-green wash in
both light and dark mode. */
::selection {
background: #8a9b66;
color: #1d1a17;
}
}
/* Marketing hero bento — one-shot reveal on mount. The card sits above the fold
(zero-scroll hero), so a plain mount-time animation is enough; no scroll
trigger. We animate `transform` (not height/width/color) to stay GPU-friendly
and leave the inline %-sizing + PALETTE colours untouched. `both` fill-mode
holds the start frame before the animation begins, so nothing flashes at full
size first. Stagger is supplied per-element via inline animation-delay. */
@keyframes pare-rise {
from {
transform: scaleY(0);
}
to {
transform: scaleY(1);
}
}
@keyframes pare-grow {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.pare-rise {
transform-origin: bottom;
animation: pare-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.pare-grow {
transform-origin: left;
animation: pare-grow 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Footer feature ticker — an infinite horizontal marquee. The track holds two
identical groups; translating it -50% swaps the second group into the first's
place for a seamless loop (each group must be ≥ viewport-wide, so the JSX
repeats the claims within a group). Hover pauses; reduced-motion stops it
(the first group sits static, still filling the strip). */
@keyframes pare-marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.pare-marquee {
animation: pare-marquee 50s linear infinite;
}
.pare-marquee:hover {
animation-play-state: paused;
}
/* Success-surface reveal — the upload RESULTS block and its opt-in cards mount
the instant a statement finishes parsing (the app's one rare, high-emotion
moment). A mount-time keyframe fires once on appear (same idiom as the landing
bento): fade + a small lift into place. Stagger is supplied per-element via
inline animation-delay. `both` fill-mode holds the start frame so nothing
flashes at final position first. transform + opacity only (GPU-friendly). */
@keyframes pare-reveal {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.pare-reveal {
animation: pare-reveal 0.3s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Confirmation pop — the copy-button checkmark scaling in once the copy lands.
Feedback for a rare setup action; center-origin scale from a visible size
(never scale(0)). A small overshoot past 1 gives the tiny (12px) icon enough
travel to actually register — a plain 0.8→1 fade is below the perception
threshold at this size and reads as no animation at all. */
@keyframes pare-pop {
0% {
opacity: 0;
transform: scale(0.5);
}
60% {
opacity: 1;
transform: scale(1.12);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.pare-pop {
animation: pare-pop 0.18s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@media (prefers-reduced-motion: reduce) {
.pare-rise,
.pare-grow {
animation: none;
}
.pare-marquee {
animation: none;
}
/* Reduced motion: keep a gentle fade (aids comprehension) but drop the
movement/scale. */
.pare-reveal,
.pare-pop {
animation: pare-fade-in 0.2s ease both;
}
}
@keyframes pare-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Blog article body. No Tailwind typography plugin here (brutalist theme reserves
colour for data, not chrome), so the long-form prose is styled by hand against
the same tokens the rest of the site uses: mono uppercase headings, Geist body,
1px borders, zero radius. `scroll-margin-top` keeps in-page TOC anchors clear of
the sticky header. */
.prose-pare {
color: var(--foreground);
font-size: 0.9375rem;
line-height: 1.75;
}
.prose-pare > * + * {
margin-top: 1.15rem;
}
.prose-pare h2 {
font-family: var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 700;
font-size: 1rem;
margin-top: 2.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
scroll-margin-top: 5rem;
}
.prose-pare h3 {
font-family: var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 700;
font-size: 0.8125rem;
color: var(--muted-foreground);
margin-top: 1.75rem;
scroll-margin-top: 5rem;
}
/* Hover-reveal heading anchor links (injected in lib/blog.ts renderMarkdown).
Server-rendered so they're crawlable + shareable; hidden until hover to keep
the brutalist headings clean. */
.prose-pare h2,
.prose-pare h3 {
position: relative;
}
.prose-pare a.heading-anchor {
position: absolute;
left: -1.15em;
color: var(--muted-foreground);
text-decoration: none;
font-weight: 400;
opacity: 0;
transition: opacity 0.15s ease;
}
.prose-pare h2:hover a.heading-anchor,
.prose-pare h3:hover a.heading-anchor,
.prose-pare a.heading-anchor:focus-visible {
opacity: 0.55;
}
@media (max-width: 640px) {
.prose-pare a.heading-anchor {
display: none;
}
}
.prose-pare p {
color: color-mix(in oklch, var(--foreground) 90%, transparent);
}
.prose-pare a,
.link {
color: var(--link);
text-decoration: underline;
text-underline-offset: 2px;
}
.prose-pare a:hover,
.link:hover {
color: color-mix(in oklch, var(--link) 65%, var(--foreground));
}
.prose-pare strong {
font-weight: 700;
}
.prose-pare ul,
.prose-pare ol {
padding-left: 1.25rem;
color: color-mix(in oklch, var(--foreground) 90%, transparent);
}
.prose-pare ul {
list-style: disc;
}
.prose-pare ol {
list-style: decimal;
}
.prose-pare li + li {
margin-top: 0.4rem;
}
.prose-pare li > ul,
.prose-pare li > ol {
margin-top: 0.4rem;
}
.prose-pare blockquote {
border-left: 2px solid var(--border);
padding-left: 1rem;
color: var(--muted-foreground);
font-style: italic;
}
.prose-pare code {
font-family: var(--font-mono);
font-size: 0.8125em;
background: var(--muted);
padding: 0.1em 0.35em;
}
.prose-pare pre {
font-family: var(--font-mono);
font-size: 0.8125rem;
background: var(--card);
border: 1px solid var(--border);
padding: 1rem;
overflow-x: auto;
}
.prose-pare pre code {
background: transparent;
padding: 0;
}
.prose-pare hr {
border: 0;
border-top: 1px solid var(--border);
margin: 2rem 0;
}
/* Comparison tables. Authored as GFM tables in content/blog/*.md — the
answer-first surface that AI answer engines lift verbatim, so the markup
stays a real <table> rather than a styled grid. The wrapper scrolls on its
own so a wide table never makes the page body scroll sideways on a phone
(Pare is an installed PWA; phone layout is a first-class target). */
.prose-pare .table-scroll {
overflow-x: auto;
border: 1px solid var(--border);
background: var(--card);
}
.prose-pare table {
border-collapse: collapse;
width: 100%;
min-width: 30rem;
font-size: 0.8125rem;
font-variant-numeric: tabular-nums;
}
.prose-pare thead th {
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted-foreground);
text-align: left;
padding: 0.75rem 0.875rem;
border-bottom: 1px solid var(--border);
white-space: nowrap;
vertical-align: bottom;
}
.prose-pare tbody td {
padding: 0.75rem 0.875rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
line-height: 1.5;
color: color-mix(in oklch, var(--foreground) 90%, transparent);
}
.prose-pare tbody tr:last-child td {
border-bottom: 0;
}
/* First column is the row label — it carries the comparison axis. */
.prose-pare tbody td:first-child {
font-weight: 500;
color: var(--foreground);
}
/* On a phone a comparison table still has to scroll, but the less it has to
scroll the better — the point of the table is seeing the columns together.
Tighten the cells and let the label column wrap harder before giving up. */
@media (max-width: 40rem) {
.prose-pare table {
min-width: 26rem;
font-size: 0.75rem;
}
.prose-pare thead th,
.prose-pare tbody td {
padding: 0.5rem 0.5rem;
}
.prose-pare tbody td:first-child {
min-width: 6rem;
}
}