Skip to content
696 linesCodeBlameRaw
1@import "tailwindcss" source(".");
2@import "@g1t/theme/tokens.css";
3@import "@g1t/theme/fonts.css";
4
5/* Tailwind's names for the shared g1t tokens. */
6@theme {
7 --font-sans: var(--g1t-font-sans);
8 --font-display: var(--g1t-font-display);
9 --font-mono: var(--g1t-font-mono);
10
11 /* Hanken and Bricolage want less tightening than a text face set close. */
12 --tracking-tight: -0.012em;
13
14 --color-bg: var(--g1t-bg);
15 --color-surface: var(--g1t-surface);
16 --color-raised: var(--g1t-raised);
17 --color-line: var(--g1t-line);
18 --color-line-strong: var(--g1t-line-strong);
19
20 --color-fg: var(--g1t-fg);
21 --color-fg-soft: var(--g1t-fg-soft);
22 --color-muted: var(--g1t-muted);
23 --color-faint: var(--g1t-faint);
24
25 --color-accent: var(--g1t-accent);
26 --color-accent-hover: var(--g1t-accent-hover);
27 --color-accent-dim: var(--g1t-accent-dim);
28
29 --color-success: var(--g1t-success);
30 --color-success-dim: var(--g1t-success-dim);
31
32 --color-info: var(--g1t-info);
33 --color-merged: var(--g1t-merged);
34 --color-warn: var(--g1t-warn);
35 --color-danger: var(--g1t-danger);
36
37 --animate-fade-up: fade-up 0.6s ease-out both;
38 --animate-dash: dash 1.2s linear infinite;
39 /* An agent's face, on hover: a small, quick wiggle (components/agent-avatar.tsx). */
40 --animate-wiggle: wiggle 0.45s ease-in-out;
41
42 /* Menus, popovers and dialogs (components/ui): in quickly, out quicker. */
43 --animate-pop-in: pop-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
44 --animate-pop-out: pop-out 0.1s ease-in forwards;
45 --animate-fade-in: fade-in 0.16s ease-out;
46 --animate-fade-out: fade-out 0.12s ease-in forwards;
47 --animate-dialog-in: dialog-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
48 --animate-dialog-out: dialog-out 0.12s ease-in forwards;
49 /* Sheets (components/ui/sheet.tsx) slide in from their edge. */
50 --animate-sheet-in-right: sheet-in-right 0.24s cubic-bezier(0.16, 1, 0.3, 1);
51 --animate-sheet-out-right: sheet-out-right 0.16s ease-in forwards;
52 --animate-sheet-in-left: sheet-in-left 0.24s cubic-bezier(0.16, 1, 0.3, 1);
53 --animate-sheet-out-left: sheet-out-left 0.16s ease-in forwards;
54}
55
56@keyframes sheet-in-bottom {
57 from {
58 transform: translateY(100%);
59 }
60}
61
62@keyframes sheet-in-right {
63 from {
64 transform: translateX(100%);
65 }
66}
67
68@keyframes sheet-out-right {
69 to {
70 transform: translateX(100%);
71 }
72}
73
74@keyframes sheet-in-left {
75 from {
76 transform: translateX(-100%);
77 }
78}
79
80@keyframes sheet-out-left {
81 to {
82 transform: translateX(-100%);
83 }
84}
85
86@keyframes pop-in {
87 from {
88 opacity: 0;
89 transform: scale(0.96) translateY(-2px);
90 }
91}
92
93@keyframes pop-out {
94 to {
95 opacity: 0;
96 transform: scale(0.96);
97 }
98}
99
100@keyframes fade-in {
101 from {
102 opacity: 0;
103 }
104}
105
106@keyframes fade-out {
107 to {
108 opacity: 0;
109 }
110}
111
112@keyframes dialog-in {
113 from {
114 opacity: 0;
115 transform: translateY(6px) scale(0.97);
116 }
117}
118
119@keyframes dialog-out {
120 to {
121 opacity: 0;
122 transform: translateY(6px) scale(0.97);
123 }
124}
125
126/* Never from nothing: the first paint already shows the words. */
127@keyframes fade-up {
128 from {
129 opacity: 0.4;
130 transform: translateY(8px);
131 }
132}
133
134@keyframes wiggle {
135 25% {
136 transform: rotate(-8deg);
137 }
138 60% {
139 transform: rotate(6deg);
140 }
141}
142
143@keyframes dash {
144 to {
145 stroke-dashoffset: -16;
146 }
147}
148
149html {
150 color-scheme: dark;
151 scroll-behavior: smooth;
152 /* A tap shows the control's own active state, not the browser's grey flash. */
153 -webkit-tap-highlight-color: transparent;
154 /* Phones: no text inflation on rotation. */
155 -webkit-text-size-adjust: 100%;
156 text-size-adjust: 100%;
157}
158
159/*
160 * Phones: a field under 16px makes iOS zoom the page when it is focused, so
161 * every field is at least that; and nothing scrolls the page sideways.
162 */
163/*
164 * The phone's tab bar (components/mobile.tsx): how much of the bottom it
165 * takes, for the shell to keep every page's end clear of it.
166 */
167:root {
168 --tabbar-h: 0px;
169}
170@media (max-width: 767px) {
171 :root {
172 --tabbar-h: calc(3.5rem + env(safe-area-inset-bottom));
173 }
174}
175
176@media (max-width: 767px) {
177 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
178 textarea,
179 select {
180 font-size: max(16px, 1em) !important;
181 }
182 body {
183 overflow-x: clip;
184 }
185}
186
187body {
188 @apply bg-bg font-sans text-fg antialiased;
189}
190
191::selection {
192 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
193}
194
195/* In the base layer, so a component can opt out with a utility. */
196@layer base {
197 :focus-visible {
198 outline: 2px solid var(--color-accent);
199 outline-offset: 2px;
200 }
201
202 /*
203 * A grid's items may shrink below their content. Without this, one long
204 * line that does not wrap (a command, a commit hash, an address) widens
205 * the whole column, and on a phone the page scrolls sideways.
206 */
207 .grid > * {
208 min-width: 0;
209 }
210
211 /*
212 * Headlines are set in Bricolage Grotesque, its optical size following
213 * the font size; small section labels that happen to be headings stay in
214 * the text face. A utility on the element still wins.
215 */
216 :where(h1, h2),
217 :where(h3):is(.text-xl, .text-2xl, .text-3xl, .text-4xl) {
218 font-family: var(--font-display);
219 font-optical-sizing: auto;
220 }
221 :where(h2):is(.text-xs, .text-sm, .text-base) {
222 font-family: var(--font-sans);
223 }
224
225 /* Checkboxes, radios and ranges in g1t's own colour, not the browser's. */
226 :root {
227 accent-color: var(--color-accent);
228 }
229}
230
231@media (prefers-reduced-motion: reduce) {
232 *,
233 *::before,
234 *::after {
235 animation: none !important;
236 scroll-behavior: auto !important;
237 }
238}
239
240/* Rendered markdown: READMEs, briefs and docs. */
241.prose > :first-child {
242 margin-top: 0;
243 padding-top: 0;
244}
245.prose {
246 @apply leading-7 text-fg;
247}
248.prose > * + * {
249 @apply mt-4;
250}
251.prose h1 {
252 @apply mt-0 text-3xl font-semibold tracking-tight;
253}
254.prose h2 {
255 @apply mt-10 border-b border-line pb-2 text-xl font-semibold tracking-tight;
256}
257.prose h3 {
258 @apply mt-8 text-base font-semibold;
259}
260.prose p,
261.prose li {
262 @apply text-[0.9375rem] text-fg/90;
263}
264.prose a {
265 @apply text-accent underline decoration-accent/40 underline-offset-4 hover:decoration-accent;
266}
267.prose ul {
268 @apply list-disc space-y-1.5 pl-6;
269}
270.prose ol {
271 @apply list-decimal space-y-1.5 pl-6;
272}
273.prose code {
274 @apply rounded bg-raised px-1.5 py-0.5 font-mono text-[0.85em];
275}
276.prose pre {
277 @apply overflow-x-auto rounded-lg border border-line bg-surface p-4 font-mono text-sm leading-6;
278}
279.prose pre code {
280 @apply bg-transparent p-0;
281}
282.prose blockquote {
283 @apply border-l-2 border-line-strong pl-4 text-muted;
284}
285.prose table {
286 @apply w-full border-collapse text-sm;
287}
288.prose th {
289 @apply border-b border-line-strong px-3 py-2 text-left font-medium text-muted;
290}
291.prose td {
292 @apply border-b border-line px-3 py-2 align-top;
293}
294.prose hr {
295 @apply border-line;
296}
297.prose h4 {
298 @apply mt-6 text-sm font-semibold;
299}
300.prose li.task-list-item {
301 @apply list-none;
302}
303.prose ul.contains-task-list {
304 @apply pl-1;
305}
306.prose kbd {
307 @apply rounded border border-line-strong bg-raised px-1.5 py-px font-mono text-[0.8em] shadow-[inset_0_-1px_0_var(--color-line-strong)];
308}
309.prose del {
310 @apply text-muted;
311}
312.prose details {
313 @apply rounded-lg border border-line px-4 py-2;
314}
315.prose summary {
316 @apply cursor-pointer font-medium;
317}
318.prose img {
319 @apply inline max-w-full;
320}
321.prose section[data-footnotes] {
322 @apply mt-10 border-t border-line pt-4 text-sm text-muted;
323}
324.prose a[data-footnote-ref] {
325 @apply no-underline;
326}
327
328/* Syntax-highlighted code from Shiki. */
329.shiki {
330 @apply bg-transparent! font-mono text-sm leading-6;
331}
332.shiki code {
333 counter-reset: line;
334 display: block;
335}
336.shiki .line::before {
337 counter-increment: line;
338 content: counter(line);
339 @apply mr-5 inline-block w-10 text-right text-faint select-none;
340}
341
342/*
343 * Artwork (components/art.tsx). Every piece loops on its drawing's
344 * `--dur` and starts at its own `--delay`, so the parts of one drawing
345 * stay in step. Only transforms, opacity and stroke offsets move. The
346 * resting style of each class is the finished picture: that is what
347 * prefers-reduced-motion shows, since the rule above stops every
348 * animation.
349 */
350.art-flow {
351 animation: dash 1.6s linear infinite;
352}
353
354/* Something travelling along a track (pathLength 100). Unseen at rest. */
355.art-packet {
356 opacity: 0;
357 stroke-dasharray: 4 200;
358 stroke-dashoffset: 4;
359 animation: art-travel var(--dur, 3s) cubic-bezier(0.45, 0, 0.35, 1) infinite;
360 animation-delay: var(--delay, 0s);
361}
362@keyframes art-travel {
363 0% {
364 stroke-dashoffset: 4;
365 opacity: 0;
366 }
367 3% {
368 opacity: 1;
369 }
370 32% {
371 opacity: 1;
372 }
373 36%,
374 100% {
375 stroke-dashoffset: -100;
376 opacity: 0;
377 }
378}
379
380/* A face that lights up for most of the loop. Lit at rest. */
381.art-lit {
382 opacity: 0.85;
383 animation: art-lit var(--dur, 8s) ease-in-out infinite both;
384 animation-delay: var(--delay, 0s);
385}
386@keyframes art-lit {
387 0%,
388 4% {
389 opacity: 0;
390 }
391 12%,
392 70% {
393 opacity: 0.9;
394 }
395 88%,
396 100% {
397 opacity: 0;
398 }
399}
400
401/* A short flash, as a check runs over a layer. Unseen at rest. */
402.art-scan {
403 opacity: 0;
404 animation: art-scan var(--dur, 8s) ease-out infinite both;
405 animation-delay: var(--delay, 0s);
406}
407@keyframes art-scan {
408 0% {
409 opacity: 0;
410 }
411 4% {
412 opacity: 1;
413 }
414 16%,
415 100% {
416 opacity: 0;
417 }
418}
419
420/* A line that draws itself, then fades. Drawn at rest. */
421.art-draw {
422 stroke-dasharray: 100;
423 stroke-dashoffset: 0;
424 animation: art-draw var(--dur, 8s) ease-in-out infinite both;
425 animation-delay: var(--delay, 0s);
426}
427@keyframes art-draw {
428 0% {
429 stroke-dashoffset: 100;
430 opacity: 1;
431 }
432 14%,
433 70% {
434 stroke-dashoffset: 0;
435 opacity: 1;
436 }
437 88%,
438 100% {
439 stroke-dashoffset: 0;
440 opacity: 0;
441 }
442}
443
444/* Something that appears, stays, and goes. Shown at rest. */
445.art-pop {
446 animation: art-pop var(--dur, 8s) ease-out infinite both;
447 animation-delay: var(--delay, 0s);
448}
449@keyframes art-pop {
450 0% {
451 opacity: 0;
452 transform: translateY(4px);
453 }
454 6%,
455 72% {
456 opacity: 1;
457 transform: none;
458 }
459 86%,
460 100% {
461 opacity: 0;
462 transform: none;
463 }
464}
465
466/* A layer dropping onto a stack. In place at rest. */
467.art-drop {
468 animation: art-drop var(--dur, 7s) cubic-bezier(0.2, 0.9, 0.3, 1.2) infinite both;
469 animation-delay: var(--delay, 0s);
470}
471@keyframes art-drop {
472 0% {
473 opacity: 0;
474 transform: translateY(-26px);
475 }
476 8%,
477 74% {
478 opacity: 1;
479 transform: none;
480 }
481 88%,
482 100% {
483 opacity: 0;
484 transform: none;
485 }
486}
487
488/* The layer that fails: drops, shakes, and is sent back. */
489.art-eject {
490 animation: art-eject var(--dur, 7s) ease-in-out infinite both;
491 animation-delay: var(--delay, 0s);
492}
493@keyframes art-eject {
494 0% {
495 opacity: 0;
496 transform: translateY(-26px);
497 }
498 8%,
499 30% {
500 opacity: 1;
501 transform: none;
502 }
503 33% {
504 transform: translateX(-3px);
505 }
506 36% {
507 transform: translateX(3px);
508 }
509 39% {
510 transform: translateX(-2px);
511 }
512 42% {
513 opacity: 1;
514 transform: none;
515 }
516 56%,
517 100% {
518 opacity: 0;
519 transform: translate(-40px, -22px);
520 }
521}
522
523/* A layer rising out of the one below it. In place at rest. */
524.art-rise {
525 animation: art-rise var(--dur, 7s) cubic-bezier(0.2, 0.8, 0.3, 1) infinite both;
526 animation-delay: var(--delay, 0s);
527}
528@keyframes art-rise {
529 0% {
530 opacity: 0;
531 transform: translateY(14px);
532 }
533 10%,
534 70% {
535 opacity: 1;
536 transform: none;
537 }
538 86%,
539 100% {
540 opacity: 0;
541 transform: none;
542 }
543}
544
545/* A soft glow breathing around a point. */
546.art-pulse {
547 transform-box: fill-box;
548 transform-origin: center;
549 animation: art-pulse var(--dur, 4s) ease-in-out infinite;
550 animation-delay: var(--delay, 0s);
551}
552@keyframes art-pulse {
553 0%,
554 100% {
555 transform: scale(0.8);
556 opacity: 1;
557 }
558 50% {
559 transform: scale(1.5);
560 opacity: 0.35;
561 }
562}
563
564/* Drawings off-screen hold still (components/art.tsx, Live). */
565[data-paused] * {
566 animation-play-state: paused !important;
567}
568/*
569 * Each drawing on its own layer, so a frame of its motion repaints only the
570 * drawing, not the page around it or the blurred header passing over it.
571 */
572[data-live] {
573 will-change: transform;
574}
575
576/* The landing page's steps, lit in turn along their line. */
577/* The ring is drawn once and only fades and grows, so the compositor runs it. */
578.flow-step::after {
579 content: "";
580 position: absolute;
581 inset: 0;
582 border-radius: inherit;
583 box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
584 opacity: 0;
585 pointer-events: none;
586 animation: flow-step 10s ease-in-out infinite both;
587 animation-delay: var(--delay, 0s);
588}
589@keyframes flow-step {
590 0%,
591 100% {
592 opacity: 0;
593 transform: scale(0.82);
594 }
595 6% {
596 opacity: 1;
597 transform: none;
598 }
599 16% {
600 opacity: 0;
601 transform: none;
602 }
603}
604.flow-runner {
605 animation: flow-runner 10s cubic-bezier(0.45, 0, 0.35, 1) infinite both;
606}
607@keyframes flow-runner {
608 0% {
609 transform: translateX(0);
610 opacity: 0;
611 }
612 4%,
613 76% {
614 opacity: 1;
615 }
616 80%,
617 100% {
618 transform: translateX(100%);
619 opacity: 0;
620 }
621}
622@media (prefers-reduced-motion: reduce) {
623 .flow-runner {
624 display: none;
625 }
626}
627
628/* The sidebar trading one far-side menu for another, in place. */
629@keyframes g1t-swap {
630 from {
631 opacity: 0;
632 transform: translateX(10px);
633 }
634 to {
635 opacity: 1;
636 transform: none;
637 }
638}
639
640/*
641 * Behind the landing page's opening: the same dark gray, with a faint
642 * lavender and peach glow low on the horizon and a few stars.
643 */
644.dusk {
645 background:
646 radial-gradient(1px 1px at 12% 18%, rgb(255 255 255 / 0.35), transparent),
647 radial-gradient(1px 1px at 27% 8%, rgb(255 255 255 / 0.25), transparent),
648 radial-gradient(1px 1px at 63% 12%, rgb(255 255 255 / 0.3), transparent),
649 radial-gradient(1.5px 1.5px at 90% 9%, rgb(255 255 255 / 0.35), transparent),
650 radial-gradient(60% 35% at 50% 100%, rgb(255 189 140 / 0.08), transparent 70%),
651 radial-gradient(80% 45% at 50% 100%, rgb(182 168 255 / 0.1), transparent 75%),
652 var(--color-bg);
653}
654/* A planet rising behind the horizon, its lit edge catching the glow. */
655.dusk-planet {
656 background: radial-gradient(circle at 70% 30%, rgb(182 168 255 / 0.12), rgb(255 189 140 / 0.04) 45%, transparent 70%);
657 box-shadow: inset 14px -8px 50px rgb(255 214 186 / 0.07);
658}
659
660/*
661 * On a phone, every field's text is at least 16px: iOS zooms the page into
662 * a field set smaller as soon as it is focused. Unlayered, so it wins over
663 * a field's text-sm utility.
664 */
665@media (max-width: 639px) {
666 input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]),
667 textarea,
668 select {
669 font-size: max(16px, 1em);
670 }
671}
672
673/* A card's Markdown preview in chat (components/chat/card.tsx): the same Markdown, smaller and tighter. */
674.card-md .prose {
675 @apply text-[0.8125rem] leading-relaxed;
676}
677.card-md .prose > * + * {
678 @apply mt-2;
679}
680.card-md .prose p,
681.card-md .prose li {
682 @apply text-[0.8125rem] text-fg/85;
683}
684.card-md .prose h1,
685.card-md .prose h2,
686.card-md .prose h3,
687.card-md .prose h4 {
688 @apply mt-3 border-0 pb-0 text-sm font-semibold;
689}
690.card-md .prose ul,
691.card-md .prose ol {
692 @apply space-y-0.5 pl-5;
693}
694.card-md .prose pre {
695 @apply p-2.5 text-xs leading-5;
696}