@aurea-uds/core 0.27.0 → 0.28.0

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/aurea.css CHANGED
@@ -5,6 +5,7 @@
5
5
  --brand-yellow-foreground:oklch(0.421 0.095 57.708);
6
6
  --brand-yellow-text:oklch(0.516 0.105 86.047);
7
7
  --font-ui:"Atkinson Hyperlegible Next","Segoe UI",sans-serif;
8
+ --font-heading:"Atkinson Hyperlegible Next","Segoe UI",sans-serif;
8
9
  --font-editorial:"Atkinson Hyperlegible Next","Segoe UI",sans-serif;
9
10
  --font-code:"Atkinson Hyperlegible Mono",SFMono-Regular,Consolas,monospace;
10
11
  --text-xs:0.75rem;
@@ -117,6 +118,7 @@
117
118
  --weight-medium:500;
118
119
  --weight-semibold:600;
119
120
  --weight-bold:700;
121
+ --weight-extrabold:800;
120
122
  --icon-sm:1rem;
121
123
  --icon-md:1.25rem;
122
124
  --icon-lg:1.5rem;
@@ -1233,7 +1235,11 @@ img,svg { display:block; }
1233
1235
  /* A base do botão carrega as VARIÁVEIS DE PASSO da camada responsiva (`--step-*`), e cada
1234
1236
  fallback é exatamente o valor fixo de antes — então o botão sem valor responsivo desenha o
1235
1237
  mesmo pixel. É o que permite a camada `size-*` repor a grandeza sem tocar no componente. */
1236
- .btn { --btn-bg:transparent; --btn-fg:var(--foreground); --btn-border:transparent; display:inline-flex; align-items:center; justify-content:center; gap:var(--step-gap,8px); block-size:var(--step-h,var(--control-h-md)); padding-inline:var(--step-px,15px); border:1px solid var(--btn-border); border-radius:var(--radius-control); background:var(--btn-bg); color:var(--btn-fg); font-size:var(--step-fs,var(--text-sm)); font-weight:var(--weight-medium); line-height:var(--leading-none); text-decoration:none; white-space:nowrap; cursor:pointer; transition:transform var(--duration-fast) var(--ease-standard),background var(--duration-fast),border-color var(--duration-fast),color var(--duration-fast),opacity var(--duration-fast); }
1238
+ .btn { --btn-bg:transparent; --btn-fg:var(--foreground); --btn-border:transparent; display:inline-flex; align-items:center; justify-content:center; gap:var(--step-gap,8px); block-size:var(--step-h,var(--control-h-md)); border:1px solid var(--btn-border); border-radius:var(--radius-control); background:var(--btn-bg); color:var(--btn-fg); font-size:var(--step-fs,var(--text-sm)); font-weight:var(--weight-medium); line-height:var(--leading-none); text-decoration:none; white-space:nowrap; cursor:pointer; transition:transform var(--duration-fast) var(--ease-standard),background var(--duration-fast),border-color var(--duration-fast),color var(--duration-fast),opacity var(--duration-fast); }
1239
+ /* GAR-14 (0.28.0): o recheio do botão vale também para o aviso preso a ele (`.toast-anchored`),
1240
+ que tem a MEDIDA do botão — pedido do Victor na bancada. Uma regra só, para os dois nunca
1241
+ divergirem e para o número não se repetir. */
1242
+ .btn,.toast-anchored { padding-inline:var(--step-px,15px); }
1237
1243
  .btn:hover:not(:disabled):not([aria-disabled="true"]) { transform:none; } .btn:active:not(:disabled) { transform:scale(.97); opacity:.9; }
1238
1244
  /* Duas formas de "não dá para fazer isto", e a diferença é QUEM alcança a explicação.
1239
1245
  `:disabled` tira o controle da ordem de foco — medido em 13/08/2026: quem usa teclado nunca
@@ -1479,7 +1485,7 @@ img,svg { display:block; }
1479
1485
  Largura em token para caber de 3 a 4 dígitos sem virar caixa de texto. */
1480
1486
  .number-field { display:inline-flex; }
1481
1487
  .number-field-group { display:inline-flex; align-items:center; gap:var(--space-1); }
1482
- .number-field-input { width:var(--space-16); text-align:center; }
1488
+ .number-field-input { width:var(--space-16); text-align:center; font-variant-numeric:tabular-nums; }
1483
1489
  /* A ALÇA DE ARRASTO (G-API-02), e só quando `scrubbable` está ligada.
1484
1490
  O motor já põe por ESTILO EM LINHA tudo o que é mecânica — `touch-action:none` e
1485
1491
  `user-select:none` na alça, e `position:fixed` mais o `transform` no cursor virtual, que ele
@@ -2044,7 +2050,9 @@ img,svg { display:block; }
2044
2050
  `tabIndex=0` (região rolável precisa ser alcançável pelo teclado) e o `.table-wrap` não é. */
2045
2051
  .table-wrap,.table-region { overflow:auto; border:1px solid var(--border); border-radius:var(--radius-card); background:var(--card); }
2046
2052
  .table,.table-wrap table { width:100%; border-collapse:collapse; min-width:720px; }
2047
- .table th,.table td,.table-wrap th,.table-wrap td { height:var(--row-h); padding:0 14px; text-align:start; border-bottom:1px solid var(--border); }
2053
+ /* GAR-16 (0.28.0): a tabela inteira com algarismos da mesma largura — a coluna de preço e de ano
2054
+ alinha. Só os algarismos mudam; o texto das outras colunas fica igual. */
2055
+ .table th,.table td,.table-wrap th,.table-wrap td { height:var(--row-h); padding:0 14px; text-align:start; border-bottom:1px solid var(--border); font-variant-numeric:tabular-nums; }
2048
2056
  .table th,.table-wrap th { color:var(--subtle-foreground); background:var(--surface-2); font-size:var(--text-xs); letter-spacing:var(--tracking-wider); text-transform:uppercase; }
2049
2057
  .table tr:last-child td,.table-wrap tr:last-child td { border-bottom:0; }
2050
2058
  .table tbody tr:hover,.table-wrap tbody tr:hover { background:var(--surface-2); }
@@ -2393,6 +2401,18 @@ img,svg { display:block; }
2393
2401
  .image { display:block; width:100%; height:auto; object-fit:cover; border-radius:var(--radius-lg); background:var(--surface-3); }
2394
2402
  .image-contain { object-fit:contain; background:transparent; }
2395
2403
  .image-broken { display:grid; place-items:center; color:var(--muted-foreground); }
2404
+ /* GAR-15 (0.28.0): a moldura de celular do `Image frame="phone"`. GENÉRICA de propósito — sem ilha,
2405
+ sem entalhe, sem botões: as regras de marketing de um fabricante proíbem simular o aparelho dele,
2406
+ e a Aurea não leva marca de terceiro. A moldura segue o tema (a superfície do cartão, a borda e a
2407
+ sombra de sempre) e o canto é o da folha de baixo, o maior que a Aurea tem; o de dentro acompanha. */
2408
+ .image-frame { display:block; }
2409
+ .image-frame-phone { padding:var(--space-2); border:var(--border-width) solid var(--border); border-radius:var(--radius-sheet); background:var(--card); box-shadow:var(--shadow-md); }
2410
+ .image-frame-phone > .image { aspect-ratio:9 / 19.5; object-position:top; border-radius:calc(var(--radius-sheet) - var(--space-2)); }
2411
+ /* A captura de cada tema (`srcDark`). Sem `data-theme` na página, vale a clara. A guarda é a do
2412
+ GAR-05 espelhada: uma faixa CLARA dentro da página escura volta a mostrar a captura clara. */
2413
+ .image-theme-dark { display:none; }
2414
+ [data-theme="dark"] .image-theme-dark:where(:not([data-theme="light"] *),[data-theme="light"] [data-theme="dark"] *) { display:block; }
2415
+ [data-theme="dark"] .image-theme-light:where(:not([data-theme="light"] *),[data-theme="light"] [data-theme="dark"] *) { display:none; }
2396
2416
 
2397
2417
  /* VÍDEO INCORPORADO (achado N-10, 24/09/2026). A caixa é a mesma superfície do `.media-player` —
2398
2418
  raio de cartão, borda e o fundo `--media-canvas` —, então um trailer do YouTube e um vídeo
@@ -2458,7 +2478,7 @@ img,svg { display:block; }
2458
2478
  A margem vai a zero porque `h1`…`h6` e `p` trazem a do navegador; o espaço entre blocos é de
2459
2479
  quem os empilha (`Stack`), não do texto. */
2460
2480
  .typography { margin:0; color:var(--foreground); }
2461
- .typography-h1,.typography-h2,.typography-h3,.typography-h4,.typography-h5,.typography-h6 { font-weight:var(--weight-semibold); line-height:var(--leading-tight); letter-spacing:var(--tracking-tight); }
2481
+ .typography-h1,.typography-h2,.typography-h3,.typography-h4,.typography-h5,.typography-h6 { font-family:var(--font-heading); font-weight:var(--weight-semibold); line-height:var(--leading-tight); letter-spacing:var(--tracking-tight); }
2462
2482
  .typography-h1 { font-size:var(--text-4xl); }
2463
2483
  .typography-h2 { font-size:var(--text-3xl); }
2464
2484
  .typography-h3 { font-size:var(--text-2xl); }
@@ -2474,6 +2494,10 @@ img,svg { display:block; }
2474
2494
  .typography-weight-medium { font-weight:var(--weight-medium); }
2475
2495
  .typography-weight-semibold { font-weight:var(--weight-semibold); }
2476
2496
  .typography-weight-bold { font-weight:var(--weight-bold); }
2497
+ .typography-weight-extrabold { font-weight:var(--weight-extrabold); }
2498
+ /* GAR-16 (0.28.0): algarismos da mesma largura, para número que alinha em coluna ou muda na tela.
2499
+ Ligado só onde se pede — no texto corrido o "1" estreito é o certo. */
2500
+ .typography-numeric { font-variant-numeric:tabular-nums; }
2477
2501
  .typography-align-start { text-align:start; }
2478
2502
  .typography-align-center { text-align:center; }
2479
2503
  .typography-align-end { text-align:end; }
@@ -2627,6 +2651,20 @@ img,svg { display:block; }
2627
2651
  .toast-success { border-color:color-mix(in srgb,var(--success-400) 38%,var(--border)); background:var(--success-bg); }
2628
2652
  .toast-warning { border-color:color-mix(in srgb,var(--warning-400) 38%,var(--border)); background:var(--warning-bg); }
2629
2653
  .toast-danger { border-color:color-mix(in srgb,var(--danger-400) 38%,var(--border)); background:var(--danger-bg); }
2654
+ /* GAR-14 (0.28.0): o aviso do `Button share`, preso ao botão — "Link copiado", ou o erro com o link à
2655
+ mostra. Tem a medida do botão (a altura, o recheio da regra de cima, a letra e a cápsula) e a pele
2656
+ do aviso de sempre. O ícone é desenho de máscara, como a seta do `KPI`: a mesma marcação serve ao
2657
+ React (o motor posiciona) e ao HTML puro (o `aurea.js` posiciona, com `.toast-anchored-fixed`). */
2658
+ .toast-anchored { display:inline-flex; align-items:center; gap:var(--space-2); min-block-size:var(--step-h,var(--control-h-md)); max-inline-size:calc(100vw - 2 * var(--space-4)); padding-block:var(--space-1); border:var(--border-width) solid var(--border); border-radius:var(--radius-control); background:var(--popover); color:var(--popover-foreground); box-shadow:var(--shadow-md); font-size:var(--step-fs,var(--text-sm)); font-weight:var(--weight-medium); line-height:var(--leading-tight); transition:opacity var(--duration-fast) var(--ease-standard); }
2659
+ .toast-anchored[data-starting-style],.toast-anchored[data-ending-style] { opacity:0; }
2660
+ .toast-anchored-icon { --toast-glifo:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E"); flex:none; inline-size:var(--icon-sm); block-size:var(--icon-sm); background:currentColor; color:var(--success-400); -webkit-mask:var(--toast-glifo) center/contain no-repeat; mask:var(--toast-glifo) center/contain no-repeat; }
2661
+ .toast-anchored-danger { border-color:color-mix(in srgb,var(--danger-400) 38%,var(--border)); background:var(--danger-bg); }
2662
+ .toast-anchored-danger .toast-anchored-icon { --toast-glifo:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z'/%3E%3C/svg%3E"); color:var(--danger-400); }
2663
+ /* O erro leva o link logo abaixo do aviso, já selecionado, para a pessoa copiar com o teclado. */
2664
+ .toast-anchored-stack { display:flex; flex-direction:column; align-items:flex-start; gap:var(--space-2); inline-size:max-content; max-inline-size:calc(100vw - 2 * var(--space-4)); }
2665
+ .toast-anchored-link { inline-size:100%; }
2666
+ .toast-anchored-fixed { position:fixed; z-index:var(--z-toast); }
2667
+ .toast-anchored-positioner { z-index:var(--z-toast); }
2630
2668
  /* CommandPaletteShell (Fase 11, achado A13). A superfície já existia aqui sob o nome
2631
2669
  `.command` — e `.command` é chrome da página de docs manual: nenhum componente React a
2632
2670
  emite. Ela passava no check 15 por ACIDENTE, porque o rótulo padrão "Type a command…" põe
package/dist/aurea.js CHANGED
@@ -47,15 +47,48 @@
47
47
  activateTab(tabs[next]);
48
48
  });
49
49
 
50
+ // O texto padrão dos avisos que este script escreve sozinho, pela língua da página. Quem emite a
51
+ // marcação (o React, o catálogo) pode mandar o seu pelo atributo `data-aurea-*` de cada gatilho.
52
+ var PT = /^pt/i.test(root.lang || "");
53
+ var TEXTO = PT
54
+ ? {copied: "Copiado", linkCopied: "Link copiado", linkCopyFailed: "Não deu para copiar. Copie o link abaixo."}
55
+ : {copied: "Copied", linkCopied: "Link copied", linkCopyFailed: "Could not copy. Copy the link below."};
56
+
57
+ // GAR-14 (0.28.0) · UMA região de status, montada cedo, para os avisos que não têm outra voz.
58
+ // Região que nasce junto com o texto nem sempre é lida; a que já existe, é (WCAG 4.1.3, a
59
+ // mensagem de status). Para repetir o mesmo texto, ela é esvaziada e preenchida de novo um
60
+ // instante depois.
61
+ var regiao = null;
62
+ function regiaoDeStatus() {
63
+ if (regiao && regiao.isConnected) return regiao;
64
+ regiao = document.createElement("div");
65
+ regiao.className = "sr-only";
66
+ regiao.setAttribute("role", "status");
67
+ regiao.setAttribute("aria-live", "polite");
68
+ regiao.setAttribute("data-aurea-status", "");
69
+ document.body.appendChild(regiao);
70
+ return regiao;
71
+ }
72
+ function anunciar(texto) {
73
+ var r = regiaoDeStatus();
74
+ r.textContent = "";
75
+ setTimeout(function () { r.textContent = texto; }, 100);
76
+ }
77
+ if (document.body) regiaoDeStatus();
78
+ else document.addEventListener("DOMContentLoaded", regiaoDeStatus);
79
+
50
80
  // Copiar código: comportamento do CORE, não do catálogo. Quem marca o gatilho com
51
81
  // [data-aurea-copy] ganha o copiar de graça — o CodeBlock do React emite esse mesmo
52
82
  // markup. Sem permissão de área de transferência, seleciona o texto pro Ctrl+C.
83
+ // 0.28.0: o "copiado" também é dito ao leitor de tela — antes só o ícone mudava, e quem não vê
84
+ // não sabia se tinha copiado.
53
85
  function copy(trigger) {
54
86
  var scope = trigger.closest("[data-aurea-copy-scope]") || trigger.parentElement;
55
87
  var source = scope && scope.querySelector("code, pre, [data-aurea-copy-source]");
56
88
  if (!source) return;
57
89
  function done() {
58
90
  trigger.classList.add("is-done");
91
+ anunciar(trigger.getAttribute("data-aurea-copy-done") || TEXTO.copied);
59
92
  setTimeout(function () { trigger.classList.remove("is-done"); }, 1500);
60
93
  }
61
94
  function select() {
@@ -75,6 +108,121 @@
75
108
  if (trigger) copy(trigger);
76
109
  });
77
110
 
111
+ // GAR-14 (0.28.0) · COMPARTILHAR. O gatilho marcado com [data-aurea-share] (o `Button share` do
112
+ // React emite isto) pergunta ao navegador se ele sabe compartilhar ESTE link — detectar o recurso,
113
+ // e não adivinhar o aparelho, é o padrão da web. Sabe: abre a janela do aparelho; a pessoa
114
+ // cancelar (`AbortError`) não é erro e não diz nada. Não sabe: copia o link e mostra "Link
115
+ // copiado" preso ao botão. A cópia falhou: avisa e mostra o link selecionado, para o Ctrl+C.
116
+ // O aviso tem a medida do botão (`.toast-anchored`, no aurea.css).
117
+ var avisoAberto = null;
118
+ function fecharAviso(devolverFoco) {
119
+ if (!avisoAberto) return;
120
+ var a = avisoAberto;
121
+ avisoAberto = null;
122
+ clearTimeout(a.timer);
123
+ window.removeEventListener("scroll", a.fechar, true);
124
+ window.removeEventListener("resize", a.fechar);
125
+ document.removeEventListener("keydown", a.tecla, true);
126
+ document.removeEventListener("pointerdown", a.fora, true);
127
+ a.el.remove();
128
+ if (devolverFoco && a.gatilho.isConnected) a.gatilho.focus();
129
+ }
130
+ function posicionar(el, gatilho) {
131
+ var r = gatilho.getBoundingClientRect();
132
+ // A distância é o token, lido da página — não um número escrito aqui.
133
+ var rem = parseFloat(getComputedStyle(root).fontSize) || 16;
134
+ var espaco = (parseFloat(getComputedStyle(root).getPropertyValue("--space-2")) || 0) * rem;
135
+ el.style.top = (r.bottom + espaco) + "px";
136
+ var largura = el.getBoundingClientRect().width;
137
+ var rtl = getComputedStyle(gatilho).direction === "rtl";
138
+ var x = rtl ? r.right - largura : r.left;
139
+ el.style.left = Math.max(espaco, Math.min(x, window.innerWidth - largura - espaco)) + "px";
140
+ }
141
+ function avisoAncorado(gatilho, falhou, texto, url) {
142
+ fecharAviso(false);
143
+ var aviso = document.createElement("div");
144
+ aviso.className = "toast-anchored" + (falhou ? " toast-anchored-danger" : "");
145
+ var icone = document.createElement("span");
146
+ icone.className = "toast-anchored-icon";
147
+ icone.setAttribute("aria-hidden", "true");
148
+ var rotulo = document.createElement("span");
149
+ rotulo.textContent = texto;
150
+ aviso.appendChild(icone);
151
+ aviso.appendChild(rotulo);
152
+ var el = aviso, campo = null;
153
+ if (falhou) {
154
+ el = document.createElement("div");
155
+ el.className = "toast-anchored-stack toast-anchored-fixed";
156
+ el.appendChild(aviso);
157
+ campo = document.createElement("input");
158
+ campo.className = "input toast-anchored-link";
159
+ campo.readOnly = true;
160
+ campo.value = url;
161
+ campo.setAttribute("aria-label", texto);
162
+ el.appendChild(campo);
163
+ } else {
164
+ aviso.className += " toast-anchored-fixed";
165
+ // O texto vai pela região de status; o desenho é só para quem vê, e não fala duas vezes.
166
+ aviso.setAttribute("aria-hidden", "true");
167
+ }
168
+ document.body.appendChild(el);
169
+ posicionar(el, gatilho);
170
+ var estado = {el: el, gatilho: gatilho, timer: 0};
171
+ // Rolar a PÁGINA solta o aviso do botão, e ele fecha. Mas o campo do link rola o próprio texto
172
+ // quando é selecionado — rolagem de dentro do aviso não conta (medido no navegador: sem esta
173
+ // guarda, o aviso de erro fechava no mesmo instante em que abria).
174
+ estado.fechar = function (e) { if (e && e.target && e.target.nodeType === 1 && el.contains(e.target)) return; fecharAviso(false); };
175
+ estado.tecla = function (e) { if (e.key === "Escape") fecharAviso(true); };
176
+ estado.fora = function (e) { if (!el.contains(e.target) && e.target !== gatilho) fecharAviso(false); };
177
+ avisoAberto = estado;
178
+ window.addEventListener("scroll", estado.fechar, true);
179
+ window.addEventListener("resize", estado.fechar);
180
+ if (falhou) {
181
+ // O erro fica até a pessoa resolver: Esc devolve o foco ao botão, clicar fora fecha.
182
+ document.addEventListener("keydown", estado.tecla, true);
183
+ document.addEventListener("pointerdown", estado.fora, true);
184
+ campo.focus();
185
+ campo.select();
186
+ } else {
187
+ estado.timer = setTimeout(estado.fechar, 1500);
188
+ }
189
+ anunciar(texto);
190
+ }
191
+ function share(trigger) {
192
+ var url = trigger.getAttribute("data-aurea-share-url") || location.href;
193
+ var dados = {url: url};
194
+ var titulo = trigger.getAttribute("data-aurea-share-title");
195
+ var texto = trigger.getAttribute("data-aurea-share-text");
196
+ if (titulo) dados.title = titulo;
197
+ if (texto) dados.text = texto;
198
+ var copiado = trigger.getAttribute("data-aurea-share-copied") || TEXTO.linkCopied;
199
+ var falhou = trigger.getAttribute("data-aurea-share-failed") || TEXTO.linkCopyFailed;
200
+ function copiar() {
201
+ if (!navigator.clipboard || !navigator.clipboard.writeText) {
202
+ avisoAncorado(trigger, true, falhou, url);
203
+ return Promise.resolve();
204
+ }
205
+ return navigator.clipboard.writeText(url).then(
206
+ function () { avisoAncorado(trigger, false, copiado); },
207
+ function () { avisoAncorado(trigger, true, falhou, url); });
208
+ }
209
+ if (typeof navigator.share === "function" && (!navigator.canShare || navigator.canShare(dados))) {
210
+ return navigator.share(dados).catch(function (e) {
211
+ if (!e || e.name !== "AbortError") return copiar();
212
+ });
213
+ }
214
+ return copiar();
215
+ }
216
+
217
+ document.addEventListener("click", function (event) {
218
+ // O `Button` do React faz o mesmo no próprio clique e marca o evento com `preventDefault`,
219
+ // para não compartilhar duas vezes quando os dois estão na página.
220
+ if (event.defaultPrevented) return;
221
+ var trigger = event.target.closest && event.target.closest("[data-aurea-share]");
222
+ if (!trigger || trigger.disabled || trigger.getAttribute("aria-disabled") === "true") return;
223
+ share(trigger);
224
+ });
225
+
78
226
  // TableOfContents: marca sozinho a seção em vista. IntersectionObserver com uma faixa
79
227
  // estreita no alto da janela — a seção "atual" é a que cruza essa faixa, não a maior
80
228
  // visível, senão o item pisca entre duas ao rolar. Sem observer (browser antigo), o
@@ -258,6 +406,7 @@
258
406
  root.dataset.theme = root.dataset.theme === "dark" ? "light" : "dark";
259
407
  },
260
408
  showToast: showToast,
261
- copy: copy
409
+ copy: copy,
410
+ share: share
262
411
  };
263
412
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/core",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "O CSS da Aurea UDS e o comportamento em JavaScript puro. Importe @aurea-uds/fonts/css antes deste.",
5
5
  "keywords": [
6
6
  "aurea",