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