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