Skip to content
1,217 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 /*
15 * Every colour is a pair: light-dark() picks the light palette or the
16 * dark one by the element's color-scheme, which follows the Appearance
17 * setting (lib/theme.ts, set on <html> below). Something drawn dark on
18 * purpose in either theme, such as a product screenshot, sets
19 * `scheme-dark` and its tokens stay dark.
20 */
21 --color-bg: light-dark(var(--g1t-light-bg), var(--g1t-bg));
22 /*
23 * The app's frame (components/shell.tsx, components/ui/sidebar.tsx):
24 * the page sits flat against the sidebar, a hairline between; the sidebar
25 * is the shell's surface, and the rail, the narrow column of icons at
26 * the window's edge, is a step apart: white in the light theme, near
27 * black in the dark one. The current row or icon is a muted fill.
28 */
29 --color-shell: light-dark(#efefed, #0b0b0d);
30 --color-sidebar: var(--color-shell);
31 --color-sidebar-accent: light-dark(#e1e1de, #1c1c20);
32 --color-sidebar-border: light-dark(#e0e0dd, #1d1d22);
33 --color-rail: light-dark(#ffffff, #050507);
34 --color-surface: light-dark(var(--g1t-light-surface), var(--g1t-surface));
35 --color-raised: light-dark(var(--g1t-light-raised), var(--g1t-raised));
36 --color-line: light-dark(var(--g1t-light-line), var(--g1t-line));
37 --color-line-strong: light-dark(var(--g1t-light-line-strong), var(--g1t-line-strong));
38
39 --color-fg: light-dark(var(--g1t-light-fg), var(--g1t-fg));
40 --color-fg-soft: light-dark(var(--g1t-light-fg-soft), var(--g1t-fg-soft));
41 /* A primary button's hover: brighter than fg on dark, softer on light. */
42 --color-fg-hover: light-dark(#3a3a41, #ffffff);
43 --color-muted: light-dark(var(--g1t-light-muted), var(--g1t-muted));
44 --color-faint: light-dark(var(--g1t-light-faint), var(--g1t-faint));
45
46 --color-accent: light-dark(var(--g1t-light-accent), var(--g1t-accent));
47 --color-accent-hover: light-dark(var(--g1t-light-accent-hover), var(--g1t-accent-hover));
48 --color-accent-dim: light-dark(var(--g1t-light-accent-dim), var(--g1t-accent-dim));
49 --color-accent-low: light-dark(var(--g1t-light-accent-low), var(--g1t-accent-low));
50 /* The accent at its palest on dark, a middle lavender on light: the headline's gradient. */
51 --color-accent-high: light-dark(#8b76e0, #d9d1ff);
52
53 --color-success: light-dark(var(--g1t-light-success), var(--g1t-success));
54 --color-success-dim: light-dark(var(--g1t-light-success-dim), var(--g1t-success-dim));
55 --color-success-low: light-dark(var(--g1t-light-success-low), var(--g1t-success-low));
56
57 --color-info: light-dark(var(--g1t-light-info), var(--g1t-info));
58 --color-merged: light-dark(var(--g1t-light-merged), var(--g1t-merged));
59 --color-warn: light-dark(var(--g1t-light-warn), var(--g1t-warn));
60 --color-danger: light-dark(var(--g1t-light-danger), var(--g1t-danger));
61
62 --animate-fade-up: fade-up 0.6s ease-out both;
63 --animate-dash: dash 1.2s linear infinite;
64 /* A template's creature, on hover: a small, quick wiggle (components/agent-avatar.tsx). */
65 --animate-wiggle: wiggle 0.45s ease-in-out;
66
67 /* Menus, popovers and dialogs (components/ui): in quickly, out quicker. */
68 --animate-pop-in: pop-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
69 --animate-pop-out: pop-out 0.1s ease-in forwards;
70 --animate-fade-in: fade-in 0.16s ease-out;
71 --animate-fade-out: fade-out 0.12s ease-in forwards;
72 --animate-dialog-in: dialog-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
73 --animate-dialog-out: dialog-out 0.12s ease-in forwards;
74 /* Sheets (components/ui/sheet.tsx) slide in from their edge. */
75 --animate-sheet-in-right: sheet-in-right 0.24s cubic-bezier(0.16, 1, 0.3, 1);
76 --animate-sheet-out-right: sheet-out-right 0.16s ease-in forwards;
77 --animate-sheet-in-left: sheet-in-left 0.24s cubic-bezier(0.16, 1, 0.3, 1);
78 --animate-sheet-out-left: sheet-out-left 0.16s ease-in forwards;
79}
80
81@keyframes sheet-in-bottom {
82 from {
83 transform: translateY(100%);
84 }
85}
86
87@keyframes sheet-in-right {
88 from {
89 transform: translateX(100%);
90 }
91}
92
93@keyframes sheet-out-right {
94 to {
95 transform: translateX(100%);
96 }
97}
98
99@keyframes sheet-in-left {
100 from {
101 transform: translateX(-100%);
102 }
103}
104
105@keyframes sheet-out-left {
106 to {
107 transform: translateX(-100%);
108 }
109}
110
111@keyframes pop-in {
112 from {
113 opacity: 0;
114 transform: scale(0.96) translateY(-2px);
115 }
116}
117
118@keyframes pop-out {
119 to {
120 opacity: 0;
121 transform: scale(0.96);
122 }
123}
124
125/* A row that just arrived on a live page (Observability's logs). */
126@keyframes g1t-fresh {
127 from {
128 background-color: color-mix(in srgb, var(--color-accent) 18%, transparent);
129 }
130}
131
132@keyframes fade-in {
133 from {
134 opacity: 0;
135 }
136}
137
138@keyframes fade-out {
139 to {
140 opacity: 0;
141 }
142}
143
144@keyframes dialog-in {
145 from {
146 opacity: 0;
147 transform: translateY(6px) scale(0.97);
148 }
149}
150
151@keyframes dialog-out {
152 to {
153 opacity: 0;
154 transform: translateY(6px) scale(0.97);
155 }
156}
157
158/* Never from nothing: the first paint already shows the words. */
159@keyframes fade-up {
160 from {
161 opacity: 0.4;
162 transform: translateY(8px);
163 }
164}
165
166@keyframes wiggle {
167 25% {
168 transform: rotate(-8deg);
169 }
170 60% {
171 transform: rotate(6deg);
172 }
173}
174
175@keyframes dash {
176 to {
177 stroke-dashoffset: -16;
178 }
179}
180
181/*
182 * An agent's face (components/agent-face.tsx). Its colours are the face's
183 * own body colour (set on the element, a light-dark() pair) and these: the
184 * ink of its eyes and mouth, a second ink for what sits outside the head,
185 * the rim around its head, a highlight, the glow of a visor or a working
186 * face, and two accents for a bow and a spark. It moves with transforms and opacity only: a blink and a breath
187 * on its own beat (the delays are set per face from its seed), a pulse
188 * while it works, a bounce when it is happy, a z when it sleeps. The
189 * reduced-motion rule below stops every one of them, and the state's
190 * still pose (narrowed or closed eyes) is a plain transform, so it holds.
191 */
192.agent-face {
193 --face-ink: #1b1b22;
194 /* What sits outside the head (antenna, hat, headphones, a z) reads on the page, not the body. */
195 --face-ink-out: light-dark(#1b1b22, #e9e9ee);
196 --face-rim: color-mix(in oklab, var(--face-body), #000 24%);
197 --face-light: #ffffff;
198 --face-glow: light-dark(#0d7a52, #86efc4);
199 --face-ribbon: light-dark(#6b52c8, #b6a8ff);
200 --face-spark: light-dark(#d4a90f, #f2dc72);
201 display: inline-block;
202 vertical-align: middle;
203}
204.agent-face .face-bob,
205.agent-face .face-eyes,
206.agent-face .face-pupils,
207.agent-face .face-glow,
208.agent-face .face-bulb,
209.agent-face .face-spark,
210.agent-face .face-z {
211 transform-box: fill-box;
212 transform-origin: center;
213}
214.agent-face .face-bob {
215 animation: face-bob 4.2s ease-in-out infinite;
216 animation-delay: var(--face-bob-delay, 0s);
217}
218.agent-face .face-eyes {
219 animation: face-blink var(--face-blink-period, 5s) ease-in-out infinite;
220 animation-delay: var(--face-blink-delay, 0s);
221}
222.agent-face .face-pupils {
223 transition: transform 0.25s ease-out;
224}
225/* A glance toward the pointer, on hover only. */
226.agent-face:hover .face-pupils,
227a:hover > .agent-face .face-pupils,
228button:hover > .agent-face .face-pupils {
229 transform: translateX(2.5px);
230}
231.agent-face .face-glow {
232 opacity: 0;
233}
234.agent-face .face-spark {
235 animation: face-twinkle 2.8s ease-in-out infinite;
236 animation-delay: var(--face-bob-delay, 0s);
237}
238/* Working: eyes narrowed, no blink, a soft pulse around the head and in the bulb. */
239.agent-face[data-state="working"] .face-eyes {
240 animation: none;
241 transform: scaleY(0.55);
242}
243.agent-face[data-state="working"] .face-glow {
244 animation: face-pulse 1.6s ease-in-out infinite;
245}
246.agent-face[data-state="working"] .face-bulb {
247 animation: face-bulb 1.6s ease-in-out infinite;
248}
249/* Asleep: eyes shut, a slower breath, a z drifting up. */
250.agent-face[data-state="asleep"] .face-eyes {
251 animation: none;
252 transform: scaleY(0.1);
253}
254.agent-face[data-state="asleep"] .face-bob {
255 animation-duration: 6s;
256}
257.agent-face[data-state="asleep"] .face-z {
258 animation: face-zz 2.6s ease-in-out infinite;
259}
260.agent-face[data-state="asleep"] .face-z-2 {
261 animation-delay: 1.3s;
262}
263/* Happy: a bounce, twice, then still. */
264.agent-face[data-state="happy"] .face-bob {
265 animation: face-happy 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) 2;
266}
267@keyframes face-blink {
268 0%,
269 91%,
270 100% {
271 transform: scaleY(1);
272 }
273 94%,
274 96% {
275 transform: scaleY(0.08);
276 }
277}
278@keyframes face-bob {
279 0%,
280 100% {
281 transform: translateY(0);
282 }
283 50% {
284 transform: translateY(-1.6px);
285 }
286}
287@keyframes face-pulse {
288 0%,
289 100% {
290 opacity: 0.15;
291 }
292 50% {
293 opacity: 0.55;
294 }
295}
296@keyframes face-bulb {
297 0%,
298 100% {
299 opacity: 0.6;
300 transform: scale(1);
301 }
302 50% {
303 opacity: 1;
304 transform: scale(1.15);
305 }
306}
307@keyframes face-twinkle {
308 0%,
309 100% {
310 opacity: 0.75;
311 transform: scale(0.9) rotate(0deg);
312 }
313 50% {
314 opacity: 1;
315 transform: scale(1.1) rotate(20deg);
316 }
317}
318@keyframes face-zz {
319 0% {
320 opacity: 0;
321 transform: translate(0, 4px);
322 }
323 30% {
324 opacity: 1;
325 }
326 100% {
327 opacity: 0;
328 transform: translate(5px, -9px);
329 }
330}
331@keyframes face-happy {
332 0%,
333 100% {
334 transform: translateY(0) scale(1);
335 }
336 40% {
337 transform: translateY(-6px) scale(1.06);
338 }
339}
340
341/*
342 * Appearance (lib/theme.ts): with no choice, or Auto, the page follows
343 * the system; a choice of Light or Dark is data-theme on <html>.
344 */
345html {
346 color-scheme: light dark;
347 scroll-behavior: smooth;
348 /* A tap shows the control's own active state, not the browser's grey flash. */
349 -webkit-tap-highlight-color: transparent;
350 /* Phones: no text inflation on rotation. */
351 -webkit-text-size-adjust: 100%;
352 text-size-adjust: 100%;
353}
354html[data-theme="light"] {
355 color-scheme: light;
356}
357html[data-theme="dark"] {
358 color-scheme: dark;
359}
360
361/*
362 * The app's frame (components/shell.tsx), set on <body data-frame="app">:
363 * the page fills the window beside the rail and the sidebar, and on a
364 * phone it fills the window above the bottom bar, flat, as on a computer.
365 * What sticks to the top or the bottom of a page reads these:
366 * --frame-top what is above the page: a phone's status bar, where it overlaps
367 * --topbar-h that gap plus the top bar: where sticky things sit
368 * --tabbar-h what the bottom takes: on a phone, the bar and the home indicator under it
369 * --page-h the height left for a page that fills the window (Chat)
370 * The public frame keeps a plain header 3.5rem tall and nothing at the bottom.
371 */
372:root {
373 --frame-top: 0px;
374 --tabbar-h: 0px;
375 --topbar-h: 3.5rem;
376 --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
377}
378body[data-frame="app"] {
379 --topbar-h: calc(var(--frame-top) + 3.5rem);
380 --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
381}
382@media (max-width: 767px) {
383 body[data-frame="app"] {
384 --frame-top: env(safe-area-inset-top);
385 /* The bottom bar: 56px tall along the bottom edge, and the home indicator's room under it. */
386 --tabbar-h: calc(3.5rem + env(safe-area-inset-bottom));
387 }
388}
389
390/*
391 * Phones: a field under 16px makes iOS zoom the page when it is focused, so
392 * every field is at least that; and nothing scrolls the page sideways.
393 */
394@media (max-width: 767px) {
395 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
396 textarea,
397 select {
398 font-size: max(16px, 1em) !important;
399 }
400 body {
401 overflow-x: clip;
402 }
403}
404
405body {
406 @apply bg-bg font-sans text-fg antialiased;
407}
408body[data-frame="app"] {
409 @apply bg-shell;
410}
411
412::selection {
413 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
414}
415
416/* In the base layer, so a component can opt out with a utility. */
417@layer base {
418 :focus-visible {
419 outline: 2px solid var(--color-accent);
420 outline-offset: 2px;
421 }
422
423 /*
424 * A grid's items may shrink below their content. Without this, one long
425 * line that does not wrap (a command, a commit hash, an address) widens
426 * the whole column, and on a phone the page scrolls sideways.
427 */
428 .grid > * {
429 min-width: 0;
430 }
431
432 /*
433 * Headlines are set in Bricolage Grotesque, its optical size following
434 * the font size; small section labels that happen to be headings stay in
435 * the text face. A utility on the element still wins.
436 */
437 :where(h1, h2),
438 :where(h3):is(.text-xl, .text-2xl, .text-3xl, .text-4xl) {
439 font-family: var(--font-display);
440 font-optical-sizing: auto;
441 }
442 :where(h2):is(.text-xs, .text-sm, .text-base) {
443 font-family: var(--font-sans);
444 }
445
446 /* Checkboxes, radios and ranges in g1t's own colour, not the browser's. */
447 :root {
448 accent-color: var(--color-accent);
449 }
450
451 /*
452 * Number fields have no spin buttons: the arrow keys still step them, and
453 * their digits line up in columns.
454 */
455 input[type="number"] {
456 -moz-appearance: textfield;
457 appearance: textfield;
458 font-variant-numeric: tabular-nums;
459 }
460 input[type="number"]::-webkit-inner-spin-button,
461 input[type="number"]::-webkit-outer-spin-button {
462 -webkit-appearance: none;
463 appearance: none;
464 margin: 0;
465 }
466
467 /*
468 * Date and time fields: the picker's icon is drawn by the browser in the
469 * page's color-scheme (set on <html> above), so it is right in both
470 * themes; it sits quiet until hovered. The part being edited is marked
471 * in the accent, not the system's blue.
472 */
473 input:is([type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"]) {
474 font-variant-numeric: tabular-nums;
475 }
476 ::-webkit-calendar-picker-indicator {
477 cursor: pointer;
478 border-radius: 0.25rem;
479 opacity: 0.55;
480 transition: opacity 150ms;
481 }
482 ::-webkit-calendar-picker-indicator:hover,
483 ::-webkit-calendar-picker-indicator:focus-visible {
484 opacity: 1;
485 }
486 ::-webkit-datetime-edit-fields-wrapper {
487 padding: 0;
488 }
489 ::-webkit-datetime-edit-text {
490 color: var(--color-faint);
491 }
492 ::-webkit-datetime-edit-year-field:focus,
493 ::-webkit-datetime-edit-month-field:focus,
494 ::-webkit-datetime-edit-day-field:focus,
495 ::-webkit-datetime-edit-hour-field:focus,
496 ::-webkit-datetime-edit-minute-field:focus,
497 ::-webkit-datetime-edit-second-field:focus,
498 ::-webkit-datetime-edit-millisecond-field:focus,
499 ::-webkit-datetime-edit-meridiem-field:focus {
500 border-radius: 0.1875rem;
501 background-color: color-mix(in srgb, var(--color-accent) 25%, transparent);
502 color: var(--color-fg);
503 }
504
505 /* A colour field is a rounded swatch with a hairline, not a grey button. */
506 input[type="color"] {
507 -webkit-appearance: none;
508 appearance: none;
509 cursor: pointer;
510 padding: 0.1875rem;
511 border: 1px solid var(--color-line);
512 border-radius: 0.375rem;
513 background-color: var(--color-bg);
514 }
515 input[type="color"]:hover {
516 border-color: var(--color-line-strong);
517 }
518 input[type="color"]::-webkit-color-swatch-wrapper {
519 padding: 0;
520 }
521 input[type="color"]::-webkit-color-swatch {
522 border: none;
523 border-radius: 0.25rem;
524 }
525 input[type="color"]::-moz-color-swatch {
526 border: none;
527 border-radius: 0.25rem;
528 }
529
530 /* A search field's clear button is the browser's own: g1t's fields don't show it. */
531 input[type="search"]::-webkit-search-cancel-button,
532 input[type="search"]::-webkit-search-decoration {
533 -webkit-appearance: none;
534 appearance: none;
535 }
536
537 /*
538 * A <summary> that keeps the browser's disclosure triangle has it quiet,
539 * in the faint text colour; one that draws its own hides it with list-none.
540 */
541 summary {
542 cursor: pointer;
543 }
544 summary::marker {
545 color: var(--color-faint);
546 }
547 summary::-webkit-details-marker {
548 color: var(--color-faint);
549 }
550}
551
552@media (prefers-reduced-motion: reduce) {
553 *,
554 *::before,
555 *::after {
556 animation: none !important;
557 scroll-behavior: auto !important;
558 }
559}
560
561/* Rendered markdown: READMEs, briefs and docs. */
562.prose > :first-child {
563 margin-top: 0;
564 padding-top: 0;
565}
566.prose {
567 @apply leading-7 text-fg;
568}
569.prose > * + * {
570 @apply mt-4;
571}
572.prose h1 {
573 @apply mt-0 text-3xl font-semibold tracking-tight;
574}
575.prose h2 {
576 @apply mt-10 border-b border-line pb-2 text-xl font-semibold tracking-tight;
577}
578.prose h3 {
579 @apply mt-8 text-base font-semibold;
580}
581.prose p,
582.prose li {
583 @apply text-[0.9375rem] text-fg/90;
584}
585.prose a {
586 @apply text-accent underline decoration-accent/40 underline-offset-4 hover:decoration-accent;
587}
588.prose ul {
589 @apply list-disc space-y-1.5 pl-6;
590}
591.prose ol {
592 @apply list-decimal space-y-1.5 pl-6;
593}
594.prose code {
595 @apply rounded bg-raised px-1.5 py-0.5 font-mono text-[0.85em];
596}
597.prose pre {
598 @apply overflow-x-auto rounded-lg border border-line bg-surface p-4 font-mono text-sm leading-6;
599}
600.prose pre code {
601 @apply bg-transparent p-0;
602}
603.prose blockquote {
604 @apply border-l-2 border-line-strong pl-4 text-muted;
605}
606.prose table {
607 @apply w-full border-collapse text-sm;
608}
609.prose th {
610 @apply border-b border-line-strong px-3 py-2 text-left font-medium text-muted;
611}
612.prose td {
613 @apply border-b border-line px-3 py-2 align-top;
614}
615.prose hr {
616 @apply border-line;
617}
618.prose h4 {
619 @apply mt-6 text-sm font-semibold;
620}
621.prose li.task-list-item {
622 @apply list-none;
623}
624.prose ul.contains-task-list {
625 @apply pl-1;
626}
627.prose kbd {
628 @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)];
629}
630.prose del {
631 @apply text-muted;
632}
633.prose details {
634 @apply rounded-lg border border-line px-4 py-2;
635}
636.prose summary {
637 @apply cursor-pointer font-medium;
638}
639.prose img {
640 @apply inline max-w-full;
641}
642.prose section[data-footnotes] {
643 @apply mt-10 border-t border-line pt-4 text-sm text-muted;
644}
645.prose a[data-footnote-ref] {
646 @apply no-underline;
647}
648
649/* Syntax-highlighted code from Shiki. */
650.shiki {
651 @apply bg-transparent! font-mono text-sm leading-6;
652}
653.shiki code {
654 counter-reset: line;
655 display: block;
656}
657.shiki .line::before {
658 counter-increment: line;
659 content: counter(line);
660 @apply mr-5 inline-block w-10 text-right text-faint select-none;
661}
662
663/*
664 * Artwork (components/art.tsx). Every piece loops on its drawing's
665 * `--dur` and starts at its own `--delay`, so the parts of one drawing
666 * stay in step. Only transforms, opacity and stroke offsets move. The
667 * resting style of each class is the finished picture: that is what
668 * prefers-reduced-motion shows, since the rule above stops every
669 * animation.
670 */
671.art-flow {
672 animation: dash 1.6s linear infinite;
673}
674
675/* Something travelling along a track (pathLength 100). Unseen at rest. */
676.art-packet {
677 opacity: 0;
678 stroke-dasharray: 4 200;
679 stroke-dashoffset: 4;
680 animation: art-travel var(--dur, 3s) cubic-bezier(0.45, 0, 0.35, 1) infinite;
681 animation-delay: var(--delay, 0s);
682}
683@keyframes art-travel {
684 0% {
685 stroke-dashoffset: 4;
686 opacity: 0;
687 }
688 3% {
689 opacity: 1;
690 }
691 32% {
692 opacity: 1;
693 }
694 36%,
695 100% {
696 stroke-dashoffset: -100;
697 opacity: 0;
698 }
699}
700
701/* A face that lights up for most of the loop. Lit at rest. */
702.art-lit {
703 opacity: 0.85;
704 animation: art-lit var(--dur, 8s) ease-in-out infinite both;
705 animation-delay: var(--delay, 0s);
706}
707@keyframes art-lit {
708 0%,
709 4% {
710 opacity: 0;
711 }
712 12%,
713 70% {
714 opacity: 0.9;
715 }
716 88%,
717 100% {
718 opacity: 0;
719 }
720}
721
722/* A short flash, as a check runs over a layer. Unseen at rest. */
723.art-scan {
724 opacity: 0;
725 animation: art-scan var(--dur, 8s) ease-out infinite both;
726 animation-delay: var(--delay, 0s);
727}
728@keyframes art-scan {
729 0% {
730 opacity: 0;
731 }
732 4% {
733 opacity: 1;
734 }
735 16%,
736 100% {
737 opacity: 0;
738 }
739}
740
741/* A line that draws itself, then fades. Drawn at rest. */
742.art-draw {
743 stroke-dasharray: 100;
744 stroke-dashoffset: 0;
745 animation: art-draw var(--dur, 8s) ease-in-out infinite both;
746 animation-delay: var(--delay, 0s);
747}
748@keyframes art-draw {
749 0% {
750 stroke-dashoffset: 100;
751 opacity: 1;
752 }
753 14%,
754 70% {
755 stroke-dashoffset: 0;
756 opacity: 1;
757 }
758 88%,
759 100% {
760 stroke-dashoffset: 0;
761 opacity: 0;
762 }
763}
764
765/* Something that appears, stays, and goes. Shown at rest. */
766.art-pop {
767 animation: art-pop var(--dur, 8s) ease-out infinite both;
768 animation-delay: var(--delay, 0s);
769}
770@keyframes art-pop {
771 0% {
772 opacity: 0;
773 transform: translateY(4px);
774 }
775 6%,
776 72% {
777 opacity: 1;
778 transform: none;
779 }
780 86%,
781 100% {
782 opacity: 0;
783 transform: none;
784 }
785}
786
787/* A layer dropping onto a stack. In place at rest. */
788.art-drop {
789 animation: art-drop var(--dur, 7s) cubic-bezier(0.2, 0.9, 0.3, 1.2) infinite both;
790 animation-delay: var(--delay, 0s);
791}
792@keyframes art-drop {
793 0% {
794 opacity: 0;
795 transform: translateY(-26px);
796 }
797 8%,
798 74% {
799 opacity: 1;
800 transform: none;
801 }
802 88%,
803 100% {
804 opacity: 0;
805 transform: none;
806 }
807}
808
809/* The layer that fails: drops, shakes, and is sent back. */
810.art-eject {
811 animation: art-eject var(--dur, 7s) ease-in-out infinite both;
812 animation-delay: var(--delay, 0s);
813}
814@keyframes art-eject {
815 0% {
816 opacity: 0;
817 transform: translateY(-26px);
818 }
819 8%,
820 30% {
821 opacity: 1;
822 transform: none;
823 }
824 33% {
825 transform: translateX(-3px);
826 }
827 36% {
828 transform: translateX(3px);
829 }
830 39% {
831 transform: translateX(-2px);
832 }
833 42% {
834 opacity: 1;
835 transform: none;
836 }
837 56%,
838 100% {
839 opacity: 0;
840 transform: translate(-40px, -22px);
841 }
842}
843
844/* A layer rising out of the one below it. In place at rest. */
845.art-rise {
846 animation: art-rise var(--dur, 7s) cubic-bezier(0.2, 0.8, 0.3, 1) infinite both;
847 animation-delay: var(--delay, 0s);
848}
849@keyframes art-rise {
850 0% {
851 opacity: 0;
852 transform: translateY(14px);
853 }
854 10%,
855 70% {
856 opacity: 1;
857 transform: none;
858 }
859 86%,
860 100% {
861 opacity: 0;
862 transform: none;
863 }
864}
865
866/* A soft glow breathing around a point. */
867.art-pulse {
868 transform-box: fill-box;
869 transform-origin: center;
870 animation: art-pulse var(--dur, 4s) ease-in-out infinite;
871 animation-delay: var(--delay, 0s);
872}
873@keyframes art-pulse {
874 0%,
875 100% {
876 transform: scale(0.8);
877 opacity: 1;
878 }
879 50% {
880 transform: scale(1.5);
881 opacity: 0.35;
882 }
883}
884
885/* Drawings off-screen hold still (components/art.tsx, Live). */
886[data-paused] * {
887 animation-play-state: paused !important;
888}
889/*
890 * Each drawing on its own layer, so a frame of its motion repaints only the
891 * drawing, not the page around it or the blurred header passing over it.
892 */
893[data-live] {
894 will-change: transform;
895}
896
897/* The landing page's steps, lit in turn along their line. */
898/* The ring is drawn once and only fades and grows, so the compositor runs it. */
899.flow-step::after {
900 content: "";
901 position: absolute;
902 inset: 0;
903 border-radius: inherit;
904 box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
905 opacity: 0;
906 pointer-events: none;
907 animation: flow-step 10s ease-in-out infinite both;
908 animation-delay: var(--delay, 0s);
909}
910@keyframes flow-step {
911 0%,
912 100% {
913 opacity: 0;
914 transform: scale(0.82);
915 }
916 6% {
917 opacity: 1;
918 transform: none;
919 }
920 16% {
921 opacity: 0;
922 transform: none;
923 }
924}
925.flow-runner {
926 animation: flow-runner 10s cubic-bezier(0.45, 0, 0.35, 1) infinite both;
927}
928@keyframes flow-runner {
929 0% {
930 transform: translateX(0);
931 opacity: 0;
932 }
933 4%,
934 76% {
935 opacity: 1;
936 }
937 80%,
938 100% {
939 transform: translateX(100%);
940 opacity: 0;
941 }
942}
943@media (prefers-reduced-motion: reduce) {
944 .flow-runner {
945 display: none;
946 }
947}
948
949/* The sidebar trading one far-side menu for another, in place. */
950@keyframes g1t-swap {
951 from {
952 opacity: 0;
953 transform: translateX(10px);
954 }
955 to {
956 opacity: 1;
957 transform: none;
958 }
959}
960
961/*
962 * Behind the landing page's opening: the same dark gray, with a faint
963 * lavender and peach glow low on the horizon and a few stars.
964 */
965.dusk {
966 background:
967 radial-gradient(1px 1px at 12% 18%, rgb(255 255 255 / 0.35), transparent),
968 radial-gradient(1px 1px at 27% 8%, rgb(255 255 255 / 0.25), transparent),
969 radial-gradient(1px 1px at 63% 12%, rgb(255 255 255 / 0.3), transparent),
970 radial-gradient(1.5px 1.5px at 90% 9%, rgb(255 255 255 / 0.35), transparent),
971 radial-gradient(60% 35% at 50% 100%, rgb(255 189 140 / 0.08), transparent 70%),
972 radial-gradient(80% 45% at 50% 100%, rgb(182 168 255 / 0.1), transparent 75%),
973 var(--color-bg);
974}
975/* A planet rising behind the horizon, its lit edge catching the glow. */
976.dusk-planet {
977 background: radial-gradient(circle at 70% 30%, rgb(182 168 255 / 0.12), rgb(255 189 140 / 0.04) 45%, transparent 70%);
978 box-shadow: inset 14px -8px 50px rgb(255 214 186 / 0.07);
979}
980
981/*
982 * On a phone, every field's text is at least 16px: iOS zooms the page into
983 * a field set smaller as soon as it is focused. Unlayered, so it wins over
984 * a field's text-sm utility.
985 */
986@media (max-width: 639px) {
987 input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]),
988 textarea,
989 select {
990 font-size: max(16px, 1em);
991 }
992}
993
994/* A card's Markdown preview in chat (components/chat/card.tsx): the same Markdown, smaller and tighter. */
995.card-md .prose {
996 @apply text-[0.8125rem] leading-relaxed;
997}
998.card-md .prose > * + * {
999 @apply mt-2;
1000}
1001.card-md .prose p,
1002.card-md .prose li {
1003 @apply text-[0.8125rem] text-fg/85;
1004}
1005.card-md .prose h1,
1006.card-md .prose h2,
1007.card-md .prose h3,
1008.card-md .prose h4 {
1009 @apply mt-3 border-0 pb-0 text-sm font-semibold;
1010}
1011.card-md .prose ul,
1012.card-md .prose ol {
1013 @apply space-y-0.5 pl-5;
1014}
1015.card-md .prose pre {
1016 @apply p-2.5 text-xs leading-5;
1017}
1018
1019/*
1020 * The Docs page editor (components/docs/editor.tsx) is BlockNote with its
1021 * shadcn components. Their classes are generated from the package, and
1022 * shadcn's token names map onto g1t's. `accent` and `muted` mean
1023 * something else in shadcn (a hover fill, a quiet fill), so inside the
1024 * editor they are redefined; g1t's own blocks there use the g1t tokens
1025 * directly.
1026 */
1027@source "../../../node_modules/@blocknote/shadcn/dist";
1028
1029@theme {
1030 --color-background: var(--color-bg);
1031 --color-foreground: var(--color-fg);
1032 --color-popover: var(--color-surface);
1033 --color-popover-foreground: var(--color-fg);
1034 --color-card: var(--color-surface);
1035 --color-card-foreground: var(--color-fg);
1036 --color-primary: var(--color-accent);
1037 --color-primary-foreground: var(--color-bg);
1038 --color-secondary: var(--color-raised);
1039 --color-secondary-foreground: var(--color-fg);
1040 --color-muted-foreground: var(--color-muted);
1041 --color-accent-foreground: var(--color-fg);
1042 --color-destructive: var(--color-danger);
1043 --color-destructive-foreground: var(--color-bg);
1044 --color-border: var(--color-line);
1045 --color-input: var(--color-line);
1046 --color-ring: var(--color-accent);
1047}
1048
1049.bn-shadcn {
1050 --color-accent: var(--color-raised);
1051 --color-muted: var(--color-raised);
1052 --bn-colors-editor-background: transparent;
1053 --bn-colors-editor-text: var(--color-fg);
1054 --bn-colors-menu-background: var(--color-surface);
1055 --bn-colors-menu-text: var(--color-fg);
1056 --bn-colors-hovered-background: var(--color-raised);
1057 --bn-colors-side-menu: var(--color-faint);
1058 --bn-colors-border: var(--color-line);
1059 --bn-border-radius: 8px;
1060}
1061
1062.g1t-editor .bn-editor {
1063 font-family: var(--g1t-font-sans);
1064 font-size: 1rem;
1065 line-height: 1.65;
1066 background: transparent;
1067 color: var(--color-fg);
1068}
1069
1070/* The drag handle and + sit in the margin, so the text lines up with the title. */
1071@media (min-width: 768px) {
1072 .g1t-editor .bn-container {
1073 margin-inline: -54px;
1074 }
1075}
1076
1077.g1t-editor .bn-block-content[data-content-type="heading"] h1,
1078.g1t-editor .bn-block-content[data-content-type="heading"] h2,
1079.g1t-editor .bn-block-content[data-content-type="heading"] h3 {
1080 font-family: var(--g1t-font-display);
1081 letter-spacing: -0.012em;
1082}
1083
1084.g1t-editor .bn-inline-content code {
1085 font-family: var(--g1t-font-mono);
1086 font-size: 0.875em;
1087 padding: 0.1em 0.35em;
1088 border-radius: 4px;
1089 background: var(--color-raised);
1090}
1091
1092.g1t-editor .bn-block-content[data-content-type="codeBlock"] {
1093 border-radius: 10px;
1094 background: var(--color-surface);
1095 border: 1px solid var(--color-line);
1096}
1097
1098.g1t-editor .bn-thread-mark {
1099 background: color-mix(in srgb, var(--color-warn) 22%, transparent);
1100 border-bottom: 2px solid color-mix(in srgb, var(--color-warn) 60%, transparent);
1101}
1102
1103.g1t-editor a {
1104 color: var(--color-accent);
1105}
1106
1107/* A page read before the editor loads: the same measure as the editor. */
1108.docs-read .prose {
1109 font-size: 1rem;
1110 line-height: 1.65;
1111}
1112
1113/* The chat composer (components/chat/editor.tsx): formatting as messages show it. */
1114.chat-editor {
1115 white-space: pre-wrap;
1116 word-break: break-word;
1117 min-height: 1.5rem;
1118}
1119.chat-editor p.is-editor-empty:first-child::before {
1120 content: attr(data-placeholder);
1121 float: left;
1122 height: 0;
1123 pointer-events: none;
1124 color: var(--color-faint);
1125}
1126.chat-editor strong {
1127 @apply font-semibold text-fg;
1128}
1129.chat-editor s {
1130 @apply text-muted;
1131}
1132.chat-editor a {
1133 @apply text-accent underline underline-offset-2;
1134}
1135.chat-editor code {
1136 @apply rounded-[4px] border border-line bg-raised px-1 py-px font-mono text-[0.84em] text-fg-soft;
1137}
1138.chat-editor pre {
1139 @apply my-1 rounded-md border border-line bg-bg px-3 py-2 font-mono text-[0.8125rem] leading-relaxed text-fg-soft;
1140}
1141.chat-editor pre code {
1142 @apply border-0 bg-transparent p-0 text-[1em];
1143}
1144.chat-editor ul {
1145 @apply list-disc pl-5 marker:text-faint;
1146}
1147.chat-editor ol {
1148 @apply list-decimal pl-5 marker:text-faint;
1149}
1150.chat-editor li > p {
1151 @apply pl-0.5;
1152}
1153.chat-editor blockquote {
1154 @apply my-0.5 border-l-2 border-line-strong pl-3 text-muted;
1155}
1156
1157/*
1158 * Colour in a job's log (lib/ansi.ts): the 16 terminal colours, for text
1159 * (`ansi-fg-N`) and its background (`ansi-bg-N`), each a pair that reads
1160 * on the log's background in the light theme and the dark one, as
1161 * GitHub's log viewer draws them. `inverse` with no colour named swaps
1162 * the text's and the background's.
1163 */
1164:root {
1165 --ansi-0: light-dark(#24292f, #6e7681);
1166 --ansi-1: light-dark(#cf222e, #ff7b72);
1167 --ansi-2: light-dark(#116329, #3fb950);
1168 --ansi-3: light-dark(#7d4e00, #d29922);
1169 --ansi-4: light-dark(#0969da, #58a6ff);
1170 --ansi-5: light-dark(#8250df, #bc8cff);
1171 --ansi-6: light-dark(#1b7c83, #39c5cf);
1172 --ansi-7: light-dark(#6e7781, #b1bac4);
1173 --ansi-8: light-dark(#57606a, #8b949e);
1174 --ansi-9: light-dark(#a40e26, #ffa198);
1175 --ansi-10: light-dark(#1a7f37, #56d364);
1176 --ansi-11: light-dark(#633c01, #e3b341);
1177 --ansi-12: light-dark(#218bff, #79c0ff);
1178 --ansi-13: light-dark(#a475f9, #d2a8ff);
1179 --ansi-14: light-dark(#3192aa, #56d4dd);
1180 --ansi-15: light-dark(#8c959f, #ffffff);
1181}
1182@layer components {
1183 .ansi-fg-0 { color: var(--ansi-0); }
1184 .ansi-fg-1 { color: var(--ansi-1); }
1185 .ansi-fg-2 { color: var(--ansi-2); }
1186 .ansi-fg-3 { color: var(--ansi-3); }
1187 .ansi-fg-4 { color: var(--ansi-4); }
1188 .ansi-fg-5 { color: var(--ansi-5); }
1189 .ansi-fg-6 { color: var(--ansi-6); }
1190 .ansi-fg-7 { color: var(--ansi-7); }
1191 .ansi-fg-8 { color: var(--ansi-8); }
1192 .ansi-fg-9 { color: var(--ansi-9); }
1193 .ansi-fg-10 { color: var(--ansi-10); }
1194 .ansi-fg-11 { color: var(--ansi-11); }
1195 .ansi-fg-12 { color: var(--ansi-12); }
1196 .ansi-fg-13 { color: var(--ansi-13); }
1197 .ansi-fg-14 { color: var(--ansi-14); }
1198 .ansi-fg-15 { color: var(--ansi-15); }
1199 .ansi-bg-0 { background-color: var(--ansi-0); }
1200 .ansi-bg-1 { background-color: var(--ansi-1); }
1201 .ansi-bg-2 { background-color: var(--ansi-2); }
1202 .ansi-bg-3 { background-color: var(--ansi-3); }
1203 .ansi-bg-4 { background-color: var(--ansi-4); }
1204 .ansi-bg-5 { background-color: var(--ansi-5); }
1205 .ansi-bg-6 { background-color: var(--ansi-6); }
1206 .ansi-bg-7 { background-color: var(--ansi-7); }
1207 .ansi-bg-8 { background-color: var(--ansi-8); }
1208 .ansi-bg-9 { background-color: var(--ansi-9); }
1209 .ansi-bg-10 { background-color: var(--ansi-10); }
1210 .ansi-bg-11 { background-color: var(--ansi-11); }
1211 .ansi-bg-12 { background-color: var(--ansi-12); }
1212 .ansi-bg-13 { background-color: var(--ansi-13); }
1213 .ansi-bg-14 { background-color: var(--ansi-14); }
1214 .ansi-bg-15 { background-color: var(--ansi-15); }
1215 .ansi-fg-inverse { color: var(--color-bg); }
1216 .ansi-bg-inverse { background-color: var(--color-fg); }
1217}