Lavender is the accent; mint means success
The logo's lavender (#b6a8ff) is now --g1t-accent across the app, docs, sudo and status: primary actions, links, focus rings, the current tab, selection, and g1t's own work. Mint becomes --g1t-success, for what passed, is live, is ready to merge or was added. Merged gets its own deeper violet (#a678f5), always beside the merge icon, so it still reads apart from the accent; the link previews follow. Every text token meets WCAG AA on the page, a card and a raised surface, and the dark base reads on an accent or success fill (9.2:1, 13.8:1); apps/web/app/lib/tokens.test.ts checks both.
| 9 | 9 | - **Pixels:** each is its own square, 0.8 of its cell, centred, with square | |
| 10 | 10 | corners. | |
| 11 | 11 | - **Colour:** G and T are `#ededef` (`--g1t-fg`) and the 1 is lavender | |
| 12 | − | `#b6a8ff` (`--g1t-merged`), on the very dark gray base `#0f0f11` | |
| 12 | + | `#b6a8ff` (`--g1t-accent`, the brand accent), on the very dark gray base `#0f0f11` | |
| 13 | 13 | (`--g1t-bg`). On light backgrounds the letters are `#161618` and the 1 is | |
| 14 | 14 | `#6b56e8`. Printed in one ink, everything is that ink. | |
| 15 | 15 | - **Icon:** the 1 alone, centred in a square, for favicons, app icons and |
| 19 | 19 | y={p.y * 10 + 1} | |
| 20 | 20 | width="8" | |
| 21 | 21 | height="8" | |
| 22 | − | fill={p.one ? 'var(--g1t-merged)' : 'currentColor'} | |
| 22 | + | fill={p.one ? 'var(--g1t-accent)' : 'currentColor'} | |
| 23 | 23 | /> | |
| 24 | 24 | ))} | |
| 25 | 25 | </svg> |
| 51 | 51 | y={p.y * 10 + 1} | |
| 52 | 52 | width="8" | |
| 53 | 53 | height="8" | |
| 54 | − | fill={p.one ? 'var(--g1t-merged)' : 'currentColor'} | |
| 54 | + | fill={p.one ? 'var(--g1t-accent)' : 'currentColor'} | |
| 55 | 55 | /> | |
| 56 | 56 | ))} | |
| 57 | 57 | </svg> | |
| ⋯ | |||
| 96 | 96 | --scalar-color-2: var(--g1t-muted); | |
| 97 | 97 | --scalar-color-3: var(--g1t-faint); | |
| 98 | 98 | --scalar-color-accent: #cfc6ff; | |
| 99 | − | --scalar-background-accent: color-mix(in srgb, var(--g1t-merged) 12%, transparent); | |
| 99 | + | --scalar-background-accent: color-mix(in srgb, var(--g1t-accent) 12%, transparent); | |
| 100 | 100 | --scalar-button-1: var(--g1t-fg); | |
| 101 | 101 | --scalar-button-1-color: var(--g1t-bg); | |
| 102 | 102 | --scalar-button-1-hover: #ffffff; | |
| 103 | − | --scalar-color-green: var(--g1t-accent); | |
| 103 | + | --scalar-color-green: var(--g1t-success); | |
| 104 | 104 | --scalar-color-blue: var(--g1t-info); | |
| 105 | 105 | --scalar-color-red: var(--g1t-danger); | |
| 106 | 106 | --scalar-color-orange: var(--g1t-warn); | |
| 9 | 9 | --sl-font-mono: var(--g1t-font-mono); | |
| 10 | 10 | ||
| 11 | 11 | /* Lavender is the docs' accent: links, the current page, highlights. */ | |
| 12 | − | --sl-color-accent-low: color-mix(in srgb, var(--g1t-merged) 14%, var(--g1t-bg)); | |
| 13 | − | --sl-color-accent: var(--g1t-merged); | |
| 12 | + | --sl-color-accent-low: color-mix(in srgb, var(--g1t-accent) 14%, var(--g1t-bg)); | |
| 13 | + | --sl-color-accent: var(--g1t-accent); | |
| 14 | 14 | --sl-color-accent-high: #d9d1ff; | |
| 15 | 15 | ||
| 16 | 16 | --sl-color-white: var(--g1t-fg); | |
| ⋯ | |||
| 130 | 130 | background: var(--g1t-surface); | |
| 131 | 131 | color: var(--g1t-fg); | |
| 132 | 132 | font-weight: 500; | |
| 133 | − | box-shadow: inset 2px 0 0 var(--g1t-merged); | |
| 133 | + | box-shadow: inset 2px 0 0 var(--g1t-accent); | |
| 134 | 134 | } | |
| 135 | 135 | ||
| 136 | 136 | /* --- Content -------------------------------------------------------------- */ | |
| ⋯ | |||
| 178 | 178 | } | |
| 179 | 179 | .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card) { | |
| 180 | 180 | color: #cfc6ff; | |
| 181 | − | text-decoration-color: color-mix(in srgb, var(--g1t-merged) 45%, transparent); | |
| 181 | + | text-decoration-color: color-mix(in srgb, var(--g1t-accent) 45%, transparent); | |
| 182 | 182 | text-underline-offset: 0.2em; | |
| 183 | 183 | } | |
| 184 | 184 | .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card):hover { | |
| 185 | − | text-decoration-color: var(--g1t-merged); | |
| 185 | + | text-decoration-color: var(--g1t-accent); | |
| 186 | 186 | } | |
| 187 | 187 | .sl-markdown-content :not(pre) > code { | |
| 188 | 188 | border-radius: 0.3rem; | |
| ⋯ | |||
| 251 | 251 | background: color-mix(in srgb, var(--g1t-info) 7%, var(--g1t-bg)); | |
| 252 | 252 | } | |
| 253 | 253 | .starlight-aside--tip { | |
| 254 | − | border-color: var(--g1t-accent); | |
| 255 | − | background: color-mix(in srgb, var(--g1t-accent) 6%, var(--g1t-bg)); | |
| 254 | + | border-color: var(--g1t-success); | |
| 255 | + | background: color-mix(in srgb, var(--g1t-success) 6%, var(--g1t-bg)); | |
| 256 | 256 | } | |
| 257 | 257 | .starlight-aside--caution { | |
| 258 | 258 | border-color: var(--g1t-warn); | |
| ⋯ | |||
| 346 | 346 | color: var(--g1t-fg); | |
| 347 | 347 | } | |
| 348 | 348 | .sl-markdown-content .card .icon { | |
| 349 | − | color: var(--g1t-merged); | |
| 350 | − | background: color-mix(in srgb, var(--g1t-merged) 12%, transparent); | |
| 349 | + | color: var(--g1t-accent); | |
| 350 | + | background: color-mix(in srgb, var(--g1t-accent) 12%, transparent); | |
| 351 | 351 | border: 0; | |
| 352 | 352 | border-radius: 0.5rem; | |
| 353 | 353 | padding: 0.3rem; | |
| ⋯ | |||
| 413 | 413 | } | |
| 414 | 414 | .g1t-method-get, | |
| 415 | 415 | .g1t-method[data-method='get'] { | |
| 416 | − | --method: var(--g1t-accent); | |
| 416 | + | --method: var(--g1t-success); | |
| 417 | 417 | } | |
| 418 | 418 | .g1t-method-post, | |
| 419 | 419 | .g1t-method[data-method='post'] { | |
| 20 | 20 | --color-faint: var(--g1t-faint); | |
| 21 | 21 | ||
| 22 | 22 | --color-accent: var(--g1t-accent); | |
| 23 | + | --color-accent-hover: var(--g1t-accent-hover); | |
| 23 | 24 | --color-accent-dim: var(--g1t-accent-dim); | |
| 24 | 25 | ||
| 26 | + | --color-success: var(--g1t-success); | |
| 27 | + | --color-success-dim: var(--g1t-success-dim); | |
| 28 | + | ||
| 25 | 29 | --color-info: var(--g1t-info); | |
| 26 | 30 | --color-merged: var(--g1t-merged); | |
| 27 | 31 | --color-warn: var(--g1t-warn); | |
| ⋯ | |||
| 37 | 41 | } | |
| 38 | 42 | ||
| 39 | 43 | ::selection { | |
| 40 | − | background: color-mix(in srgb, var(--color-merged) 35%, transparent); | |
| 44 | + | background: color-mix(in srgb, var(--color-accent) 35%, transparent); | |
| 41 | 45 | } | |
| 42 | 46 | ||
| 43 | 47 | @layer base { | |
| 44 | 48 | :focus-visible { | |
| 45 | − | outline: 2px solid var(--color-merged); | |
| 49 | + | outline: 2px solid var(--color-accent); | |
| 46 | 50 | outline-offset: 2px; | |
| 47 | 51 | } | |
| 48 | 52 | ||
| ⋯ | |||
| 57 | 61 | } | |
| 58 | 62 | ||
| 59 | 63 | :root { | |
| 60 | − | accent-color: var(--color-merged); | |
| 64 | + | accent-color: var(--color-accent); | |
| 61 | 65 | } | |
| 62 | 66 | ||
| 63 | 67 | /* Figures line up in columns of money. */ | |
| 185 | 185 | ); | |
| 186 | 186 | } | |
| 187 | 187 | return ( | |
| 188 | − | <section id="review" className={`scroll-mt-20 rounded-lg border p-4 sm:p-5 ${danger ? "border-warn/40 bg-warn/5" : "border-merged/40 bg-merged/5"}`}> | |
| 188 | + | <section id="review" className={`scroll-mt-20 rounded-lg border p-4 sm:p-5 ${danger ? "border-warn/40 bg-warn/5" : "border-accent/40 bg-accent/5"}`}> | |
| 189 | 189 | <h2 className="font-semibold tracking-tight">{title}</h2> | |
| 190 | 190 | <div className="mt-3">{body}</div> | |
| 191 | 191 | <form method="post" action={`${pathname}#${RESULT_ANCHOR[review.intent] ?? "top"}`} className="mt-4 flex flex-wrap items-center gap-2"> | |
| ⋯ | |||
| 462 | 462 | /** A radio drawn as one button of a segmented row; still a plain radio without CSS. */ | |
| 463 | 463 | function Segment({ name, value, checked, children }: { name: string; value: string; checked: boolean; children: ReactNode }) { | |
| 464 | 464 | return ( | |
| 465 | − | <label className="flex-1 cursor-pointer border-r border-line px-3 py-1.5 text-center text-sm whitespace-nowrap text-muted transition-colors last:border-r-0 hover:bg-raised hover:text-fg has-checked:bg-merged has-checked:font-medium has-checked:text-bg has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-merged"> | |
| 465 | + | <label className="flex-1 cursor-pointer border-r border-line px-3 py-1.5 text-center text-sm whitespace-nowrap text-muted transition-colors last:border-r-0 hover:bg-raised hover:text-fg has-checked:bg-accent has-checked:font-medium has-checked:text-bg has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-accent"> | |
| 466 | 466 | <input type="radio" name={name} value={value} defaultChecked={checked} className="sr-only" /> | |
| 467 | 467 | {children} | |
| 468 | 468 | </label> | |
| ⋯ | |||
| 534 | 534 | {CREDIT_KINDS.map((option) => ( | |
| 535 | 535 | <label | |
| 536 | 536 | key={option.value} | |
| 537 | − | className="flex cursor-pointer gap-2.5 rounded-md border border-line bg-bg px-3 py-2.5 transition-colors hover:border-line-strong has-checked:border-merged/60 has-checked:bg-merged/8" | |
| 537 | + | className="flex cursor-pointer gap-2.5 rounded-md border border-line bg-bg px-3 py-2.5 transition-colors hover:border-line-strong has-checked:border-accent/60 has-checked:bg-accent/8" | |
| 538 | 538 | > | |
| 539 | 539 | <input type="radio" name="kind" value={option.value} defaultChecked={kind === option.value} className="mt-0.5" required /> | |
| 540 | 540 | <span> | |
| ⋯ | |||
| 633 | 633 | <Badge tone="lavender">{kindLabel(grant.kind)}</Badge> | |
| 634 | 634 | <Badge tone={state.tone}>{state.label}</Badge> | |
| 635 | 635 | {showWorkspace && ( | |
| 636 | − | <Link to={`/workspaces/${encodeURIComponent(grant.workspace)}#credits`} className="font-mono text-xs text-merged hover:underline"> | |
| 636 | + | <Link to={`/workspaces/${encodeURIComponent(grant.workspace)}#credits`} className="font-mono text-xs text-accent hover:underline"> | |
| 637 | 637 | {grant.workspace} | |
| 638 | 638 | </Link> | |
| 639 | 639 | )} | |
| ⋯ | |||
| 859 | 859 | {entry.creditKind ? `Credit · ${kindLabel(entry.creditKind)}` : (ENTRY_KIND[entry.kind] ?? entry.kind)} | |
| 860 | 860 | </Badge> | |
| 861 | 861 | {showWorkspace && entry.workspace && ( | |
| 862 | − | <Link to={`/workspaces/${encodeURIComponent(entry.workspace)}`} className="font-mono text-xs text-merged hover:underline"> | |
| 862 | + | <Link to={`/workspaces/${encodeURIComponent(entry.workspace)}`} className="font-mono text-xs text-accent hover:underline"> | |
| 863 | 863 | {entry.workspace} | |
| 864 | 864 | </Link> | |
| 865 | 865 | )} | |
| ⋯ | |||
| 882 | 882 | .join(" · ")} | |
| 883 | 883 | </p> | |
| 884 | 884 | </td> | |
| 885 | − | <td className={`tabular px-4 py-2.5 text-right whitespace-nowrap sm:pr-5 ${entry.amountMicros > 0 ? "text-accent" : "text-fg-soft"}`}> | |
| 885 | + | <td className={`tabular px-4 py-2.5 text-right whitespace-nowrap sm:pr-5 ${entry.amountMicros > 0 ? "text-success" : "text-fg-soft"}`}> | |
| 886 | 886 | {usd(entry.amountMicros, { signed: true })} | |
| 887 | 887 | </td> | |
| 888 | 888 | </tr> | |
| ⋯ | |||
| 908 | 908 | ) : ( | |
| 909 | 909 | <ol className="space-y-3"> | |
| 910 | 910 | {audit.map((entry) => ( | |
| 911 | − | <li key={entry.id} className="border-l-2 border-merged/40 pl-3"> | |
| 911 | + | <li key={entry.id} className="border-l-2 border-accent/40 pl-3"> | |
| 912 | 912 | <p className="flex flex-wrap items-center gap-x-2 text-sm"> | |
| 913 | − | <span className="font-medium text-merged">{actionLabel(entry.action)}</span> | |
| 913 | + | <span className="font-medium text-accent">{actionLabel(entry.action)}</span> | |
| 914 | 914 | <span className="text-xs text-faint"> | |
| 915 | 915 | <When at={entry.createdAt} time /> | |
| 916 | 916 | </span> | |
| 9 | 9 | import { usd } from "~/lib/money"; | |
| 10 | 10 | ||
| 11 | 11 | /** The two series: what was charged (lavender) beside what it cost g1t (gray). */ | |
| 12 | − | const CHARGED = "var(--g1t-merged)"; | |
| 12 | + | const CHARGED = "var(--g1t-accent)"; | |
| 13 | 13 | const COST = "#7a7a84"; | |
| 14 | 14 | ||
| 15 | 15 | function Legend() { | |
| ⋯ | |||
| 131 | 131 | <td className="py-1.5 pr-3 text-right">{usd(month.chargedMicros)}</td> | |
| 132 | 132 | <td className="py-1.5 pr-3 text-right">{usd(month.plansMicros ?? 0)}</td> | |
| 133 | 133 | <td className="py-1.5 pr-3 text-right text-muted">{usd(month.costMicros)}</td> | |
| 134 | − | <td className="py-1.5 pr-3 text-right text-merged">{usd(month.givenMicros ?? 0)}</td> | |
| 134 | + | <td className="py-1.5 pr-3 text-right text-accent">{usd(month.givenMicros ?? 0)}</td> | |
| 135 | 135 | <td className={`py-1.5 pr-3 text-right ${margin.micros < 0 ? "text-danger" : "text-fg-soft"}`}> | |
| 136 | 136 | {usd(margin.micros)} | |
| 137 | 137 | {margin.percent != null && <span className="text-faint"> {margin.percent}%</span>} | |
| 12 | 12 | ||
| 13 | 13 | export function chip(active: boolean) { | |
| 14 | 14 | return `inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs whitespace-nowrap transition-colors ${ | |
| 15 | − | active ? "border-merged/50 bg-merged/10 text-merged" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 15 | + | active ? "border-accent/50 bg-accent/10 text-accent" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 16 | 16 | }`; | |
| 17 | 17 | } | |
| 18 | 18 | ||
| ⋯ | |||
| 62 | 62 | key={tab.page} | |
| 63 | 63 | to={costsHref(tab.page, range)} | |
| 64 | 64 | aria-current={tab.page === page ? "page" : undefined} | |
| 65 | − | className={`-mb-px border-b-2 pb-2 text-sm ${tab.page === page ? "border-merged text-fg" : "border-transparent text-muted hover:text-fg"}`} | |
| 65 | + | className={`-mb-px border-b-2 pb-2 text-sm ${tab.page === page ? "border-accent text-fg" : "border-transparent text-muted hover:text-fg"}`} | |
| 66 | 66 | > | |
| 67 | 67 | {tab.title} | |
| 68 | 68 | </Link> | |
| 8 | 8 | import { type DayFigures, marginPercent, percentLabel } from "~/lib/costs"; | |
| 9 | 9 | import { usd } from "~/lib/money"; | |
| 10 | 10 | ||
| 11 | − | const CHARGED = "var(--g1t-merged)"; | |
| 11 | + | const CHARGED = "var(--g1t-accent)"; | |
| 12 | 12 | const COST = "#7a7a84"; | |
| 13 | 13 | const LOSS = "var(--g1t-danger)"; | |
| 14 | 14 |
| 49 | 49 | ||
| 50 | 50 | /** One timer: a label, a figure, and a line under it. */ | |
| 51 | 51 | export function Timer({ label, value, hint, tone }: { label: string; value: ReactNode; hint?: ReactNode; tone?: "danger" | "warn" | "mint" }) { | |
| 52 | − | const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-accent" : "text-fg"; | |
| 52 | + | const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-success" : "text-fg"; | |
| 53 | 53 | return ( | |
| 54 | 54 | <div className="min-w-0 px-4 py-3"> | |
| 55 | 55 | <p className="text-xs text-muted">{label}</p> |
| 29 | 29 | y={p.y * 10 + 1} | |
| 30 | 30 | width="8" | |
| 31 | 31 | height="8" | |
| 32 | − | fill={p.one ? "var(--g1t-merged)" : "currentColor"} | |
| 32 | + | fill={p.one ? "var(--g1t-accent)" : "currentColor"} | |
| 33 | 33 | /> | |
| 34 | 34 | )); | |
| 35 | 35 | } | |
| ⋯ | |||
| 55 | 55 | <Pixels of={WORD} /> | |
| 56 | 56 | </svg> | |
| 57 | 57 | </span> | |
| 58 | − | <span className="rounded-full bg-merged/12 px-2 py-0.5 font-mono text-[0.7rem] font-semibold tracking-wide text-merged ring-1 ring-merged/35 ring-inset"> | |
| 58 | + | <span className="rounded-full bg-accent/12 px-2 py-0.5 font-mono text-[0.7rem] font-semibold tracking-wide text-accent ring-1 ring-accent/35 ring-inset"> | |
| 59 | 59 | sudo | |
| 60 | 60 | </span> | |
| 61 | 61 | </span> | |
| 169 | 169 | return ( | |
| 170 | 170 | <ol className="mt-4 space-y-3"> | |
| 171 | 171 | {sorted.map((note) => ( | |
| 172 | − | <li key={note.id} className="border-l-2 border-merged/40 pl-3"> | |
| 172 | + | <li key={note.id} className="border-l-2 border-accent/40 pl-3"> | |
| 173 | 173 | <p className="text-sm break-words whitespace-pre-line text-fg-soft">{note.text}</p> | |
| 174 | 174 | <p className="mt-1 flex flex-wrap items-center gap-x-2 text-xs text-faint"> | |
| 175 | 175 | <StaffName email={note.by} me={me} /> | |
| ⋯ | |||
| 200 | 200 | function StripeLink({ url, children }: { url: string | null; children: ReactNode }) { | |
| 201 | 201 | if (!url || !url.startsWith("https://")) return null; | |
| 202 | 202 | return ( | |
| 203 | − | <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-merged hover:underline"> | |
| 203 | + | <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-accent hover:underline"> | |
| 204 | 204 | {children} | |
| 205 | 205 | </a> | |
| 206 | 206 | ); | |
| 92 | 92 | return ( | |
| 93 | 93 | <span | |
| 94 | 94 | title={label} | |
| 95 | − | className="shrink-0 rounded-full bg-merged/15 px-1.5 py-px text-[0.6875rem] font-semibold text-merged tabular-nums ring-1 ring-merged/30" | |
| 95 | + | className="shrink-0 rounded-full bg-accent/15 px-1.5 py-px text-[0.6875rem] font-semibold text-accent tabular-nums ring-1 ring-accent/30" | |
| 96 | 96 | > | |
| 97 | 97 | {count > 99 ? "99+" : count} | |
| 98 | 98 | <span className="sr-only"> {label}</span> | |
| ⋯ | |||
| 127 | 127 | current ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 128 | 128 | }`} | |
| 129 | 129 | > | |
| 130 | − | <NavGlyph icon={item.icon} className={`shrink-0 ${current ? "text-merged" : "text-faint group-hover:text-muted"}`} /> | |
| 130 | + | <NavGlyph icon={item.icon} className={`shrink-0 ${current ? "text-accent" : "text-faint group-hover:text-muted"}`} /> | |
| 131 | 131 | <span className="min-w-0 grow truncate">{item.label}</span> | |
| 132 | 132 | <CountPill count={countFor([item], counts)} label={countLabel(item)} /> | |
| 133 | 133 | {item.soon && <SoonPill />} | |
| ⋯ | |||
| 145 | 145 | aria-current={current ? "page" : undefined} | |
| 146 | 146 | className={`relative flex h-8 items-center gap-2 rounded-md pr-2 pl-[2.375rem] text-[0.8125rem] transition-colors ${ | |
| 147 | 147 | current | |
| 148 | − | ? "bg-raised font-medium text-fg before:absolute before:top-1.5 before:bottom-1.5 before:left-[1.1875rem] before:z-10 before:w-px before:bg-merged" | |
| 148 | + | ? "bg-raised font-medium text-fg before:absolute before:top-1.5 before:bottom-1.5 before:left-[1.1875rem] before:z-10 before:w-px before:bg-accent" | |
| 149 | 149 | : item.soon | |
| 150 | 150 | ? "text-faint hover:bg-raised/60 hover:text-muted" | |
| 151 | 151 | : "text-muted hover:bg-raised/60 hover:text-fg" | |
| ⋯ | |||
| 240 | 240 | <div className="border-t border-line bg-raised/40 px-4 py-3"> | |
| 241 | 241 | {email && ( | |
| 242 | 242 | <p title="Signed in through Cloudflare Access" className="flex items-center gap-2 text-[0.8125rem]"> | |
| 243 | − | <ShieldCheck size={15} className="shrink-0 text-merged" /> | |
| 243 | + | <ShieldCheck size={15} className="shrink-0 text-accent" /> | |
| 244 | 244 | <span className="min-w-0 truncate font-mono text-xs text-fg-soft">{email}</span> | |
| 245 | 245 | </p> | |
| 246 | 246 | )} | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | 36 | const CONTROL = | |
| 37 | − | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-merged/60"; | |
| 37 | + | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent/60"; | |
| 38 | 38 | ||
| 39 | 39 | /** Tells password managers that a field is not a login. */ | |
| 40 | 40 | const NOT_A_CREDENTIAL = { | |
| ⋯ | |||
| 64 | 64 | ||
| 65 | 65 | const BUTTON_VARIANTS: Record<Variant, string> = { | |
| 66 | 66 | primary: "bg-fg text-bg hover:bg-white", | |
| 67 | − | lavender: "bg-merged text-bg hover:bg-[#c8bdff]", | |
| 67 | + | lavender: "bg-accent text-bg hover:bg-accent-hover", | |
| 68 | 68 | quiet: "border border-line text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg", | |
| 69 | 69 | danger: "border border-danger/40 text-danger hover:border-danger/70 hover:bg-danger/10", | |
| 70 | 70 | }; | |
| ⋯ | |||
| 117 | 117 | } | |
| 118 | 118 | ||
| 119 | 119 | const NOTICE = { | |
| 120 | − | ok: { icon: CheckCircle2, className: "border-accent/30 bg-accent/8 text-accent" }, | |
| 120 | + | ok: { icon: CheckCircle2, className: "border-success/30 bg-success/8 text-success" }, | |
| 121 | 121 | error: { icon: AlertTriangle, className: "border-danger/30 bg-danger/8 text-danger" }, | |
| 122 | 122 | warn: { icon: AlertTriangle, className: "border-warn/30 bg-warn/8 text-warn" }, | |
| 123 | − | info: { icon: Info, className: "border-merged/30 bg-merged/8 text-merged" }, | |
| 123 | + | info: { icon: Info, className: "border-accent/30 bg-accent/8 text-accent" }, | |
| 124 | 124 | } as const; | |
| 125 | 125 | ||
| 126 | 126 | export function Notice({ tone, children }: { tone: keyof typeof NOTICE; children: ReactNode }) { | |
| ⋯ | |||
| 137 | 137 | export function Badge({ tone = "plain", children }: { tone?: "plain" | "lavender" | "mint" | "warn" | "danger" | "info"; children: ReactNode }) { | |
| 138 | 138 | const tones = { | |
| 139 | 139 | plain: "border-line text-muted", | |
| 140 | − | lavender: "border-merged/35 bg-merged/10 text-merged", | |
| 141 | − | mint: "border-accent/30 bg-accent/8 text-accent", | |
| 140 | + | lavender: "border-accent/35 bg-accent/10 text-accent", | |
| 141 | + | mint: "border-success/30 bg-success/8 text-success", | |
| 142 | 142 | warn: "border-warn/35 bg-warn/10 text-warn", | |
| 143 | 143 | danger: "border-danger/35 bg-danger/10 text-danger", | |
| 144 | 144 | info: "border-info/35 bg-info/10 text-info", | |
| ⋯ | |||
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | 199 | const STATE = { | |
| 200 | − | ok: { label: "OK", fill: "var(--g1t-accent)", text: "text-accent" }, | |
| 200 | + | ok: { label: "OK", fill: "var(--g1t-success)", text: "text-success" }, | |
| 201 | 201 | warning: { label: "Warning", fill: "var(--g1t-warn)", text: "text-warn" }, | |
| 202 | 202 | stopped: { label: "Stopped", fill: "var(--g1t-danger)", text: "text-danger" }, | |
| 203 | 203 | } as const; | |
| ⋯ | |||
| 236 | 236 | ||
| 237 | 237 | /** A label and a figure, for the strip of totals over a page. */ | |
| 238 | 238 | export function Stat({ label, value, hint, tone }: { label: string; value: ReactNode; hint?: ReactNode; tone?: "danger" | "warn" | "mint" }) { | |
| 239 | − | const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-accent" : "text-fg"; | |
| 239 | + | const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-success" : "text-fg"; | |
| 240 | 240 | return ( | |
| 241 | 241 | <div className="rounded-lg border border-line bg-surface px-4 py-3"> | |
| 242 | 242 | <p className="text-xs text-muted">{label}</p> | |
| 72 | 72 | <ul className="list-disc space-y-1 pl-5 text-sm text-muted"> | |
| 73 | 73 | <li> | |
| 74 | 74 | Disputes and declined cards are signals in{" "} | |
| 75 | − | <Link to={reachOutHref({ kind: "declined" })} className="text-merged hover:underline"> | |
| 75 | + | <Link to={reachOutHref({ kind: "declined" })} className="text-accent hover:underline"> | |
| 76 | 76 | Reach out | |
| 77 | 77 | </Link> | |
| 78 | 78 | . Stripe Radar's early fraud warnings are not in sudo yet. |
| 102 | 102 | {before && ( | |
| 103 | 103 | <p className="mt-4 text-xs text-faint"> | |
| 104 | 104 | Changes before <When at={before} time />.{" "} | |
| 105 | − | <Link to={auditHref({ by, action })} className="text-merged hover:underline"> | |
| 105 | + | <Link to={auditHref({ by, action })} className="text-accent hover:underline"> | |
| 106 | 106 | Back to the newest | |
| 107 | 107 | </Link> | |
| 108 | 108 | </p> | |
| ⋯ | |||
| 130 | 130 | </p> | |
| 131 | 131 | <div className="min-w-0 grow"> | |
| 132 | 132 | <p className="flex flex-wrap items-center gap-x-2 text-sm"> | |
| 133 | − | <span className={`font-medium ${stripe ? "text-info" : "text-merged"}`}>{actionLabel(entry.action)}</span> | |
| 133 | + | <span className={`font-medium ${stripe ? "text-info" : "text-accent"}`}>{actionLabel(entry.action)}</span> | |
| 134 | 134 | {path && ( | |
| 135 | 135 | <Link to={path} className="inline-flex items-center gap-0.5 text-fg-soft hover:text-fg hover:underline hover:underline-offset-4"> | |
| 136 | 136 | <span className={entry.account.startsWith("ws_") ? "font-mono text-xs" : ""}>{accountName(entry.account, nameMap)}</span> | |
| 211 | 211 | </Field> | |
| 212 | 212 | <div className="flex flex-col justify-end gap-2"> | |
| 213 | 213 | <label className="flex items-center gap-2 text-sm text-muted"> | |
| 214 | − | <input type="checkbox" name="scaleToOwn" className="accent-[var(--g1t-merged)]" /> Scale to g1t's count | |
| 214 | + | <input type="checkbox" name="scaleToOwn" className="accent-[var(--g1t-accent)]" /> Scale to g1t's count | |
| 215 | 215 | </label> | |
| 216 | 216 | <div className="flex gap-2"> | |
| 217 | 217 | <Button type="submit">Save mapping</Button> | |
| ⋯ | |||
| 233 | 233 | <input type="hidden" name="intent" value="settings" /> | |
| 234 | 234 | <Field label="Apply small moves on their own"> | |
| 235 | 235 | <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft"> | |
| 236 | − | <input type="checkbox" name="autoApply" defaultChecked={report.settings.autoApply} className="accent-[var(--g1t-merged)]" /> On | |
| 236 | + | <input type="checkbox" name="autoApply" defaultChecked={report.settings.autoApply} className="accent-[var(--g1t-accent)]" /> On | |
| 237 | 237 | </label> | |
| 238 | 238 | </Field> | |
| 239 | 239 | <Field label="Up to, either way, %"> | |
| ⋯ | |||
| 259 | 259 | </Field> | |
| 260 | 260 | <Field label="Card fee on AI credit bought by card"> | |
| 261 | 261 | <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft"> | |
| 262 | − | <input type="checkbox" name="cardFee" defaultChecked={report.settings.cardFee ?? true} className="accent-[var(--g1t-merged)]" /> Passed on as its own line | |
| 262 | + | <input type="checkbox" name="cardFee" defaultChecked={report.settings.cardFee ?? true} className="accent-[var(--g1t-accent)]" /> Passed on as its own line | |
| 263 | 263 | </label> | |
| 264 | 264 | </Field> | |
| 265 | 265 | <div className="sm:col-span-2 lg:col-span-4"> | |
| ⋯ | |||
| 291 | 291 | </div> | |
| 292 | 292 | </div> | |
| 293 | 293 | <p className="tabular mt-1.5 text-sm"> | |
| 294 | − | Cost {unitDollars(p.currentCostMicros)} → <span className={rise ? "text-warn" : "text-accent"}>{unitDollars(p.proposedCostMicros)}</span> per {p.unit || "unit"}{" "} | |
| 294 | + | Cost {unitDollars(p.currentCostMicros)} → <span className={rise ? "text-warn" : "text-success"}>{unitDollars(p.proposedCostMicros)}</span> per {p.unit || "unit"}{" "} | |
| 295 | 295 | <span className="text-faint">({percentLabel(p.changePercent, { signed: true })})</span> | |
| 296 | 296 | </p> | |
| 297 | 297 | <p className="mt-1 text-xs text-muted">{p.reason}</p> | |
| 169 | 169 | ||
| 170 | 170 | function CapMeter({ label, used, cap, hint }: { label: string; used: number; cap: number; hint: ReactNode }) { | |
| 171 | 171 | const percent = capPercent(used, cap); | |
| 172 | − | const tone = cap > 0 && used >= cap ? "bg-danger" : percent >= 75 ? "bg-warn" : "bg-merged"; | |
| 172 | + | const tone = cap > 0 && used >= cap ? "bg-danger" : percent >= 75 ? "bg-warn" : "bg-accent"; | |
| 173 | 173 | return ( | |
| 174 | 174 | <div className="rounded-md border border-line px-4 py-3"> | |
| 175 | 175 | <div className="flex flex-wrap items-baseline justify-between gap-2"> |
| 293 | 293 | function StripeLink({ url }: { url: string | null }) { | |
| 294 | 294 | if (!url || !url.startsWith("https://")) return <span className="text-faint">—</span>; | |
| 295 | 295 | return ( | |
| 296 | − | <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-merged hover:underline"> | |
| 296 | + | <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-accent hover:underline"> | |
| 297 | 297 | Stripe | |
| 298 | 298 | <ExternalLink size={12} /> | |
| 299 | 299 | </a> |
| 84 | 84 | {INCIDENT_SEVERITIES.map((s) => ( | |
| 85 | 85 | <label | |
| 86 | 86 | key={s.value} | |
| 87 | − | className="flex cursor-pointer gap-2.5 rounded-md border border-line px-3 py-2 has-checked:border-merged/60 has-checked:bg-merged/8" | |
| 87 | + | className="flex cursor-pointer gap-2.5 rounded-md border border-line px-3 py-2 has-checked:border-accent/60 has-checked:bg-accent/8" | |
| 88 | 88 | > | |
| 89 | − | <input type="radio" name="severity" value={s.value} required defaultChecked={(v.severity || "sev3") === s.value} className="mt-1 accent-[#b6a8ff]" /> | |
| 89 | + | <input type="radio" name="severity" value={s.value} required defaultChecked={(v.severity || "sev3") === s.value} className="mt-1 accent-[var(--g1t-accent)]" /> | |
| 90 | 90 | <span> | |
| 91 | 91 | <span className="block text-sm font-semibold">{s.label}</span> | |
| 92 | 92 | <span className="block text-xs text-muted">{s.about}</span> |
| 109 | 109 | } | |
| 110 | 110 | actions={ | |
| 111 | 111 | published ? ( | |
| 112 | − | <a href={`${incident.url}#postmortem`} className="inline-flex items-center gap-1.5 text-sm text-merged hover:underline"> | |
| 112 | + | <a href={`${incident.url}#postmortem`} className="inline-flex items-center gap-1.5 text-sm text-accent hover:underline"> | |
| 113 | 113 | On the status page <ExternalLink size={13} aria-hidden="true" /> | |
| 114 | 114 | </a> | |
| 115 | 115 | ) : undefined |
| 128 | 128 | ); | |
| 129 | 129 | if (entry.kind === "update") { | |
| 130 | 130 | return ( | |
| 131 | − | <li className="rounded-md border border-merged/30 bg-merged/6 px-3.5 py-3"> | |
| 131 | + | <li className="rounded-md border border-accent/30 bg-accent/6 px-3.5 py-3"> | |
| 132 | 132 | <div className="flex flex-wrap items-center gap-x-2 gap-y-1"> | |
| 133 | − | <Globe size={14} aria-hidden="true" className="text-merged" /> | |
| 134 | − | <span className="text-xs font-semibold text-merged">Public update</span> | |
| 133 | + | <Globe size={14} aria-hidden="true" className="text-accent" /> | |
| 134 | + | <span className="text-xs font-semibold text-accent">Public update</span> | |
| 135 | 135 | {entry.status && <Badge tone="lavender">{statusLabel(entry.status)}</Badge>} | |
| 136 | 136 | {meta} | |
| 137 | 137 | {entry.notified != null && <span className="text-xs text-faint">· emailed {entry.notified}</span>} | |
| ⋯ | |||
| 214 | 214 | {error && <Notice tone="error">{error}</Notice>} | |
| 215 | 215 | {!draft && ( | |
| 216 | 216 | <fieldset className="flex flex-wrap gap-2" aria-label="Who sees it"> | |
| 217 | − | <label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-line px-3 py-1.5 text-sm has-checked:border-merged/60 has-checked:bg-merged/10"> | |
| 218 | − | <input type="radio" name="visibility" value="public" defaultChecked className="accent-[#b6a8ff]" /> | |
| 217 | + | <label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-line px-3 py-1.5 text-sm has-checked:border-accent/60 has-checked:bg-accent/10"> | |
| 218 | + | <input type="radio" name="visibility" value="public" defaultChecked className="accent-[var(--g1t-accent)]" /> | |
| 219 | 219 | <Globe size={14} aria-hidden="true" /> Public update | |
| 220 | 220 | </label> | |
| 221 | 221 | <label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-line px-3 py-1.5 text-sm has-checked:border-line-strong has-checked:bg-raised"> | |
| 222 | − | <input type="radio" name="visibility" value="internal" className="accent-[#b6a8ff]" /> | |
| 222 | + | <input type="radio" name="visibility" value="internal" className="accent-[var(--g1t-accent)]" /> | |
| 223 | 223 | <Lock size={14} aria-hidden="true" /> Internal note | |
| 224 | 224 | </label> | |
| 225 | 225 | </fieldset> | |
| ⋯ | |||
| 259 | 259 | <div className="flex flex-wrap items-center justify-between gap-3"> | |
| 260 | 260 | {!draft ? ( | |
| 261 | 261 | <label className="flex items-center gap-2 text-sm"> | |
| 262 | − | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[#b6a8ff]" /> | |
| 262 | + | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[var(--g1t-accent)]" /> | |
| 263 | 263 | Email subscribers (public updates only) | |
| 264 | 264 | </label> | |
| 265 | 265 | ) : ( | |
| ⋯ | |||
| 324 | 324 | </Field> | |
| 325 | 325 | <div className="flex flex-wrap items-center justify-between gap-3"> | |
| 326 | 326 | <label className="flex items-center gap-2 text-sm"> | |
| 327 | − | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[#b6a8ff]" /> | |
| 327 | + | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[var(--g1t-accent)]" /> | |
| 328 | 328 | Email subscribers | |
| 329 | 329 | </label> | |
| 330 | 330 | <Button type="submit" variant="lavender"> | |
| ⋯ | |||
| 366 | 366 | {errorFor("resolve") && <Notice tone="error">{errorFor("resolve")}</Notice>} | |
| 367 | 367 | <Textarea name="message" rows={3} required maxLength={4000} aria-label="Resolution update" placeholder="Pushes work again. A bad deploy was rolled back; nothing was lost." /> | |
| 368 | 368 | <label className="flex items-center gap-2 text-sm"> | |
| 369 | − | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[#b6a8ff]" /> | |
| 369 | + | <input type="checkbox" name="notify" defaultChecked={notifyByDefault(incident.severity)} className="accent-[var(--g1t-accent)]" /> | |
| 370 | 370 | Email subscribers | |
| 371 | 371 | </label> | |
| 372 | 372 | <Button type="submit" variant="lavender" className="w-full"> | |
| ⋯ | |||
| 379 | 379 | {!open && isPublic && ( | |
| 380 | 380 | <Section title="Postmortem" description={incident.postmortem_published_at ? "Published on the incident's page." : incident.postmortem ? "Drafted, not published." : "Not started."}> | |
| 381 | 381 | <div className="flex flex-wrap items-center gap-2"> | |
| 382 | − | <Link to={`/incidents/${incident.id}/postmortem`} className="inline-flex items-center gap-1.5 text-sm font-medium text-merged hover:underline"> | |
| 382 | + | <Link to={`/incidents/${incident.id}/postmortem`} className="inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline"> | |
| 383 | 383 | <FileText size={14} aria-hidden="true" /> | |
| 384 | 384 | {incident.postmortem ? "Edit the postmortem" : "Write the postmortem"} | |
| 385 | 385 | </Link> | |
| ⋯ | |||
| 436 | 436 | <button | |
| 437 | 437 | type="submit" | |
| 438 | 438 | aria-label={f.done_at ? `Mark “${f.title}” not done` : `Mark “${f.title}” done`} | |
| 439 | − | className="mt-0.5 shrink-0 rounded text-faint hover:text-merged" | |
| 439 | + | className="mt-0.5 shrink-0 rounded text-faint hover:text-accent" | |
| 440 | 440 | > | |
| 441 | − | {f.done_at ? <CheckCircle2 size={16} className="text-accent" /> : <Circle size={16} />} | |
| 441 | + | {f.done_at ? <CheckCircle2 size={16} className="text-success" /> : <Circle size={16} />} | |
| 442 | 442 | </button> | |
| 443 | 443 | <span className="min-w-0 text-sm"> | |
| 444 | 444 | <span className={f.done_at ? "text-faint line-through" : ""}>{f.title}</span> | |
| ⋯ | |||
| 466 | 466 | <ul className="space-y-1.5 text-sm"> | |
| 467 | 467 | {isPublic ? ( | |
| 468 | 468 | <li> | |
| 469 | − | <a href={incident.url} className="inline-flex items-center gap-1.5 text-merged hover:underline"> | |
| 469 | + | <a href={incident.url} className="inline-flex items-center gap-1.5 text-accent hover:underline"> | |
| 470 | 470 | Status page permalink <ExternalLink size={13} aria-hidden="true" /> | |
| 471 | 471 | </a> | |
| 472 | 472 | <span className="block truncate font-mono text-xs text-faint">{incident.url}</span> | |
| 180 | 180 | to={incidentsHref({ ...filters, tab: tab.value })} | |
| 181 | 181 | aria-current={current ? "page" : undefined} | |
| 182 | 182 | className={`-mb-px flex shrink-0 items-center gap-2 border-b-2 px-3 py-2 text-sm whitespace-nowrap ${ | |
| 183 | − | current ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg" | |
| 183 | + | current ? "border-accent font-medium text-fg" : "border-transparent text-muted hover:text-fg" | |
| 184 | 184 | }`} | |
| 185 | 185 | > | |
| 186 | 186 | {tab.label} | |
| 187 | − | <span className={`tabular rounded-full px-1.5 text-xs ${counts[tab.value] && tab.value !== "resolved" ? "bg-merged/15 text-merged" : "text-faint"}`}> | |
| 187 | + | <span className={`tabular rounded-full px-1.5 text-xs ${counts[tab.value] && tab.value !== "resolved" ? "bg-accent/15 text-accent" : "text-faint"}`}> | |
| 188 | 188 | {counts[tab.value]} | |
| 189 | 189 | </span> | |
| 190 | 190 | </Link> | |
| ⋯ | |||
| 229 | 229 | {filters.tab === "maintenance" ? ( | |
| 230 | 230 | shownMaintenance.length === 0 ? ( | |
| 231 | 231 | <EmptyState title="No maintenance scheduled"> | |
| 232 | − | <Link to="/incidents/maintenance/new" className="text-merged hover:underline"> | |
| 232 | + | <Link to="/incidents/maintenance/new" className="text-accent hover:underline"> | |
| 233 | 233 | Schedule maintenance | |
| 234 | 234 | </Link>{" "} | |
| 235 | 235 | to tell people ahead of planned work. | |
| 256 | 256 | <span className="font-medium">{waiting.length} waiting</span> | |
| 257 | 257 | <span className="text-muted"> · tick some, or </span> | |
| 258 | 258 | {selectAll ? ( | |
| 259 | − | <a href={clearHref} className="text-merged hover:underline"> | |
| 259 | + | <a href={clearHref} className="text-accent hover:underline"> | |
| 260 | 260 | clear | |
| 261 | 261 | </a> | |
| 262 | 262 | ) : ( | |
| 263 | − | <a href={selectHref} className="text-merged hover:underline"> | |
| 263 | + | <a href={selectHref} className="text-accent hover:underline"> | |
| 264 | 264 | tick all {Math.min(waiting.length, MAX_BULK)} | |
| 265 | 265 | </a> | |
| 266 | 266 | )} | |
| ⋯ | |||
| 300 | 300 | value={entry.id} | |
| 301 | 301 | defaultChecked={selectAll && index < MAX_BULK} | |
| 302 | 302 | aria-label={`Tick ${entry.email}`} | |
| 303 | − | className="mt-1 size-4 shrink-0 accent-[var(--color-merged)]" | |
| 303 | + | className="mt-1 size-4 shrink-0 accent-[var(--color-accent)]" | |
| 304 | 304 | /> | |
| 305 | 305 | )} | |
| 306 | 306 | <div className="min-w-0 grow space-y-2"> | |
| ⋯ | |||
| 594 | 594 | to={invitesHref(value)} | |
| 595 | 595 | aria-current={value === tab ? "page" : undefined} | |
| 596 | 596 | className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs whitespace-nowrap transition-colors ${ | |
| 597 | − | value === tab ? "border-merged/50 bg-merged/10 text-merged" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 597 | + | value === tab ? "border-accent/50 bg-accent/10 text-accent" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 598 | 598 | }`} | |
| 599 | 599 | > | |
| 600 | 600 | {TAB_LABEL[value]} | |
| 194 | 194 | <span> | |
| 195 | 195 | <When at={invoice.createdAt} /> | |
| 196 | 196 | {invoice.paidAt ? ( | |
| 197 | − | <span className="text-accent"> | |
| 197 | + | <span className="text-success"> | |
| 198 | 198 | {" · "}paid <When at={invoice.paidAt} /> | |
| 199 | 199 | </span> | |
| 200 | 200 | ) : null} | |
| ⋯ | |||
| 206 | 206 | const safe = safeUrl(url); | |
| 207 | 207 | if (!safe) return <span className="text-faint">—</span>; | |
| 208 | 208 | return ( | |
| 209 | − | <a href={safe} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-merged hover:underline"> | |
| 209 | + | <a href={safe} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-accent hover:underline"> | |
| 210 | 210 | Stripe | |
| 211 | 211 | <ExternalLink size={12} /> | |
| 212 | 212 | </a> | |
| 88 | 88 | {components.map((c) => ( | |
| 89 | 89 | <label | |
| 90 | 90 | key={c.key} | |
| 91 | − | className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-line px-2.5 py-1.5 text-sm has-checked:border-merged/60 has-checked:bg-merged/10" | |
| 91 | + | className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-line px-2.5 py-1.5 text-sm has-checked:border-accent/60 has-checked:bg-accent/10" | |
| 92 | 92 | > | |
| 93 | − | <input type="checkbox" name="components" value={c.key} defaultChecked={failed?.chosen.includes(c.key)} className="accent-[#b6a8ff]" /> | |
| 93 | + | <input type="checkbox" name="components" value={c.key} defaultChecked={failed?.chosen.includes(c.key)} className="accent-[var(--g1t-accent)]" /> | |
| 94 | 94 | {c.name} | |
| 95 | 95 | </label> | |
| 96 | 96 | ))} | |
| ⋯ | |||
| 99 | 99 | <Section title="Message" description="What will happen and what people will notice. Shown publicly."> | |
| 100 | 100 | <Textarea name="message" rows={4} required maxLength={4000} aria-label="Message" defaultValue={v.message} placeholder="We are upgrading the database behind git. Pushes may pause for up to five minutes; clones and the website keep working." /> | |
| 101 | 101 | <label className="mt-3 flex items-center gap-2 text-sm"> | |
| 102 | − | <input type="checkbox" name="notify" defaultChecked className="accent-[#b6a8ff]" /> | |
| 102 | + | <input type="checkbox" name="notify" defaultChecked className="accent-[var(--g1t-accent)]" /> | |
| 103 | 103 | Email subscribers now, when it starts, and when it ends | |
| 104 | 104 | </label> | |
| 105 | 105 | </Section> | |
| 75 | 75 | </> | |
| 76 | 76 | } | |
| 77 | 77 | actions={ | |
| 78 | − | <a href={m.url} className="inline-flex items-center gap-1.5 text-sm text-merged hover:underline"> | |
| 78 | + | <a href={m.url} className="inline-flex items-center gap-1.5 text-sm text-accent hover:underline"> | |
| 79 | 79 | On the status page <ExternalLink size={13} aria-hidden="true" /> | |
| 80 | 80 | </a> | |
| 81 | 81 | } | |
| ⋯ | |||
| 93 | 93 | <Textarea name="message" rows={3} maxLength={4000} aria-label="Update" placeholder="Running a little long: about 20 more minutes." /> | |
| 94 | 94 | <div className="flex flex-wrap items-center justify-between gap-3"> | |
| 95 | 95 | <label className="flex items-center gap-2 text-sm"> | |
| 96 | − | <input type="checkbox" name="notify" className="accent-[#b6a8ff]" /> | |
| 96 | + | <input type="checkbox" name="notify" className="accent-[var(--g1t-accent)]" /> | |
| 97 | 97 | Email subscribers | |
| 98 | 98 | </label> | |
| 99 | 99 | <Button type="submit" name="intent" value="update" variant="lavender"> | |
| ⋯ | |||
| 128 | 128 | <Textarea name="message" rows={2} maxLength={4000} placeholder="Said for you when empty." /> | |
| 129 | 129 | </Field> | |
| 130 | 130 | <label className="flex items-center gap-2 text-sm"> | |
| 131 | − | <input type="checkbox" name="notify" defaultChecked className="accent-[#b6a8ff]" /> | |
| 131 | + | <input type="checkbox" name="notify" defaultChecked className="accent-[var(--g1t-accent)]" /> | |
| 132 | 132 | Email subscribers | |
| 133 | 133 | </label> | |
| 134 | 134 | <div className="flex flex-wrap gap-2"> | |
| 63 | 63 | </p> | |
| 64 | 64 | ||
| 65 | 65 | {review && ( | |
| 66 | − | <section id="review" className="mt-6 scroll-mt-20 rounded-lg border border-merged/40 bg-merged/5 p-4 sm:p-5"> | |
| 66 | + | <section id="review" className="mt-6 scroll-mt-20 rounded-lg border border-accent/40 bg-accent/5 p-4 sm:p-5"> | |
| 67 | 67 | <h2 className="font-semibold tracking-tight">Create {review.name}?</h2> | |
| 68 | 68 | <p className="mt-1 text-sm text-muted"> | |
| 69 | 69 | These workspaces will be billed to it from now on, under its limit and terms instead of their own: |
| 277 | 277 | return ( | |
| 278 | 278 | <div className={`flex justify-between gap-4 ${strong ? "border-t border-line pt-1 font-medium" : ""}`}> | |
| 279 | 279 | <dt className="text-muted">{label}</dt> | |
| 280 | − | <dd className={`tabular ${tone === "mint" ? "text-accent" : tone === "warn" ? "text-warn" : "text-fg"}`}>{value}</dd> | |
| 280 | + | <dd className={`tabular ${tone === "mint" ? "text-success" : tone === "warn" ? "text-warn" : "text-fg"}`}>{value}</dd> | |
| 281 | 281 | </div> | |
| 282 | 282 | ); | |
| 283 | 283 | } |
| 246 | 246 | description={signalsError ? undefined : signalCount === 0 ? "Nobody needs a word right now." : `The most urgent of ${signalCount}.`} | |
| 247 | 247 | actions={ | |
| 248 | 248 | signalCount > 0 && ( | |
| 249 | − | <Link to="/reach-out" className="inline-flex items-center gap-1 text-sm text-merged hover:underline hover:underline-offset-4"> | |
| 249 | + | <Link to="/reach-out" className="inline-flex items-center gap-1 text-sm text-accent hover:underline hover:underline-offset-4"> | |
| 250 | 250 | All {signalCount} | |
| 251 | 251 | <ArrowRight size={14} /> | |
| 252 | 252 | </Link> |
| 83 | 83 | to={to} | |
| 84 | 84 | aria-current={active ? "page" : undefined} | |
| 85 | 85 | className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs whitespace-nowrap transition-colors ${ | |
| 86 | − | active ? "border-merged/50 bg-merged/10 text-merged" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 86 | + | active ? "border-accent/50 bg-accent/10 text-accent" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 87 | 87 | }`} | |
| 88 | 88 | > | |
| 89 | 89 | {children} | |
| 90 | − | {count != null && <span className={`tabular ${active ? "text-merged/80" : "text-faint"}`}>{count}</span>} | |
| 90 | + | {count != null && <span className={`tabular ${active ? "text-accent/80" : "text-faint"}`}>{count}</span>} | |
| 91 | 91 | </Link> | |
| 92 | 92 | ); | |
| 93 | 93 | } |
| 65 | 65 | to={requestsHref(value)} | |
| 66 | 66 | aria-current={value === status ? "page" : undefined} | |
| 67 | 67 | className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs whitespace-nowrap transition-colors ${ | |
| 68 | − | value === status ? "border-merged/50 bg-merged/10 text-merged" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 68 | + | value === status ? "border-accent/50 bg-accent/10 text-accent" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 69 | 69 | }`} | |
| 70 | 70 | > | |
| 71 | 71 | {STATUS_LABEL[value]} | |
| ⋯ | |||
| 201 | 201 | {overage ? ( | |
| 202 | 202 | <p className="text-sm text-muted"> | |
| 203 | 203 | Usually answered with a goodwill credit on{" "} | |
| 204 | − | <Link to={`/overages#${encodeURIComponent(request.workspace)}`} className="text-merged hover:underline"> | |
| 204 | + | <Link to={`/overages#${encodeURIComponent(request.workspace)}`} className="text-accent hover:underline"> | |
| 205 | 205 | Overages | |
| 206 | 206 | </Link> | |
| 207 | 207 | , which also closes this request. Or answer it here. | |
| 47 | 47 | return ( | |
| 48 | 48 | <main className="mx-auto max-w-3xl px-4 py-8 sm:py-14"> | |
| 49 | 49 | <div className="flex items-center gap-3"> | |
| 50 | − | <span className="flex size-10 items-center justify-center rounded-lg bg-merged/10 text-merged ring-1 ring-merged/30 ring-inset"> | |
| 50 | + | <span className="flex size-10 items-center justify-center rounded-lg bg-accent/10 text-accent ring-1 ring-accent/30 ring-inset"> | |
| 51 | 51 | <NavGlyph icon={icon} size={18} /> | |
| 52 | 52 | </span> | |
| 53 | 53 | <Badge tone="lavender">Soon</Badge> | |
| ⋯ | |||
| 66 | 66 | <ul className="mt-3 space-y-2.5"> | |
| 67 | 67 | {soon.plans.map((plan) => ( | |
| 68 | 68 | <li key={plan} className="flex gap-2.5 text-sm text-fg-soft"> | |
| 69 | − | <Check size={15} className="mt-0.5 shrink-0 text-merged" /> | |
| 69 | + | <Check size={15} className="mt-0.5 shrink-0 text-accent" /> | |
| 70 | 70 | <span>{plan}</span> | |
| 71 | 71 | </li> | |
| 72 | 72 | ))} | |
| ⋯ | |||
| 81 | 81 | {soon.meanwhile.to && ( | |
| 82 | 82 | <> | |
| 83 | 83 | {" "} | |
| 84 | − | <Link to={soon.meanwhile.to} className="inline-flex items-center gap-1 text-merged hover:underline hover:underline-offset-4"> | |
| 84 | + | <Link to={soon.meanwhile.to} className="inline-flex items-center gap-1 text-accent hover:underline hover:underline-offset-4"> | |
| 85 | 85 | {soon.meanwhile.link ?? "Open"} | |
| 86 | 86 | <ArrowRight size={13} /> | |
| 87 | 87 | </Link> | |
| 340 | 340 | <Section id="terms" title="Terms" description={`Charged on ${billedTo.name}'s terms while it pays for this workspace.`}> | |
| 341 | 341 | <div className="flex flex-wrap items-center justify-between gap-3"> | |
| 342 | 342 | <TermsBadge terms={terms} /> | |
| 343 | − | <Link to={`/enterprises/${encodeURIComponent(billedTo.id)}#terms`} className="text-sm text-merged hover:underline hover:underline-offset-4"> | |
| 343 | + | <Link to={`/enterprises/${encodeURIComponent(billedTo.id)}#terms`} className="text-sm text-accent hover:underline hover:underline-offset-4"> | |
| 344 | 344 | Change them on {billedTo.name} | |
| 345 | 345 | </Link> | |
| 346 | 346 | </div> | |
| ⋯ | |||
| 376 | 376 | billedTo ? ( | |
| 377 | 377 | <> | |
| 378 | 378 | Changes about this workspace in {billedTo.name}'s log.{" "} | |
| 379 | − | <Link to={`/enterprises/${encodeURIComponent(billedTo.id)}`} className="text-merged hover:underline"> | |
| 379 | + | <Link to={`/enterprises/${encodeURIComponent(billedTo.id)}`} className="text-accent hover:underline"> | |
| 380 | 380 | Full log | |
| 381 | 381 | </Link> | |
| 382 | 382 | </> | |
| ⋯ | |||
| 484 | 484 | ) : enterprises.length === 0 ? ( | |
| 485 | 485 | <p className="text-sm text-muted"> | |
| 486 | 486 | Itself. There are no enterprises to move it onto yet;{" "} | |
| 487 | − | <Link to="/enterprises/new" className="text-merged hover:underline hover:underline-offset-4"> | |
| 487 | + | <Link to="/enterprises/new" className="text-accent hover:underline hover:underline-offset-4"> | |
| 488 | 488 | create one | |
| 489 | 489 | </Link> | |
| 490 | 490 | . | |
| ⋯ | |||
| 608 | 608 | </div> | |
| 609 | 609 | <div className="flex justify-between gap-3"> | |
| 610 | 610 | <dt className="text-muted">g1t's margin, returned</dt> | |
| 611 | − | <dd className="tabular text-accent">{usd(overage.goodwill.marginMicros)}</dd> | |
| 611 | + | <dd className="tabular text-success">{usd(overage.goodwill.marginMicros)}</dd> | |
| 612 | 612 | </div> | |
| 613 | 613 | <div className="flex justify-between gap-3"> | |
| 614 | 614 | <dt className="text-muted">Real cost absorbed</dt> | |
| 135 | 135 | aria-label="Search workspaces" | |
| 136 | 136 | autoComplete="off" | |
| 137 | 137 | data-1p-ignore | |
| 138 | − | className="w-full rounded-md border border-line bg-bg py-2 pr-3 pl-8 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-merged/60" | |
| 138 | + | className="w-full rounded-md border border-line bg-bg py-2 pr-3 pl-8 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent/60" | |
| 139 | 139 | /> | |
| 140 | 140 | </div> | |
| 141 | 141 | <fieldset className="flex flex-wrap items-center gap-2"> | |
| ⋯ | |||
| 143 | 143 | {(Object.keys(FILTERS) as Filter[]).map((key) => ( | |
| 144 | 144 | <label | |
| 145 | 145 | key={key} | |
| 146 | − | className="inline-flex cursor-pointer items-center gap-2 rounded-full border border-line px-3 py-1.5 text-xs text-muted transition-colors select-none hover:border-line-strong has-checked:border-merged/50 has-checked:bg-merged/10 has-checked:text-merged" | |
| 146 | + | className="inline-flex cursor-pointer items-center gap-2 rounded-full border border-line px-3 py-1.5 text-xs text-muted transition-colors select-none hover:border-line-strong has-checked:border-accent/50 has-checked:bg-accent/10 has-checked:text-accent" | |
| 147 | 147 | > | |
| 148 | 148 | <input type="checkbox" name="show" value={key} defaultChecked={show.includes(key)} className="size-3.5" /> | |
| 149 | 149 | {FILTERS[key].label} | |
| 23 | 23 | --color-faint: var(--g1t-faint); | |
| 24 | 24 | ||
| 25 | 25 | --color-accent: var(--g1t-accent); | |
| 26 | + | --color-accent-hover: var(--g1t-accent-hover); | |
| 26 | 27 | --color-accent-dim: var(--g1t-accent-dim); | |
| 27 | 28 | ||
| 29 | + | --color-success: var(--g1t-success); | |
| 30 | + | --color-success-dim: var(--g1t-success-dim); | |
| 31 | + | ||
| 28 | 32 | --color-info: var(--g1t-info); | |
| 29 | 33 | --color-merged: var(--g1t-merged); | |
| 30 | 34 | --color-warn: var(--g1t-warn); | |
| ⋯ | |||
| 526 | 530 | position: absolute; | |
| 527 | 531 | inset: 0; | |
| 528 | 532 | border-radius: inherit; | |
| 529 | − | box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-merged) 18%, transparent); | |
| 533 | + | box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent); | |
| 530 | 534 | opacity: 0; | |
| 531 | 535 | pointer-events: none; | |
| 532 | 536 | animation: flow-step 10s ease-in-out infinite both; | |
| 88 | 88 | {REPO_ROLES.map((role) => ( | |
| 89 | 89 | <td key={role} className="px-1 py-2 text-center"> | |
| 90 | 90 | {allows(role, row.capability) ? ( | |
| 91 | − | <Check size={15} aria-label="Yes" className={`inline ${owners ? "text-muted" : "text-accent"}`} /> | |
| 91 | + | <Check size={15} aria-label="Yes" className={`inline ${owners ? "text-muted" : "text-success"}`} /> | |
| 92 | 92 | ) : ( | |
| 93 | 93 | <Minus size={13} aria-label="No" className="inline text-line-strong" /> | |
| 94 | 94 | )} |
| 19 | 19 | switch (conclusion) { | |
| 20 | 20 | case "success": | |
| 21 | 21 | return ( | |
| 22 | − | <span className="inline-flex shrink-0 rounded-full bg-accent/15 p-0.5 text-accent" aria-label="Succeeded"> | |
| 22 | + | <span className="inline-flex shrink-0 rounded-full bg-success/15 p-0.5 text-success" aria-label="Succeeded"> | |
| 23 | 23 | <Check size={size - 4} strokeWidth={3} /> | |
| 24 | 24 | </span> | |
| 25 | 25 | ); |
| 33 | 33 | case "issue.opened": | |
| 34 | 34 | return { | |
| 35 | 35 | icon: <CircleDot size={14} />, | |
| 36 | − | tone: isG1t(actor) ? "text-merged" : "text-muted", | |
| 36 | + | tone: isG1t(actor) ? "text-accent" : "text-muted", | |
| 37 | 37 | actor, | |
| 38 | 38 | text: isG1t(actor) ? <>filed {ref(event.data.number)} for something it found</> : <>opened {ref(event.data.number)}</>, | |
| 39 | 39 | coordination: isG1t(actor), | |
| ⋯ | |||
| 41 | 41 | case "pull.opened": | |
| 42 | 42 | return { | |
| 43 | 43 | icon: <Play size={14} />, | |
| 44 | − | tone: "text-merged", | |
| 44 | + | tone: "text-accent", | |
| 45 | 45 | actor: event.data.agent, | |
| 46 | 46 | text: ( | |
| 47 | 47 | <> | |
| ⋯ | |||
| 54 | 54 | case "checks.completed": | |
| 55 | 55 | return { | |
| 56 | 56 | icon: <Terminal size={14} />, | |
| 57 | − | tone: event.data.status === "passed" ? "text-accent" : "text-danger", | |
| 57 | + | tone: event.data.status === "passed" ? "text-success" : "text-danger", | |
| 58 | 58 | actor: null, | |
| 59 | 59 | text: ( | |
| 60 | 60 | <> | |
| ⋯ | |||
| 65 | 65 | case "review.completed": | |
| 66 | 66 | return { | |
| 67 | 67 | icon: event.data.verdict === "approve" ? <CircleCheck size={14} /> : <CircleSlash size={14} />, | |
| 68 | − | tone: event.data.verdict === "approve" ? "text-accent" : "text-warn", | |
| 68 | + | tone: event.data.verdict === "approve" ? "text-success" : "text-warn", | |
| 69 | 69 | actor: "g1t", | |
| 70 | 70 | text: ( | |
| 71 | 71 | <> | |
| ⋯ | |||
| 77 | 77 | if (event.data.verdict) { | |
| 78 | 78 | return { | |
| 79 | 79 | icon: event.data.verdict === "approve" ? <CircleCheck size={14} /> : <CircleSlash size={14} />, | |
| 80 | − | tone: event.data.verdict === "approve" ? "text-accent" : "text-warn", | |
| 80 | + | tone: event.data.verdict === "approve" ? "text-success" : "text-warn", | |
| 81 | 81 | actor, | |
| 82 | 82 | text: ( | |
| 83 | 83 | <> | |
| ⋯ | |||
| 88 | 88 | } | |
| 89 | 89 | return { | |
| 90 | 90 | icon: <MessageSquare size={14} />, | |
| 91 | − | tone: isG1t(actor) ? "text-merged" : "text-muted", | |
| 91 | + | tone: isG1t(actor) ? "text-accent" : "text-muted", | |
| 92 | 92 | actor, | |
| 93 | 93 | text: <>commented on {ref(event.data.number)}</>, | |
| 94 | 94 | coordination: isG1t(actor), | |
| ⋯ | |||
| 96 | 96 | case "pull.merged": | |
| 97 | 97 | return { icon: <GitMerge size={14} />, tone: "text-accent", actor, text: <>landed {ref(event.data.number)} on main</> }; | |
| 98 | 98 | case "agent.asked": | |
| 99 | − | return { icon: <MessageCircleQuestion size={14} />, tone: "text-merged", actor, text: <>asked the agent on {ref(event.data.number)}, and g1t woke it to answer</> }; | |
| 99 | + | return { icon: <MessageCircleQuestion size={14} />, tone: "text-accent", actor, text: <>asked the agent on {ref(event.data.number)}, and g1t woke it to answer</> }; | |
| 100 | 100 | case "pull.merge_requested": | |
| 101 | 101 | return { icon: <GitMerge size={14} />, tone: "text-muted", actor, text: <>is bringing {ref(event.data.number)} up to date</> }; | |
| 102 | 102 | default: | |
| ⋯ | |||
| 117 | 117 | <li | |
| 118 | 118 | key={event.id} | |
| 119 | 119 | className={`flex items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm ${ | |
| 120 | − | line!.coordination ? "bg-merged/[0.07] ring-1 ring-merged/25" : "" | |
| 120 | + | line!.coordination ? "bg-accent/[0.07] ring-1 ring-accent/25" : "" | |
| 121 | 121 | }`} | |
| 122 | 122 | > | |
| 123 | 123 | <span className={`shrink-0 ${line!.tone}`}>{line!.icon}</span> | |
| ⋯ | |||
| 154 | 154 | const state = exchange.answer | |
| 155 | 155 | ? exchange.declined | |
| 156 | 156 | ? { label: "Declined", tone: "text-warn ring-warn/40" } | |
| 157 | − | : { label: exchange.kind === "handoff" ? "Taken on" : "Answered", tone: "text-accent ring-accent/40" } | |
| 157 | + | : { label: exchange.kind === "handoff" ? "Taken on" : "Answered", tone: "text-success ring-success/40" } | |
| 158 | 158 | : exchange.deliveredAt | |
| 159 | 159 | ? { label: "Read", tone: "text-info ring-info/40" } | |
| 160 | 160 | : { label: "Waiting to be read", tone: "text-faint ring-line" }; | |
| 161 | 161 | return ( | |
| 162 | − | <li key={exchange.id} className="rounded-2xl bg-merged/[0.05] p-4 ring-1 ring-merged/25"> | |
| 162 | + | <li key={exchange.id} className="rounded-2xl bg-accent/[0.05] p-4 ring-1 ring-accent/25"> | |
| 163 | 163 | <p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted"> | |
| 164 | 164 | <Avatar name="g1t" size={14} /> | |
| 165 | 165 | <span> | |
| ⋯ | |||
| 170 | 170 | </p> | |
| 171 | 171 | <p className="mt-2 text-sm">{exchange.body}</p> | |
| 172 | 172 | {exchange.answer && ( | |
| 173 | − | <p className="mt-2 border-l-2 border-merged/40 pl-3 text-sm text-fg-soft">{exchange.answer}</p> | |
| 173 | + | <p className="mt-2 border-l-2 border-accent/40 pl-3 text-sm text-fg-soft">{exchange.answer}</p> | |
| 174 | 174 | )} | |
| 175 | 175 | </li> | |
| 176 | 176 | ); | |
| 75 | 75 | > | |
| 76 | 76 | <input type="hidden" name="intent" value="delegate" /> | |
| 77 | 77 | <p className="flex items-center gap-2 text-sm font-semibold"> | |
| 78 | − | <Sparkles size={15} className="text-merged" /> | |
| 78 | + | <Sparkles size={15} className="text-accent" /> | |
| 79 | 79 | Put an agent on it | |
| 80 | 80 | </p> | |
| 81 | 81 | <p className="mt-1 text-xs leading-5 text-muted"> | |
| ⋯ | |||
| 156 | 156 | <button | |
| 157 | 157 | type="submit" | |
| 158 | 158 | disabled={busy || repos.length === 0} | |
| 159 | − | className="inline-flex shrink-0 items-center gap-1.5 rounded-md bg-merged px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-[#c9bfff] disabled:opacity-60" | |
| 159 | + | className="inline-flex shrink-0 items-center gap-1.5 rounded-md bg-accent px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-accent-hover disabled:opacity-60" | |
| 160 | 160 | > | |
| 161 | 161 | {busy ? <LoaderCircle size={14} className="animate-spin" /> : <Sparkles size={14} />} | |
| 162 | 162 | {busy ? "Starting…" : "Put an agent on it"} | |
| 105 | 105 | onClick={copy} | |
| 106 | 106 | className="absolute top-1.5 right-1.5 rounded-md p-1.5 text-faint transition-colors hover:bg-raised hover:text-fg" | |
| 107 | 107 | > | |
| 108 | − | {copied ? <Check size={14} className="text-accent" /> : <Copy size={14} />} | |
| 108 | + | {copied ? <Check size={14} className="text-success" /> : <Copy size={14} />} | |
| 109 | 109 | </button> | |
| 110 | 110 | </div> | |
| 111 | 111 | </div> |
| 83 | 83 | queued: { label: "Starting", tone: "text-muted border-line", icon: <Clock size={12} /> }, | |
| 84 | 84 | running: { | |
| 85 | 85 | label: "Running", | |
| 86 | − | tone: "text-merged border-merged/40 bg-merged/10", | |
| 86 | + | tone: "text-accent border-accent/40 bg-accent/10", | |
| 87 | 87 | icon: <Loader2 size={12} className="animate-spin" />, | |
| 88 | 88 | }, | |
| 89 | − | succeeded: { label: "Done", tone: "text-accent border-accent/40 bg-accent/10", icon: <Bot size={12} /> }, | |
| 89 | + | succeeded: { label: "Done", tone: "text-success border-success/40 bg-success/10", icon: <Bot size={12} /> }, | |
| 90 | 90 | failed: { label: "Failed", tone: "text-danger border-danger/40 bg-danger/10", icon: <TriangleAlert size={12} /> }, | |
| 91 | 91 | stopped: { label: "Stopped", tone: "text-warn border-warn/40 bg-warn/10", icon: <OctagonX size={12} /> }, | |
| 92 | 92 | }; | |
| ⋯ | |||
| 194 | 194 | className="w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none placeholder:text-faint focus:border-accent-dim" | |
| 195 | 195 | /> | |
| 196 | 196 | {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>} | |
| 197 | − | {fetcher.data?.ok && fetcher.data.notice && <p className="text-sm text-accent">{fetcher.data.notice}</p>} | |
| 197 | + | {fetcher.data?.ok && fetcher.data.notice && <p className="text-sm text-success">{fetcher.data.notice}</p>} | |
| 198 | 198 | <div className="flex justify-end"> | |
| 199 | 199 | <SubmitButton | |
| 200 | 200 | fetcher={fetcher} | |
| ⋯ | |||
| 246 | 246 | return ( | |
| 247 | 247 | <li | |
| 248 | 248 | className={`rounded-xl border bg-surface p-4 transition-colors hover:border-line-strong ${ | |
| 249 | − | active ? "border-merged/30" : "border-line" | |
| 249 | + | active ? "border-accent/30" : "border-line" | |
| 250 | 250 | }`} | |
| 251 | 251 | > | |
| 252 | 252 | <div className="flex flex-wrap items-center gap-x-3 gap-y-2"> | |
| ⋯ | |||
| 260 | 260 | </div> | |
| 261 | 261 | {run.step && ( | |
| 262 | 262 | <p className={`mt-2.5 truncate font-mono text-xs ${active ? "text-fg/85" : "text-muted"}`} title={inlinePlain(shownStep(run.step))}> | |
| 263 | − | {active && <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" />} | |
| 263 | + | {active && <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-accent align-middle" />} | |
| 264 | 264 | <InlineMarkdown text={shownStep(run.step)} /> | |
| 265 | 265 | </p> | |
| 266 | 266 | )} | |
| ⋯ | |||
| 351 | 351 | ||
| 352 | 352 | /** "Agent confidence: Low — tests not added, 3 revisions", and what the agent said it was unsure of. */ | |
| 353 | 353 | export function ConfidenceLine({ confidence }: { confidence: Confidence }) { | |
| 354 | − | const tone = confidence.level === "low" ? "text-danger" : confidence.level === "medium" ? "text-warn" : "text-accent"; | |
| 354 | + | const tone = confidence.level === "low" ? "text-danger" : confidence.level === "medium" ? "text-warn" : "text-success"; | |
| 355 | 355 | const level = { low: "Low", medium: "Medium", high: "High" }[confidence.level]; | |
| 356 | 356 | return ( | |
| 357 | 357 | <div className="mt-3 text-xs leading-5"> | |
| ⋯ | |||
| 404 | 404 | return ( | |
| 405 | 405 | <section | |
| 406 | 406 | aria-label="Agent" | |
| 407 | − | className={`mt-4 rounded-2xl bg-surface p-4 ring-1 ${active ? "ring-merged/40" : "ring-line"}`} | |
| 407 | + | className={`mt-4 rounded-2xl bg-surface p-4 ring-1 ${active ? "ring-accent/40" : "ring-line"}`} | |
| 408 | 408 | > | |
| 409 | 409 | <div className="flex flex-wrap items-center gap-x-3 gap-y-2"> | |
| 410 | 410 | <Avatar name={current.agent} size={24} /> | |
| ⋯ | |||
| 438 | 438 | </div> | |
| 439 | 439 | {current.step && ( | |
| 440 | 440 | <p className="mt-3 truncate rounded-lg bg-bg px-3 py-2 font-mono text-xs text-fg/85 ring-1 ring-line" title={inlinePlain(shownStep(current.step))}> | |
| 441 | − | {active && <span className="mr-2 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" />} | |
| 441 | + | {active && <span className="mr-2 inline-block size-1.5 animate-pulse rounded-full bg-accent align-middle" />} | |
| 442 | 442 | <InlineMarkdown text={shownStep(current.step)} /> | |
| 443 | 443 | </p> | |
| 444 | 444 | )} | |
| ⋯ | |||
| 478 | 478 | return ( | |
| 479 | 479 | <span | |
| 480 | 480 | title={run.step ? inlinePlain(shownStep(run.step)) : undefined} | |
| 481 | − | className="inline-flex shrink-0 items-center gap-1 rounded-full border border-merged/40 bg-merged/10 px-2 py-0.5 text-xs text-merged" | |
| 481 | + | className="inline-flex shrink-0 items-center gap-1 rounded-full border border-accent/40 bg-accent/10 px-2 py-0.5 text-xs text-accent" | |
| 482 | 482 | > | |
| 483 | 483 | <Loader2 size={11} className="animate-spin" /> | |
| 484 | 484 | {RUN_KIND_LABEL[run.kind]} | |
| 17 | 17 | import { type Point, iso, isoPath, pts, stagger, timing } from "../lib/art"; | |
| 18 | 18 | ||
| 19 | 19 | const LINE = "var(--color-fg)"; | |
| 20 | − | const MINT = "var(--color-accent)"; | |
| 21 | − | const LAVENDER = "var(--color-merged)"; | |
| 20 | + | const MINT = "var(--color-success)"; | |
| 21 | + | const LAVENDER = "var(--color-accent)"; | |
| 22 | 22 | const PEACH = "var(--color-warn)"; | |
| 23 | 23 | const DANGER = "var(--color-danger)"; | |
| 24 | 24 |
| 14 | 14 | <section aria-label="Agent" className="space-y-3"> | |
| 15 | 15 | <div aria-disabled="true" className="rounded-xl border border-line bg-surface opacity-80"> | |
| 16 | 16 | <p className="flex items-center gap-2 px-4 pt-3 text-sm font-medium text-fg"> | |
| 17 | − | <Sparkles size={14} className="text-merged" aria-hidden="true" /> | |
| 17 | + | <Sparkles size={14} className="text-accent" aria-hidden="true" /> | |
| 18 | 18 | Agent | |
| 19 | 19 | <span className="rounded-full border border-line px-1.5 text-[0.6875rem] font-normal text-muted">Coming later</span> | |
| 20 | 20 | </p> |
| 86 | 86 | title={ | |
| 87 | 87 | <> | |
| 88 | 88 | {plan?.title ?? "g1t"} | |
| 89 | − | <span className={`rounded-full px-2 py-0.5 text-xs ${on && status.kind !== "past_due" && status.kind !== "canceling" ? "bg-accent/15 text-accent" : status.kind === "free" || status.kind === "trial" ? "border border-line text-muted" : "bg-warn/15 text-warn"}`}> | |
| 89 | + | <span className={`rounded-full px-2 py-0.5 text-xs ${on && status.kind !== "past_due" && status.kind !== "canceling" ? "bg-success/15 text-success" : status.kind === "free" || status.kind === "trial" ? "border border-line text-muted" : "bg-warn/15 text-warn"}`}> | |
| 90 | 90 | {status.label} | |
| 91 | 91 | {status.kind === "canceling" && subscription?.periodEnd ? `, ${new Date(subscription.periodEnd).toLocaleDateString()}` : ""} | |
| 92 | 92 | </span> | |
| ⋯ | |||
| 298 | 298 | <div className="mt-5 rounded-lg border border-line bg-bg/40 p-4"> | |
| 299 | 299 | <div className="flex flex-wrap items-baseline justify-between gap-2"> | |
| 300 | 300 | <p className="text-sm font-medium">Auto-reload</p> | |
| 301 | − | <span className={`rounded-full px-2 py-0.5 text-xs ${r.enabled ? "bg-accent/15 text-accent" : "border border-line text-muted"}`}>{r.enabled ? "On" : "Off"}</span> | |
| 301 | + | <span className={`rounded-full px-2 py-0.5 text-xs ${r.enabled ? "bg-success/15 text-success" : "border border-line text-muted"}`}>{r.enabled ? "On" : "Off"}</span> | |
| 302 | 302 | </div> | |
| 303 | 303 | {r.failedAt && ( | |
| 304 | 304 | <p className="mt-2 text-sm text-danger"> | |
| ⋯ | |||
| 353 | 353 | </span> | |
| 354 | 354 | </div> | |
| 355 | 355 | <div className="relative mt-1.5 h-1.5 rounded-full bg-line" role="presentation"> | |
| 356 | − | <div className={`h-full rounded-full ${spent >= of ? "bg-danger" : spent * 4 >= of * 3 ? "bg-warn" : "bg-accent"}`} style={{ width: `${Math.min(100, (spent / Math.max(1, of)) * 100)}%` }} /> | |
| 356 | + | <div className={`h-full rounded-full ${spent >= of ? "bg-danger" : spent * 4 >= of * 3 ? "bg-warn" : "bg-success"}`} style={{ width: `${Math.min(100, (spent / Math.max(1, of)) * 100)}%` }} /> | |
| 357 | 357 | {levels.filter((l) => l < 100).map((l) => ( | |
| 358 | 358 | <span key={l} className="absolute -top-0.5 h-2.5 w-px bg-fg/40" style={{ left: `${l}%` }} /> | |
| 359 | 359 | ))} | |
| ⋯ | |||
| 498 | 498 | <p className="text-xs text-muted">{row.about}</p> | |
| 499 | 499 | </div> | |
| 500 | 500 | <span className="tabular-nums text-muted">{row.price}</span> | |
| 501 | − | <span className={`rounded-full px-2 py-0.5 text-xs ${row.on ? "bg-accent/15 text-accent" : "border border-line text-muted"}`}>{row.label}</span> | |
| 501 | + | <span className={`rounded-full px-2 py-0.5 text-xs ${row.on ? "bg-success/15 text-success" : "border border-line text-muted"}`}>{row.label}</span> | |
| 502 | 502 | {row.control} | |
| 503 | 503 | </li> | |
| 504 | 504 | ))} | |
| ⋯ | |||
| 635 | 635 | <li key={invoice.id} className="flex flex-wrap items-center gap-x-4 gap-y-1 px-5 py-2.5 text-sm"> | |
| 636 | 636 | <span className="w-24 shrink-0 text-muted tabular-nums">{new Date(invoice.createdAt).toLocaleDateString("en-US", { timeZone: "UTC" })}</span> | |
| 637 | 637 | <span className="min-w-0 flex-1 truncate">{invoice.description ?? invoice.number ?? invoice.id}</span> | |
| 638 | − | <span className={`rounded-full px-2 py-0.5 text-xs ${invoice.status === "paid" ? "bg-accent/15 text-accent" : invoice.status === "open" ? "bg-warn/15 text-warn" : "border border-line text-muted"}`}> | |
| 638 | + | <span className={`rounded-full px-2 py-0.5 text-xs ${invoice.status === "paid" ? "bg-success/15 text-success" : invoice.status === "open" ? "bg-warn/15 text-warn" : "border border-line text-muted"}`}> | |
| 639 | 639 | {STATUS[invoice.status] ?? invoice.status} | |
| 640 | 640 | </span> | |
| 641 | 641 | <span className="w-20 text-right tabular-nums">{money(invoice.totalCents * 10_000)}</span> | |
| 117 | 117 | calm?: boolean; | |
| 118 | 118 | }) { | |
| 119 | 119 | const part = share(used, of); | |
| 120 | − | const bar = calm ? "bg-accent" : of != null && used >= of ? "bg-danger" : part >= 0.75 ? "bg-warn" : "bg-accent"; | |
| 120 | + | const bar = calm ? "bg-success" : of != null && used >= of ? "bg-danger" : part >= 0.75 ? "bg-warn" : "bg-success"; | |
| 121 | 121 | return ( | |
| 122 | 122 | <div className="mt-4"> | |
| 123 | 123 | <div className="flex flex-wrap justify-between gap-x-4 text-sm"> | |
| ⋯ | |||
| 213 | 213 | status.kind === "past_due" || status.kind === "canceling" | |
| 214 | 214 | ? "bg-warn/15 text-warn" | |
| 215 | 215 | : on | |
| 216 | − | ? "bg-accent/15 text-accent" | |
| 216 | + | ? "bg-success/15 text-success" | |
| 217 | 217 | : "border border-line text-muted" | |
| 218 | 218 | }`} | |
| 219 | 219 | > | |
| ⋯ | |||
| 239 | 239 | <ul className="mt-4 grid gap-1.5 text-sm text-muted sm:grid-cols-2"> | |
| 240 | 240 | {plan.includes.map((line) => ( | |
| 241 | 241 | <li key={line} className="flex gap-2"> | |
| 242 | − | <span className="text-accent">✓</span> | |
| 242 | + | <span className="text-success">✓</span> | |
| 243 | 243 | {line} | |
| 244 | 244 | </li> | |
| 245 | 245 | ))} | |
| ⋯ | |||
| 542 | 542 | <span | |
| 543 | 543 | className={`rounded-full px-2 py-0.5 text-xs ${ | |
| 544 | 544 | request.status === "approved" | |
| 545 | − | ? "bg-accent/15 text-accent" | |
| 545 | + | ? "bg-success/15 text-success" | |
| 546 | 546 | : request.status === "declined" | |
| 547 | 547 | ? "bg-danger/15 text-danger" | |
| 548 | 548 | : "border border-line text-muted" | |
| 92 | 92 | return ( | |
| 93 | 93 | <div> | |
| 94 | 94 | <p className="flex items-center gap-2 border-b border-line px-4 py-2 text-xs text-muted"> | |
| 95 | − | <Sparkles size={13} className="text-merged" /> | |
| 95 | + | <Sparkles size={13} className="text-accent" /> | |
| 96 | 96 | Pick any line to see why it is the way it is: the commit, the pull request and issue it | |
| 97 | 97 | came from, and what the agent was thinking. | |
| 98 | 98 | </p> | |
| ⋯ | |||
| 152 | 152 | rows.push( | |
| 153 | 153 | <tr key={`why-${line}`}> | |
| 154 | 154 | <td colSpan={4} className="border-y border-line bg-bg/60 px-3 py-3 font-sans whitespace-normal md:pl-80"> | |
| 155 | − | <div className="max-w-2xl overflow-hidden rounded-xl border border-merged/30 bg-surface shadow-xl shadow-black/40"> | |
| 155 | + | <div className="max-w-2xl overflow-hidden rounded-xl border border-accent/30 bg-surface shadow-xl shadow-black/40"> | |
| 156 | 156 | <WhyPanel | |
| 157 | 157 | base={base} | |
| 158 | 158 | picked={picked} | |
| ⋯ | |||
| 199 | 199 | return ( | |
| 200 | 200 | <div> | |
| 201 | 201 | <header className="flex items-center gap-2 border-b border-line px-4 py-3"> | |
| 202 | − | <Sparkles size={15} className="text-merged" /> | |
| 202 | + | <Sparkles size={15} className="text-accent" /> | |
| 203 | 203 | <span className="grow text-sm font-medium"> | |
| 204 | 204 | Why line{picked.start === picked.end ? ` ${picked.start}` : `s ${picked.start}–${picked.end}`} | |
| 205 | 205 | </span> | |
| ⋯ | |||
| 253 | 253 | <p className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted"> | |
| 254 | 254 | {current.pull.runtime === "hosted" ? ( | |
| 255 | 255 | <> | |
| 256 | − | <span className="flex items-center gap-1 text-merged"> | |
| 256 | + | <span className="flex items-center gap-1 text-accent"> | |
| 257 | 257 | <Sparkles size={11} /> | |
| 258 | 258 | written by {current.pull.agent} | |
| 259 | 259 | </span> | |
| ⋯ | |||
| 303 | 303 | .map((step) => ( | |
| 304 | 304 | <blockquote | |
| 305 | 305 | key={step.seq} | |
| 306 | − | className="mt-3 border-l-2 border-merged/50 pl-3 text-[0.8125rem] whitespace-pre-line text-fg/90" | |
| 306 | + | className="mt-3 border-l-2 border-accent/50 pl-3 text-[0.8125rem] whitespace-pre-line text-fg/90" | |
| 307 | 307 | > | |
| 308 | 308 | {step.text} | |
| 309 | 309 | </blockquote> | |
| 56 | 56 | title={`${branch.drift.ahead} ${branch.drift.ahead === 1 ? "commit" : "commits"} ahead of ${main}, ${branch.drift.behind} behind`} | |
| 57 | 57 | > | |
| 58 | 58 | <span className="inline-flex items-center gap-0.5"> | |
| 59 | − | <ArrowUp size={11} className={branch.drift.ahead > 0 ? "text-accent" : "text-faint"} /> | |
| 59 | + | <ArrowUp size={11} className={branch.drift.ahead > 0 ? "text-success" : "text-faint"} /> | |
| 60 | 60 | {branch.drift.ahead} | |
| 61 | 61 | </span> | |
| 62 | 62 | <span className="inline-flex items-center gap-0.5"> | |
| ⋯ | |||
| 71 | 71 | className="inline-flex items-center gap-1.5 rounded-md border border-line px-1.5 py-0.5 text-muted hover:border-line-strong hover:text-fg" | |
| 72 | 72 | title={branch.pull.title} | |
| 73 | 73 | > | |
| 74 | − | <GitPullRequest size={12} className={branch.pull.draft ? "text-faint" : "text-accent"} />#{branch.pull.number} | |
| 74 | + | <GitPullRequest size={12} className={branch.pull.draft ? "text-faint" : "text-success"} />#{branch.pull.number} | |
| 75 | 75 | <CheckBadge status={branch.pull.checkStatus} /> | |
| 76 | 76 | </Link> | |
| 77 | 77 | ) : branch.drift?.ahead === 0 ? ( | |
| ⋯ | |||
| 93 | 93 | className="inline-flex max-w-48 items-center gap-1 truncate rounded-md border border-line px-1.5 py-0.5 text-muted hover:border-line-strong hover:text-fg" | |
| 94 | 94 | title={host(branch.preview)} | |
| 95 | 95 | > | |
| 96 | − | <Globe size={12} className="shrink-0 text-merged" /> | |
| 96 | + | <Globe size={12} className="shrink-0 text-accent" /> | |
| 97 | 97 | Preview | |
| 98 | 98 | </a> | |
| 99 | 99 | )} | |
| 46 | 46 | {done}/{total} | |
| 47 | 47 | </span> | |
| 48 | 48 | <span className="hidden h-1.5 w-24 overflow-hidden rounded-full bg-line sm:block" aria-hidden> | |
| 49 | − | <span className="block h-full rounded-full bg-accent" style={{ width: `${(done / total) * 100}%` }} /> | |
| 49 | + | <span className="block h-full rounded-full bg-success" style={{ width: `${(done / total) * 100}%` }} /> | |
| 50 | 50 | </span> | |
| 51 | 51 | <button | |
| 52 | 52 | type="button" | |
| ⋯ | |||
| 74 | 74 | <span | |
| 75 | 75 | className={cn( | |
| 76 | 76 | "mt-px flex size-4.5 shrink-0 items-center justify-center rounded-full border", | |
| 77 | − | item.done ? "border-accent bg-accent text-bg" : item.key === next ? "border-accent" : "border-line-strong", | |
| 77 | + | item.done ? "border-success bg-success text-bg" : item.key === next ? "border-success" : "border-line-strong", | |
| 78 | 78 | )} | |
| 79 | 79 | aria-hidden | |
| 80 | 80 | > | |
| 17 | 17 | }; | |
| 18 | 18 | ||
| 19 | 19 | export function CheckIcon({ status, size = 15 }: { status: CheckStatus; size?: number }) { | |
| 20 | − | if (status === "passed") return <CircleCheck size={size} className="shrink-0 text-accent" />; | |
| 20 | + | if (status === "passed") return <CircleCheck size={size} className="shrink-0 text-success" />; | |
| 21 | 21 | if (status === "failed") return <CircleX size={size} className="shrink-0 text-danger" />; | |
| 22 | 22 | if (status === "errored") return <TriangleAlert size={size} className="shrink-0 text-muted" />; | |
| 23 | 23 | return <LoaderCircle size={size} className="shrink-0 animate-spin text-info" />; |
| 136 | 136 | {errors > 0 ? ( | |
| 137 | 137 | <FileWarning size={16} className="shrink-0 text-danger" /> | |
| 138 | 138 | ) : ( | |
| 139 | − | <ShieldCheck size={16} className="shrink-0 text-accent" /> | |
| 139 | + | <ShieldCheck size={16} className="shrink-0 text-success" /> | |
| 140 | 140 | )} | |
| 141 | 141 | <Link to={blobHref(base, branch, path)} className="font-mono text-sm hover:text-accent hover:underline"> | |
| 142 | 142 | {path} | |
| ⋯ | |||
| 194 | 194 | if (errors === 0) { | |
| 195 | 195 | return ( | |
| 196 | 196 | <p className="mb-4 flex items-center gap-2 text-sm text-muted" role="status"> | |
| 197 | − | <CircleCheck size={15} className="shrink-0 text-accent" /> | |
| 197 | + | <CircleCheck size={15} className="shrink-0 text-success" /> | |
| 198 | 198 | No errors in this CODEOWNERS file. | |
| 199 | 199 | </p> | |
| 200 | 200 | ); | |
| ⋯ | |||
| 225 | 225 | } | |
| 226 | 226 | if (review.satisfied && review.approved_by.length > 0) { | |
| 227 | 227 | return ( | |
| 228 | − | <span className="flex items-center gap-1 text-xs text-accent"> | |
| 228 | + | <span className="flex items-center gap-1 text-xs text-success"> | |
| 229 | 229 | <CircleCheck size={13} className="shrink-0" /> Approved by {names(review.approved_by)} | |
| 230 | 230 | </span> | |
| 231 | 231 | ); | |
| ⋯ | |||
| 235 | 235 | } | |
| 236 | 236 | if (review.satisfied) { | |
| 237 | 237 | return ( | |
| 238 | − | <span className="flex items-center gap-1 text-xs text-accent"> | |
| 238 | + | <span className="flex items-center gap-1 text-xs text-success"> | |
| 239 | 239 | <CircleCheck size={13} className="shrink-0" /> Approved | |
| 240 | 240 | </span> | |
| 241 | 241 | ); | |
| 483 | 483 | function Rule({ rule, project, action }: { rule: RuleResult; project: string; action: string }) { | |
| 484 | 484 | const fetcher = useFetcher<Done>(); | |
| 485 | 485 | const icon = | |
| 486 | − | rule.status === "pass" ? <Check size={14} className="text-accent" /> : rule.status === "fail" ? <XCircle size={14} className="text-danger" /> : <CircleDashed size={14} className="text-faint" />; | |
| 486 | + | rule.status === "pass" ? <Check size={14} className="text-success" /> : rule.status === "fail" ? <XCircle size={14} className="text-danger" /> : <CircleDashed size={14} className="text-faint" />; | |
| 487 | 487 | return ( | |
| 488 | 488 | <li className="flex items-start gap-2.5 py-1.5"> | |
| 489 | 489 | <span className="mt-0.5">{icon}</span> | |
| 490 | 490 | <div className="min-w-0 grow text-sm"> | |
| 491 | 491 | <span className={rule.status === "na" ? "text-muted" : ""}>{rule.title}</span> | |
| 492 | 492 | <p className="text-xs text-muted">{rule.detail}</p> | |
| 493 | − | {fetcher.data?.message && <p className="mt-1 text-xs text-accent">{fetcher.data.message}</p>} | |
| 493 | + | {fetcher.data?.message && <p className="mt-1 text-xs text-success">{fetcher.data.message}</p>} | |
| 494 | 494 | {fetcher.data?.error && <p className="mt-1 text-xs text-danger">{fetcher.data.error}</p>} | |
| 495 | 495 | </div> | |
| 496 | 496 | {rule.fix && !fetcher.data?.ok && ( | |
| ⋯ | |||
| 524 | 524 | <Link to={`/${card.repo.namespace}/${card.repo.name}`} className="font-medium hover:text-accent"> | |
| 525 | 525 | {card.name} | |
| 526 | 526 | </Link> | |
| 527 | − | <span className={`text-xs ${card.passed === card.total ? "text-accent" : "text-muted"}`}> | |
| 527 | + | <span className={`text-xs ${card.passed === card.total ? "text-success" : "text-muted"}`}> | |
| 528 | 528 | {card.passed} of {card.total} | |
| 529 | 529 | </span> | |
| 530 | 530 | </div> | |
| 150 | 150 | ) : ( | |
| 151 | 151 | entry.lastResult && ( | |
| 152 | 152 | <p className="mt-2 flex items-start gap-1.5 text-xs text-muted"> | |
| 153 | − | <CircleCheck size={12} className="mt-0.5 shrink-0 text-accent" /> | |
| 153 | + | <CircleCheck size={12} className="mt-0.5 shrink-0 text-success" /> | |
| 154 | 154 | <span className="wrap-anywhere"> | |
| 155 | 155 | <Said text={entry.lastResult} /> | |
| 156 | 156 | </span> | |
| ⋯ | |||
| 191 | 191 | ) : pull.state === "requested" ? ( | |
| 192 | 192 | <Loader size={13} className="text-info" /> | |
| 193 | 193 | ) : ( | |
| 194 | − | <GitPullRequest size={13} className="text-accent" /> | |
| 194 | + | <GitPullRequest size={13} className="text-success" /> | |
| 195 | 195 | ); | |
| 196 | 196 | const count = pull.dependencies.length; | |
| 197 | 197 | return ( | |
| 4 | 4 | export const DEPLOY_STATUS: Record<DeployStatus, { label: string; tone: string }> = { | |
| 5 | 5 | queued: { label: "Queued", tone: "text-muted" }, | |
| 6 | 6 | building: { label: "Building", tone: "text-warn" }, | |
| 7 | − | ready: { label: "Live", tone: "text-accent" }, | |
| 7 | + | ready: { label: "Live", tone: "text-success" }, | |
| 8 | 8 | replaced: { label: "Replaced", tone: "text-faint" }, | |
| 9 | 9 | down: { label: "Down", tone: "text-faint" }, | |
| 10 | 10 | failed: { label: "Failed", tone: "text-danger" }, |
| 26 | 26 | ||
| 27 | 27 | const ROW_STYLES: Record<DiffLine["kind"], string> = { | |
| 28 | 28 | context: "", | |
| 29 | − | add: "bg-accent/[0.09]", | |
| 29 | + | add: "bg-success/[0.09]", | |
| 30 | 30 | delete: "bg-danger/[0.09]", | |
| 31 | 31 | }; | |
| 32 | 32 | ||
| 33 | 33 | const GUTTER_STYLES: Record<DiffLine["kind"], string> = { | |
| 34 | 34 | context: "text-faint", | |
| 35 | − | add: "bg-accent/[0.14] text-accent/70", | |
| 35 | + | add: "bg-success/[0.14] text-success/70", | |
| 36 | 36 | delete: "bg-danger/[0.14] text-danger/70", | |
| 37 | 37 | }; | |
| 38 | 38 | ||
| ⋯ | |||
| 71 | 71 | const green = total === 0 ? 0 : Math.round((additions / total) * 5); | |
| 72 | 72 | return ( | |
| 73 | 73 | <span className="flex shrink-0 items-center gap-2 font-mono text-xs"> | |
| 74 | − | <span className="text-accent">+{additions}</span> | |
| 74 | + | <span className="text-success">+{additions}</span> | |
| 75 | 75 | <span className="text-danger">−{deletions}</span> | |
| 76 | 76 | <span className="flex gap-px" aria-hidden="true"> | |
| 77 | 77 | {Array.from({ length: 5 }, (_, i) => ( | |
| 78 | 78 | <span | |
| 79 | 79 | key={i} | |
| 80 | 80 | className={`size-2 rounded-xs ${ | |
| 81 | − | total === 0 ? "bg-line" : i < green ? "bg-accent" : "bg-danger" | |
| 81 | + | total === 0 ? "bg-line" : i < green ? "bg-success" : "bg-danger" | |
| 82 | 82 | }`} | |
| 83 | 83 | /> | |
| 84 | 84 | ))} | |
| ⋯ | |||
| 92 | 92 | } | |
| 93 | 93 | ||
| 94 | 94 | function fileTone(status: HighlightedFile["status"]) { | |
| 95 | − | return status === "added" ? "text-accent" : status === "deleted" ? "text-danger" : "text-faint"; | |
| 95 | + | return status === "added" ? "text-success" : status === "deleted" ? "text-danger" : "text-faint"; | |
| 96 | 96 | } | |
| 97 | 97 | ||
| 98 | 98 | /** One comment made on a line, shown under that line. */ | |
| ⋯ | |||
| 239 | 239 | } | |
| 240 | 240 | ||
| 241 | 241 | function fileToneFor(kind: DiffLine["kind"]) { | |
| 242 | − | return kind === "add" ? "text-accent" : kind === "delete" ? "text-danger" : "text-faint"; | |
| 242 | + | return kind === "add" ? "text-success" : kind === "delete" ? "text-danger" : "text-faint"; | |
| 243 | 243 | } | |
| 244 | 244 | ||
| 245 | 245 | /** Lines side by side: a run of deletions beside the additions that replaced it. */ | |
| ⋯ | |||
| 563 | 563 | <Icon size={13} className={`shrink-0 ${fileTone(file.status)}`} /> | |
| 564 | 564 | <span className="min-w-0 grow truncate">{node.name}</span> | |
| 565 | 565 | <span className="shrink-0 text-[0.625rem] tabular-nums"> | |
| 566 | − | {file.additions > 0 && <span className="text-accent">+{file.additions}</span>} | |
| 566 | + | {file.additions > 0 && <span className="text-success">+{file.additions}</span>} | |
| 567 | 567 | {file.deletions > 0 && <span className="ml-1 text-danger">−{file.deletions}</span>} | |
| 568 | 568 | </span> | |
| 569 | 569 | </button> | |
| 388 | 388 | </span> | |
| 389 | 389 | of {cap}m | |
| 390 | 390 | </p> | |
| 391 | − | <Meter share={timeShare} tone={timeShare != null && timeShare > 0.85 ? "bg-warn" : "bg-accent"} /> | |
| 391 | + | <Meter share={timeShare} tone={timeShare != null && timeShare > 0.85 ? "bg-warn" : "bg-success"} /> | |
| 392 | 392 | </div> | |
| 393 | 393 | )} | |
| 394 | 394 | {member && ( | |
| ⋯ | |||
| 398 | 398 | {spent ? <span className="tabular-nums text-fg">{spent}</span> : <span>Spend</span>} | |
| 399 | 399 | of {formatCap(budget)} | |
| 400 | 400 | </p> | |
| 401 | − | <Meter share={costShare} tone={costShare != null && costShare > 0.85 ? "bg-warn" : "bg-accent"} /> | |
| 401 | + | <Meter share={costShare} tone={costShare != null && costShare > 0.85 ? "bg-warn" : "bg-success"} /> | |
| 402 | 402 | {active && !spent && budget != null && ( | |
| 403 | 403 | <p className="mt-1.5 text-xs text-faint"> | |
| 404 | 404 | The agent stops itself at the cap. What it spent is reported when the run ends. | |
| 31 | 31 | const ICON: Record<InboxSeverity, { icon: ReactNode; ring: string }> = { | |
| 32 | 32 | error: { icon: <CircleX size={15} />, ring: "bg-danger/10 text-danger ring-danger/30" }, | |
| 33 | 33 | warning: { icon: <Hand size={15} />, ring: "bg-warn/10 text-warn ring-warn/30" }, | |
| 34 | − | success: { icon: <CircleCheck size={15} />, ring: "bg-accent/10 text-accent ring-accent/30" }, | |
| 34 | + | success: { icon: <CircleCheck size={15} />, ring: "bg-success/10 text-success ring-success/30" }, | |
| 35 | 35 | info: { icon: <Info size={15} />, ring: "bg-info/10 text-info ring-info/30" }, | |
| 36 | 36 | }; | |
| 37 | 37 | ||
| ⋯ | |||
| 363 | 363 | aria-label="Agent" | |
| 364 | 364 | className="pointer-events-none flex h-9 items-center gap-1.5 rounded-md border border-line px-2.5 text-sm text-muted opacity-60 sm:px-3" | |
| 365 | 365 | > | |
| 366 | − | <Sparkles size={15} className="text-merged" /> | |
| 366 | + | <Sparkles size={15} className="text-accent" /> | |
| 367 | 367 | <span className="hidden sm:inline">Agent</span> | |
| 368 | 368 | </button> | |
| 369 | 369 | </span> | |
| 51 | 51 | aria-label={`${percent}% complete`} | |
| 52 | 52 | className={cn("block h-2 overflow-hidden rounded-full bg-line", className)} | |
| 53 | 53 | > | |
| 54 | − | <span className="block h-full rounded-full bg-accent transition-[width]" style={{ width: `${percent}%` }} /> | |
| 54 | + | <span className="block h-full rounded-full bg-success transition-[width]" style={{ width: `${percent}%` }} /> | |
| 55 | 55 | </span> | |
| 56 | 56 | ); | |
| 57 | 57 | } |
| 15 | 15 | ||
| 16 | 16 | /** A small label above a heading, in the mono face. */ | |
| 17 | 17 | function Eyebrow({ children }: { children: ReactNode }) { | |
| 18 | − | return <p className="font-mono text-xs tracking-[0.2em] text-merged uppercase">{children}</p>; | |
| 18 | + | return <p className="font-mono text-xs tracking-[0.2em] text-accent uppercase">{children}</p>; | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | function Tags({ items }: { items: string[] }) { | |
| ⋯ | |||
| 36 | 36 | /** A link in running copy that leads to the docs or another page. */ | |
| 37 | 37 | function More({ to, children }: { to: string; children: ReactNode }) { | |
| 38 | 38 | return ( | |
| 39 | − | <Link to={to} className="group mt-7 inline-flex items-center gap-1.5 text-sm font-medium text-fg hover:text-merged"> | |
| 39 | + | <Link to={to} className="group mt-7 inline-flex items-center gap-1.5 text-sm font-medium text-fg hover:text-accent"> | |
| 40 | 40 | {children} | |
| 41 | 41 | <ArrowRight size={14} className="transition-transform group-hover:translate-x-0.5" /> | |
| 42 | 42 | </Link> | |
| ⋯ | |||
| 86 | 86 | <ul className="mt-6 grid gap-2.5 text-sm sm:grid-cols-2"> | |
| 87 | 87 | {points.map((point) => ( | |
| 88 | 88 | <li key={point} className="flex gap-2.5 text-fg-soft"> | |
| 89 | − | <Check size={15} className="mt-0.5 shrink-0 text-merged" /> | |
| 89 | + | <Check size={15} className="mt-0.5 shrink-0 text-accent" /> | |
| 90 | 90 | <span>{point}</span> | |
| 91 | 91 | </li> | |
| 92 | 92 | ))} | |
| ⋯ | |||
| 227 | 227 | to={`${DOCS}/concepts/overview/`} | |
| 228 | 228 | className="inline-flex animate-fade-up items-center gap-2 rounded-full bg-bg/50 px-3 py-1 text-xs text-fg-soft ring-1 ring-white/10 backdrop-blur transition-colors hover:bg-bg/70" | |
| 229 | 229 | > | |
| 230 | − | <span className="size-1.5 rounded-full bg-merged" /> | |
| 230 | + | <span className="size-1.5 rounded-full bg-accent" /> | |
| 231 | 231 | Open source · built on Cloudflare | |
| 232 | 232 | <ArrowRight size={12} /> | |
| 233 | 233 | </Link> | |
| 234 | 234 | <h1 className="mx-auto mt-7 max-w-4xl animate-fade-up text-[2.75rem] leading-[1.04] font-semibold tracking-tight text-balance sm:text-7xl"> | |
| 235 | 235 | Where people and agents{" "} | |
| 236 | − | <span className="bg-gradient-to-r from-fg via-[#d9d1ff] to-merged bg-clip-text text-transparent"> | |
| 236 | + | <span className="bg-gradient-to-r from-fg via-[#d9d1ff] to-accent bg-clip-text text-transparent"> | |
| 237 | 237 | ship software together. | |
| 238 | 238 | </span> | |
| 239 | 239 | </h1> | |
| ⋯ | |||
| 402 | 402 | column, so the line stops one column, less that, from the end. */} | |
| 403 | 403 | <span | |
| 404 | 404 | aria-hidden="true" | |
| 405 | − | className="absolute top-4 right-[calc((100%-6rem)/5-1rem)] left-4 hidden h-px bg-gradient-to-r from-merged/50 via-warn/50 to-accent/70 md:block" | |
| 405 | + | className="absolute top-4 right-[calc((100%-6rem)/5-1rem)] left-4 hidden h-px bg-gradient-to-r from-accent/50 via-warn/50 to-success/70 md:block" | |
| 406 | 406 | > | |
| 407 | 407 | <span className="flow-runner absolute inset-0"> | |
| 408 | − | <span className="absolute -top-[3px] -left-[3.5px] size-[7px] rounded-full bg-merged shadow-[0_0_12px_2px_rgb(182_168_255/0.6)]" /> | |
| 408 | + | <span className="absolute -top-[3px] -left-[3.5px] size-[7px] rounded-full bg-accent shadow-[0_0_12px_2px_rgb(182_168_255/0.6)]" /> | |
| 409 | 409 | </span> | |
| 410 | 410 | </span> | |
| 411 | 411 | {FLOW.map(([step, about], index) => ( | |
| 412 | 412 | <li key={step} className="relative"> | |
| 413 | 413 | <span | |
| 414 | 414 | className={`flow-step relative flex size-8 items-center justify-center rounded-full font-mono text-xs ring-1 ${ | |
| 415 | − | index === FLOW.length - 1 ? "bg-accent text-bg ring-accent" : "bg-bg text-fg-soft ring-line-strong" | |
| 415 | + | index === FLOW.length - 1 ? "bg-success text-bg ring-success" : "bg-bg text-fg-soft ring-line-strong" | |
| 416 | 416 | }`} | |
| 417 | 417 | style={{ "--delay": `${index * 2}s` } as CSSProperties} | |
| 418 | 418 | > | |
| ⋯ | |||
| 462 | 462 | to={item.to} | |
| 463 | 463 | className="group rounded-2xl bg-surface p-6 ring-1 ring-line transition-colors hover:ring-line-strong" | |
| 464 | 464 | > | |
| 465 | − | <span className="flex size-9 items-center justify-center rounded-lg bg-bg text-merged ring-1 ring-line">{item.icon}</span> | |
| 465 | + | <span className="flex size-9 items-center justify-center rounded-lg bg-bg text-accent ring-1 ring-line">{item.icon}</span> | |
| 466 | 466 | <h3 className="mt-5 font-semibold">{item.title}</h3> | |
| 467 | 467 | <p className="mt-2 text-sm leading-6 text-muted">{item.about}</p> | |
| 468 | 468 | </Link> | |
| 41 | 41 | key={step} | |
| 42 | 42 | className={`size-1.5 rounded-full ${ | |
| 43 | 43 | index < current || stage === "ready" | |
| 44 | − | ? "bg-accent" | |
| 44 | + | ? "bg-success" | |
| 45 | 45 | : index === current | |
| 46 | 46 | ? "animate-pulse bg-accent" | |
| 47 | 47 | : "bg-line-strong" | |
| ⋯ | |||
| 105 | 105 | <span | |
| 106 | 106 | aria-hidden="true" | |
| 107 | 107 | className={`block h-1 rounded-full ${ | |
| 108 | − | done ? "bg-accent" : active ? "animate-pulse bg-accent/60 motion-reduce:animate-none" : "bg-line" | |
| 108 | + | done ? "bg-success" : active ? "animate-pulse bg-success/60 motion-reduce:animate-none" : "bg-line" | |
| 109 | 109 | }`} | |
| 110 | 110 | /> | |
| 111 | 111 | <span className={`mt-1.5 block truncate text-[0.6875rem] ${active ? "font-medium text-fg" : done ? "text-muted" : "text-faint"}`}> | |
| 28 | 28 | y={p.y * 10 + 1} | |
| 29 | 29 | width="8" | |
| 30 | 30 | height="8" | |
| 31 | − | fill={p.one ? "var(--g1t-merged)" : "currentColor"} | |
| 31 | + | fill={p.one ? "var(--g1t-accent)" : "currentColor"} | |
| 32 | 32 | /> | |
| 33 | 33 | )); | |
| 34 | 34 | } |
| 47 | 47 | ||
| 48 | 48 | const ALERT: Record<AlertKind, { title: string; icon: ReactNode; tone: string }> = { | |
| 49 | 49 | note: { title: "Note", icon: <Info size={15} />, tone: "border-info/60 [&_.alert-title]:text-info" }, | |
| 50 | − | tip: { title: "Tip", icon: <Lightbulb size={15} />, tone: "border-accent/60 [&_.alert-title]:text-accent" }, | |
| 50 | + | tip: { title: "Tip", icon: <Lightbulb size={15} />, tone: "border-success/60 [&_.alert-title]:text-accent" }, | |
| 51 | 51 | important: { | |
| 52 | 52 | title: "Important", | |
| 53 | 53 | icon: <MessageSquareWarning size={15} />, | |
| ⋯ | |||
| 136 | 136 | }} | |
| 137 | 137 | className="rounded-md border border-line bg-raised p-1.5 text-muted hover:text-fg" | |
| 138 | 138 | > | |
| 139 | − | {copied ? <Check size={13} className="text-accent" /> : <Copy size={13} />} | |
| 139 | + | {copied ? <Check size={13} className="text-success" /> : <Copy size={13} />} | |
| 140 | 140 | </button> | |
| 141 | 141 | </div> | |
| 142 | 142 | </div> | |
| 238 | 238 | <input type="hidden" name="intent" value="add" /> | |
| 239 | 239 | <input type="hidden" name="scope" value={scope} /> | |
| 240 | 240 | <label className="flex items-center gap-2 text-sm font-medium"> | |
| 241 | − | <Brain size={15} className="text-merged" /> | |
| 241 | + | <Brain size={15} className="text-accent" /> | |
| 242 | 242 | Add to {scope === "workspace" ? "the workspace's" : "this project's"} memory | |
| 243 | 243 | </label> | |
| 244 | 244 | <textarea name="text" required rows={2} maxLength={1000} placeholder={placeholder} className={`${TEXTAREA} mt-3`} /> |
| 101 | 101 | }`} | |
| 102 | 102 | > | |
| 103 | 103 | {handle === AGENT_HANDLE ? ( | |
| 104 | − | <Bot size={12} className="shrink-0 text-merged" /> | |
| 104 | + | <Bot size={12} className="shrink-0 text-accent" /> | |
| 105 | 105 | ) : ( | |
| 106 | 106 | <Users size={12} className="shrink-0 text-faint" /> | |
| 107 | 107 | )} |
| 43 | 43 | const ANSI_COLOURS: Record<number, string> = { | |
| 44 | 44 | 30: "text-faint", | |
| 45 | 45 | 31: "text-danger", | |
| 46 | − | 32: "text-accent", | |
| 46 | + | 32: "text-success", | |
| 47 | 47 | 33: "text-warn", | |
| 48 | 48 | 34: "text-info", | |
| 49 | 49 | 35: "text-merged", | |
| ⋯ | |||
| 51 | 51 | 37: "text-fg", | |
| 52 | 52 | 90: "text-faint", | |
| 53 | 53 | 91: "text-danger", | |
| 54 | − | 92: "text-accent", | |
| 54 | + | 92: "text-success", | |
| 55 | 55 | 93: "text-warn", | |
| 56 | 56 | 94: "text-info", | |
| 57 | 57 | 95: "text-merged", | |
| ⋯ | |||
| 139 | 139 | setTimeout(() => setCopied(false), 1500); | |
| 140 | 140 | }} | |
| 141 | 141 | > | |
| 142 | − | {copied ? <Check size={13} className="text-accent" /> : <Copy size={13} />} | |
| 142 | + | {copied ? <Check size={13} className="text-success" /> : <Copy size={13} />} | |
| 143 | 143 | </button> | |
| 144 | 144 | {cut && ( | |
| 145 | 145 | <p className="border-b border-line px-3 py-1.5 text-xs text-faint"> | |
| ⋯ | |||
| 179 | 179 | type Standing = "passed" | "failed" | "running" | "queued" | "skipped"; | |
| 180 | 180 | ||
| 181 | 181 | function StandingIcon({ standing, size = 15 }: { standing: Standing; size?: number }) { | |
| 182 | − | if (standing === "passed") return <CircleCheck size={size} className="shrink-0 text-accent" aria-label="Passed" />; | |
| 182 | + | if (standing === "passed") return <CircleCheck size={size} className="shrink-0 text-success" aria-label="Passed" />; | |
| 183 | 183 | if (standing === "failed") return <CircleX size={size} className="shrink-0 text-danger" aria-label="Failed" />; | |
| 184 | 184 | if (standing === "running") | |
| 185 | 185 | return <LoaderCircle size={size} className="shrink-0 animate-spin text-warn" aria-label="Running" />; | |
| ⋯ | |||
| 395 | 395 | ) : standing === "running" ? ( | |
| 396 | 396 | <LoaderCircle size={16} className="animate-spin text-warn" /> | |
| 397 | 397 | ) : ( | |
| 398 | − | <CircleCheck size={16} className="text-accent" /> | |
| 398 | + | <CircleCheck size={16} className="text-success" /> | |
| 399 | 399 | )} | |
| 400 | 400 | </span> | |
| 401 | 401 | <div className="min-w-0 grow"> | |
| ⋯ | |||
| 759 | 759 | if (mergeable === "clean") { | |
| 760 | 760 | return ( | |
| 761 | 761 | <div className="flex gap-3 px-4 py-3 text-sm"> | |
| 762 | − | <CircleCheck size={16} className="mt-0.5 shrink-0 text-accent" /> | |
| 762 | + | <CircleCheck size={16} className="mt-0.5 shrink-0 text-success" /> | |
| 763 | 763 | <p className="font-medium"> | |
| 764 | 764 | This branch has no conflicts with <span className="font-mono">{defaultBranch}</span> | |
| 765 | 765 | </p> | |
| ⋯ | |||
| 842 | 842 | } | |
| 843 | 843 | return ( | |
| 844 | 844 | <div className="flex gap-3 px-4 py-3 text-sm"> | |
| 845 | − | <LoaderCircle size={16} className="mt-0.5 shrink-0 animate-spin text-merged" /> | |
| 845 | + | <LoaderCircle size={16} className="mt-0.5 shrink-0 animate-spin text-accent" /> | |
| 846 | 846 | <div className="min-w-0 grow"> | |
| 847 | 847 | <p className="font-medium">{catchUpTitle(update.reason, defaultBranch)}</p> | |
| 848 | 848 | <p className="mt-0.5 text-muted"> | |
| ⋯ | |||
| 859 | 859 | className="mt-2 truncate rounded-lg bg-bg px-3 py-2 font-mono text-xs text-fg/85 ring-1 ring-line" | |
| 860 | 860 | title={run?.step ?? undefined} | |
| 861 | 861 | > | |
| 862 | − | <span className="mr-2 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" /> | |
| 862 | + | <span className="mr-2 inline-block size-1.5 animate-pulse rounded-full bg-accent align-middle" /> | |
| 863 | 863 | {run?.step ?? (run ? "Starting a sandbox…" : "Waiting for a sandbox…")} | |
| 864 | 864 | </p> | |
| 865 | 865 | <p className="mt-2 flex flex-wrap items-center gap-x-3 text-xs text-muted"> | |
| 63 | 63 | outside_guardrails: "border-warn/35 bg-warn/10 text-warn", | |
| 64 | 64 | low_confidence: "border-warn/35 bg-warn/10 text-warn", | |
| 65 | 65 | stalled: "border-warn/35 bg-warn/10 text-warn", | |
| 66 | − | asked_for_you: "border-merged/35 bg-merged/10 text-merged", | |
| 66 | + | asked_for_you: "border-accent/35 bg-accent/10 text-accent", | |
| 67 | 67 | needs_review: "border-info/35 bg-info/10 text-info", | |
| 68 | − | ready_to_merge: "border-accent/35 bg-accent/10 text-accent", | |
| 68 | + | ready_to_merge: "border-success/35 bg-success/10 text-success", | |
| 69 | 69 | }; | |
| 70 | 70 | ||
| 71 | 71 | function Chip({ children, tone }: { children: ReactNode; tone: string }) { | |
| ⋯ | |||
| 95 | 95 | return <p className="font-mono text-[0.625rem] font-medium tracking-[0.12em] text-faint uppercase">{children}</p>; | |
| 96 | 96 | } | |
| 97 | 97 | ||
| 98 | − | const FACT_TONE = { good: "text-accent", warn: "text-warn", bad: "text-danger" } as const; | |
| 98 | + | const FACT_TONE = { good: "text-success", warn: "text-warn", bad: "text-danger" } as const; | |
| 99 | 99 | ||
| 100 | 100 | function Facts({ facts }: { facts: Fact[] }) { | |
| 101 | 101 | if (facts.length === 0) return <p className="text-sm text-muted">Nothing more is known about it yet.</p>; | |
| ⋯ | |||
| 139 | 139 | className={cn( | |
| 140 | 140 | "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-default", | |
| 141 | 141 | variant === "accent" | |
| 142 | − | ? "bg-merged text-bg hover:bg-[#c9bfff]" | |
| 142 | + | ? "bg-accent text-bg hover:bg-accent-hover" | |
| 143 | 143 | : "border border-line-strong text-fg/90 hover:bg-raised hover:text-fg", | |
| 144 | − | done && "border-accent/40 text-accent", | |
| 144 | + | done && "border-success/40 text-success", | |
| 145 | 145 | )} | |
| 146 | 146 | > | |
| 147 | 147 | {done ? <Check size={14} /> : null} | |
| ⋯ | |||
| 188 | 188 | {by && ( | |
| 189 | 189 | <> | |
| 190 | 190 | <span className="text-faint">·</span> | |
| 191 | − | <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-merged" : "")} /> | |
| 191 | + | <Person who={by} size={14} className={cn("shrink-0", by.agent ? "text-accent" : "")} /> | |
| 192 | 192 | </> | |
| 193 | 193 | )} | |
| 194 | 194 | {sub && ( | |
| ⋯ | |||
| 308 | 308 | sub={row.detail} | |
| 309 | 309 | chip={ | |
| 310 | 310 | <Chip tone="border-line-strong bg-raised text-muted"> | |
| 311 | − | {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-accent" />} | |
| 311 | + | {row.live && <span className="mr-1.5 size-1.5 animate-pulse rounded-full bg-success" />} | |
| 312 | 312 | {row.chip} | |
| 313 | 313 | </Chip> | |
| 314 | 314 | } | |
| ⋯ | |||
| 359 | 359 | sub={row.byAgents ? "landed without a person" : `merged by ${row.by?.name ?? "a person"}`} | |
| 360 | 360 | chip={ | |
| 361 | 361 | row.byAgents ? ( | |
| 362 | − | <Chip tone="border-merged/35 bg-merged/10 text-merged">By agents</Chip> | |
| 362 | + | <Chip tone="border-accent/35 bg-accent/10 text-accent">By agents</Chip> | |
| 363 | 363 | ) : ( | |
| 364 | 364 | <Chip tone="border-warn/35 bg-warn/10 text-warn">Needed a person</Chip> | |
| 365 | 365 | ) | |
| ⋯ | |||
| 442 | 442 | <span | |
| 443 | 443 | className={cn( | |
| 444 | 444 | "rounded-full px-2 py-0.5 text-xs font-medium tabular-nums ring-1", | |
| 445 | − | delta >= 0 ? "text-accent ring-accent/30" : "text-warn ring-warn/30", | |
| 445 | + | delta >= 0 ? "text-success ring-success/30" : "text-warn ring-warn/30", | |
| 446 | 446 | )} | |
| 447 | 447 | > | |
| 448 | 448 | {signedPercent(delta)} vs last week | |
| ⋯ | |||
| 464 | 464 | const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4))); | |
| 465 | 465 | // Top to bottom: agents alone, agents with a person, people. | |
| 466 | 466 | const segments = [ | |
| 467 | − | { n: day.agents, tone: "bg-merged" }, | |
| 467 | + | { n: day.agents, tone: "bg-accent" }, | |
| 468 | 468 | { n: day.assisted, tone: "bg-warn" }, | |
| 469 | 469 | { n: day.people, tone: "bg-info" }, | |
| 470 | 470 | ].filter((segment) => segment.n > 0); | |
| ⋯ | |||
| 490 | 490 | <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 hidden -translate-x-1/2 rounded-md border border-line-strong bg-raised px-2.5 py-1.5 text-xs whitespace-nowrap shadow-lg shadow-black/40 group-hover:block"> | |
| 491 | 491 | <p className="font-medium text-fg">{day.label}</p> | |
| 492 | 492 | <p className="mt-0.5 flex items-center gap-1.5 text-muted"> | |
| 493 | − | <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own | |
| 493 | + | <span className="size-1.5 rounded-full bg-accent" /> {day.agents} by agents on their own | |
| 494 | 494 | </p> | |
| 495 | 495 | <p className="flex items-center gap-1.5 text-muted"> | |
| 496 | 496 | <span className="size-1.5 rounded-full bg-warn" /> {day.assisted} by agents, merged by a person | |
| ⋯ | |||
| 520 | 520 | )} | |
| 521 | 521 | <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}> | |
| 522 | 522 | <span className="inline-flex items-center gap-1.5"> | |
| 523 | − | <span className="size-2 rounded-sm bg-merged" /> Agents, on their own | |
| 523 | + | <span className="size-2 rounded-sm bg-accent" /> Agents, on their own | |
| 524 | 524 | </span> | |
| 525 | 525 | <span className="inline-flex items-center gap-1.5"> | |
| 526 | 526 | <span className="size-2 rounded-sm bg-warn" /> Agents, merged by a person | |
| ⋯ | |||
| 591 | 591 | <Avatar name={actor} size={22} /> | |
| 592 | 592 | </span> | |
| 593 | 593 | <span className="min-w-0 grow text-[0.8125rem] leading-5 text-muted"> | |
| 594 | − | <span className={cn("font-medium", agent ? "text-merged" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "} | |
| 594 | + | <span className={cn("font-medium", agent ? "text-accent" : "text-fg")}>{actor}</span> {VERB[part.verb]}{" "} | |
| 595 | 595 | {part.verb === "learned" ? ( | |
| 596 | 596 | <Link to={part.to ?? `${base}/memory`} className="text-fg-soft hover:text-fg"> | |
| 597 | 597 | “{part.texts[0]}” | |
| ⋯ | |||
| 639 | 639 | <span | |
| 640 | 640 | className={cn( | |
| 641 | 641 | "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-medium", | |
| 642 | − | step.done ? "bg-accent text-bg" : "text-muted ring-1 ring-line-strong", | |
| 642 | + | step.done ? "bg-success text-bg" : "text-muted ring-1 ring-line-strong", | |
| 643 | 643 | )} | |
| 644 | 644 | > | |
| 645 | 645 | {step.done ? <Check size={12} /> : index + 1} | |
| ⋯ | |||
| 828 | 828 | } | |
| 829 | 829 | > | |
| 830 | 830 | <span className="inline-flex cursor-pointer items-center gap-1.5 rounded-l-md border border-line-strong bg-raised px-3 py-2 text-sm font-medium text-fg transition-colors hover:bg-line/60 group-open/composer:bg-line/60"> | |
| 831 | − | <Sparkles size={14} className="text-merged" /> Put an agent on it | |
| 831 | + | <Sparkles size={14} className="text-accent" /> Put an agent on it | |
| 832 | 832 | </span> | |
| 833 | 833 | </AgentComposer> | |
| 834 | 834 | <DropdownMenu> | |
| ⋯ | |||
| 895 | 895 | <Link | |
| 896 | 896 | to={`${link({ tab: "needs", all: null })}#work`} | |
| 897 | 897 | preventScrollReset | |
| 898 | − | className="inline-flex items-center gap-1.5 rounded-md bg-merged px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-[#c9bfff]" | |
| 898 | + | className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3.5 py-2 text-sm font-semibold text-bg transition-colors hover:bg-accent-hover" | |
| 899 | 899 | > | |
| 900 | 900 | Review {needs.length} that need{needs.length === 1 ? "s" : ""} you <ArrowRight size={14} /> | |
| 901 | 901 | </Link> | |
| ⋯ | |||
| 922 | 922 | <Stat | |
| 923 | 923 | label="Agents" | |
| 924 | 924 | value={loaded.runsLoaded || !workspace ? String(liveTotal) : "—"} | |
| 925 | − | dot={liveTotal > 0 ? "bg-accent animate-pulse" : undefined} | |
| 925 | + | dot={liveTotal > 0 ? "bg-success animate-pulse" : undefined} | |
| 926 | 926 | hint={ | |
| 927 | 927 | liveTotal > 0 | |
| 928 | 928 | ? `live now · ${stats.agentHours < 10 ? stats.agentHours.toFixed(1) : Math.round(stats.agentHours)}h this week` | |
| ⋯ | |||
| 940 | 940 | /> | |
| 941 | 941 | <Stat | |
| 942 | 942 | label="Landed without you" | |
| 943 | − | dot="bg-merged" | |
| 943 | + | dot="bg-accent" | |
| 944 | 944 | value={share == null ? "—" : `${Math.round(share * 100)}%`} | |
| 945 | 945 | hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`} | |
| 946 | 946 | title="Of the changes agents wrote, those g1t merged by auto-merge or the merge queue, with no person pressing merge. People's own changes are not counted." | |
| ⋯ | |||
| 967 | 967 | aria-current={tab === value ? "page" : undefined} | |
| 968 | 968 | className={cn( | |
| 969 | 969 | "flex shrink-0 items-center gap-1.5 border-b-2 px-2 py-3 text-sm whitespace-nowrap transition-colors sm:px-2.5", | |
| 970 | − | tab === value ? "border-merged font-medium text-fg" : "border-transparent text-muted hover:text-fg", | |
| 970 | + | tab === value ? "border-accent font-medium text-fg" : "border-transparent text-muted hover:text-fg", | |
| 971 | 971 | )} | |
| 972 | 972 | > | |
| 973 | 973 | <span className="sm:hidden">{TAB_SHORT[value]}</span> | |
| 113 | 113 | runner: { icon: <TimerOff size={15} />, tone: "text-warn", label: "No runner" }, | |
| 114 | 114 | review: { icon: <Eye size={15} />, tone: "text-info", label: "Review" }, | |
| 115 | 115 | checks: { icon: <Terminal size={15} />, tone: "text-danger", label: "Checks" }, | |
| 116 | − | ready: { icon: <GitMerge size={15} />, tone: "text-accent", label: "Ready" }, | |
| 116 | + | ready: { icon: <GitMerge size={15} />, tone: "text-success", label: "Ready" }, | |
| 117 | 117 | }; | |
| 118 | 118 | ||
| 119 | 119 | /** What is waiting on the viewer, most urgent first, each with its next step. */ | |
| ⋯ | |||
| 166 | 166 | ||
| 167 | 167 | const VERB: Record<Verb, { icon: ReactNode; tone: string; text: (n: string) => string }> = { | |
| 168 | 168 | landed: { icon: <GitMerge size={14} />, tone: "text-merged", text: (n) => `landed ${n}` }, | |
| 169 | − | opened_issue: { icon: <CircleDot size={14} />, tone: "text-accent", text: (n) => `opened ${n}` }, | |
| 169 | + | opened_issue: { icon: <CircleDot size={14} />, tone: "text-success", text: (n) => `opened ${n}` }, | |
| 170 | 170 | closed_issue: { icon: <CircleSlash size={14} />, tone: "text-faint", text: (n) => `closed ${n}` }, | |
| 171 | − | started: { icon: <Play size={14} />, tone: "text-merged", text: (n) => `started on ${n}` }, | |
| 171 | + | started: { icon: <Play size={14} />, tone: "text-accent", text: (n) => `started on ${n}` }, | |
| 172 | 172 | ready: { icon: <GitPullRequest size={14} />, tone: "text-info", text: (n) => `marked ${n} ready for review` }, | |
| 173 | − | checks_passed: { icon: <Terminal size={14} />, tone: "text-accent", text: (n) => `checks passed on ${n}` }, | |
| 173 | + | checks_passed: { icon: <Terminal size={14} />, tone: "text-success", text: (n) => `checks passed on ${n}` }, | |
| 174 | 174 | checks_failed: { icon: <Terminal size={14} />, tone: "text-danger", text: (n) => `checks failed on ${n}` }, | |
| 175 | − | approved: { icon: <CircleCheck size={14} />, tone: "text-accent", text: (n) => `approved ${n}` }, | |
| 175 | + | approved: { icon: <CircleCheck size={14} />, tone: "text-success", text: (n) => `approved ${n}` }, | |
| 176 | 176 | changes_requested: { icon: <CircleSlash size={14} />, tone: "text-warn", text: (n) => `asked for changes on ${n}` }, | |
| 177 | 177 | commented: { icon: <MessageSquare size={14} />, tone: "text-muted", text: (n) => `commented on ${n}` }, | |
| 178 | − | asked: { icon: <MessageCircleQuestion size={14} />, tone: "text-merged", text: (n) => `asked the agent on ${n}` }, | |
| 179 | − | deployed: { icon: <Rocket size={14} />, tone: "text-accent", text: () => "deployed production" }, | |
| 178 | + | asked: { icon: <MessageCircleQuestion size={14} />, tone: "text-accent", text: (n) => `asked the agent on ${n}` }, | |
| 179 | + | deployed: { icon: <Rocket size={14} />, tone: "text-success", text: () => "deployed production" }, | |
| 180 | 180 | deploy_failed: { icon: <Rocket size={14} />, tone: "text-danger", text: () => "production build failed" }, | |
| 181 | − | learned: { icon: <Brain size={14} />, tone: "text-merged", text: () => "learned" }, | |
| 181 | + | learned: { icon: <Brain size={14} />, tone: "text-accent", text: () => "learned" }, | |
| 182 | 182 | }; | |
| 183 | 183 | ||
| 184 | 184 | function Refs({ numbers, base }: { numbers: number[]; base: string }) { | |
| ⋯ | |||
| 343 | 343 | </span> | |
| 344 | 344 | <span className="min-w-0 grow leading-6 text-muted"> | |
| 345 | 345 | {group.actor ? ( | |
| 346 | − | <span className={cn("mr-1.5 inline-flex items-center gap-1.5 align-middle font-medium", agent ? "text-merged" : "text-fg")}> | |
| 346 | + | <span className={cn("mr-1.5 inline-flex items-center gap-1.5 align-middle font-medium", agent ? "text-accent" : "text-fg")}> | |
| 347 | 347 | <Avatar name={group.actor} size={16} /> | |
| 348 | 348 | {group.actor} | |
| 349 | 349 | </span> | |
| ⋯ | |||
| 465 | 465 | // --- Deploys ------------------------------------------------------------------ | |
| 466 | 466 | ||
| 467 | 467 | const STRIP_TONE: Record<DeployStatus, string> = { | |
| 468 | − | ready: "bg-accent", | |
| 468 | + | ready: "bg-success", | |
| 469 | 469 | // Served once, until a newer build or a take-down: it worked. | |
| 470 | − | replaced: "bg-accent/45", | |
| 470 | + | replaced: "bg-success/45", | |
| 471 | 471 | down: "bg-line-strong", | |
| 472 | 472 | failed: "bg-danger", | |
| 473 | 473 | building: "bg-warn animate-pulse", | |
| ⋯ | |||
| 504 | 504 | } | |
| 505 | 505 | ||
| 506 | 506 | /** A small meter for a share, 0 to 1. */ | |
| 507 | − | export function Meter({ value, tone = "bg-accent" }: { value: number | null; tone?: string }) { | |
| 507 | + | export function Meter({ value, tone = "bg-success" }: { value: number | null; tone?: string }) { | |
| 508 | 508 | return ( | |
| 509 | 509 | <span className="block h-1.5 w-full overflow-hidden rounded-full bg-line"> | |
| 510 | 510 | {value != null && <span className={cn("block h-full rounded-full", tone)} style={{ width: `${Math.round(value * 100)}%` }} />} | |
| 478 | 478 | <SubmitButton variant="quiet" match={{ intent: "routes" }} pending="Saving…"> | |
| 479 | 479 | Save routing | |
| 480 | 480 | </SubmitButton> | |
| 481 | − | {saved && <span className="text-sm text-accent">Saved. The next runs use it.</span>} | |
| 481 | + | {saved && <span className="text-sm text-success">Saved. The next runs use it.</span>} | |
| 482 | 482 | </div> | |
| 483 | 483 | )} | |
| 484 | 484 | </Form> |
| 39 | 39 | working: { | |
| 40 | 40 | label: "Agent working", | |
| 41 | 41 | icon: <Sparkles size={13} />, | |
| 42 | − | ring: "ring-merged/60", | |
| 43 | − | text: "text-merged", | |
| 44 | − | bar: "bg-merged", | |
| 42 | + | ring: "ring-accent/60", | |
| 43 | + | text: "text-accent", | |
| 44 | + | bar: "bg-accent", | |
| 45 | 45 | live: true, | |
| 46 | 46 | }, | |
| 47 | 47 | revising: { | |
| 48 | 48 | label: "Revising", | |
| 49 | 49 | icon: <Sparkles size={13} />, | |
| 50 | − | ring: "ring-merged/60", | |
| 51 | − | text: "text-merged", | |
| 52 | − | bar: "bg-merged", | |
| 50 | + | ring: "ring-accent/60", | |
| 51 | + | text: "text-accent", | |
| 52 | + | bar: "bg-accent", | |
| 53 | 53 | live: true, | |
| 54 | 54 | }, | |
| 55 | 55 | answering: { | |
| 56 | 56 | label: "Answering an agent", | |
| 57 | 57 | icon: <Sparkles size={13} />, | |
| 58 | − | ring: "ring-merged/60", | |
| 59 | − | text: "text-merged", | |
| 60 | − | bar: "bg-merged", | |
| 58 | + | ring: "ring-accent/60", | |
| 59 | + | text: "text-accent", | |
| 60 | + | bar: "bg-accent", | |
| 61 | 61 | live: true, | |
| 62 | 62 | }, | |
| 63 | 63 | catching_up: { | |
| 64 | 64 | label: "Catching up", | |
| 65 | 65 | icon: <Loader2 size={13} className="animate-spin" />, | |
| 66 | − | ring: "ring-merged/60", | |
| 67 | − | text: "text-merged", | |
| 68 | − | bar: "bg-merged", | |
| 66 | + | ring: "ring-accent/60", | |
| 67 | + | text: "text-accent", | |
| 68 | + | bar: "bg-accent", | |
| 69 | 69 | live: true, | |
| 70 | 70 | }, | |
| 71 | 71 | checking: { | |
| ⋯ | |||
| 87 | 87 | queued: { | |
| 88 | 88 | label: "In the merge queue", | |
| 89 | 89 | icon: <Layers size={13} />, | |
| 90 | − | ring: "ring-accent/60", | |
| 91 | − | text: "text-accent", | |
| 92 | − | bar: "bg-accent/60", | |
| 90 | + | ring: "ring-success/60", | |
| 91 | + | text: "text-success", | |
| 92 | + | bar: "bg-success/60", | |
| 93 | 93 | live: true, | |
| 94 | 94 | }, | |
| 95 | − | ready: { label: "Ready to merge", icon: <Check size={13} />, ring: "ring-accent/60", text: "text-accent", bar: "bg-accent/60" }, | |
| 95 | + | ready: { label: "Ready to merge", icon: <Check size={13} />, ring: "ring-success/60", text: "text-success", bar: "bg-success/60" }, | |
| 96 | 96 | needs_you: { label: "Needs you", icon: <Hand size={13} />, ring: "ring-warn/70", text: "text-warn", bar: "bg-warn" }, | |
| 97 | − | landed: { label: "Landed", icon: <Check size={13} />, ring: "ring-accent/30", text: "text-accent", bar: "bg-accent" }, | |
| 97 | + | landed: { label: "Landed", icon: <Check size={13} />, ring: "ring-success/30", text: "text-success", bar: "bg-success" }, | |
| 98 | 98 | closed: { label: "Closed", icon: <X size={13} />, ring: "ring-line", text: "text-faint", bar: "bg-line" }, | |
| 99 | 99 | }; | |
| 100 | 100 | ||
| ⋯ | |||
| 199 | 199 | key={index} | |
| 200 | 200 | d={path.d} | |
| 201 | 201 | fill="none" | |
| 202 | − | stroke={path.done ? "var(--color-accent)" : "var(--color-line-strong)"} | |
| 202 | + | stroke={path.done ? "var(--color-success)" : "var(--color-line-strong)"} | |
| 203 | 203 | strokeOpacity={path.done ? 0.7 : 1} | |
| 204 | 204 | strokeWidth={1.5} | |
| 205 | 205 | strokeDasharray={path.done ? undefined : "4 5"} | |
| ⋯ | |||
| 229 | 229 | <div> | |
| 230 | 230 | <div className="grid gap-4 sm:grid-cols-4"> | |
| 231 | 231 | {[ | |
| 232 | − | ["Landed", `${landed} of ${total}`, "text-accent"], | |
| 233 | − | ["Agents at work", String(live), "text-merged"], | |
| 232 | + | ["Landed", `${landed} of ${total}`, "text-success"], | |
| 233 | + | ["Agents at work", String(live), "text-accent"], | |
| 234 | 234 | ["Needs you", String(needsYou), needsYou > 0 ? "text-warn" : "text-muted"], | |
| 235 | 235 | [ | |
| 236 | 236 | "Agents have cost", | |
| 103 | 103 | </SubmitButton> | |
| 104 | 104 | {saved && !busy && !error && ( | |
| 105 | 105 | <span className="inline-flex items-center gap-1.5 text-sm text-muted"> | |
| 106 | − | <Check size={14} className="text-accent" /> | |
| 106 | + | <Check size={14} className="text-success" /> | |
| 107 | 107 | Saved | |
| 108 | 108 | </span> | |
| 109 | 109 | )} |
| 10 | 10 | import { Tabs, TabsList, TabsTrigger } from "./ui/tabs"; | |
| 11 | 11 | ||
| 12 | 12 | const STATUS: Record<Runner["status"], { dot: string; label: string }> = { | |
| 13 | − | online: { dot: "bg-accent", label: "Idle" }, | |
| 13 | + | online: { dot: "bg-success", label: "Idle" }, | |
| 14 | 14 | busy: { dot: "bg-info", label: "Busy" }, | |
| 15 | 15 | offline: { dot: "bg-faint", label: "Offline" }, | |
| 16 | 16 | }; | |
| ⋯ | |||
| 411 | 411 | return ( | |
| 412 | 412 | <div className="max-w-5xl space-y-8"> | |
| 413 | 413 | <div className="min-h-6"> | |
| 414 | − | {action?.notice && <p className="text-sm text-accent">{action.notice}</p>} | |
| 414 | + | {action?.notice && <p className="text-sm text-success">{action.notice}</p>} | |
| 415 | 415 | <ErrorText>{action?.error ?? data.error ?? null}</ErrorText> | |
| 416 | 416 | </div> | |
| 417 | 417 | <Section | |
| 28 | 28 | function stateIcon(hit: SiteHit): ReactNode { | |
| 29 | 29 | if (hit.kind === "issue") { | |
| 30 | 30 | return hit.state === "open" ? ( | |
| 31 | − | <CircleDot size={15} className="text-accent" aria-label="Open" /> | |
| 31 | + | <CircleDot size={15} className="text-success" aria-label="Open" /> | |
| 32 | 32 | ) : ( | |
| 33 | 33 | <CircleCheck size={15} className="text-merged" aria-label="Closed" /> | |
| 34 | 34 | ); | |
| ⋯ | |||
| 41 | 41 | case "draft": | |
| 42 | 42 | return <GitPullRequestDraft size={15} className="text-faint" aria-label="Draft" />; | |
| 43 | 43 | default: | |
| 44 | − | return <GitPullRequest size={15} className="text-accent" aria-label="Open" />; | |
| 44 | + | return <GitPullRequest size={15} className="text-success" aria-label="Open" />; | |
| 45 | 45 | } | |
| 46 | 46 | } | |
| 47 | 47 | ||
| 34 | 34 | <h2 className="flex flex-wrap items-center gap-2 text-base font-semibold"> | |
| 35 | 35 | <ShieldCheck size={16} className="text-accent" /> | |
| 36 | 36 | {plan.title} | |
| 37 | − | <span className={`rounded-full px-2 py-0.5 text-xs ${on ? "bg-accent/15 text-accent" : "border border-line text-muted"}`}>{label}</span> | |
| 37 | + | <span className={`rounded-full px-2 py-0.5 text-xs ${on ? "bg-success/15 text-success" : "border border-line text-muted"}`}>{label}</span> | |
| 38 | 38 | </h2> | |
| 39 | 39 | <p className="mt-1.5 max-w-2xl text-sm text-muted"> | |
| 40 | 40 | The security suite's paid features for the workspace's private repositories. Public repositories have them free, and | |
| ⋯ | |||
| 50 | 50 | <ul className="mt-4 grid gap-1.5 text-sm text-muted sm:grid-cols-2"> | |
| 51 | 51 | {plan.includes.map((line) => ( | |
| 52 | 52 | <li key={line} className="flex gap-2"> | |
| 53 | − | <span className="text-accent">✓</span> | |
| 53 | + | <span className="text-success">✓</span> | |
| 54 | 54 | {line} | |
| 55 | 55 | </li> | |
| 56 | 56 | ))} | |
| 288 | 288 | ||
| 289 | 289 | function On({ on, children }: { on: boolean; children?: ReactNode }) { | |
| 290 | 290 | return on ? ( | |
| 291 | − | <span className="inline-flex items-center gap-1 text-accent"> | |
| 291 | + | <span className="inline-flex items-center gap-1 text-success"> | |
| 292 | 292 | <CircleCheck size={13} aria-hidden="true" /> | |
| 293 | 293 | {children ?? "On"} | |
| 294 | 294 | </span> | |
| ⋯ | |||
| 336 | 336 | </td> | |
| 337 | 337 | <td className="px-3 py-2.5 text-xs whitespace-nowrap"> | |
| 338 | 338 | {open === 0 ? ( | |
| 339 | − | <span className="inline-flex items-center gap-1 text-accent"> | |
| 339 | + | <span className="inline-flex items-center gap-1 text-success"> | |
| 340 | 340 | <ShieldCheck size={13} /> None | |
| 341 | 341 | </span> | |
| 342 | 342 | ) : ( | |
| ⋯ | |||
| 355 | 355 | </td> | |
| 356 | 356 | <td className="px-3 py-2.5 text-xs"> | |
| 357 | 357 | {repo.codeScanningAt ? ( | |
| 358 | − | <span className="text-accent"> | |
| 358 | + | <span className="text-success"> | |
| 359 | 359 | <TimeAgo at={repo.codeScanningAt} /> | |
| 360 | 360 | </span> | |
| 361 | 361 | ) : ( | |
| ⋯ | |||
| 411 | 411 | const [reason, setReason] = useState(""); | |
| 412 | 412 | if (fetcher.data?.ok) { | |
| 413 | 413 | return ( | |
| 414 | − | <p className="rounded-md border border-accent/30 bg-accent/5 px-3 py-2 text-sm text-accent"> | |
| 414 | + | <p className="rounded-md border border-success/30 bg-success/5 px-3 py-2 text-sm text-success"> | |
| 415 | 415 | {fetcher.data.requested | |
| 416 | 416 | ? "Asked. The workspace's owners and the repository's admins were told; push again once one approves." | |
| 417 | 417 | : "Bypassed. Push again and it goes through."} | |
| ⋯ | |||
| 468 | 468 | const chars = [...text]; | |
| 469 | 469 | return ( | |
| 470 | 470 | <li className="flex items-start gap-2 font-mono text-xs break-all"> | |
| 471 | − | <CircleCheck size={13} className="mt-0.5 shrink-0 text-accent" /> | |
| 471 | + | <CircleCheck size={13} className="mt-0.5 shrink-0 text-success" /> | |
| 472 | 472 | <span> | |
| 473 | 473 | {chars.slice(0, found[0]).join("")} | |
| 474 | 474 | <mark className="rounded-sm bg-accent/25 text-fg">{chars.slice(found[0], found[1]).join("")}</mark> | |
| ⋯ | |||
| 584 | 584 | )} | |
| 585 | 585 | </div> | |
| 586 | 586 | {save.data?.error && <p className="text-sm text-danger">{save.data.error}</p>} | |
| 587 | − | {save.data?.ok && (saved ? <p className="text-sm text-accent">Saved{saved.pattern.state === "published" ? " and published: the history is scanned again for it" : " as a draft"}.</p> : <p className="text-sm text-accent">Deleted. The alerts it found stay.</p>)} | |
| 587 | + | {save.data?.ok && (saved ? <p className="text-sm text-success">Saved{saved.pattern.state === "published" ? " and published: the history is scanned again for it" : " as a draft"}.</p> : <p className="text-sm text-success">Deleted. The alerts it found stay.</p>)} | |
| 588 | 588 | {dry.data?.error && <p className="text-sm text-danger">{dry.data.error}</p>} | |
| 589 | 589 | {dry.data?.dryRun && <DryRunResults dryRun={dry.data.dryRun} />} | |
| 590 | 590 | </div> | |
| 93 | 93 | const shown = SEVERITIES.filter((severity) => counts[severity] > 0); | |
| 94 | 94 | if (shown.length === 0) { | |
| 95 | 95 | return ( | |
| 96 | − | <span className="inline-flex items-center gap-1 text-xs text-accent"> | |
| 96 | + | <span className="inline-flex items-center gap-1 text-xs text-success"> | |
| 97 | 97 | <ShieldCheck size={13} /> | |
| 98 | 98 | Nothing open | |
| 99 | 99 | </span> | |
| ⋯ | |||
| 307 | 307 | about: finding.status === "allowed" ? "Pushes carrying it go through." : "Dismissed.", | |
| 308 | 308 | }; | |
| 309 | 309 | } | |
| 310 | − | if (finding.state === "fixed") return { label: "Revoked", tone: "accent", about: "Revoked or rotated." }; | |
| 310 | + | if (finding.state === "fixed") return { label: "Revoked", tone: "success", about: "Revoked or rotated." }; | |
| 311 | 311 | if (finding.status === "blocked") { | |
| 312 | 312 | return { label: "Push blocked", tone: finding.testValue ? "neutral" : "warn", about: "A push carrying it was refused, so it never landed." }; | |
| 313 | 313 | } | |
| ⋯ | |||
| 414 | 414 | function Empty({ title, children }: { title: string; children: ReactNode }) { | |
| 415 | 415 | return ( | |
| 416 | 416 | <div className="rounded-xl border border-dashed border-line px-6 py-10 text-center"> | |
| 417 | − | <ShieldCheck size={22} className="mx-auto text-accent" /> | |
| 417 | + | <ShieldCheck size={22} className="mx-auto text-success" /> | |
| 418 | 418 | <p className="mt-2 font-medium">{title}</p> | |
| 419 | 419 | <p className="mt-1 text-sm text-muted">{children}</p> | |
| 420 | 420 | </div> | |
| ⋯ | |||
| 508 | 508 | <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs"> | |
| 509 | 509 | <span className="text-muted">Upgrade issue</span> | |
| 510 | 510 | <Link to={`${base}/issues/${issue}`} className="inline-flex items-center gap-1 text-fg-soft hover:text-fg"> | |
| 511 | − | {fix?.state === "closed" ? <CircleCheck size={12} className="text-merged" /> : <CircleDot size={12} className="text-accent" />}#{issue} | |
| 511 | + | {fix?.state === "closed" ? <CircleCheck size={12} className="text-merged" /> : <CircleDot size={12} className="text-success" />}#{issue} | |
| 512 | 512 | </Link> | |
| 513 | 513 | {pull && ( | |
| 514 | 514 | <Link to={`${base}/pull/${pull.number}`} className="inline-flex items-center gap-1 text-muted hover:text-fg"> | |
| ⋯ | |||
| 521 | 521 | ||
| 522 | 522 | const PULL_ICON: Record<PullStatus, ReactNode> = { | |
| 523 | 523 | draft: <GitPullRequest size={13} className="text-muted" />, | |
| 524 | − | open: <GitPullRequest size={13} className="text-accent" />, | |
| 524 | + | open: <GitPullRequest size={13} className="text-success" />, | |
| 525 | 525 | merged: <GitMerge size={13} className="text-merged" />, | |
| 526 | 526 | closed: <GitPullRequestClosed size={13} className="text-danger" />, | |
| 527 | 527 | }; | |
| 374 | 374 | aria-label={`Account menu for ${user.username}`} | |
| 375 | 375 | onPointerEnter={prefetch} | |
| 376 | 376 | onFocus={prefetch} | |
| 377 | − | className="flex h-10 w-full items-center gap-2.5 rounded-md px-2 text-left outline-none transition-colors hover:bg-raised focus-visible:ring-2 focus-visible:ring-merged/60 data-[state=open]:bg-raised" | |
| 377 | + | className="flex h-10 w-full items-center gap-2.5 rounded-md px-2 text-left outline-none transition-colors hover:bg-raised focus-visible:ring-2 focus-visible:ring-accent/60 data-[state=open]:bg-raised" | |
| 378 | 378 | > | |
| 379 | 379 | <Avatar name={user.username} image={user.avatar} size={22} /> | |
| 380 | 380 | <span className="min-w-0 grow truncate text-[0.8125rem] font-medium">{user.username}</span> | |
| ⋯ | |||
| 1102 | 1102 | /** Help, status and the fine print under a visitor's sign-in buttons. */ | |
| 1103 | 1103 | function VisitorLinks() { | |
| 1104 | 1104 | const status = useSiteStatus(); | |
| 1105 | − | const quiet = `${LEGAL_LINK} hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-merged/60`; | |
| 1105 | + | const quiet = `${LEGAL_LINK} hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-accent/60`; | |
| 1106 | 1106 | const dot = ( | |
| 1107 | 1107 | <span aria-hidden="true" className="text-[0.625rem] text-line-strong"> | |
| 1108 | 1108 | · | |
| 113 | 113 | </div> | |
| 114 | 114 | <div className="px-4 py-3"> | |
| 115 | 115 | <dt className="text-xs text-faint">{discountLabel}</dt> | |
| 116 | − | <dd className="mt-0.5 font-mono tabular-nums text-accent">{charge(-(totals.discountMicros ?? 0))}</dd> | |
| 116 | + | <dd className="mt-0.5 font-mono tabular-nums text-success">{charge(-(totals.discountMicros ?? 0))}</dd> | |
| 117 | 117 | </div> | |
| 118 | 118 | <div className="px-4 py-3"> | |
| 119 | 119 | <dt className="text-xs text-faint">Charged</dt> | |
| ⋯ | |||
| 146 | 146 | {totals.covered?.map((paid) => ( | |
| 147 | 147 | <li key={paid.source} className="flex justify-between gap-4"> | |
| 148 | 148 | <span className="text-muted">{paid.label}</span> | |
| 149 | − | <span className="font-mono tabular-nums text-accent">{charge(paid.micros)}</span> | |
| 149 | + | <span className="font-mono tabular-nums text-success">{charge(paid.micros)}</span> | |
| 150 | 150 | </li> | |
| 151 | 151 | ))} | |
| 152 | 152 | {(totals.carriedMicros ?? 0) > 0 && ( | |
| ⋯ | |||
| 191 | 191 | <li className="flex items-center gap-3 px-4 py-3 text-sm"> | |
| 192 | 192 | <span className="w-3.5 shrink-0" /> | |
| 193 | 193 | <span className="grow truncate text-muted">{discountLabel}</span> | |
| 194 | − | <span className="w-24 shrink-0 text-right font-mono tabular-nums text-accent">{charge(-(g.discountMicros ?? 0))}</span> | |
| 194 | + | <span className="w-24 shrink-0 text-right font-mono tabular-nums text-success">{charge(-(g.discountMicros ?? 0))}</span> | |
| 195 | 195 | </li> | |
| 196 | 196 | )} | |
| 197 | 197 | </ul> | |
| ⋯ | |||
| 272 | 272 | {coveredMicros > 0 && ` · ${charge(coveredMicros)} paid for`} | |
| 273 | 273 | </span> | |
| 274 | 274 | <span | |
| 275 | − | className={`w-24 shrink-0 text-right font-mono tabular-nums ${moneyIn ? "text-accent" : "text-fg"}`} | |
| 275 | + | className={`w-24 shrink-0 text-right font-mono tabular-nums ${moneyIn ? "text-success" : "text-fg"}`} | |
| 276 | 276 | > | |
| 277 | 277 | {moneyIn ? `+${dollars(-chargedMicros)}` : charge(chargedMicros)} | |
| 278 | 278 | </span> | |
| ⋯ | |||
| 299 | 299 | <span className="shrink-0 text-right font-mono text-xs tabular-nums text-muted"> | |
| 300 | 300 | {entry.amountMicros > 0 ? `+${dollars(entry.amountMicros)}` : charge(entryPrice(entry))} | |
| 301 | 301 | {(entry.discountMicros ?? 0) > 0 && ( | |
| 302 | − | <span className="block text-[0.6875rem] text-accent">{charge(-(entry.discountMicros ?? 0))} discount</span> | |
| 302 | + | <span className="block text-[0.6875rem] text-success">{charge(-(entry.discountMicros ?? 0))} discount</span> | |
| 303 | 303 | )} | |
| 304 | 304 | </span> | |
| 305 | 305 | </li> | |
| 28 | 28 | }; | |
| 29 | 29 | ||
| 30 | 30 | /** The daily grid's single hue, stronger with more tokens. */ | |
| 31 | − | const HEAT_SHADE = ["var(--color-line)", ...Array.from({ length: HEAT_LEVELS }, (_, i) => `color-mix(in oklab, var(--color-merged) ${[28, 48, 72, 100][i]}%, var(--color-surface))`)]; | |
| 31 | + | const HEAT_SHADE = ["var(--color-line)", ...Array.from({ length: HEAT_LEVELS }, (_, i) => `color-mix(in oklab, var(--color-accent) ${[28, 48, 72, 100][i]}%, var(--color-surface))`)]; | |
| 32 | 32 | ||
| 33 | 33 | type Scope = "workspace" | "mine"; | |
| 34 | 34 |
| 5 | 5 | // shadcn/ui's badge, styled with g1t's tokens: a small outlined label whose | |
| 6 | 6 | // tone says how much it matters. | |
| 7 | 7 | ||
| 8 | − | export type BadgeTone = "neutral" | "accent" | "info" | "merged" | "warn" | "danger"; | |
| 8 | + | export type BadgeTone = "neutral" | "accent" | "success" | "info" | "merged" | "warn" | "danger"; | |
| 9 | 9 | ||
| 10 | 10 | const TONES: Record<BadgeTone, string> = { | |
| 11 | 11 | neutral: "border-line text-muted", | |
| 12 | 12 | accent: "border-accent/40 bg-accent/10 text-accent", | |
| 13 | + | success: "border-success/40 bg-success/10 text-success", | |
| 13 | 14 | info: "border-info/40 bg-info/10 text-info", | |
| 14 | 15 | merged: "border-merged/40 bg-merged/10 text-merged", | |
| 15 | 16 | warn: "border-warn/40 bg-warn/10 text-warn", |
| 118 | 118 | ||
| 119 | 119 | const BUTTON_VARIANTS: Record<Variant, string> = { | |
| 120 | 120 | primary: "bg-fg text-bg hover:bg-white", | |
| 121 | − | accent: "bg-accent text-bg hover:bg-[#aaf5d6]", | |
| 121 | + | accent: "bg-accent text-bg hover:bg-accent-hover", | |
| 122 | 122 | quiet: | |
| 123 | 123 | "border border-line text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg", | |
| 124 | 124 | // For what cannot be undone: transferring, deleting. | |
| ⋯ | |||
| 397 | 397 | setTimeout(() => setCopied(false), 1500); | |
| 398 | 398 | }} | |
| 399 | 399 | > | |
| 400 | − | {copied ? <Check size={14} className="text-accent" /> : <Copy size={14} />} | |
| 400 | + | {copied ? <Check size={14} className="text-success" /> : <Copy size={14} />} | |
| 401 | 401 | </button> | |
| 402 | 402 | </div> | |
| 403 | 403 | ); | |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.