Skip to content
1,057 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 * Appearance (lib/theme.ts): with no choice, or Auto, the page follows
183 * the system; a choice of Light or Dark is data-theme on <html>.
184 */
185html {
186 color-scheme: light dark;
187 scroll-behavior: smooth;
188 /* A tap shows the control's own active state, not the browser's grey flash. */
189 -webkit-tap-highlight-color: transparent;
190 /* Phones: no text inflation on rotation. */
191 -webkit-text-size-adjust: 100%;
192 text-size-adjust: 100%;
193}
194html[data-theme="light"] {
195 color-scheme: light;
196}
197html[data-theme="dark"] {
198 color-scheme: dark;
199}
200
201/*
202 * The app's frame (components/shell.tsx), set on <body data-frame="app">:
203 * the page fills the window beside the rail and the sidebar, and on a
204 * phone it fills the window above the bottom bar, flat, as on a computer.
205 * What sticks to the top or the bottom of a page reads these:
206 * --frame-top what is above the page: a phone's status bar, where it overlaps
207 * --topbar-h that gap plus the top bar: where sticky things sit
208 * --tabbar-h what the bottom takes: on a phone, the bar and the home indicator under it
209 * --page-h the height left for a page that fills the window (Chat)
210 * The public frame keeps a plain header 3.5rem tall and nothing at the bottom.
211 */
212:root {
213 --frame-top: 0px;
214 --tabbar-h: 0px;
215 --topbar-h: 3.5rem;
216 --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
217}
218body[data-frame="app"] {
219 --topbar-h: calc(var(--frame-top) + 3.5rem);
220 --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
221}
222@media (max-width: 767px) {
223 body[data-frame="app"] {
224 --frame-top: env(safe-area-inset-top);
225 /* The bottom bar: 56px tall along the bottom edge, and the home indicator's room under it. */
226 --tabbar-h: calc(3.5rem + env(safe-area-inset-bottom));
227 }
228}
229
230/*
231 * Phones: a field under 16px makes iOS zoom the page when it is focused, so
232 * every field is at least that; and nothing scrolls the page sideways.
233 */
234@media (max-width: 767px) {
235 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
236 textarea,
237 select {
238 font-size: max(16px, 1em) !important;
239 }
240 body {
241 overflow-x: clip;
242 }
243}
244
245body {
246 @apply bg-bg font-sans text-fg antialiased;
247}
248body[data-frame="app"] {
249 @apply bg-shell;
250}
251
252::selection {
253 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
254}
255
256/* In the base layer, so a component can opt out with a utility. */
257@layer base {
258 :focus-visible {
259 outline: 2px solid var(--color-accent);
260 outline-offset: 2px;
261 }
262
263 /*
264 * A grid's items may shrink below their content. Without this, one long
265 * line that does not wrap (a command, a commit hash, an address) widens
266 * the whole column, and on a phone the page scrolls sideways.
267 */
268 .grid > * {
269 min-width: 0;
270 }
271
272 /*
273 * Headlines are set in Bricolage Grotesque, its optical size following
274 * the font size; small section labels that happen to be headings stay in
275 * the text face. A utility on the element still wins.
276 */
277 :where(h1, h2),
278 :where(h3):is(.text-xl, .text-2xl, .text-3xl, .text-4xl) {
279 font-family: var(--font-display);
280 font-optical-sizing: auto;
281 }
282 :where(h2):is(.text-xs, .text-sm, .text-base) {
283 font-family: var(--font-sans);
284 }
285
286 /* Checkboxes, radios and ranges in g1t's own colour, not the browser's. */
287 :root {
288 accent-color: var(--color-accent);
289 }
290
291 /*
292 * Number fields have no spin buttons: the arrow keys still step them, and
293 * their digits line up in columns.
294 */
295 input[type="number"] {
296 -moz-appearance: textfield;
297 appearance: textfield;
298 font-variant-numeric: tabular-nums;
299 }
300 input[type="number"]::-webkit-inner-spin-button,
301 input[type="number"]::-webkit-outer-spin-button {
302 -webkit-appearance: none;
303 appearance: none;
304 margin: 0;
305 }
306
307 /*
308 * Date and time fields: the picker's icon is drawn by the browser in the
309 * page's color-scheme (set on <html> above), so it is right in both
310 * themes; it sits quiet until hovered. The part being edited is marked
311 * in the accent, not the system's blue.
312 */
313 input:is([type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"]) {
314 font-variant-numeric: tabular-nums;
315 }
316 ::-webkit-calendar-picker-indicator {
317 cursor: pointer;
318 border-radius: 0.25rem;
319 opacity: 0.55;
320 transition: opacity 150ms;
321 }
322 ::-webkit-calendar-picker-indicator:hover,
323 ::-webkit-calendar-picker-indicator:focus-visible {
324 opacity: 1;
325 }
326 ::-webkit-datetime-edit-fields-wrapper {
327 padding: 0;
328 }
329 ::-webkit-datetime-edit-text {
330 color: var(--color-faint);
331 }
332 ::-webkit-datetime-edit-year-field:focus,
333 ::-webkit-datetime-edit-month-field:focus,
334 ::-webkit-datetime-edit-day-field:focus,
335 ::-webkit-datetime-edit-hour-field:focus,
336 ::-webkit-datetime-edit-minute-field:focus,
337 ::-webkit-datetime-edit-second-field:focus,
338 ::-webkit-datetime-edit-millisecond-field:focus,
339 ::-webkit-datetime-edit-meridiem-field:focus {
340 border-radius: 0.1875rem;
341 background-color: color-mix(in srgb, var(--color-accent) 25%, transparent);
342 color: var(--color-fg);
343 }
344
345 /* A colour field is a rounded swatch with a hairline, not a grey button. */
346 input[type="color"] {
347 -webkit-appearance: none;
348 appearance: none;
349 cursor: pointer;
350 padding: 0.1875rem;
351 border: 1px solid var(--color-line);
352 border-radius: 0.375rem;
353 background-color: var(--color-bg);
354 }
355 input[type="color"]:hover {
356 border-color: var(--color-line-strong);
357 }
358 input[type="color"]::-webkit-color-swatch-wrapper {
359 padding: 0;
360 }
361 input[type="color"]::-webkit-color-swatch {
362 border: none;
363 border-radius: 0.25rem;
364 }
365 input[type="color"]::-moz-color-swatch {
366 border: none;
367 border-radius: 0.25rem;
368 }
369
370 /* A search field's clear button is the browser's own: g1t's fields don't show it. */
371 input[type="search"]::-webkit-search-cancel-button,
372 input[type="search"]::-webkit-search-decoration {
373 -webkit-appearance: none;
374 appearance: none;
375 }
376
377 /*
378 * A <summary> that keeps the browser's disclosure triangle has it quiet,
379 * in the faint text colour; one that draws its own hides it with list-none.
380 */
381 summary {
382 cursor: pointer;
383 }
384 summary::marker {
385 color: var(--color-faint);
386 }
387 summary::-webkit-details-marker {
388 color: var(--color-faint);
389 }
390}
391
392@media (prefers-reduced-motion: reduce) {
393 *,
394 *::before,
395 *::after {
396 animation: none !important;
397 scroll-behavior: auto !important;
398 }
399}
400
401/* Rendered markdown: READMEs, briefs and docs. */
402.prose > :first-child {
403 margin-top: 0;
404 padding-top: 0;
405}
406.prose {
407 @apply leading-7 text-fg;
408}
409.prose > * + * {
410 @apply mt-4;
411}
412.prose h1 {
413 @apply mt-0 text-3xl font-semibold tracking-tight;
414}
415.prose h2 {
416 @apply mt-10 border-b border-line pb-2 text-xl font-semibold tracking-tight;
417}
418.prose h3 {
419 @apply mt-8 text-base font-semibold;
420}
421.prose p,
422.prose li {
423 @apply text-[0.9375rem] text-fg/90;
424}
425.prose a {
426 @apply text-accent underline decoration-accent/40 underline-offset-4 hover:decoration-accent;
427}
428.prose ul {
429 @apply list-disc space-y-1.5 pl-6;
430}
431.prose ol {
432 @apply list-decimal space-y-1.5 pl-6;
433}
434.prose code {
435 @apply rounded bg-raised px-1.5 py-0.5 font-mono text-[0.85em];
436}
437.prose pre {
438 @apply overflow-x-auto rounded-lg border border-line bg-surface p-4 font-mono text-sm leading-6;
439}
440.prose pre code {
441 @apply bg-transparent p-0;
442}
443.prose blockquote {
444 @apply border-l-2 border-line-strong pl-4 text-muted;
445}
446.prose table {
447 @apply w-full border-collapse text-sm;
448}
449.prose th {
450 @apply border-b border-line-strong px-3 py-2 text-left font-medium text-muted;
451}
452.prose td {
453 @apply border-b border-line px-3 py-2 align-top;
454}
455.prose hr {
456 @apply border-line;
457}
458.prose h4 {
459 @apply mt-6 text-sm font-semibold;
460}
461.prose li.task-list-item {
462 @apply list-none;
463}
464.prose ul.contains-task-list {
465 @apply pl-1;
466}
467.prose kbd {
468 @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)];
469}
470.prose del {
471 @apply text-muted;
472}
473.prose details {
474 @apply rounded-lg border border-line px-4 py-2;
475}
476.prose summary {
477 @apply cursor-pointer font-medium;
478}
479.prose img {
480 @apply inline max-w-full;
481}
482.prose section[data-footnotes] {
483 @apply mt-10 border-t border-line pt-4 text-sm text-muted;
484}
485.prose a[data-footnote-ref] {
486 @apply no-underline;
487}
488
489/* Syntax-highlighted code from Shiki. */
490.shiki {
491 @apply bg-transparent! font-mono text-sm leading-6;
492}
493.shiki code {
494 counter-reset: line;
495 display: block;
496}
497.shiki .line::before {
498 counter-increment: line;
499 content: counter(line);
500 @apply mr-5 inline-block w-10 text-right text-faint select-none;
501}
502
503/*
504 * Artwork (components/art.tsx). Every piece loops on its drawing's
505 * `--dur` and starts at its own `--delay`, so the parts of one drawing
506 * stay in step. Only transforms, opacity and stroke offsets move. The
507 * resting style of each class is the finished picture: that is what
508 * prefers-reduced-motion shows, since the rule above stops every
509 * animation.
510 */
511.art-flow {
512 animation: dash 1.6s linear infinite;
513}
514
515/* Something travelling along a track (pathLength 100). Unseen at rest. */
516.art-packet {
517 opacity: 0;
518 stroke-dasharray: 4 200;
519 stroke-dashoffset: 4;
520 animation: art-travel var(--dur, 3s) cubic-bezier(0.45, 0, 0.35, 1) infinite;
521 animation-delay: var(--delay, 0s);
522}
523@keyframes art-travel {
524 0% {
525 stroke-dashoffset: 4;
526 opacity: 0;
527 }
528 3% {
529 opacity: 1;
530 }
531 32% {
532 opacity: 1;
533 }
534 36%,
535 100% {
536 stroke-dashoffset: -100;
537 opacity: 0;
538 }
539}
540
541/* A face that lights up for most of the loop. Lit at rest. */
542.art-lit {
543 opacity: 0.85;
544 animation: art-lit var(--dur, 8s) ease-in-out infinite both;
545 animation-delay: var(--delay, 0s);
546}
547@keyframes art-lit {
548 0%,
549 4% {
550 opacity: 0;
551 }
552 12%,
553 70% {
554 opacity: 0.9;
555 }
556 88%,
557 100% {
558 opacity: 0;
559 }
560}
561
562/* A short flash, as a check runs over a layer. Unseen at rest. */
563.art-scan {
564 opacity: 0;
565 animation: art-scan var(--dur, 8s) ease-out infinite both;
566 animation-delay: var(--delay, 0s);
567}
568@keyframes art-scan {
569 0% {
570 opacity: 0;
571 }
572 4% {
573 opacity: 1;
574 }
575 16%,
576 100% {
577 opacity: 0;
578 }
579}
580
581/* A line that draws itself, then fades. Drawn at rest. */
582.art-draw {
583 stroke-dasharray: 100;
584 stroke-dashoffset: 0;
585 animation: art-draw var(--dur, 8s) ease-in-out infinite both;
586 animation-delay: var(--delay, 0s);
587}
588@keyframes art-draw {
589 0% {
590 stroke-dashoffset: 100;
591 opacity: 1;
592 }
593 14%,
594 70% {
595 stroke-dashoffset: 0;
596 opacity: 1;
597 }
598 88%,
599 100% {
600 stroke-dashoffset: 0;
601 opacity: 0;
602 }
603}
604
605/* Something that appears, stays, and goes. Shown at rest. */
606.art-pop {
607 animation: art-pop var(--dur, 8s) ease-out infinite both;
608 animation-delay: var(--delay, 0s);
609}
610@keyframes art-pop {
611 0% {
612 opacity: 0;
613 transform: translateY(4px);
614 }
615 6%,
616 72% {
617 opacity: 1;
618 transform: none;
619 }
620 86%,
621 100% {
622 opacity: 0;
623 transform: none;
624 }
625}
626
627/* A layer dropping onto a stack. In place at rest. */
628.art-drop {
629 animation: art-drop var(--dur, 7s) cubic-bezier(0.2, 0.9, 0.3, 1.2) infinite both;
630 animation-delay: var(--delay, 0s);
631}
632@keyframes art-drop {
633 0% {
634 opacity: 0;
635 transform: translateY(-26px);
636 }
637 8%,
638 74% {
639 opacity: 1;
640 transform: none;
641 }
642 88%,
643 100% {
644 opacity: 0;
645 transform: none;
646 }
647}
648
649/* The layer that fails: drops, shakes, and is sent back. */
650.art-eject {
651 animation: art-eject var(--dur, 7s) ease-in-out infinite both;
652 animation-delay: var(--delay, 0s);
653}
654@keyframes art-eject {
655 0% {
656 opacity: 0;
657 transform: translateY(-26px);
658 }
659 8%,
660 30% {
661 opacity: 1;
662 transform: none;
663 }
664 33% {
665 transform: translateX(-3px);
666 }
667 36% {
668 transform: translateX(3px);
669 }
670 39% {
671 transform: translateX(-2px);
672 }
673 42% {
674 opacity: 1;
675 transform: none;
676 }
677 56%,
678 100% {
679 opacity: 0;
680 transform: translate(-40px, -22px);
681 }
682}
683
684/* A layer rising out of the one below it. In place at rest. */
685.art-rise {
686 animation: art-rise var(--dur, 7s) cubic-bezier(0.2, 0.8, 0.3, 1) infinite both;
687 animation-delay: var(--delay, 0s);
688}
689@keyframes art-rise {
690 0% {
691 opacity: 0;
692 transform: translateY(14px);
693 }
694 10%,
695 70% {
696 opacity: 1;
697 transform: none;
698 }
699 86%,
700 100% {
701 opacity: 0;
702 transform: none;
703 }
704}
705
706/* A soft glow breathing around a point. */
707.art-pulse {
708 transform-box: fill-box;
709 transform-origin: center;
710 animation: art-pulse var(--dur, 4s) ease-in-out infinite;
711 animation-delay: var(--delay, 0s);
712}
713@keyframes art-pulse {
714 0%,
715 100% {
716 transform: scale(0.8);
717 opacity: 1;
718 }
719 50% {
720 transform: scale(1.5);
721 opacity: 0.35;
722 }
723}
724
725/* Drawings off-screen hold still (components/art.tsx, Live). */
726[data-paused] * {
727 animation-play-state: paused !important;
728}
729/*
730 * Each drawing on its own layer, so a frame of its motion repaints only the
731 * drawing, not the page around it or the blurred header passing over it.
732 */
733[data-live] {
734 will-change: transform;
735}
736
737/* The landing page's steps, lit in turn along their line. */
738/* The ring is drawn once and only fades and grows, so the compositor runs it. */
739.flow-step::after {
740 content: "";
741 position: absolute;
742 inset: 0;
743 border-radius: inherit;
744 box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
745 opacity: 0;
746 pointer-events: none;
747 animation: flow-step 10s ease-in-out infinite both;
748 animation-delay: var(--delay, 0s);
749}
750@keyframes flow-step {
751 0%,
752 100% {
753 opacity: 0;
754 transform: scale(0.82);
755 }
756 6% {
757 opacity: 1;
758 transform: none;
759 }
760 16% {
761 opacity: 0;
762 transform: none;
763 }
764}
765.flow-runner {
766 animation: flow-runner 10s cubic-bezier(0.45, 0, 0.35, 1) infinite both;
767}
768@keyframes flow-runner {
769 0% {
770 transform: translateX(0);
771 opacity: 0;
772 }
773 4%,
774 76% {
775 opacity: 1;
776 }
777 80%,
778 100% {
779 transform: translateX(100%);
780 opacity: 0;
781 }
782}
783@media (prefers-reduced-motion: reduce) {
784 .flow-runner {
785 display: none;
786 }
787}
788
789/* The sidebar trading one far-side menu for another, in place. */
790@keyframes g1t-swap {
791 from {
792 opacity: 0;
793 transform: translateX(10px);
794 }
795 to {
796 opacity: 1;
797 transform: none;
798 }
799}
800
801/*
802 * Behind the landing page's opening: the same dark gray, with a faint
803 * lavender and peach glow low on the horizon and a few stars.
804 */
805.dusk {
806 background:
807 radial-gradient(1px 1px at 12% 18%, rgb(255 255 255 / 0.35), transparent),
808 radial-gradient(1px 1px at 27% 8%, rgb(255 255 255 / 0.25), transparent),
809 radial-gradient(1px 1px at 63% 12%, rgb(255 255 255 / 0.3), transparent),
810 radial-gradient(1.5px 1.5px at 90% 9%, rgb(255 255 255 / 0.35), transparent),
811 radial-gradient(60% 35% at 50% 100%, rgb(255 189 140 / 0.08), transparent 70%),
812 radial-gradient(80% 45% at 50% 100%, rgb(182 168 255 / 0.1), transparent 75%),
813 var(--color-bg);
814}
815/* A planet rising behind the horizon, its lit edge catching the glow. */
816.dusk-planet {
817 background: radial-gradient(circle at 70% 30%, rgb(182 168 255 / 0.12), rgb(255 189 140 / 0.04) 45%, transparent 70%);
818 box-shadow: inset 14px -8px 50px rgb(255 214 186 / 0.07);
819}
820
821/*
822 * On a phone, every field's text is at least 16px: iOS zooms the page into
823 * a field set smaller as soon as it is focused. Unlayered, so it wins over
824 * a field's text-sm utility.
825 */
826@media (max-width: 639px) {
827 input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]),
828 textarea,
829 select {
830 font-size: max(16px, 1em);
831 }
832}
833
834/* A card's Markdown preview in chat (components/chat/card.tsx): the same Markdown, smaller and tighter. */
835.card-md .prose {
836 @apply text-[0.8125rem] leading-relaxed;
837}
838.card-md .prose > * + * {
839 @apply mt-2;
840}
841.card-md .prose p,
842.card-md .prose li {
843 @apply text-[0.8125rem] text-fg/85;
844}
845.card-md .prose h1,
846.card-md .prose h2,
847.card-md .prose h3,
848.card-md .prose h4 {
849 @apply mt-3 border-0 pb-0 text-sm font-semibold;
850}
851.card-md .prose ul,
852.card-md .prose ol {
853 @apply space-y-0.5 pl-5;
854}
855.card-md .prose pre {
856 @apply p-2.5 text-xs leading-5;
857}
858
859/*
860 * The Docs page editor (components/docs/editor.tsx) is BlockNote with its
861 * shadcn components. Their classes are generated from the package, and
862 * shadcn's token names map onto g1t's. `accent` and `muted` mean
863 * something else in shadcn (a hover fill, a quiet fill), so inside the
864 * editor they are redefined; g1t's own blocks there use the g1t tokens
865 * directly.
866 */
867@source "../../../node_modules/@blocknote/shadcn/dist";
868
869@theme {
870 --color-background: var(--color-bg);
871 --color-foreground: var(--color-fg);
872 --color-popover: var(--color-surface);
873 --color-popover-foreground: var(--color-fg);
874 --color-card: var(--color-surface);
875 --color-card-foreground: var(--color-fg);
876 --color-primary: var(--color-accent);
877 --color-primary-foreground: var(--color-bg);
878 --color-secondary: var(--color-raised);
879 --color-secondary-foreground: var(--color-fg);
880 --color-muted-foreground: var(--color-muted);
881 --color-accent-foreground: var(--color-fg);
882 --color-destructive: var(--color-danger);
883 --color-destructive-foreground: var(--color-bg);
884 --color-border: var(--color-line);
885 --color-input: var(--color-line);
886 --color-ring: var(--color-accent);
887}
888
889.bn-shadcn {
890 --color-accent: var(--color-raised);
891 --color-muted: var(--color-raised);
892 --bn-colors-editor-background: transparent;
893 --bn-colors-editor-text: var(--color-fg);
894 --bn-colors-menu-background: var(--color-surface);
895 --bn-colors-menu-text: var(--color-fg);
896 --bn-colors-hovered-background: var(--color-raised);
897 --bn-colors-side-menu: var(--color-faint);
898 --bn-colors-border: var(--color-line);
899 --bn-border-radius: 8px;
900}
901
902.g1t-editor .bn-editor {
903 font-family: var(--g1t-font-sans);
904 font-size: 1rem;
905 line-height: 1.65;
906 background: transparent;
907 color: var(--color-fg);
908}
909
910/* The drag handle and + sit in the margin, so the text lines up with the title. */
911@media (min-width: 768px) {
912 .g1t-editor .bn-container {
913 margin-inline: -54px;
914 }
915}
916
917.g1t-editor .bn-block-content[data-content-type="heading"] h1,
918.g1t-editor .bn-block-content[data-content-type="heading"] h2,
919.g1t-editor .bn-block-content[data-content-type="heading"] h3 {
920 font-family: var(--g1t-font-display);
921 letter-spacing: -0.012em;
922}
923
924.g1t-editor .bn-inline-content code {
925 font-family: var(--g1t-font-mono);
926 font-size: 0.875em;
927 padding: 0.1em 0.35em;
928 border-radius: 4px;
929 background: var(--color-raised);
930}
931
932.g1t-editor .bn-block-content[data-content-type="codeBlock"] {
933 border-radius: 10px;
934 background: var(--color-surface);
935 border: 1px solid var(--color-line);
936}
937
938.g1t-editor .bn-thread-mark {
939 background: color-mix(in srgb, var(--color-warn) 22%, transparent);
940 border-bottom: 2px solid color-mix(in srgb, var(--color-warn) 60%, transparent);
941}
942
943.g1t-editor a {
944 color: var(--color-accent);
945}
946
947/* A page read before the editor loads: the same measure as the editor. */
948.docs-read .prose {
949 font-size: 1rem;
950 line-height: 1.65;
951}
952
953/* The chat composer (components/chat/editor.tsx): formatting as messages show it. */
954.chat-editor {
955 white-space: pre-wrap;
956 word-break: break-word;
957 min-height: 1.5rem;
958}
959.chat-editor p.is-editor-empty:first-child::before {
960 content: attr(data-placeholder);
961 float: left;
962 height: 0;
963 pointer-events: none;
964 color: var(--color-faint);
965}
966.chat-editor strong {
967 @apply font-semibold text-fg;
968}
969.chat-editor s {
970 @apply text-muted;
971}
972.chat-editor a {
973 @apply text-accent underline underline-offset-2;
974}
975.chat-editor code {
976 @apply rounded-[4px] border border-line bg-raised px-1 py-px font-mono text-[0.84em] text-fg-soft;
977}
978.chat-editor pre {
979 @apply my-1 rounded-md border border-line bg-bg px-3 py-2 font-mono text-[0.8125rem] leading-relaxed text-fg-soft;
980}
981.chat-editor pre code {
982 @apply border-0 bg-transparent p-0 text-[1em];
983}
984.chat-editor ul {
985 @apply list-disc pl-5 marker:text-faint;
986}
987.chat-editor ol {
988 @apply list-decimal pl-5 marker:text-faint;
989}
990.chat-editor li > p {
991 @apply pl-0.5;
992}
993.chat-editor blockquote {
994 @apply my-0.5 border-l-2 border-line-strong pl-3 text-muted;
995}
996
997/*
998 * Colour in a job's log (lib/ansi.ts): the 16 terminal colours, for text
999 * (`ansi-fg-N`) and its background (`ansi-bg-N`), each a pair that reads
1000 * on the log's background in the light theme and the dark one, as
1001 * GitHub's log viewer draws them. `inverse` with no colour named swaps
1002 * the text's and the background's.
1003 */
1004:root {
1005 --ansi-0: light-dark(#24292f, #6e7681);
1006 --ansi-1: light-dark(#cf222e, #ff7b72);
1007 --ansi-2: light-dark(#116329, #3fb950);
1008 --ansi-3: light-dark(#7d4e00, #d29922);
1009 --ansi-4: light-dark(#0969da, #58a6ff);
1010 --ansi-5: light-dark(#8250df, #bc8cff);
1011 --ansi-6: light-dark(#1b7c83, #39c5cf);
1012 --ansi-7: light-dark(#6e7781, #b1bac4);
1013 --ansi-8: light-dark(#57606a, #8b949e);
1014 --ansi-9: light-dark(#a40e26, #ffa198);
1015 --ansi-10: light-dark(#1a7f37, #56d364);
1016 --ansi-11: light-dark(#633c01, #e3b341);
1017 --ansi-12: light-dark(#218bff, #79c0ff);
1018 --ansi-13: light-dark(#a475f9, #d2a8ff);
1019 --ansi-14: light-dark(#3192aa, #56d4dd);
1020 --ansi-15: light-dark(#8c959f, #ffffff);
1021}
1022@layer components {
1023 .ansi-fg-0 { color: var(--ansi-0); }
1024 .ansi-fg-1 { color: var(--ansi-1); }
1025 .ansi-fg-2 { color: var(--ansi-2); }
1026 .ansi-fg-3 { color: var(--ansi-3); }
1027 .ansi-fg-4 { color: var(--ansi-4); }
1028 .ansi-fg-5 { color: var(--ansi-5); }
1029 .ansi-fg-6 { color: var(--ansi-6); }
1030 .ansi-fg-7 { color: var(--ansi-7); }
1031 .ansi-fg-8 { color: var(--ansi-8); }
1032 .ansi-fg-9 { color: var(--ansi-9); }
1033 .ansi-fg-10 { color: var(--ansi-10); }
1034 .ansi-fg-11 { color: var(--ansi-11); }
1035 .ansi-fg-12 { color: var(--ansi-12); }
1036 .ansi-fg-13 { color: var(--ansi-13); }
1037 .ansi-fg-14 { color: var(--ansi-14); }
1038 .ansi-fg-15 { color: var(--ansi-15); }
1039 .ansi-bg-0 { background-color: var(--ansi-0); }
1040 .ansi-bg-1 { background-color: var(--ansi-1); }
1041 .ansi-bg-2 { background-color: var(--ansi-2); }
1042 .ansi-bg-3 { background-color: var(--ansi-3); }
1043 .ansi-bg-4 { background-color: var(--ansi-4); }
1044 .ansi-bg-5 { background-color: var(--ansi-5); }
1045 .ansi-bg-6 { background-color: var(--ansi-6); }
1046 .ansi-bg-7 { background-color: var(--ansi-7); }
1047 .ansi-bg-8 { background-color: var(--ansi-8); }
1048 .ansi-bg-9 { background-color: var(--ansi-9); }
1049 .ansi-bg-10 { background-color: var(--ansi-10); }
1050 .ansi-bg-11 { background-color: var(--ansi-11); }
1051 .ansi-bg-12 { background-color: var(--ansi-12); }
1052 .ansi-bg-13 { background-color: var(--ansi-13); }
1053 .ansi-bg-14 { background-color: var(--ansi-14); }
1054 .ansi-bg-15 { background-color: var(--ansi-15); }
1055 .ansi-fg-inverse { color: var(--color-bg); }
1056 .ansi-bg-inverse { background-color: var(--color-fg); }
1057}