flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/web/app/app.css

575 lines12,267 bytesCodeBlame
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 --color-bg: var(--g1t-bg);
15 --color-surface: var(--g1t-surface);
16 --color-raised: var(--g1t-raised);
17 --color-line: var(--g1t-line);
18 --color-line-strong: var(--g1t-line-strong);
19
20 --color-fg: var(--g1t-fg);
21 --color-fg-soft: var(--g1t-fg-soft);
22 --color-muted: var(--g1t-muted);
23 --color-faint: var(--g1t-faint);
24
25 --color-accent: var(--g1t-accent);
26 --color-accent-dim: var(--g1t-accent-dim);
27
28 --color-info: var(--g1t-info);
29 --color-merged: var(--g1t-merged);
30 --color-warn: var(--g1t-warn);
31 --color-danger: var(--g1t-danger);
32
33 --animate-fade-up: fade-up 0.6s ease-out both;
34 --animate-dash: dash 1.2s linear infinite;
35
36 /* Menus, popovers and dialogs (components/ui): in quickly, out quicker. */
37 --animate-pop-in: pop-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
38 --animate-pop-out: pop-out 0.1s ease-in forwards;
39 --animate-fade-in: fade-in 0.16s ease-out;
40 --animate-fade-out: fade-out 0.12s ease-in forwards;
41 --animate-dialog-in: dialog-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
42 --animate-dialog-out: dialog-out 0.12s ease-in forwards;
43}
44
45@keyframes pop-in {
46 from {
47 opacity: 0;
48 transform: scale(0.96) translateY(-2px);
49 }
50}
51
52@keyframes pop-out {
53 to {
54 opacity: 0;
55 transform: scale(0.96);
56 }
57}
58
59@keyframes fade-in {
60 from {
61 opacity: 0;
62 }
63}
64
65@keyframes fade-out {
66 to {
67 opacity: 0;
68 }
69}
70
71@keyframes dialog-in {
72 from {
73 opacity: 0;
74 transform: translateY(6px) scale(0.97);
75 }
76}
77
78@keyframes dialog-out {
79 to {
80 opacity: 0;
81 transform: translateY(6px) scale(0.97);
82 }
83}
84
85/* Never from nothing: the first paint already shows the words. */
86@keyframes fade-up {
87 from {
88 opacity: 0.4;
89 transform: translateY(8px);
90 }
91}
92
93@keyframes dash {
94 to {
95 stroke-dashoffset: -16;
96 }
97}
98
99html {
100 color-scheme: dark;
101 scroll-behavior: smooth;
102}
103
104body {
105 @apply bg-bg font-sans text-fg antialiased;
106}
107
108::selection {
109 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
110}
111
112/* In the base layer, so a component can opt out with a utility. */
113@layer base {
114 :focus-visible {
115 outline: 2px solid var(--color-accent);
116 outline-offset: 2px;
117 }
118
119 /*
120 * A grid's items may shrink below their content. Without this, one long
121 * line that does not wrap (a command, a commit hash, an address) widens
122 * the whole column, and on a phone the page scrolls sideways.
123 */
124 .grid > * {
125 min-width: 0;
126 }
127
128 /*
129 * Headlines are set in Bricolage Grotesque, its optical size following
130 * the font size; small section labels that happen to be headings stay in
131 * the text face. A utility on the element still wins.
132 */
133 :where(h1, h2),
134 :where(h3):is(.text-xl, .text-2xl, .text-3xl, .text-4xl) {
135 font-family: var(--font-display);
136 font-optical-sizing: auto;
137 }
138 :where(h2):is(.text-xs, .text-sm, .text-base) {
139 font-family: var(--font-sans);
140 }
141
142 /* Checkboxes, radios and ranges in g1t's own colour, not the browser's. */
143 :root {
144 accent-color: var(--color-accent);
145 }
146}
147
148@media (prefers-reduced-motion: reduce) {
149 *,
150 *::before,
151 *::after {
152 animation: none !important;
153 scroll-behavior: auto !important;
154 }
155}
156
157/* Rendered markdown: READMEs, briefs and docs. */
158.prose > :first-child {
159 margin-top: 0;
160 padding-top: 0;
161}
162.prose {
163 @apply leading-7 text-fg;
164}
165.prose > * + * {
166 @apply mt-4;
167}
168.prose h1 {
169 @apply mt-0 text-3xl font-semibold tracking-tight;
170}
171.prose h2 {
172 @apply mt-10 border-b border-line pb-2 text-xl font-semibold tracking-tight;
173}
174.prose h3 {
175 @apply mt-8 text-base font-semibold;
176}
177.prose p,
178.prose li {
179 @apply text-[0.9375rem] text-fg/90;
180}
181.prose a {
182 @apply text-accent underline decoration-accent/40 underline-offset-4 hover:decoration-accent;
183}
184.prose ul {
185 @apply list-disc space-y-1.5 pl-6;
186}
187.prose ol {
188 @apply list-decimal space-y-1.5 pl-6;
189}
190.prose code {
191 @apply rounded bg-raised px-1.5 py-0.5 font-mono text-[0.85em];
192}
193.prose pre {
194 @apply overflow-x-auto rounded-lg border border-line bg-surface p-4 font-mono text-sm leading-6;
195}
196.prose pre code {
197 @apply bg-transparent p-0;
198}
199.prose blockquote {
200 @apply border-l-2 border-line-strong pl-4 text-muted;
201}
202.prose table {
203 @apply w-full border-collapse text-sm;
204}
205.prose th {
206 @apply border-b border-line-strong px-3 py-2 text-left font-medium text-muted;
207}
208.prose td {
209 @apply border-b border-line px-3 py-2 align-top;
210}
211.prose hr {
212 @apply border-line;
213}
214.prose h4 {
215 @apply mt-6 text-sm font-semibold;
216}
217.prose li.task-list-item {
218 @apply list-none;
219}
220.prose ul.contains-task-list {
221 @apply pl-1;
222}
223.prose kbd {
224 @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)];
225}
226.prose del {
227 @apply text-muted;
228}
229.prose details {
230 @apply rounded-lg border border-line px-4 py-2;
231}
232.prose summary {
233 @apply cursor-pointer font-medium;
234}
235.prose img {
236 @apply inline max-w-full;
237}
238.prose section[data-footnotes] {
239 @apply mt-10 border-t border-line pt-4 text-sm text-muted;
240}
241.prose a[data-footnote-ref] {
242 @apply no-underline;
243}
244
245/* Syntax-highlighted code from Shiki. */
246.shiki {
247 @apply bg-transparent! font-mono text-sm leading-6;
248}
249.shiki code {
250 counter-reset: line;
251 display: block;
252}
253.shiki .line::before {
254 counter-increment: line;
255 content: counter(line);
256 @apply mr-5 inline-block w-10 text-right text-faint select-none;
257}
258
259/*
260 * Artwork (components/art.tsx). Every piece loops on its drawing's
261 * `--dur` and starts at its own `--delay`, so the parts of one drawing
262 * stay in step. Only transforms, opacity and stroke offsets move. The
263 * resting style of each class is the finished picture: that is what
264 * prefers-reduced-motion shows, since the rule above stops every
265 * animation.
266 */
267.art-flow {
268 animation: dash 1.6s linear infinite;
269}
270
271/* Something travelling along a track (pathLength 100). Unseen at rest. */
272.art-packet {
273 opacity: 0;
274 stroke-dasharray: 4 200;
275 stroke-dashoffset: 4;
276 animation: art-travel var(--dur, 3s) cubic-bezier(0.45, 0, 0.35, 1) infinite;
277 animation-delay: var(--delay, 0s);
278}
279@keyframes art-travel {
280 0% {
281 stroke-dashoffset: 4;
282 opacity: 0;
283 }
284 3% {
285 opacity: 1;
286 }
287 32% {
288 opacity: 1;
289 }
290 36%,
291 100% {
292 stroke-dashoffset: -100;
293 opacity: 0;
294 }
295}
296
297/* A face that lights up for most of the loop. Lit at rest. */
298.art-lit {
299 opacity: 0.85;
300 animation: art-lit var(--dur, 8s) ease-in-out infinite both;
301 animation-delay: var(--delay, 0s);
302}
303@keyframes art-lit {
304 0%,
305 4% {
306 opacity: 0;
307 }
308 12%,
309 70% {
310 opacity: 0.9;
311 }
312 88%,
313 100% {
314 opacity: 0;
315 }
316}
317
318/* A short flash, as a check runs over a layer. Unseen at rest. */
319.art-scan {
320 opacity: 0;
321 animation: art-scan var(--dur, 8s) ease-out infinite both;
322 animation-delay: var(--delay, 0s);
323}
324@keyframes art-scan {
325 0% {
326 opacity: 0;
327 }
328 4% {
329 opacity: 1;
330 }
331 16%,
332 100% {
333 opacity: 0;
334 }
335}
336
337/* A line that draws itself, then fades. Drawn at rest. */
338.art-draw {
339 stroke-dasharray: 100;
340 stroke-dashoffset: 0;
341 animation: art-draw var(--dur, 8s) ease-in-out infinite both;
342 animation-delay: var(--delay, 0s);
343}
344@keyframes art-draw {
345 0% {
346 stroke-dashoffset: 100;
347 opacity: 1;
348 }
349 14%,
350 70% {
351 stroke-dashoffset: 0;
352 opacity: 1;
353 }
354 88%,
355 100% {
356 stroke-dashoffset: 0;
357 opacity: 0;
358 }
359}
360
361/* Something that appears, stays, and goes. Shown at rest. */
362.art-pop {
363 animation: art-pop var(--dur, 8s) ease-out infinite both;
364 animation-delay: var(--delay, 0s);
365}
366@keyframes art-pop {
367 0% {
368 opacity: 0;
369 transform: translateY(4px);
370 }
371 6%,
372 72% {
373 opacity: 1;
374 transform: none;
375 }
376 86%,
377 100% {
378 opacity: 0;
379 transform: none;
380 }
381}
382
383/* A layer dropping onto a stack. In place at rest. */
384.art-drop {
385 animation: art-drop var(--dur, 7s) cubic-bezier(0.2, 0.9, 0.3, 1.2) infinite both;
386 animation-delay: var(--delay, 0s);
387}
388@keyframes art-drop {
389 0% {
390 opacity: 0;
391 transform: translateY(-26px);
392 }
393 8%,
394 74% {
395 opacity: 1;
396 transform: none;
397 }
398 88%,
399 100% {
400 opacity: 0;
401 transform: none;
402 }
403}
404
405/* The layer that fails: drops, shakes, and is sent back. */
406.art-eject {
407 animation: art-eject var(--dur, 7s) ease-in-out infinite both;
408 animation-delay: var(--delay, 0s);
409}
410@keyframes art-eject {
411 0% {
412 opacity: 0;
413 transform: translateY(-26px);
414 }
415 8%,
416 30% {
417 opacity: 1;
418 transform: none;
419 }
420 33% {
421 transform: translateX(-3px);
422 }
423 36% {
424 transform: translateX(3px);
425 }
426 39% {
427 transform: translateX(-2px);
428 }
429 42% {
430 opacity: 1;
431 transform: none;
432 }
433 56%,
434 100% {
435 opacity: 0;
436 transform: translate(-40px, -22px);
437 }
438}
439
440/* A layer rising out of the one below it. In place at rest. */
441.art-rise {
442 animation: art-rise var(--dur, 7s) cubic-bezier(0.2, 0.8, 0.3, 1) infinite both;
443 animation-delay: var(--delay, 0s);
444}
445@keyframes art-rise {
446 0% {
447 opacity: 0;
448 transform: translateY(14px);
449 }
450 10%,
451 70% {
452 opacity: 1;
453 transform: none;
454 }
455 86%,
456 100% {
457 opacity: 0;
458 transform: none;
459 }
460}
461
462/* A soft glow breathing around a point. */
463.art-pulse {
464 transform-box: fill-box;
465 transform-origin: center;
466 animation: art-pulse var(--dur, 4s) ease-in-out infinite;
467 animation-delay: var(--delay, 0s);
468}
469@keyframes art-pulse {
470 0%,
471 100% {
472 transform: scale(0.8);
473 opacity: 1;
474 }
475 50% {
476 transform: scale(1.5);
477 opacity: 0.35;
478 }
479}
480
481/* Drawings off-screen hold still (components/art.tsx, Live). */
482[data-paused] * {
483 animation-play-state: paused !important;
484}
485/*
486 * Each drawing on its own layer, so a frame of its motion repaints only the
487 * drawing, not the page around it or the blurred header passing over it.
488 */
489[data-live] {
490 will-change: transform;
491}
492
493/* The landing page's steps, lit in turn along their line. */
494/* The ring is drawn once and only fades and grows, so the compositor runs it. */
495.flow-step::after {
496 content: "";
497 position: absolute;
498 inset: 0;
499 border-radius: inherit;
500 box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-merged) 18%, transparent);
501 opacity: 0;
502 pointer-events: none;
503 animation: flow-step 10s ease-in-out infinite both;
504 animation-delay: var(--delay, 0s);
505}
506@keyframes flow-step {
507 0%,
508 100% {
509 opacity: 0;
510 transform: scale(0.82);
511 }
512 6% {
513 opacity: 1;
514 transform: none;
515 }
516 16% {
517 opacity: 0;
518 transform: none;
519 }
520}
521.flow-runner {
522 animation: flow-runner 10s cubic-bezier(0.45, 0, 0.35, 1) infinite both;
523}
524@keyframes flow-runner {
525 0% {
526 transform: translateX(0);
527 opacity: 0;
528 }
529 4%,
530 76% {
531 opacity: 1;
532 }
533 80%,
534 100% {
535 transform: translateX(100%);
536 opacity: 0;
537 }
538}
539@media (prefers-reduced-motion: reduce) {
540 .flow-runner {
541 display: none;
542 }
543}
544
545/* The sidebar trading one far-side menu for another, in place. */
546@keyframes g1t-swap {
547 from {
548 opacity: 0;
549 transform: translateX(10px);
550 }
551 to {
552 opacity: 1;
553 transform: none;
554 }
555}
556
557/*
558 * Behind the landing page's opening: the same dark gray, with a faint
559 * lavender and peach glow low on the horizon and a few stars.
560 */
561.dusk {
562 background:
563 radial-gradient(1px 1px at 12% 18%, rgb(255 255 255 / 0.35), transparent),
564 radial-gradient(1px 1px at 27% 8%, rgb(255 255 255 / 0.25), transparent),
565 radial-gradient(1px 1px at 63% 12%, rgb(255 255 255 / 0.3), transparent),
566 radial-gradient(1.5px 1.5px at 90% 9%, rgb(255 255 255 / 0.35), transparent),
567 radial-gradient(60% 35% at 50% 100%, rgb(255 189 140 / 0.08), transparent 70%),
568 radial-gradient(80% 45% at 50% 100%, rgb(182 168 255 / 0.1), transparent 75%),
569 var(--color-bg);
570}
571/* A planet rising behind the horizon, its lit edge catching the glow. */
572.dusk-planet {
573 background: radial-gradient(circle at 70% 30%, rgb(182 168 255 / 0.12), rgb(255 189 140 / 0.04) 45%, transparent 70%);
574 box-shadow: inset 14px -8px 50px rgb(255 214 186 / 0.07);
575}