Skip to content
795 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 /* The top bar's height (components/shell.tsx), for what sticks just under it. */
170 --topbar-h: 3.5rem;
171}
172@media (max-width: 767px) {
173 :root {
174 --tabbar-h: calc(3.5rem + env(safe-area-inset-bottom));
175 /* On a phone it grows by the notch. */
176 --topbar-h: calc(3.5rem + env(safe-area-inset-top));
177 }
178}
179
180@media (max-width: 767px) {
181 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
182 textarea,
183 select {
184 font-size: max(16px, 1em) !important;
185 }
186 body {
187 overflow-x: clip;
188 }
189}
190
191body {
192 @apply bg-bg font-sans text-fg antialiased;
193}
194
195::selection {
196 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
197}
198
199/* In the base layer, so a component can opt out with a utility. */
200@layer base {
201 :focus-visible {
202 outline: 2px solid var(--color-accent);
203 outline-offset: 2px;
204 }
205
206 /*
207 * A grid's items may shrink below their content. Without this, one long
208 * line that does not wrap (a command, a commit hash, an address) widens
209 * the whole column, and on a phone the page scrolls sideways.
210 */
211 .grid > * {
212 min-width: 0;
213 }
214
215 /*
216 * Headlines are set in Bricolage Grotesque, its optical size following
217 * the font size; small section labels that happen to be headings stay in
218 * the text face. A utility on the element still wins.
219 */
220 :where(h1, h2),
221 :where(h3):is(.text-xl, .text-2xl, .text-3xl, .text-4xl) {
222 font-family: var(--font-display);
223 font-optical-sizing: auto;
224 }
225 :where(h2):is(.text-xs, .text-sm, .text-base) {
226 font-family: var(--font-sans);
227 }
228
229 /* Checkboxes, radios and ranges in g1t's own colour, not the browser's. */
230 :root {
231 accent-color: var(--color-accent);
232 }
233}
234
235@media (prefers-reduced-motion: reduce) {
236 *,
237 *::before,
238 *::after {
239 animation: none !important;
240 scroll-behavior: auto !important;
241 }
242}
243
244/* Rendered markdown: READMEs, briefs and docs. */
245.prose > :first-child {
246 margin-top: 0;
247 padding-top: 0;
248}
249.prose {
250 @apply leading-7 text-fg;
251}
252.prose > * + * {
253 @apply mt-4;
254}
255.prose h1 {
256 @apply mt-0 text-3xl font-semibold tracking-tight;
257}
258.prose h2 {
259 @apply mt-10 border-b border-line pb-2 text-xl font-semibold tracking-tight;
260}
261.prose h3 {
262 @apply mt-8 text-base font-semibold;
263}
264.prose p,
265.prose li {
266 @apply text-[0.9375rem] text-fg/90;
267}
268.prose a {
269 @apply text-accent underline decoration-accent/40 underline-offset-4 hover:decoration-accent;
270}
271.prose ul {
272 @apply list-disc space-y-1.5 pl-6;
273}
274.prose ol {
275 @apply list-decimal space-y-1.5 pl-6;
276}
277.prose code {
278 @apply rounded bg-raised px-1.5 py-0.5 font-mono text-[0.85em];
279}
280.prose pre {
281 @apply overflow-x-auto rounded-lg border border-line bg-surface p-4 font-mono text-sm leading-6;
282}
283.prose pre code {
284 @apply bg-transparent p-0;
285}
286.prose blockquote {
287 @apply border-l-2 border-line-strong pl-4 text-muted;
288}
289.prose table {
290 @apply w-full border-collapse text-sm;
291}
292.prose th {
293 @apply border-b border-line-strong px-3 py-2 text-left font-medium text-muted;
294}
295.prose td {
296 @apply border-b border-line px-3 py-2 align-top;
297}
298.prose hr {
299 @apply border-line;
300}
301.prose h4 {
302 @apply mt-6 text-sm font-semibold;
303}
304.prose li.task-list-item {
305 @apply list-none;
306}
307.prose ul.contains-task-list {
308 @apply pl-1;
309}
310.prose kbd {
311 @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)];
312}
313.prose del {
314 @apply text-muted;
315}
316.prose details {
317 @apply rounded-lg border border-line px-4 py-2;
318}
319.prose summary {
320 @apply cursor-pointer font-medium;
321}
322.prose img {
323 @apply inline max-w-full;
324}
325.prose section[data-footnotes] {
326 @apply mt-10 border-t border-line pt-4 text-sm text-muted;
327}
328.prose a[data-footnote-ref] {
329 @apply no-underline;
330}
331
332/* Syntax-highlighted code from Shiki. */
333.shiki {
334 @apply bg-transparent! font-mono text-sm leading-6;
335}
336.shiki code {
337 counter-reset: line;
338 display: block;
339}
340.shiki .line::before {
341 counter-increment: line;
342 content: counter(line);
343 @apply mr-5 inline-block w-10 text-right text-faint select-none;
344}
345
346/*
347 * Artwork (components/art.tsx). Every piece loops on its drawing's
348 * `--dur` and starts at its own `--delay`, so the parts of one drawing
349 * stay in step. Only transforms, opacity and stroke offsets move. The
350 * resting style of each class is the finished picture: that is what
351 * prefers-reduced-motion shows, since the rule above stops every
352 * animation.
353 */
354.art-flow {
355 animation: dash 1.6s linear infinite;
356}
357
358/* Something travelling along a track (pathLength 100). Unseen at rest. */
359.art-packet {
360 opacity: 0;
361 stroke-dasharray: 4 200;
362 stroke-dashoffset: 4;
363 animation: art-travel var(--dur, 3s) cubic-bezier(0.45, 0, 0.35, 1) infinite;
364 animation-delay: var(--delay, 0s);
365}
366@keyframes art-travel {
367 0% {
368 stroke-dashoffset: 4;
369 opacity: 0;
370 }
371 3% {
372 opacity: 1;
373 }
374 32% {
375 opacity: 1;
376 }
377 36%,
378 100% {
379 stroke-dashoffset: -100;
380 opacity: 0;
381 }
382}
383
384/* A face that lights up for most of the loop. Lit at rest. */
385.art-lit {
386 opacity: 0.85;
387 animation: art-lit var(--dur, 8s) ease-in-out infinite both;
388 animation-delay: var(--delay, 0s);
389}
390@keyframes art-lit {
391 0%,
392 4% {
393 opacity: 0;
394 }
395 12%,
396 70% {
397 opacity: 0.9;
398 }
399 88%,
400 100% {
401 opacity: 0;
402 }
403}
404
405/* A short flash, as a check runs over a layer. Unseen at rest. */
406.art-scan {
407 opacity: 0;
408 animation: art-scan var(--dur, 8s) ease-out infinite both;
409 animation-delay: var(--delay, 0s);
410}
411@keyframes art-scan {
412 0% {
413 opacity: 0;
414 }
415 4% {
416 opacity: 1;
417 }
418 16%,
419 100% {
420 opacity: 0;
421 }
422}
423
424/* A line that draws itself, then fades. Drawn at rest. */
425.art-draw {
426 stroke-dasharray: 100;
427 stroke-dashoffset: 0;
428 animation: art-draw var(--dur, 8s) ease-in-out infinite both;
429 animation-delay: var(--delay, 0s);
430}
431@keyframes art-draw {
432 0% {
433 stroke-dashoffset: 100;
434 opacity: 1;
435 }
436 14%,
437 70% {
438 stroke-dashoffset: 0;
439 opacity: 1;
440 }
441 88%,
442 100% {
443 stroke-dashoffset: 0;
444 opacity: 0;
445 }
446}
447
448/* Something that appears, stays, and goes. Shown at rest. */
449.art-pop {
450 animation: art-pop var(--dur, 8s) ease-out infinite both;
451 animation-delay: var(--delay, 0s);
452}
453@keyframes art-pop {
454 0% {
455 opacity: 0;
456 transform: translateY(4px);
457 }
458 6%,
459 72% {
460 opacity: 1;
461 transform: none;
462 }
463 86%,
464 100% {
465 opacity: 0;
466 transform: none;
467 }
468}
469
470/* A layer dropping onto a stack. In place at rest. */
471.art-drop {
472 animation: art-drop var(--dur, 7s) cubic-bezier(0.2, 0.9, 0.3, 1.2) infinite both;
473 animation-delay: var(--delay, 0s);
474}
475@keyframes art-drop {
476 0% {
477 opacity: 0;
478 transform: translateY(-26px);
479 }
480 8%,
481 74% {
482 opacity: 1;
483 transform: none;
484 }
485 88%,
486 100% {
487 opacity: 0;
488 transform: none;
489 }
490}
491
492/* The layer that fails: drops, shakes, and is sent back. */
493.art-eject {
494 animation: art-eject var(--dur, 7s) ease-in-out infinite both;
495 animation-delay: var(--delay, 0s);
496}
497@keyframes art-eject {
498 0% {
499 opacity: 0;
500 transform: translateY(-26px);
501 }
502 8%,
503 30% {
504 opacity: 1;
505 transform: none;
506 }
507 33% {
508 transform: translateX(-3px);
509 }
510 36% {
511 transform: translateX(3px);
512 }
513 39% {
514 transform: translateX(-2px);
515 }
516 42% {
517 opacity: 1;
518 transform: none;
519 }
520 56%,
521 100% {
522 opacity: 0;
523 transform: translate(-40px, -22px);
524 }
525}
526
527/* A layer rising out of the one below it. In place at rest. */
528.art-rise {
529 animation: art-rise var(--dur, 7s) cubic-bezier(0.2, 0.8, 0.3, 1) infinite both;
530 animation-delay: var(--delay, 0s);
531}
532@keyframes art-rise {
533 0% {
534 opacity: 0;
535 transform: translateY(14px);
536 }
537 10%,
538 70% {
539 opacity: 1;
540 transform: none;
541 }
542 86%,
543 100% {
544 opacity: 0;
545 transform: none;
546 }
547}
548
549/* A soft glow breathing around a point. */
550.art-pulse {
551 transform-box: fill-box;
552 transform-origin: center;
553 animation: art-pulse var(--dur, 4s) ease-in-out infinite;
554 animation-delay: var(--delay, 0s);
555}
556@keyframes art-pulse {
557 0%,
558 100% {
559 transform: scale(0.8);
560 opacity: 1;
561 }
562 50% {
563 transform: scale(1.5);
564 opacity: 0.35;
565 }
566}
567
568/* Drawings off-screen hold still (components/art.tsx, Live). */
569[data-paused] * {
570 animation-play-state: paused !important;
571}
572/*
573 * Each drawing on its own layer, so a frame of its motion repaints only the
574 * drawing, not the page around it or the blurred header passing over it.
575 */
576[data-live] {
577 will-change: transform;
578}
579
580/* The landing page's steps, lit in turn along their line. */
581/* The ring is drawn once and only fades and grows, so the compositor runs it. */
582.flow-step::after {
583 content: "";
584 position: absolute;
585 inset: 0;
586 border-radius: inherit;
587 box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
588 opacity: 0;
589 pointer-events: none;
590 animation: flow-step 10s ease-in-out infinite both;
591 animation-delay: var(--delay, 0s);
592}
593@keyframes flow-step {
594 0%,
595 100% {
596 opacity: 0;
597 transform: scale(0.82);
598 }
599 6% {
600 opacity: 1;
601 transform: none;
602 }
603 16% {
604 opacity: 0;
605 transform: none;
606 }
607}
608.flow-runner {
609 animation: flow-runner 10s cubic-bezier(0.45, 0, 0.35, 1) infinite both;
610}
611@keyframes flow-runner {
612 0% {
613 transform: translateX(0);
614 opacity: 0;
615 }
616 4%,
617 76% {
618 opacity: 1;
619 }
620 80%,
621 100% {
622 transform: translateX(100%);
623 opacity: 0;
624 }
625}
626@media (prefers-reduced-motion: reduce) {
627 .flow-runner {
628 display: none;
629 }
630}
631
632/* The sidebar trading one far-side menu for another, in place. */
633@keyframes g1t-swap {
634 from {
635 opacity: 0;
636 transform: translateX(10px);
637 }
638 to {
639 opacity: 1;
640 transform: none;
641 }
642}
643
644/*
645 * Behind the landing page's opening: the same dark gray, with a faint
646 * lavender and peach glow low on the horizon and a few stars.
647 */
648.dusk {
649 background:
650 radial-gradient(1px 1px at 12% 18%, rgb(255 255 255 / 0.35), transparent),
651 radial-gradient(1px 1px at 27% 8%, rgb(255 255 255 / 0.25), transparent),
652 radial-gradient(1px 1px at 63% 12%, rgb(255 255 255 / 0.3), transparent),
653 radial-gradient(1.5px 1.5px at 90% 9%, rgb(255 255 255 / 0.35), transparent),
654 radial-gradient(60% 35% at 50% 100%, rgb(255 189 140 / 0.08), transparent 70%),
655 radial-gradient(80% 45% at 50% 100%, rgb(182 168 255 / 0.1), transparent 75%),
656 var(--color-bg);
657}
658/* A planet rising behind the horizon, its lit edge catching the glow. */
659.dusk-planet {
660 background: radial-gradient(circle at 70% 30%, rgb(182 168 255 / 0.12), rgb(255 189 140 / 0.04) 45%, transparent 70%);
661 box-shadow: inset 14px -8px 50px rgb(255 214 186 / 0.07);
662}
663
664/*
665 * On a phone, every field's text is at least 16px: iOS zooms the page into
666 * a field set smaller as soon as it is focused. Unlayered, so it wins over
667 * a field's text-sm utility.
668 */
669@media (max-width: 639px) {
670 input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]),
671 textarea,
672 select {
673 font-size: max(16px, 1em);
674 }
675}
676
677/* A card's Markdown preview in chat (components/chat/card.tsx): the same Markdown, smaller and tighter. */
678.card-md .prose {
679 @apply text-[0.8125rem] leading-relaxed;
680}
681.card-md .prose > * + * {
682 @apply mt-2;
683}
684.card-md .prose p,
685.card-md .prose li {
686 @apply text-[0.8125rem] text-fg/85;
687}
688.card-md .prose h1,
689.card-md .prose h2,
690.card-md .prose h3,
691.card-md .prose h4 {
692 @apply mt-3 border-0 pb-0 text-sm font-semibold;
693}
694.card-md .prose ul,
695.card-md .prose ol {
696 @apply space-y-0.5 pl-5;
697}
698.card-md .prose pre {
699 @apply p-2.5 text-xs leading-5;
700}
701
702/*
703 * The Docs page editor (components/docs/editor.tsx) is BlockNote with its
704 * shadcn components. Their classes are generated from the package, and
705 * shadcn's token names map onto g1t's. `accent` and `muted` mean
706 * something else in shadcn (a hover fill, a quiet fill), so inside the
707 * editor they are redefined; g1t's own blocks there use the g1t tokens
708 * directly.
709 */
710@source "../../../node_modules/@blocknote/shadcn/dist";
711
712@theme {
713 --color-background: var(--g1t-bg);
714 --color-foreground: var(--g1t-fg);
715 --color-popover: var(--g1t-surface);
716 --color-popover-foreground: var(--g1t-fg);
717 --color-card: var(--g1t-surface);
718 --color-card-foreground: var(--g1t-fg);
719 --color-primary: var(--g1t-accent);
720 --color-primary-foreground: var(--g1t-bg);
721 --color-secondary: var(--g1t-raised);
722 --color-secondary-foreground: var(--g1t-fg);
723 --color-muted-foreground: var(--g1t-muted);
724 --color-accent-foreground: var(--g1t-fg);
725 --color-destructive: var(--g1t-danger);
726 --color-destructive-foreground: var(--g1t-bg);
727 --color-border: var(--g1t-line);
728 --color-input: var(--g1t-line);
729 --color-ring: var(--g1t-accent);
730}
731
732.bn-shadcn {
733 --color-accent: var(--g1t-raised);
734 --color-muted: var(--g1t-raised);
735 --bn-colors-editor-background: transparent;
736 --bn-colors-editor-text: var(--g1t-fg);
737 --bn-colors-menu-background: var(--g1t-surface);
738 --bn-colors-menu-text: var(--g1t-fg);
739 --bn-colors-hovered-background: var(--g1t-raised);
740 --bn-colors-side-menu: var(--g1t-faint);
741 --bn-colors-border: var(--g1t-line);
742 --bn-border-radius: 8px;
743}
744
745.g1t-editor .bn-editor {
746 font-family: var(--g1t-font-sans);
747 font-size: 1rem;
748 line-height: 1.65;
749 background: transparent;
750 color: var(--g1t-fg);
751}
752
753/* The drag handle and + sit in the margin, so the text lines up with the title. */
754@media (min-width: 768px) {
755 .g1t-editor .bn-container {
756 margin-inline: -54px;
757 }
758}
759
760.g1t-editor .bn-block-content[data-content-type="heading"] h1,
761.g1t-editor .bn-block-content[data-content-type="heading"] h2,
762.g1t-editor .bn-block-content[data-content-type="heading"] h3 {
763 font-family: var(--g1t-font-display);
764 letter-spacing: -0.012em;
765}
766
767.g1t-editor .bn-inline-content code {
768 font-family: var(--g1t-font-mono);
769 font-size: 0.875em;
770 padding: 0.1em 0.35em;
771 border-radius: 4px;
772 background: var(--g1t-raised);
773}
774
775.g1t-editor .bn-block-content[data-content-type="codeBlock"] {
776 border-radius: 10px;
777 background: var(--g1t-surface);
778 border: 1px solid var(--g1t-line);
779}
780
781.g1t-editor .bn-thread-mark {
782 background: color-mix(in srgb, var(--g1t-warn) 22%, transparent);
783 border-bottom: 2px solid color-mix(in srgb, var(--g1t-warn) 60%, transparent);
784}
785
786.g1t-editor a {
787 color: var(--g1t-accent);
788}
789
790/* A page read before the editor loads: the same measure as the editor. */
791.docs-read .prose {
792 font-size: 1rem;
793 line-height: 1.65;
794}
795