synthesisui 0.16.429 → 0.16.431
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +12 -1
- package/dist/commands/import.js +163 -8
- package/dist/report-html.js +394 -0
- package/dist/report-theme.js +610 -0
- package/dist/transcript.js +50 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -47,6 +47,12 @@ const IGNORED = [
|
|
|
47
47
|
/** O que a última medição viu, para a próxima dizer o que mudou - descreve ESTE clone. */
|
|
48
48
|
".last-sync.json",
|
|
49
49
|
"not-expressed.md",
|
|
50
|
+
/**
|
|
51
|
+
* O RELATÓRIO QUE O `inspect` ESCREVE - a leitura inteira com forma, para ele abrir no navegador.
|
|
52
|
+
* Mesma natureza do censo: é a MEDIÇÃO, reescrita por inteiro a cada rodada, então versioná-la é
|
|
53
|
+
* conflito toda vez e ainda poria um HTML de meio megabyte no diff de quem só rodou o comando.
|
|
54
|
+
*/
|
|
55
|
+
"report.html",
|
|
50
56
|
/**
|
|
51
57
|
* O MODO DELA - ver `mode.ts`. Commitado, ele barraria o time inteiro porque UMA pessoa ligou
|
|
52
58
|
* o bloqueio dela, e o aceite da etapa 06 é literal: *"a escolha de uma pessoa nunca barra
|
|
@@ -78,7 +84,12 @@ export async function ensureGovernanceHome(projectRoot) {
|
|
|
78
84
|
await mkdir(join(projectRoot, "_synthesisui"), { recursive: true });
|
|
79
85
|
await writeGovernanceIgnore(projectRoot);
|
|
80
86
|
}
|
|
81
|
-
|
|
87
|
+
/**
|
|
88
|
+
* EXPORTADO desde 18/09 porque o `inspect` passou a escrever um arquivo na pasta: o relatório. Um
|
|
89
|
+
* comando de primeiro contato que deixa um HTML de 30 KB aparecendo no `git status` de quem só quis
|
|
90
|
+
* olhar cobra da pessoa uma limpeza que ela não pediu - e a pasta já é declarada gerenciada aqui.
|
|
91
|
+
*/
|
|
92
|
+
export async function writeGovernanceIgnore(projectRoot) {
|
|
82
93
|
const path = join(projectRoot, "_synthesisui", ".gitignore");
|
|
83
94
|
const existing = await readFile(path, "utf8").catch(() => null);
|
|
84
95
|
/**
|
package/dist/commands/import.js
CHANGED
|
@@ -55,12 +55,14 @@ import { body, bodyWrapped, paint, section } from "../output.js";
|
|
|
55
55
|
import { consumersOf, outsideScope, } from "../outside-scope.js";
|
|
56
56
|
import { phase, startProgress } from "../progress.js";
|
|
57
57
|
import { repoStateOf } from "../repo-state.js";
|
|
58
|
+
import { relatorioHtml, secoesDe } from "../report-html.js";
|
|
58
59
|
import { runtimeDeclaredVars, runtimeDeclaredVarsIn } from "../runtime-vars.js";
|
|
59
60
|
import { detectStack, resolveDeps, stackVersions } from "../stack.js";
|
|
61
|
+
import { desligarCaptura, ligarCaptura } from "../transcript.js";
|
|
60
62
|
import { wiredSlugs, wiringWarning } from "../wired-slugs.js";
|
|
61
63
|
import { harvestWorkspaceCss } from "../workspace-css.js";
|
|
62
64
|
import { placeInWorkspace } from "../workspace-place.js";
|
|
63
|
-
import { add } from "./add.js";
|
|
65
|
+
import { add, writeGovernanceIgnore } from "./add.js";
|
|
64
66
|
import { walk, walkAll } from "./doctor.js";
|
|
65
67
|
/**
|
|
66
68
|
* UMA PÁGINA DELE, como ele a montou - ver `Census.pages`.
|
|
@@ -3720,6 +3722,154 @@ export function defaultScheme(declared) {
|
|
|
3720
3722
|
return dark > light ? "dark" : "light";
|
|
3721
3723
|
}
|
|
3722
3724
|
/** On a dry run there is nobody to ask, so say what was measured. */
|
|
3725
|
+
/**
|
|
3726
|
+
* A TELA DO `inspect`, E O ARQUIVO QUE CARREGA O RESTO (dono, 18/09).
|
|
3727
|
+
*
|
|
3728
|
+
* O QUE O CLIENTE GANHA: ele decide sem rolar. Ficam na tela os quatro blocos que MUDAM o que ele
|
|
3729
|
+
* vai fazer em seguida - quanto foi lido, se o root tem mais de um app, o que não resolve, e onde
|
|
3730
|
+
* está a leitura inteira. Medido na saída dele em 18/09: 250 linhas, 94 delas a mesma frase sobre
|
|
3731
|
+
* nome de parte. Nada se perde: o texto inteiro está no relatório, escrito pela MESMA esteira.
|
|
3732
|
+
*
|
|
3733
|
+
* A FRASE DO CONTRATO MUDA, E É A ÚNICA COISA QUE MUDA: era "nothing was written, and nothing was
|
|
3734
|
+
* sent". Passa a dizer a verdade - nada enviado, um arquivo escrito, e aqui está ele. Uma frase que
|
|
3735
|
+
* promete silêncio enquanto o comando escreve um arquivo é pior que o arquivo.
|
|
3736
|
+
*/
|
|
3737
|
+
async function mostrarInspecao(c, root, transcrito) {
|
|
3738
|
+
const secoes = secoesDe(transcrito);
|
|
3739
|
+
const valores = c.values?.classes;
|
|
3740
|
+
const decisoes = valores ? valores.seen - valores.structure : 0;
|
|
3741
|
+
const pct = valores && decisoes > 0
|
|
3742
|
+
? Math.round((valores.interpreted / decisoes) * 100)
|
|
3743
|
+
: null;
|
|
3744
|
+
const lidos = c.coverage?.read ?? 0;
|
|
3745
|
+
const total = c.coverage?.components ?? 0;
|
|
3746
|
+
const arquivo = join(root, "_synthesisui", "report.html");
|
|
3747
|
+
const html = () => relatorioHtml({
|
|
3748
|
+
cabecalho: {
|
|
3749
|
+
repo: c.measured?.repo ?? basename(root),
|
|
3750
|
+
medidoEm: c.measured?.at ?? "",
|
|
3751
|
+
cli: c.ledger?.cli ?? "",
|
|
3752
|
+
},
|
|
3753
|
+
/**
|
|
3754
|
+
* OS TRÊS NÚMEROS DA ABERTURA, e os dois primeiros carregam o denominador para virarem
|
|
3755
|
+
* BARRA. O terceiro não tem fatia possível - "quantos `var()` não resolvem" não é parte de
|
|
3756
|
+
* nada -, então ele fica só número, e desenhar uma barra ali inventaria uma proporção.
|
|
3757
|
+
*/
|
|
3758
|
+
destaques: [
|
|
3759
|
+
{
|
|
3760
|
+
numero: `${lidos} of ${total}`,
|
|
3761
|
+
rotulo: "components came out with a blueprint",
|
|
3762
|
+
parte: lidos,
|
|
3763
|
+
de: total,
|
|
3764
|
+
},
|
|
3765
|
+
...(pct === null
|
|
3766
|
+
? []
|
|
3767
|
+
: [
|
|
3768
|
+
{
|
|
3769
|
+
numero: `${pct}%`,
|
|
3770
|
+
rotulo: `of ${decisoes} style decisions carry your vocabulary`,
|
|
3771
|
+
parte: valores?.interpreted ?? 0,
|
|
3772
|
+
de: decisoes,
|
|
3773
|
+
},
|
|
3774
|
+
]),
|
|
3775
|
+
{
|
|
3776
|
+
numero: String(c.brokenRefs?.length ?? 0),
|
|
3777
|
+
rotulo: "references that resolve to nothing",
|
|
3778
|
+
},
|
|
3779
|
+
],
|
|
3780
|
+
secoes,
|
|
3781
|
+
});
|
|
3782
|
+
console.log(section("What came out of your files"));
|
|
3783
|
+
console.log(body(`${paint.strong(`${lidos} of ${total}`)} components came out with a blueprint${pct === null
|
|
3784
|
+
? ""
|
|
3785
|
+
: `, and ${paint.strong(`${pct}%`)} of your ${decisoes} style decisions carry your own vocabulary`}.`));
|
|
3786
|
+
/**
|
|
3787
|
+
* OS DOIS BLOCOS QUE MUDAM O QUE ELE FAZ EM SEGUIDA, e eles saem do transcrito em vez de serem
|
|
3788
|
+
* reescritos: a régua que os produziu é a mesma, então a tela nunca diverge do relatório.
|
|
3789
|
+
*/
|
|
3790
|
+
for (const titulo of DECIDEM) {
|
|
3791
|
+
const achada = secoes.find((s) => s.titulo === titulo);
|
|
3792
|
+
if (!achada)
|
|
3793
|
+
continue;
|
|
3794
|
+
console.log(section(titulo));
|
|
3795
|
+
for (const linha of achada.linhas)
|
|
3796
|
+
console.log(linha);
|
|
3797
|
+
}
|
|
3798
|
+
/**
|
|
3799
|
+
* O ARQUIVO SÓ NASCE SE ELE PEDIR (dono, 18/09) - e é isto que mantém `INV-INSPECT-01` de pé.
|
|
3800
|
+
*
|
|
3801
|
+
* A promessa que faz alguém rodar este comando sem medo é que nada acontece: ele lê, mostra, e
|
|
3802
|
+
* o repositório continua exatamente como estava. Escrever o relatório por conta própria
|
|
3803
|
+
* quebraria isso para TODO mundo, inclusive para quem só queria olhar - e num primeiro contato
|
|
3804
|
+
* essa é a confiança inteira. Perguntando, a escrita deixa de ser nossa e passa a ser dele.
|
|
3805
|
+
*
|
|
3806
|
+
* SEM TERMINAL, NÃO PERGUNTA E NÃO ESCREVE: um `inspect` dentro de um pipe, de um CI ou de um
|
|
3807
|
+
* agente não tem quem responda, e um default que escreve seria a mesma quebra por outra porta.
|
|
3808
|
+
* É a mesma guarda que `askName` já usa.
|
|
3809
|
+
*/
|
|
3810
|
+
console.log(section("The whole reading, in one file"));
|
|
3811
|
+
console.log(body(`${secoes.length} sections did not print here - every value, every fragment, every part.`));
|
|
3812
|
+
console.log(body(paint.faint("A single self-contained page in your project. Nothing is sent either way.")));
|
|
3813
|
+
if (!(await quer(` write the full report? [Y/n]: `))) {
|
|
3814
|
+
console.log("");
|
|
3815
|
+
console.log(body("Nothing was written, and nothing was sent."));
|
|
3816
|
+
console.log("");
|
|
3817
|
+
console.log(body("To let your agent ask before it invents:"));
|
|
3818
|
+
console.log(body(` ${paint.strong("npx synthesisui@latest connect")}`));
|
|
3819
|
+
console.log("");
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
await mkdir(dirname(arquivo), { recursive: true });
|
|
3823
|
+
/**
|
|
3824
|
+
* A PASTA SE DECLARA GERENCIADA ANTES DE RECEBER O ARQUIVO: sem isto, o HTML apareceria no
|
|
3825
|
+
* `git status` de quem acabou de pedir um relatório, e não um arquivo versionado.
|
|
3826
|
+
*/
|
|
3827
|
+
await writeGovernanceIgnore(root).catch(() => null);
|
|
3828
|
+
await writeFile(arquivo, html(), "utf8");
|
|
3829
|
+
console.log("");
|
|
3830
|
+
console.log(body(` ${paint.strong(relative(root, arquivo))}`));
|
|
3831
|
+
console.log(body(` ${paint.dim(`file://${arquivo}`)}`));
|
|
3832
|
+
console.log("");
|
|
3833
|
+
console.log(body("To let your agent ask before it invents:"));
|
|
3834
|
+
console.log(body(` ${paint.strong("npx synthesisui@latest connect")}`));
|
|
3835
|
+
console.log("");
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* A PERGUNTA DE UMA TECLA, e o silêncio de quem não tem terminal.
|
|
3839
|
+
*
|
|
3840
|
+
* Fora de um terminal a resposta é NÃO, sempre: ninguém está lá para responder, e assumir o sim
|
|
3841
|
+
* escreveria no repositório de um CI que só queria ler. Enter é sim porque a pessoa acabou de ler
|
|
3842
|
+
* a oferta na linha acima - exigir uma letra para receber o que a tela ofereceu é atrito sem
|
|
3843
|
+
* proteção nenhuma.
|
|
3844
|
+
*/
|
|
3845
|
+
async function quer(pergunta) {
|
|
3846
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
3847
|
+
return false;
|
|
3848
|
+
const { createInterface } = await import("node:readline/promises");
|
|
3849
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3850
|
+
try {
|
|
3851
|
+
console.log("");
|
|
3852
|
+
const resposta = (await rl.question(pergunta)).trim().toLowerCase();
|
|
3853
|
+
return resposta === "" || resposta === "y" || resposta === "yes";
|
|
3854
|
+
}
|
|
3855
|
+
catch {
|
|
3856
|
+
return false;
|
|
3857
|
+
}
|
|
3858
|
+
finally {
|
|
3859
|
+
rl.close();
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
/**
|
|
3863
|
+
* AS SEÇÕES QUE CONTINUAM NA TELA, e o critério é um só: elas mudam o comando seguinte.
|
|
3864
|
+
*
|
|
3865
|
+
* Um root com três apps mede a média de três paletas, o que é diagnóstico e não sistema - e a saída
|
|
3866
|
+
* já traz o `--scope`/`--usage` exato. Um `var()` que não resolve não pinta cor nenhuma no
|
|
3867
|
+
* navegador dele. As outras quinze seções descrevem; estas duas pedem uma decisão.
|
|
3868
|
+
*/
|
|
3869
|
+
const DECIDEM = [
|
|
3870
|
+
"This root holds several projects",
|
|
3871
|
+
"These resolve to nothing",
|
|
3872
|
+
];
|
|
3723
3873
|
function sayReach(c) {
|
|
3724
3874
|
const reach = ladderReach(c.declared);
|
|
3725
3875
|
console.log("");
|
|
@@ -4332,6 +4482,16 @@ export async function runImport(opts) {
|
|
|
4332
4482
|
if (!opts.inspect)
|
|
4333
4483
|
phase(1, 3, "Measuring the repository");
|
|
4334
4484
|
console.log(section("Reading your project"));
|
|
4485
|
+
/**
|
|
4486
|
+
* A PARTIR DAQUI O `inspect` GUARDA EM VEZ DE IMPRIMIR - ver `transcript.ts`.
|
|
4487
|
+
*
|
|
4488
|
+
* As dezessete seções continuam sendo escritas exatamente como sempre foram; o que muda é o
|
|
4489
|
+
* destino. Elas vão para o relatório, e o terminal fica com os quatro blocos que uma pessoa
|
|
4490
|
+
* usa para DECIDIR. Medido na saída do `codelevel-monorepo` em 18/09: 250 linhas, das quais 94
|
|
4491
|
+
* eram a mesma frase sobre nome de parte - 40% do relatório num detalhe sem ação possível.
|
|
4492
|
+
*/
|
|
4493
|
+
if (opts.inspect)
|
|
4494
|
+
ligarCaptura();
|
|
4335
4495
|
/**
|
|
4336
4496
|
* UMA MEDIÇÃO POR ESCOPO, E DEPOIS A FUSÃO.
|
|
4337
4497
|
*
|
|
@@ -4448,13 +4608,8 @@ export async function runImport(opts) {
|
|
|
4448
4608
|
*/
|
|
4449
4609
|
if (opts.inspect) {
|
|
4450
4610
|
sayReach(census);
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
console.log(body("Nothing was written, and nothing was sent."));
|
|
4454
|
-
console.log("");
|
|
4455
|
-
console.log(body("To let it ask before it invents:"));
|
|
4456
|
-
console.log(body(` ${paint.strong("npx synthesisui@latest connect")}`));
|
|
4457
|
-
console.log("");
|
|
4611
|
+
const transcrito = desligarCaptura();
|
|
4612
|
+
await mostrarInspecao(census, root, transcrito);
|
|
4458
4613
|
return;
|
|
4459
4614
|
}
|
|
4460
4615
|
const out = opts.census ?? join(root, "_synthesisui", "census.json");
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { FAMILY_SEAM_PREFIX } from "./fonts.js";
|
|
2
|
+
import { HALOGEN_TOKENS, HALOGEN_VERSION } from "./report-theme.js";
|
|
3
|
+
import { semCor } from "./transcript.js";
|
|
4
|
+
/**
|
|
5
|
+
* O QUE O TERMINAL DIRIA, LIDO DE VOLTA COMO DOCUMENTO.
|
|
6
|
+
*
|
|
7
|
+
* A régua `── Título ──────` é a única marca estrutural que a saída tem, e ela é desenhada num
|
|
8
|
+
* lugar só (`section`), então é ela que separa os capítulos. Tudo antes da primeira régua é ruído
|
|
9
|
+
* de progresso e não entra: o documento começa no primeiro capítulo, não no meio do carregamento.
|
|
10
|
+
*/
|
|
11
|
+
export function secoesDe(linhas) {
|
|
12
|
+
const secoes = [];
|
|
13
|
+
for (const bruta of linhas) {
|
|
14
|
+
for (const linha of semCor(bruta).split("\n")) {
|
|
15
|
+
const cabeca = /^──\s(.+?)\s─*$/.exec(linha.trimEnd());
|
|
16
|
+
if (cabeca) {
|
|
17
|
+
secoes.push({ titulo: cabeca[1].trim(), linhas: [] });
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (secoes.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
secoes[secoes.length - 1].linhas.push(linha.replace(/\s+$/, ""));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Capítulo que não disse nada não vira capítulo - uma régua sozinha não é conteúdo. */
|
|
26
|
+
return secoes
|
|
27
|
+
.map((s) => ({ ...s, linhas: aparar(s.linhas) }))
|
|
28
|
+
.filter((s) => s.linhas.length > 0);
|
|
29
|
+
}
|
|
30
|
+
/** Tira as linhas em branco das pontas, e nunca as do meio - lá elas são respiro. */
|
|
31
|
+
function aparar(linhas) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
let f = linhas.length;
|
|
34
|
+
while (i < f && linhas[i].trim() === "")
|
|
35
|
+
i += 1;
|
|
36
|
+
while (f > i && linhas[f - 1].trim() === "")
|
|
37
|
+
f -= 1;
|
|
38
|
+
return linhas.slice(i, f);
|
|
39
|
+
}
|
|
40
|
+
function escapar(s) {
|
|
41
|
+
return s
|
|
42
|
+
.replace(/&/g, "&")
|
|
43
|
+
.replace(/</g, "<")
|
|
44
|
+
.replace(/>/g, ">")
|
|
45
|
+
.replace(/"/g, """);
|
|
46
|
+
}
|
|
47
|
+
export function relatorioHtml(input) {
|
|
48
|
+
const { cabecalho, destaques, secoes } = input;
|
|
49
|
+
const quando = legivel(cabecalho.medidoEm);
|
|
50
|
+
const indice = secoes
|
|
51
|
+
.map((s, i) => `<li><a href="#s${i}">${escapar(s.titulo)}</a></li>`)
|
|
52
|
+
.join("\n ");
|
|
53
|
+
const corpo = secoes
|
|
54
|
+
.map((s, i) => ` <section id="s${i}" class="chapter" style="--ordem:${Math.min(i, 12)}">
|
|
55
|
+
<h2>${escapar(s.titulo)}</h2>
|
|
56
|
+
${desenharCorpo(s)}
|
|
57
|
+
</section>`)
|
|
58
|
+
.join("\n");
|
|
59
|
+
const numeros = destaques.map(cartaoDeNumero).join("\n");
|
|
60
|
+
return `<!doctype html>
|
|
61
|
+
<html lang="en" data-scheme="light">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
+
<title>${escapar(cabecalho.repo)} - read by SynthesisUI</title>
|
|
66
|
+
<style>
|
|
67
|
+
${HALOGEN_TOKENS}
|
|
68
|
+
</style>
|
|
69
|
+
<style>
|
|
70
|
+
${layout()}
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<header class="bar">
|
|
75
|
+
<div class="bar-in">
|
|
76
|
+
<div class="bar-id">
|
|
77
|
+
<p class="kicker">Read by SynthesisUI</p>
|
|
78
|
+
<p class="bar-repo">${escapar(cabecalho.repo)}</p>
|
|
79
|
+
</div>
|
|
80
|
+
<p class="meta">${escapar(quando)} · CLI ${escapar(cabecalho.cli)} · nothing left this machine</p>
|
|
81
|
+
</div>
|
|
82
|
+
</header>
|
|
83
|
+
|
|
84
|
+
<main class="wrap">
|
|
85
|
+
<section class="hero chapter" style="--ordem:0">
|
|
86
|
+
<h1>${escapar(cabecalho.repo)}</h1>
|
|
87
|
+
<p class="lede">This is what your own code says about itself.</p>
|
|
88
|
+
<div class="stats">
|
|
89
|
+
${numeros}
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
<nav class="chapter" style="--ordem:1">
|
|
94
|
+
<p class="kicker">What is in here</p>
|
|
95
|
+
<ol>
|
|
96
|
+
${indice}
|
|
97
|
+
</ol>
|
|
98
|
+
</nav>
|
|
99
|
+
|
|
100
|
+
${corpo}
|
|
101
|
+
|
|
102
|
+
<footer>
|
|
103
|
+
<p>Dressed by Halogen v${HALOGEN_VERSION}, a design system from the SynthesisUI gallery.</p>
|
|
104
|
+
<p>This file lives in your project and was never sent anywhere. Re-run the command to refresh it.</p>
|
|
105
|
+
</footer>
|
|
106
|
+
</main>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* O NÚMERO COM A FATIA DESENHADA - e a barra só aparece quando existe denominador medido.
|
|
113
|
+
*
|
|
114
|
+
* `aria-hidden` na barra porque ela não diz nada que o número ao lado já não diga; um leitor de
|
|
115
|
+
* tela que a anunciasse leria a mesma informação duas vezes.
|
|
116
|
+
*/
|
|
117
|
+
function cartaoDeNumero(d) {
|
|
118
|
+
const temBarra = typeof d.parte === "number" && typeof d.de === "number" && d.de > 0;
|
|
119
|
+
const pct = temBarra
|
|
120
|
+
? Math.max(0, Math.min(100, Math.round(((d.parte ?? 0) / (d.de ?? 1)) * 100)))
|
|
121
|
+
: 0;
|
|
122
|
+
return ` <div class="stat">
|
|
123
|
+
<strong>${escapar(d.numero)}</strong>
|
|
124
|
+
<span>${escapar(d.rotulo)}</span>${temBarra
|
|
125
|
+
? `
|
|
126
|
+
<div class="bar-track" aria-hidden="true"><i style="--pct:${pct}%"></i></div>`
|
|
127
|
+
: ""}
|
|
128
|
+
</div>`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* COMO CADA CAPÍTULO É DESENHADO - e a decisão é sobre REPETIÇÃO, não sobre o assunto.
|
|
132
|
+
*
|
|
133
|
+
* Medido no `codelevel-monorepo` em 18/09: uma única seção trazia 94 linhas com a mesma forma
|
|
134
|
+
* (`two parts named X - the second is Y`), num bloco monoespaçado onde nada se acha. Uma lista de
|
|
135
|
+
* itens repetidos é uma TABELA, e uma tabela é varrida com o olho; o bloco de texto é lido linha a
|
|
136
|
+
* linha ou não é lido.
|
|
137
|
+
*
|
|
138
|
+
* O corte é mecânico e não conhece assunto nenhum: uma família só vira tabela a partir de seis
|
|
139
|
+
* linhas irmãs, porque abaixo disso a tabela custa mais atenção do que economiza.
|
|
140
|
+
*/
|
|
141
|
+
function desenharCorpo(s) {
|
|
142
|
+
const familias = agrupar(s.linhas);
|
|
143
|
+
return familias
|
|
144
|
+
.map((f) => f.tipo === "tabela"
|
|
145
|
+
? ` <details class="familia" open>
|
|
146
|
+
<summary>${escapar(f.rotulo)} <b>${f.itens.length}</b></summary>
|
|
147
|
+
<ul class="grade">
|
|
148
|
+
${f.itens.map((i) => ` <li>${escapar(i)}</li>`).join("\n")}
|
|
149
|
+
</ul>
|
|
150
|
+
</details>`
|
|
151
|
+
: ` <pre>${escapar(f.itens.join("\n"))}</pre>`)
|
|
152
|
+
.join("\n");
|
|
153
|
+
}
|
|
154
|
+
const MINIMO_PARA_TABELA = 6;
|
|
155
|
+
/**
|
|
156
|
+
* A FORMA DE UMA LINHA, sem ler o que ela diz: as palavras ficam, os nomes próprios viram `·`.
|
|
157
|
+
*
|
|
158
|
+
* É isso que faz `two parts named \`box\`` e `two parts named \`nav\`` caírem na mesma família sem
|
|
159
|
+
* ninguém escrever uma regra sobre partes - e é isso que faz a regra valer para a próxima classe de
|
|
160
|
+
* linha repetida, que ninguém ainda viu (`INV-GERAL-07`).
|
|
161
|
+
*/
|
|
162
|
+
function forma(linha) {
|
|
163
|
+
return linha
|
|
164
|
+
.trim()
|
|
165
|
+
.replace(/`[^`]*`/g, "·")
|
|
166
|
+
.replace(/\d+/g, "#")
|
|
167
|
+
.split(/\s+/)
|
|
168
|
+
.slice(0, 6)
|
|
169
|
+
.join(" ");
|
|
170
|
+
}
|
|
171
|
+
function agrupar(linhas) {
|
|
172
|
+
const saida = [];
|
|
173
|
+
let i = 0;
|
|
174
|
+
while (i < linhas.length) {
|
|
175
|
+
const atual = linhas[i];
|
|
176
|
+
if (!atual.trim()) {
|
|
177
|
+
empilharProsa(saida, atual);
|
|
178
|
+
i += 1;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const f = forma(atual);
|
|
182
|
+
let j = i;
|
|
183
|
+
const irmas = [];
|
|
184
|
+
while (j < linhas.length && linhas[j].trim() && forma(linhas[j]) === f) {
|
|
185
|
+
irmas.push(linhas[j].trim());
|
|
186
|
+
j += 1;
|
|
187
|
+
}
|
|
188
|
+
if (irmas.length >= MINIMO_PARA_TABELA) {
|
|
189
|
+
saida.push({ tipo: "tabela", rotulo: rotuloDe(f), itens: irmas });
|
|
190
|
+
i = j;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
empilharProsa(saida, atual);
|
|
194
|
+
i += 1;
|
|
195
|
+
}
|
|
196
|
+
return saida.filter((f) => f.itens.some((l) => l.trim()));
|
|
197
|
+
}
|
|
198
|
+
function empilharProsa(saida, linha) {
|
|
199
|
+
const ultimo = saida[saida.length - 1];
|
|
200
|
+
if (ultimo && ultimo.tipo === "prosa")
|
|
201
|
+
ultimo.itens.push(linha);
|
|
202
|
+
else
|
|
203
|
+
saida.push({ tipo: "prosa", rotulo: "", itens: [linha] });
|
|
204
|
+
}
|
|
205
|
+
/** O rótulo da família é a própria forma, com os buracos ditos em vez de mostrados. */
|
|
206
|
+
function rotuloDe(f) {
|
|
207
|
+
return f.replace(/·/g, "…").replace(/#/g, "n").trim() || "repeated lines";
|
|
208
|
+
}
|
|
209
|
+
/** A data em palavras. Data ilegível some da frase em vez de imprimir `Invalid Date`. */
|
|
210
|
+
function legivel(iso) {
|
|
211
|
+
if (!iso || Number.isNaN(Date.parse(iso)))
|
|
212
|
+
return "just now";
|
|
213
|
+
return new Date(iso).toLocaleDateString("en-US", {
|
|
214
|
+
month: "short",
|
|
215
|
+
day: "numeric",
|
|
216
|
+
year: "numeric",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* O LAYOUT DESTA PÁGINA, e só ele: cor, raio e fonte vêm dos tokens do halogen logo acima.
|
|
221
|
+
*
|
|
222
|
+
* A BARRA É FIXA porque os três números são a razão de a página existir - e numa leitura de
|
|
223
|
+
* dezesseis capítulos eles somem na primeira rolagem, que é quando a pessoa mais precisa deles
|
|
224
|
+
* para saber o que está olhando.
|
|
225
|
+
*
|
|
226
|
+
* O MOVIMENTO CARREGA INFORMAÇÃO: cada capítulo entra de baixo, na ordem em que a esteira o
|
|
227
|
+
* escreveu, e o atraso é a posição dele. Ele para inteiro sob `prefers-reduced-motion` - uma
|
|
228
|
+
* página que desliza dezesseis vezes são dezesseis chances de passar mal.
|
|
229
|
+
*
|
|
230
|
+
* E ela imprime: `@media print` existe porque o navegador já exporta PDF, e um relatório que sai
|
|
231
|
+
* torto no papel obriga a construir um gerador de PDF para resolver o que uma regra resolve.
|
|
232
|
+
*/
|
|
233
|
+
function layout() {
|
|
234
|
+
/**
|
|
235
|
+
* A COSTURA DAS FAMÍLIAS VEM DA CONST, e não do literal: renomear o prefixo em `fonts.ts` tem
|
|
236
|
+
* que alcançar esta página também. Um literal solto aqui divergiria em silêncio, que é
|
|
237
|
+
* exatamente o que o portão de `fonts.spec.ts` existe para impedir.
|
|
238
|
+
*/
|
|
239
|
+
const corpo = `var(${FAMILY_SEAM_PREFIX}body, ui-sans-serif, system-ui, sans-serif)`;
|
|
240
|
+
const mono = `var(${FAMILY_SEAM_PREFIX}mono, ui-monospace, monospace)`;
|
|
241
|
+
const display = `var(${FAMILY_SEAM_PREFIX}display, inherit)`;
|
|
242
|
+
return `body {
|
|
243
|
+
margin: 0;
|
|
244
|
+
background: var(--ds-color-semantic-canvas);
|
|
245
|
+
color: var(--ds-color-semantic-foreground);
|
|
246
|
+
font-family: ${corpo};
|
|
247
|
+
font-size: 15px;
|
|
248
|
+
line-height: 1.6;
|
|
249
|
+
}
|
|
250
|
+
* { box-sizing: border-box; }
|
|
251
|
+
|
|
252
|
+
.bar {
|
|
253
|
+
position: sticky;
|
|
254
|
+
top: 0;
|
|
255
|
+
z-index: 10;
|
|
256
|
+
background: var(--ds-color-semantic-raised, var(--ds-color-semantic-surface));
|
|
257
|
+
border-bottom: 1px solid var(--ds-color-semantic-border);
|
|
258
|
+
}
|
|
259
|
+
.bar-in {
|
|
260
|
+
max-width: 920px;
|
|
261
|
+
margin-inline: auto;
|
|
262
|
+
padding: 12px 24px;
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-wrap: wrap;
|
|
265
|
+
gap: 8px 24px;
|
|
266
|
+
align-items: baseline;
|
|
267
|
+
justify-content: space-between;
|
|
268
|
+
}
|
|
269
|
+
.bar-repo { margin: 2px 0 0; font-weight: 600; }
|
|
270
|
+
.bar-id { min-width: 0; }
|
|
271
|
+
|
|
272
|
+
.wrap { max-width: 920px; margin-inline: auto; padding: 0 24px 96px; }
|
|
273
|
+
|
|
274
|
+
.kicker {
|
|
275
|
+
margin: 0;
|
|
276
|
+
font-family: ${mono};
|
|
277
|
+
font-size: 11px;
|
|
278
|
+
letter-spacing: 0.16em;
|
|
279
|
+
text-transform: uppercase;
|
|
280
|
+
color: var(--ds-color-semantic-accent);
|
|
281
|
+
}
|
|
282
|
+
.meta { margin: 0; color: var(--ds-color-semantic-muted); font-size: 13px; }
|
|
283
|
+
|
|
284
|
+
.hero { padding: 56px 0 8px; }
|
|
285
|
+
h1 {
|
|
286
|
+
margin: 10px 0 8px;
|
|
287
|
+
font-family: ${display};
|
|
288
|
+
font-size: 40px;
|
|
289
|
+
line-height: 1.1;
|
|
290
|
+
letter-spacing: -0.02em;
|
|
291
|
+
}
|
|
292
|
+
.lede { margin: 0 0 28px; color: var(--ds-color-semantic-muted); font-size: 17px; }
|
|
293
|
+
|
|
294
|
+
.stats {
|
|
295
|
+
display: grid;
|
|
296
|
+
gap: 12px;
|
|
297
|
+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
298
|
+
}
|
|
299
|
+
.stat {
|
|
300
|
+
background: var(--ds-color-semantic-surface);
|
|
301
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
302
|
+
border-radius: var(--ds-radius-lg, 14px);
|
|
303
|
+
padding: 18px 20px;
|
|
304
|
+
}
|
|
305
|
+
.stat strong { display: block; font-size: 28px; letter-spacing: -0.02em; }
|
|
306
|
+
.stat span { display: block; margin-top: 4px; color: var(--ds-color-semantic-muted); font-size: 13px; }
|
|
307
|
+
.bar-track {
|
|
308
|
+
margin-top: 14px;
|
|
309
|
+
height: 6px;
|
|
310
|
+
border-radius: 999px;
|
|
311
|
+
background: var(--ds-color-semantic-overlay, var(--ds-color-semantic-border));
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
}
|
|
314
|
+
.bar-track i {
|
|
315
|
+
display: block;
|
|
316
|
+
height: 100%;
|
|
317
|
+
width: var(--pct);
|
|
318
|
+
border-radius: inherit;
|
|
319
|
+
background: var(--ds-color-semantic-accent);
|
|
320
|
+
animation: crescer 900ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
nav { margin: 44px 0 8px; }
|
|
324
|
+
nav ol { margin: 10px 0 0; padding-left: 20px; color: var(--ds-color-semantic-muted); }
|
|
325
|
+
nav li { margin: 3px 0; }
|
|
326
|
+
nav a { color: inherit; text-underline-offset: 3px; }
|
|
327
|
+
nav a:hover { color: var(--ds-color-semantic-accent); }
|
|
328
|
+
|
|
329
|
+
section.chapter {
|
|
330
|
+
background: var(--ds-color-semantic-surface);
|
|
331
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
332
|
+
border-radius: var(--ds-radius-lg, 14px);
|
|
333
|
+
padding: 24px 26px;
|
|
334
|
+
margin-top: 16px;
|
|
335
|
+
scroll-margin-top: 84px;
|
|
336
|
+
}
|
|
337
|
+
.hero, nav { background: none; border: 0; padding-inline: 0; }
|
|
338
|
+
h2 { margin: 0 0 14px; font-size: 18px; letter-spacing: -0.01em; }
|
|
339
|
+
|
|
340
|
+
pre {
|
|
341
|
+
margin: 0 0 12px;
|
|
342
|
+
white-space: pre-wrap;
|
|
343
|
+
word-break: break-word;
|
|
344
|
+
font-family: ${mono};
|
|
345
|
+
font-size: 12.5px;
|
|
346
|
+
line-height: 1.7;
|
|
347
|
+
color: var(--ds-color-semantic-muted);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.familia {
|
|
351
|
+
margin: 0 0 12px;
|
|
352
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
353
|
+
border-radius: var(--ds-radius-md, 10px);
|
|
354
|
+
padding: 10px 14px;
|
|
355
|
+
background: var(--ds-color-semantic-canvas);
|
|
356
|
+
}
|
|
357
|
+
.familia summary {
|
|
358
|
+
cursor: pointer;
|
|
359
|
+
font-size: 13px;
|
|
360
|
+
color: var(--ds-color-semantic-muted);
|
|
361
|
+
}
|
|
362
|
+
.familia summary b {
|
|
363
|
+
color: var(--ds-color-semantic-foreground);
|
|
364
|
+
font-variant-numeric: tabular-nums;
|
|
365
|
+
}
|
|
366
|
+
.grade {
|
|
367
|
+
list-style: none;
|
|
368
|
+
margin: 12px 0 4px;
|
|
369
|
+
padding: 0;
|
|
370
|
+
display: grid;
|
|
371
|
+
gap: 4px 20px;
|
|
372
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
373
|
+
font-family: ${mono};
|
|
374
|
+
font-size: 12px;
|
|
375
|
+
color: var(--ds-color-semantic-muted);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
footer { margin-top: 40px; color: var(--ds-color-semantic-muted); font-size: 13px; }
|
|
379
|
+
|
|
380
|
+
@keyframes crescer { from { width: 0; } }
|
|
381
|
+
@keyframes entrar { from { opacity: 0; transform: translateY(12px); } }
|
|
382
|
+
.chapter { animation: entrar 620ms cubic-bezier(0.22, 1, 0.36, 1) both; animation-delay: calc(var(--ordem, 0) * 55ms); }
|
|
383
|
+
|
|
384
|
+
@media (prefers-reduced-motion: reduce) {
|
|
385
|
+
.chapter, .bar-track i { animation: none; }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@media print {
|
|
389
|
+
.bar { position: static; }
|
|
390
|
+
nav { display: none; }
|
|
391
|
+
.stat, section.chapter { break-inside: avoid; }
|
|
392
|
+
.familia { break-inside: auto; }
|
|
393
|
+
}`;
|
|
394
|
+
}
|
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O HALOGEN, EMBUTIDO NO PACOTE - os VALORES dele, nunca o marcador dele.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA: o relatório do `inspect` é desenhado por um design system de verdade, o
|
|
5
|
+
* mesmo que qualquer pessoa instala da galeria. A primeira coisa que ela vê do produto já é o
|
|
6
|
+
* produto funcionando, e não uma folha que alguém escreveu à mão para a ocasião.
|
|
7
|
+
*
|
|
8
|
+
* O ESCOPO FOI REESCRITO PARA A RAIZ, e isto é `INV-GERAL-13`: nada nosso é pedido, escrito ou
|
|
9
|
+
* sugerido dentro da árvore dele. O `tokens.css` publicado vive sob o escopo do sistema porque
|
|
10
|
+
* ele foi feito para ser INSTALADO num app; aqui ele veste um documento nosso, então as
|
|
11
|
+
* declarações passam a morar em `:root` e nenhum atributo nosso aparece no repositório de
|
|
12
|
+
* ninguém. O que atravessa é a cor, o raio e a tipografia - que é tudo o que uma página precisa.
|
|
13
|
+
*
|
|
14
|
+
* POR QUE DENTRO DO PACOTE: `inspect` não faz uma única chamada de rede, e isso é contrato
|
|
15
|
+
* (`INV-INSPECT-01`, com spec que conta os `fetch`). Buscar o CSS na hora de gerar quebraria o
|
|
16
|
+
* comando exatamente para quem tem menos motivo para confiar nele - alguém que acabou de apontar
|
|
17
|
+
* a ferramenta para o próprio repositório. Então ele vem junto, medido e congelado.
|
|
18
|
+
*
|
|
19
|
+
* COMO ATUALIZAR, quando o halogen publicar uma versão nova: baixe
|
|
20
|
+
* `https://www.synthesisui.com/api/registry/ds/halogen`, pegue `artifacts["tokens.css"]` e
|
|
21
|
+
* reescreva o escopo para a raiz como acima. É uma cópia deliberada, não uma dependência
|
|
22
|
+
* esquecida - e por isso a versão viaja ao lado, dita na própria página.
|
|
23
|
+
*
|
|
24
|
+
* O ESQUEMA: a raiz sozinha é ESCURA; `:root[data-scheme="light"]` é a clara. O relatório abre em
|
|
25
|
+
* claro por decisão dele (18/09), e o escuro continua ali para quem trocar o atributo.
|
|
26
|
+
*/
|
|
27
|
+
/** A versão do halogen que estas folhas descrevem - dita na página, para a divergência ser vista. */
|
|
28
|
+
export const HALOGEN_VERSION = 18;
|
|
29
|
+
/** `tokens.css` do halogen, com o escopo reescrito para a raiz e nada mais tocado. */
|
|
30
|
+
export const HALOGEN_TOKENS = String.raw `/* 39 more tokens of this system are not emitted here: nothing in it references them. They stay in the system and arrive the moment a recipe, a utility or a component asks. */
|
|
31
|
+
|
|
32
|
+
:root {
|
|
33
|
+
--ds-color-moss-400: #4f9d76;
|
|
34
|
+
--ds-color-moss-500: #3f8a66;
|
|
35
|
+
--ds-color-rose-400: #e0576a;
|
|
36
|
+
--ds-color-rose-500: #cf3f54;
|
|
37
|
+
--ds-color-veil-500: #05070ab3;
|
|
38
|
+
--ds-color-amber-400: #c9973f;
|
|
39
|
+
--ds-color-amber-500: #b3842f;
|
|
40
|
+
--ds-color-blush-400: #f79ce0;
|
|
41
|
+
--ds-color-slate-50: #ffffff;
|
|
42
|
+
--ds-color-slate-100: #f7f8f8;
|
|
43
|
+
--ds-color-slate-200: #e2e4e7;
|
|
44
|
+
--ds-color-slate-300: #d0d6e0;
|
|
45
|
+
--ds-color-slate-400: #8a8f98;
|
|
46
|
+
--ds-color-slate-500: #62666d;
|
|
47
|
+
--ds-color-slate-600: #383b3f;
|
|
48
|
+
--ds-color-slate-700: #23252a;
|
|
49
|
+
--ds-color-slate-800: #18191c;
|
|
50
|
+
--ds-color-slate-900: #0f1011;
|
|
51
|
+
--ds-color-slate-950: #08090a;
|
|
52
|
+
--ds-color-indigo-300: #a5b4ff;
|
|
53
|
+
--ds-color-indigo-400: #7c8cff;
|
|
54
|
+
--ds-color-indigo-500: #5b6cff;
|
|
55
|
+
--ds-color-semantic-info: var(--ds-color-indigo-400);
|
|
56
|
+
--ds-color-semantic-faint: var(--ds-color-slate-500);
|
|
57
|
+
--ds-color-semantic-muted: var(--ds-color-slate-400);
|
|
58
|
+
--ds-color-semantic-scrim: var(--ds-color-veil-500);
|
|
59
|
+
--ds-color-semantic-accent: var(--ds-color-blush-400);
|
|
60
|
+
--ds-color-semantic-border: var(--ds-color-slate-700);
|
|
61
|
+
--ds-color-semantic-canvas: var(--ds-color-slate-950);
|
|
62
|
+
--ds-color-semantic-danger: var(--ds-color-rose-400);
|
|
63
|
+
--ds-color-semantic-raised: var(--ds-color-slate-800);
|
|
64
|
+
--ds-color-semantic-overlay: var(--ds-color-slate-700);
|
|
65
|
+
--ds-color-semantic-primary: var(--ds-color-indigo-500);
|
|
66
|
+
--ds-color-semantic-success: var(--ds-color-moss-400);
|
|
67
|
+
--ds-color-semantic-surface: var(--ds-color-slate-900);
|
|
68
|
+
--ds-color-semantic-warning: var(--ds-color-amber-400);
|
|
69
|
+
--ds-color-semantic-foreground: var(--ds-color-slate-100);
|
|
70
|
+
--ds-color-series-1: var(--ds-color-indigo-500);
|
|
71
|
+
--ds-color-series-2: var(--ds-color-amber-500);
|
|
72
|
+
--ds-color-series-3: var(--ds-color-moss-500);
|
|
73
|
+
--ds-color-series-4: var(--ds-color-rose-500);
|
|
74
|
+
--ds-color-series-5: var(--ds-color-blush-400);
|
|
75
|
+
--ds-color-series-6: var(--ds-color-indigo-300);
|
|
76
|
+
--ds-color-series-7: var(--ds-color-moss-400);
|
|
77
|
+
--ds-color-series-8: var(--ds-color-rose-400);
|
|
78
|
+
--ds-radius-lg: 12px;
|
|
79
|
+
--ds-radius-md: 8px;
|
|
80
|
+
--ds-radius-sm: 6px;
|
|
81
|
+
--ds-radius-xs: 2px;
|
|
82
|
+
--ds-radius-full: 9999px;
|
|
83
|
+
--ds-shadow-md: 0 8px 24px #00000080;
|
|
84
|
+
--ds-shadow-sm: 0 2px 4px #00000066;
|
|
85
|
+
--ds-shadow-glow: 0 0 24px -6px #5b6cffaa;
|
|
86
|
+
--ds-shadow-ring: 0 0 0 1px #23252a inset;
|
|
87
|
+
--ds-spacing-lg: 2rem;
|
|
88
|
+
--ds-spacing-md: 1.5rem;
|
|
89
|
+
--ds-spacing-sm: 1rem;
|
|
90
|
+
--ds-spacing-xl: 3rem;
|
|
91
|
+
--ds-spacing-xs: 0.75rem;
|
|
92
|
+
--ds-spacing-2xl: 4rem;
|
|
93
|
+
--ds-spacing-2xs: 0.5rem;
|
|
94
|
+
--ds-spacing-3xl: 6rem;
|
|
95
|
+
--ds-spacing-3xs: 0.25rem;
|
|
96
|
+
--ds-typography-scale-lg-font-size: 1.25rem;
|
|
97
|
+
--ds-typography-scale-lg-line-height: 1.4;
|
|
98
|
+
--ds-typography-scale-sm-font-size: 0.875rem;
|
|
99
|
+
--ds-typography-scale-sm-line-height: 1.5;
|
|
100
|
+
--ds-typography-scale-xl-font-size: 1.5rem;
|
|
101
|
+
--ds-typography-scale-xl-line-height: 1.3;
|
|
102
|
+
--ds-typography-scale-xl-letter-spacing: -0.01em;
|
|
103
|
+
--ds-typography-scale-xs-font-size: 0.75rem;
|
|
104
|
+
--ds-typography-scale-xs-line-height: 1rem;
|
|
105
|
+
--ds-typography-scale-2xl-font-size: 2rem;
|
|
106
|
+
--ds-typography-scale-2xl-line-height: 1.2;
|
|
107
|
+
--ds-typography-scale-2xl-letter-spacing: -0.02em;
|
|
108
|
+
--ds-typography-scale-base-font-size: 1rem;
|
|
109
|
+
--ds-typography-scale-base-line-height: 1.6;
|
|
110
|
+
--ds-typography-scale-display-font-size: 4rem;
|
|
111
|
+
--ds-typography-scale-display-line-height: 1.05;
|
|
112
|
+
--ds-typography-scale-display-letter-spacing: -0.03em;
|
|
113
|
+
--ds-typography-weights-medium: 510;
|
|
114
|
+
--ds-typography-weights-regular: 400;
|
|
115
|
+
--ds-typography-weights-semibold: 590;
|
|
116
|
+
--ds-typography-families-body: "Inter Variable", sans-serif;
|
|
117
|
+
--ds-typography-families-mono: "Berkeley Mono", monospace;
|
|
118
|
+
--ds-typography-families-display: "Inter Variable", sans-serif;
|
|
119
|
+
--ds-motion-durations-base: 200ms;
|
|
120
|
+
--ds-motion-durations-fast: 120ms;
|
|
121
|
+
--ds-motion-durations-slow: 320ms;
|
|
122
|
+
--ds-motion-durations-ambient: 3.5s;
|
|
123
|
+
--ds-motion-easings-out: ease-out;
|
|
124
|
+
--ds-motion-easings-swift: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
125
|
+
--ds-motion-easings-standard: ease;
|
|
126
|
+
--color-info: var(--ds-color-semantic-info);
|
|
127
|
+
--color-faint: var(--ds-color-semantic-faint);
|
|
128
|
+
--color-muted: var(--ds-color-semantic-muted);
|
|
129
|
+
--color-scrim: var(--ds-color-semantic-scrim);
|
|
130
|
+
--color-accent: var(--ds-color-semantic-accent);
|
|
131
|
+
--color-border: var(--ds-color-semantic-border);
|
|
132
|
+
--color-canvas: var(--ds-color-semantic-canvas);
|
|
133
|
+
--color-danger: var(--ds-color-semantic-danger);
|
|
134
|
+
--color-raised: var(--ds-color-semantic-raised);
|
|
135
|
+
--color-overlay: var(--ds-color-semantic-overlay);
|
|
136
|
+
--color-primary: var(--ds-color-semantic-primary);
|
|
137
|
+
--color-success: var(--ds-color-semantic-success);
|
|
138
|
+
--color-surface: var(--ds-color-semantic-surface);
|
|
139
|
+
--color-warning: var(--ds-color-semantic-warning);
|
|
140
|
+
--color-foreground: var(--ds-color-semantic-foreground);
|
|
141
|
+
--color-series-1: var(--ds-color-series-1);
|
|
142
|
+
--color-series-2: var(--ds-color-series-2);
|
|
143
|
+
--color-series-3: var(--ds-color-series-3);
|
|
144
|
+
--color-series-4: var(--ds-color-series-4);
|
|
145
|
+
--color-series-5: var(--ds-color-series-5);
|
|
146
|
+
--color-series-6: var(--ds-color-series-6);
|
|
147
|
+
--color-series-7: var(--ds-color-series-7);
|
|
148
|
+
--color-series-8: var(--ds-color-series-8);
|
|
149
|
+
--spacing-lg: var(--ds-spacing-lg);
|
|
150
|
+
--spacing-md: var(--ds-spacing-md);
|
|
151
|
+
--spacing-sm: var(--ds-spacing-sm);
|
|
152
|
+
--spacing-xl: var(--ds-spacing-xl);
|
|
153
|
+
--spacing-xs: var(--ds-spacing-xs);
|
|
154
|
+
--spacing-2xl: var(--ds-spacing-2xl);
|
|
155
|
+
--spacing-2xs: var(--ds-spacing-2xs);
|
|
156
|
+
--spacing-3xl: var(--ds-spacing-3xl);
|
|
157
|
+
--spacing-3xs: var(--ds-spacing-3xs);
|
|
158
|
+
--radius-lg: var(--ds-radius-lg);
|
|
159
|
+
--radius-md: var(--ds-radius-md);
|
|
160
|
+
--radius-sm: var(--ds-radius-sm);
|
|
161
|
+
--radius-xs: var(--ds-radius-xs);
|
|
162
|
+
--radius-full: var(--ds-radius-full);
|
|
163
|
+
--shadow-md: var(--ds-shadow-md);
|
|
164
|
+
--shadow-sm: var(--ds-shadow-sm);
|
|
165
|
+
--shadow-glow: var(--ds-shadow-glow);
|
|
166
|
+
--shadow-ring: var(--ds-shadow-ring);
|
|
167
|
+
--font-body: var(--ds-typography-families-body);
|
|
168
|
+
--font-mono: var(--ds-typography-families-mono);
|
|
169
|
+
--font-display: var(--ds-typography-families-display);
|
|
170
|
+
--font-weight-medium: var(--ds-typography-weights-medium);
|
|
171
|
+
--font-weight-regular: var(--ds-typography-weights-regular);
|
|
172
|
+
--font-weight-semibold: var(--ds-typography-weights-semibold);
|
|
173
|
+
--ease-out: var(--ds-motion-easings-out);
|
|
174
|
+
--ease-swift: var(--ds-motion-easings-swift);
|
|
175
|
+
--ease-standard: var(--ds-motion-easings-standard);
|
|
176
|
+
--text-lg: var(--ds-typography-scale-lg-font-size);
|
|
177
|
+
--text-lg--line-height: var(--ds-typography-scale-lg-line-height);
|
|
178
|
+
--text-sm: var(--ds-typography-scale-sm-font-size);
|
|
179
|
+
--text-sm--line-height: var(--ds-typography-scale-sm-line-height);
|
|
180
|
+
--text-xl: var(--ds-typography-scale-xl-font-size);
|
|
181
|
+
--text-xl--line-height: var(--ds-typography-scale-xl-line-height);
|
|
182
|
+
--text-xl--letter-spacing: var(--ds-typography-scale-xl-letter-spacing);
|
|
183
|
+
--text-xs: var(--ds-typography-scale-xs-font-size);
|
|
184
|
+
--text-xs--line-height: var(--ds-typography-scale-xs-line-height);
|
|
185
|
+
--text-2xl: var(--ds-typography-scale-2xl-font-size);
|
|
186
|
+
--text-2xl--line-height: var(--ds-typography-scale-2xl-line-height);
|
|
187
|
+
--text-2xl--letter-spacing: var(--ds-typography-scale-2xl-letter-spacing);
|
|
188
|
+
--text-base: var(--ds-typography-scale-base-font-size);
|
|
189
|
+
--text-base--line-height: var(--ds-typography-scale-base-line-height);
|
|
190
|
+
--text-display: var(--ds-typography-scale-display-font-size);
|
|
191
|
+
--text-display--line-height: var(--ds-typography-scale-display-line-height);
|
|
192
|
+
--text-display--letter-spacing: var(--ds-typography-scale-display-letter-spacing);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@layer base {
|
|
196
|
+
:root {
|
|
197
|
+
color: var(--ds-color-semantic-foreground);
|
|
198
|
+
font-family: var(--ds-typography-families-body);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
:root[data-scheme="light"] {
|
|
203
|
+
--ds-color-semantic-faint: var(--ds-color-slate-500);
|
|
204
|
+
--ds-color-semantic-muted: var(--ds-color-slate-600);
|
|
205
|
+
--ds-color-semantic-border: var(--ds-color-slate-300);
|
|
206
|
+
--ds-color-semantic-canvas: var(--ds-color-slate-100);
|
|
207
|
+
--ds-color-semantic-raised: var(--ds-color-slate-50);
|
|
208
|
+
--ds-color-semantic-overlay: var(--ds-color-slate-200);
|
|
209
|
+
--ds-color-semantic-surface: var(--ds-color-slate-50);
|
|
210
|
+
--ds-color-semantic-foreground: var(--ds-color-slate-900);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
:root[data-theme="light"] {
|
|
214
|
+
--ds-color-semantic-faint: var(--ds-color-slate-500);
|
|
215
|
+
--ds-color-semantic-muted: var(--ds-color-slate-600);
|
|
216
|
+
--ds-color-semantic-border: var(--ds-color-slate-300);
|
|
217
|
+
--ds-color-semantic-canvas: var(--ds-color-slate-100);
|
|
218
|
+
--ds-color-semantic-raised: var(--ds-color-slate-50);
|
|
219
|
+
--ds-color-semantic-overlay: var(--ds-color-slate-200);
|
|
220
|
+
--ds-color-semantic-surface: var(--ds-color-slate-50);
|
|
221
|
+
--ds-color-semantic-foreground: var(--ds-color-slate-900);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@layer base {
|
|
225
|
+
:root :is(a[href], button, summary, [role="button"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="tab"], [role="switch"], [role="option"], [tabindex]:not([tabindex="-1"])):focus-visible {
|
|
226
|
+
outline-width: 2px;
|
|
227
|
+
outline-style: solid;
|
|
228
|
+
outline-color: var(--ds-color-semantic-primary);
|
|
229
|
+
outline-offset: 2px;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@layer base {
|
|
234
|
+
:root .ds-avatar { flex-shrink: 0; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@layer base {
|
|
238
|
+
@media (prefers-reduced-motion: reduce) {
|
|
239
|
+
:root *, :root *::before, :root *::after {
|
|
240
|
+
animation-duration: 0.01ms !important;
|
|
241
|
+
animation-iteration-count: 1 !important;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@keyframes ds-halogen-rise {
|
|
247
|
+
to {
|
|
248
|
+
opacity: 1;
|
|
249
|
+
transform: translateY(0);
|
|
250
|
+
}
|
|
251
|
+
from {
|
|
252
|
+
opacity: 0;
|
|
253
|
+
transform: translateY(12px);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@keyframes ds-halogen-blink {
|
|
258
|
+
0% {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
}
|
|
261
|
+
50% {
|
|
262
|
+
opacity: 0;
|
|
263
|
+
}
|
|
264
|
+
100% {
|
|
265
|
+
opacity: 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@keyframes ds-halogen-float {
|
|
270
|
+
0% {
|
|
271
|
+
transform: translateY(0);
|
|
272
|
+
}
|
|
273
|
+
50% {
|
|
274
|
+
transform: translateY(-6px);
|
|
275
|
+
}
|
|
276
|
+
100% {
|
|
277
|
+
transform: translateY(0);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@keyframes ds-halogen-pulse {
|
|
282
|
+
0% {
|
|
283
|
+
opacity: 0.5;
|
|
284
|
+
}
|
|
285
|
+
50% {
|
|
286
|
+
opacity: 1;
|
|
287
|
+
}
|
|
288
|
+
100% {
|
|
289
|
+
opacity: 0.5;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@keyframes ds-halogen-sheen {
|
|
294
|
+
to {
|
|
295
|
+
transform: translateX(120%);
|
|
296
|
+
}
|
|
297
|
+
from {
|
|
298
|
+
transform: translateX(-120%);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@keyframes ds-halogen-expand {
|
|
303
|
+
to {
|
|
304
|
+
opacity: 1;
|
|
305
|
+
transform: scaleX(1);
|
|
306
|
+
}
|
|
307
|
+
from {
|
|
308
|
+
opacity: 0;
|
|
309
|
+
transform: scaleX(0);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@keyframes ds-halogen-slide-in {
|
|
314
|
+
to {
|
|
315
|
+
opacity: 1;
|
|
316
|
+
transform: translateX(0);
|
|
317
|
+
}
|
|
318
|
+
from {
|
|
319
|
+
opacity: 0;
|
|
320
|
+
transform: translateX(-10px);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@layer base {
|
|
325
|
+
:root {
|
|
326
|
+
--animate-rise: ds-halogen-rise var(--ds-motion-durations-base, 200ms) var(--ds-motion-easings-standard, ease) both;
|
|
327
|
+
--animate-blink: ds-halogen-blink var(--ds-motion-durations-slow, 400ms) var(--ds-motion-easings-standard, ease);
|
|
328
|
+
--animate-float: ds-halogen-float var(--ds-motion-durations-ambient, 2s) var(--ds-motion-easings-gentle, ease-in-out) infinite;
|
|
329
|
+
--animate-pulse: ds-halogen-pulse var(--ds-motion-durations-ambient, 2s) var(--ds-motion-easings-gentle, ease-in-out) infinite;
|
|
330
|
+
--animate-sheen: ds-halogen-sheen var(--ds-motion-durations-ambient, 2s) var(--ds-motion-easings-gentle, ease-in-out) infinite;
|
|
331
|
+
--animate-expand: ds-halogen-expand var(--ds-motion-durations-base, 200ms) var(--ds-motion-easings-standard, ease) both;
|
|
332
|
+
--animate-slide-in: ds-halogen-slide-in var(--ds-motion-durations-base, 200ms) var(--ds-motion-easings-standard, ease) both;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@layer components {
|
|
337
|
+
:root .ds-card {
|
|
338
|
+
padding: var(--ds-spacing-md);
|
|
339
|
+
box-shadow: var(--ds-shadow-ring);
|
|
340
|
+
transition: box-shadow var(--ds-motion-durations-base) var(--ds-motion-easings-swift);
|
|
341
|
+
border-radius: var(--ds-radius-lg);
|
|
342
|
+
background-color: var(--ds-color-semantic-surface);
|
|
343
|
+
color: inherit;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
:root .ds-card[data-padding="none"] {
|
|
347
|
+
padding: 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
:root .ds-card[data-emphasis="elevated"] {
|
|
351
|
+
box-shadow: var(--ds-shadow-md);
|
|
352
|
+
background-color: var(--ds-color-semantic-raised);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
:root .ds-card:hover {
|
|
356
|
+
box-shadow: var(--ds-shadow-ring), var(--ds-shadow-md);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
:root .ds-card[disabled] {
|
|
360
|
+
cursor: not-allowed;
|
|
361
|
+
opacity: 0.5;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
:root .ds-card[aria-disabled="true"] {
|
|
365
|
+
cursor: not-allowed;
|
|
366
|
+
opacity: 0.5;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
:root .ds-card[data-disabled] {
|
|
370
|
+
cursor: not-allowed;
|
|
371
|
+
opacity: 0.5;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
:root .ds-badge {
|
|
375
|
+
gap: var(--ds-spacing-3xs);
|
|
376
|
+
display: inline-flex;
|
|
377
|
+
padding: var(--ds-spacing-3xs) var(--ds-spacing-2xs);
|
|
378
|
+
font-size: var(--ds-typography-scale-xs-font-size);
|
|
379
|
+
align-items: center;
|
|
380
|
+
font-family: var(--ds-typography-families-mono);
|
|
381
|
+
line-height: var(--ds-typography-scale-xs-line-height);
|
|
382
|
+
border-radius: var(--ds-radius-sm);
|
|
383
|
+
letter-spacing: 0.08em;
|
|
384
|
+
text-transform: uppercase;
|
|
385
|
+
color: inherit;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
:root .ds-badge[data-intent="info"] {
|
|
389
|
+
color: var(--ds-color-semantic-info);
|
|
390
|
+
background-color: var(--ds-color-slate-800);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
:root .ds-badge[data-intent="accent"] {
|
|
394
|
+
color: var(--ds-color-semantic-accent);
|
|
395
|
+
background-color: var(--ds-color-slate-800);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
:root .ds-badge[data-intent="danger"] {
|
|
399
|
+
color: var(--ds-color-semantic-danger);
|
|
400
|
+
background-color: var(--ds-color-slate-800);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
:root .ds-badge[data-intent="neutral"] {
|
|
404
|
+
color: var(--ds-color-semantic-muted);
|
|
405
|
+
background-color: var(--ds-color-slate-800);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
:root .ds-badge[data-intent="success"] {
|
|
409
|
+
color: var(--ds-color-semantic-success);
|
|
410
|
+
background-color: var(--ds-color-slate-800);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
:root .ds-badge[data-intent="warning"] {
|
|
414
|
+
color: var(--ds-color-semantic-warning);
|
|
415
|
+
background-color: var(--ds-color-slate-800);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
:root .ds-input {
|
|
419
|
+
color: var(--ds-color-semantic-foreground);
|
|
420
|
+
width: 100%;
|
|
421
|
+
padding: var(--ds-spacing-2xs) var(--ds-spacing-xs);
|
|
422
|
+
font-size: var(--ds-typography-scale-sm-font-size);
|
|
423
|
+
box-shadow: var(--ds-shadow-ring);
|
|
424
|
+
font-family: var(--ds-typography-families-body);
|
|
425
|
+
line-height: var(--ds-typography-scale-sm-line-height);
|
|
426
|
+
transition: box-shadow var(--ds-motion-durations-fast) var(--ds-motion-easings-swift);
|
|
427
|
+
border-radius: var(--ds-radius-md);
|
|
428
|
+
background-color: var(--ds-color-semantic-canvas);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
:root .ds-input:focus-visible {
|
|
432
|
+
box-shadow: var(--ds-shadow-glow);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
:root .ds-input[disabled] {
|
|
436
|
+
cursor: not-allowed;
|
|
437
|
+
opacity: 0.5;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
:root .ds-input[aria-disabled="true"] {
|
|
441
|
+
cursor: not-allowed;
|
|
442
|
+
opacity: 0.5;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
:root .ds-input[data-disabled] {
|
|
446
|
+
cursor: not-allowed;
|
|
447
|
+
opacity: 0.5;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
:root .ds-avatar {
|
|
451
|
+
color: var(--ds-color-semantic-foreground);
|
|
452
|
+
width: 40px;
|
|
453
|
+
height: 40px;
|
|
454
|
+
display: inline-flex;
|
|
455
|
+
font-size: var(--ds-typography-scale-sm-font-size);
|
|
456
|
+
align-items: center;
|
|
457
|
+
font-family: var(--ds-typography-families-body);
|
|
458
|
+
font-weight: var(--ds-typography-weights-medium);
|
|
459
|
+
line-height: var(--ds-typography-scale-sm-line-height);
|
|
460
|
+
border-radius: var(--ds-radius-full);
|
|
461
|
+
justify-content: center;
|
|
462
|
+
background-color: var(--ds-color-semantic-overlay);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
:root .ds-avatar[data-size="lg"] {
|
|
466
|
+
width: 56px;
|
|
467
|
+
height: 56px;
|
|
468
|
+
font-size: var(--ds-typography-scale-lg-font-size);
|
|
469
|
+
line-height: var(--ds-typography-scale-lg-line-height);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
:root .ds-avatar[data-size="md"] {
|
|
473
|
+
width: 40px;
|
|
474
|
+
height: 40px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
:root .ds-avatar[data-size="sm"] {
|
|
478
|
+
width: 28px;
|
|
479
|
+
height: 28px;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
:root .ds-button {
|
|
483
|
+
gap: var(--ds-spacing-2xs);
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
padding: var(--ds-spacing-2xs) var(--ds-spacing-sm);
|
|
486
|
+
font-size: var(--ds-typography-scale-sm-font-size);
|
|
487
|
+
align-items: center;
|
|
488
|
+
font-family: var(--ds-typography-families-body);
|
|
489
|
+
font-weight: var(--ds-typography-weights-medium);
|
|
490
|
+
line-height: var(--ds-typography-scale-sm-line-height);
|
|
491
|
+
transition: all var(--ds-motion-durations-fast) var(--ds-motion-easings-swift);
|
|
492
|
+
border-radius: var(--ds-radius-md);
|
|
493
|
+
justify-content: center;
|
|
494
|
+
color: inherit;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
:root .ds-button[data-intent="ghost"] {
|
|
498
|
+
color: var(--ds-color-semantic-muted);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
:root .ds-button[data-intent="primary"] {
|
|
502
|
+
color: var(--ds-color-semantic-canvas);
|
|
503
|
+
box-shadow: var(--ds-shadow-glow);
|
|
504
|
+
background-color: var(--ds-color-semantic-primary);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
:root .ds-button[data-intent="secondary"] {
|
|
508
|
+
color: var(--ds-color-semantic-foreground);
|
|
509
|
+
box-shadow: var(--ds-shadow-ring);
|
|
510
|
+
background-color: var(--ds-color-slate-800);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
:root .ds-button:hover {
|
|
514
|
+
filter: brightness(1.12);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
:root .ds-button:active {
|
|
518
|
+
transform: translateY(1px);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
:root .ds-button[disabled] {
|
|
522
|
+
cursor: not-allowed;
|
|
523
|
+
opacity: 0.5;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
:root .ds-button[aria-disabled="true"] {
|
|
527
|
+
cursor: not-allowed;
|
|
528
|
+
opacity: 0.5;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
:root .ds-button[data-disabled] {
|
|
532
|
+
cursor: not-allowed;
|
|
533
|
+
opacity: 0.5;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
:root .ds-button:focus-visible {
|
|
537
|
+
outline: 2px solid var(--ds-color-semantic-primary);
|
|
538
|
+
outline-offset: 2px;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
:root .ds-select {
|
|
542
|
+
gap: var(--ds-spacing-2xs);
|
|
543
|
+
color: var(--ds-color-semantic-foreground);
|
|
544
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
545
|
+
display: inline-flex;
|
|
546
|
+
padding: var(--ds-spacing-2xs) var(--ds-spacing-xs);
|
|
547
|
+
font-size: var(--ds-typography-scale-sm-font-size);
|
|
548
|
+
align-items: center;
|
|
549
|
+
font-family: var(--ds-typography-families-body);
|
|
550
|
+
line-height: var(--ds-typography-scale-sm-line-height);
|
|
551
|
+
transition: border-color var(--ds-motion-durations-fast) var(--ds-motion-easings-standard);
|
|
552
|
+
border-radius: var(--ds-radius-md);
|
|
553
|
+
justify-content: space-between;
|
|
554
|
+
background-color: var(--ds-color-semantic-surface);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
:root .ds-select:focus-visible {
|
|
558
|
+
border-color: var(--ds-color-semantic-primary);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
:root .ds-select[disabled] {
|
|
562
|
+
cursor: not-allowed;
|
|
563
|
+
opacity: 0.5;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
:root .ds-select[aria-disabled="true"] {
|
|
567
|
+
cursor: not-allowed;
|
|
568
|
+
opacity: 0.5;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
:root .ds-select[data-disabled] {
|
|
572
|
+
cursor: not-allowed;
|
|
573
|
+
opacity: 0.5;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
:root .ds-command-bar {
|
|
577
|
+
gap: var(--ds-spacing-2xs);
|
|
578
|
+
color: var(--ds-color-semantic-muted);
|
|
579
|
+
width: 100%;
|
|
580
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
581
|
+
display: flex;
|
|
582
|
+
padding: var(--ds-spacing-2xs) var(--ds-spacing-sm);
|
|
583
|
+
font-size: var(--ds-typography-scale-sm-font-size);
|
|
584
|
+
box-shadow: var(--ds-shadow-ring);
|
|
585
|
+
align-items: center;
|
|
586
|
+
font-family: var(--ds-typography-families-mono);
|
|
587
|
+
line-height: var(--ds-typography-scale-sm-line-height);
|
|
588
|
+
border-radius: var(--ds-radius-md);
|
|
589
|
+
background-color: var(--ds-color-semantic-surface);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
:root .ds-command-bar:focus-visible {
|
|
593
|
+
box-shadow: var(--ds-shadow-glow);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
:root .ds-command-bar[disabled] {
|
|
597
|
+
cursor: not-allowed;
|
|
598
|
+
opacity: 0.5;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
:root .ds-command-bar[aria-disabled="true"] {
|
|
602
|
+
cursor: not-allowed;
|
|
603
|
+
opacity: 0.5;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
:root .ds-command-bar[data-disabled] {
|
|
607
|
+
cursor: not-allowed;
|
|
608
|
+
opacity: 0.5;
|
|
609
|
+
}
|
|
610
|
+
}`;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O QUE O TERMINAL TERIA IMPRIMIDO, GUARDADO EM VEZ DE MOSTRADO.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA: a saída do `inspect` volta a caber numa tela, e nada do que ela dizia se
|
|
5
|
+
* perde - o texto inteiro vai para o relatório que ele abre quando quiser.
|
|
6
|
+
*
|
|
7
|
+
* POR QUE AQUI E NÃO EM CADA IMPRESSOR: são dezessete seções escritas em dezessete lugares, e a
|
|
8
|
+
* leitura delas é a mesma coisa que a esteira já sabe dizer. Reescrevê-las num gerador de HTML
|
|
9
|
+
* criaria duas redações da mesma medição - e no dia em que uma fosse editada, o relatório passaria
|
|
10
|
+
* a afirmar algo que o terminal não afirma mais. Capturando, existe UMA fonte: o que a esteira diz.
|
|
11
|
+
*
|
|
12
|
+
* ELE É DELIBERADAMENTE BURRO: liga, guarda, desliga. Sem filtro, sem formatação e sem saber o que
|
|
13
|
+
* é seção - quem entende do assunto é quem transforma isto em documento.
|
|
14
|
+
*/
|
|
15
|
+
let guardadas = null;
|
|
16
|
+
let originalLog = null;
|
|
17
|
+
/**
|
|
18
|
+
* A REDE DE SEGURANÇA, e ela existe por um motivo específico: uma medição que lança no meio
|
|
19
|
+
* deixaria o `console.log` desviado pelo resto do processo, e a pessoa veria o comando morrer sem
|
|
20
|
+
* uma linha de explicação. O terminal volta ao normal mesmo quando ninguém se lembra de desligar.
|
|
21
|
+
*/
|
|
22
|
+
process.once("exit", () => {
|
|
23
|
+
if (originalLog)
|
|
24
|
+
console.log = originalLog;
|
|
25
|
+
});
|
|
26
|
+
/** Começa a guardar em vez de imprimir. Chamar duas vezes não empilha - a segunda é um no-op. */
|
|
27
|
+
export function ligarCaptura() {
|
|
28
|
+
if (guardadas)
|
|
29
|
+
return;
|
|
30
|
+
guardadas = [];
|
|
31
|
+
originalLog = console.log;
|
|
32
|
+
console.log = (...args) => {
|
|
33
|
+
guardadas?.push(args.map((a) => (typeof a === "string" ? a : String(a))).join(" "));
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Devolve o terminal a quem é dele, e entrega o que foi guardado. */
|
|
37
|
+
export function desligarCaptura() {
|
|
38
|
+
const linhas = guardadas ?? [];
|
|
39
|
+
if (originalLog)
|
|
40
|
+
console.log = originalLog;
|
|
41
|
+
guardadas = null;
|
|
42
|
+
originalLog = null;
|
|
43
|
+
return linhas;
|
|
44
|
+
}
|
|
45
|
+
/** O caractere de escape ANSI, montado por código para não viajar cru no arquivo. */
|
|
46
|
+
const ESC = String.fromCharCode(27);
|
|
47
|
+
/** Tira a cor: o que vai para um arquivo não carrega sequência de escape de terminal. */
|
|
48
|
+
export function semCor(line) {
|
|
49
|
+
return line.split(new RegExp(`${ESC}\\[[0-9;]*m`)).join("");
|
|
50
|
+
}
|
package/package.json
CHANGED