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