synthesisui 0.16.505 → 0.16.506

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.
@@ -37,7 +37,14 @@ const clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
37
37
  const hex2 = (n) => clamp255(n).toString(16).padStart(2, "0");
38
38
  /** `#f0a` -> `#ff00aa`, e `#ff00aa80` devolve o alfa junto. */
39
39
  function fromHex(raw) {
40
- const h = raw.trim().replace(/^#/, "");
40
+ /**
41
+ * O `#` É OBRIGATÓRIO: sem ele, um peso de fonte (`300`) é hex de três dígitos e vira `#330000`. Medido em 22/10 no
42
+ * sistema lido de stripe.com: 30 variáveis `--hds-font-*-weight` entraram como cores escuras. Em CSS, hex sem `#`
43
+ * não é cor.
44
+ */
45
+ if (!raw.trim().startsWith("#"))
46
+ return null;
47
+ const h = raw.trim().slice(1);
41
48
  if (!/^[0-9a-f]{3,8}$/i.test(h))
42
49
  return null;
43
50
  const dup = (s) => s
@@ -592,7 +592,7 @@ export const COUNTED_DIFFERENTLY = "this run counts a value as named only when Y
592
592
  * E (E02, no mesmo PR) guarda o tamanho de título sem papel para a próxima leitura perguntar, e cobra o papel que a pessoa
593
593
  * escolheu no "não" (`decided` no rules.json): um hook pinado antes diz "papel novo" uma vez e nunca cobra.
594
594
  */
595
- export const CHECKER_SINCE = "0.16.504";
595
+ export const CHECKER_SINCE = "0.16.506";
596
596
  /**
597
597
  * A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
598
598
  *
@@ -16,13 +16,19 @@ export const PAGE_SCRIPT = String.raw `(() => {
16
16
  const cs = getComputedStyle(e);
17
17
  if (cs.backgroundImage && cs.backgroundImage !== "none") img = img || /url\(|gradient/.test(cs.backgroundImage);
18
18
  const bg = parse(cs.backgroundColor); if (bg && bg.a > 0) { camadas.push(bg); if (bg.a >= 1) break; }
19
- if (e.parentElement && [...e.parentElement.children].some((s) => s !== e && /^(VIDEO|IMG)$/.test(s.tagName) && getComputedStyle(s).position === "absolute")) img = true;
19
+ if (e.parentElement && [...e.parentElement.children].some((s) => s !== e && /^(VIDEO|IMG|CANVAS|svg)$/.test(s.tagName) && getComputedStyle(s).position === "absolute")) img = true;
20
20
  }
21
21
  let cor = { r: 255, g: 255, b: 255, a: 1 };
22
22
  for (let i = camadas.length - 1; i >= 0; i--) cor = mix(camadas[i], cor);
23
23
  return { cor, img };
24
24
  };
25
25
  const secao = (el) => { for (let e = el; e; e = e.parentElement) { if (/^(SECTION|HEADER|FOOTER|NAV|MAIN|ASIDE|ARTICLE|DIALOG)$/.test(e.tagName) || e.id) return e.tagName.toLowerCase() + (e.id ? "#" + e.id : ""); } return "body"; };
26
+ /* O QUE MISTURA O QUE ESTÁ EMBAIXO, medido uma vez: em stripe.com (22/10) o título é desenhado duas vezes, uma cópia
27
+ verde opaca e, por cima, a mesma frase com mix-blend-mode: hard-light - a tela mostra a mistura, e a cópia de baixo
28
+ sozinha nunca aparece. Um texto debaixo de qualquer um destes (ancestral ou camada por cima) tem pintura que o
29
+ estilo não conta. O desfoque de fundo (backdrop-filter) só conta POR CIMA do texto: no cartão de vidro que o
30
+ contém, ele desfoca o que está atrás do cartão, e o texto segue com a cor dele (medido em linear.app, 22/10). */
31
+ const misturadores = [...document.querySelectorAll("body *")].filter((e) => { const c = getComputedStyle(e); return c.mixBlendMode !== "normal" || (c.backdropFilter && c.backdropFilter !== "none") || c.webkitBackgroundClip === "text" || c.backgroundClip === "text"; }).slice(0, 200).map((e) => { const q = e.getBoundingClientRect(); const c = getComputedStyle(e); return { e, so: !(c.mixBlendMode !== "normal" || c.webkitBackgroundClip === "text" || c.backgroundClip === "text"), x: q.left + scrollX, y: q.top + scrollY, w: q.width, h: q.height }; });
26
32
  const textos = [];
27
33
  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
28
34
  const vistos = new Set();
@@ -49,12 +55,23 @@ export const PAGE_SCRIPT = String.raw `(() => {
49
55
  }
50
56
  let op = 1; for (let e = el; e; e = e.parentElement) op *= Number(getComputedStyle(e).opacity);
51
57
  const cor = parse(cs.color); if (cor) cor.a *= op;
58
+ /* A PINTURA QUE O ESTILO NÃO CONTA (22/10, stripe.com): o título é rgba(0, 14, 255, .5) com mix-blend-mode:
59
+ hard-light sobre um degradê animado - a cor na tela é a mistura, e nenhum "color" a revela. Texto misturado,
60
+ recortado no fundo (background-clip: text), sem tinta própria ou sob filtro: só a foto diz o contraste. */
61
+ let mistura = false;
62
+ for (let e = el; e && e !== document.documentElement && !mistura; e = e.parentElement) {
63
+ const c = getComputedStyle(e);
64
+ if (c.filter && c.filter !== "none") mistura = true;
65
+ }
66
+ const cx = r.left + scrollX + r.width / 2, cy = r.top + scrollY + r.height / 2;
67
+ if (!mistura) mistura = misturadores.some((m) => (m.e === el || !el.contains(m.e)) && !(m.so && m.e.contains(el)) && cx >= m.x && cx <= m.x + m.w && cy >= m.y && cy <= m.y + m.h);
68
+ if (/^rgba\([^)]*,\s*0\)$|^transparent$/.test(cs.webkitTextFillColor || "")) mistura = true;
52
69
  const f = fundo(el);
53
70
  textos.push({
54
71
  texto: txt.slice(0, 60), tag: el.tagName.toLowerCase(),
55
72
  papel: (el.closest("h1,h2,h3,h4,h5,h6,button,a,label,th,figcaption,blockquote,input") || { tagName: "" }).tagName.toLowerCase() || null,
56
73
  tam: parseFloat(cs.fontSize), familia: cs.fontFamily.split(",")[0].replace(/["']/g, "").trim(), peso: Number(cs.fontWeight),
57
- cor, fundo: f.cor, sobreImagem: f.img, decorativo: !!el.closest("[aria-hidden=true]"),
74
+ cor, fundo: f.cor, sobreImagem: f.img, mistura, decorativo: !!el.closest("[aria-hidden=true]"),
58
75
  cartao, corrido, giro, enfase, comp,
59
76
  cls: [el, ...(tit && tit !== el ? [tit] : []), el.parentElement].filter(Boolean).map((x) => x.getAttribute("class") || "").join(" | ").slice(0, 600),
60
77
  secao: secao(el), x: r.left + scrollX, y: r.top + scrollY, w: r.width, h: r.height,
@@ -78,12 +78,34 @@ export async function readSite(browser, base, routes, opts = {}) {
78
78
  };
79
79
  try {
80
80
  for (const rota of routes) {
81
- const byWidth = await Promise.all(VIEWPORTS.map(([disp, w, h]) => open(rota, disp, w, h)));
81
+ const timed = async (disp, w, h) => {
82
+ const t = performance.now();
83
+ const textos = await open(rota, disp, w, h);
84
+ opts.onWidth?.(disp, Math.round(performance.now() - t));
85
+ return textos;
86
+ };
87
+ const byWidth = [];
88
+ if (opts.oneAtATime)
89
+ for (const [disp, w, h] of VIEWPORTS)
90
+ byWidth.push(await timed(disp, w, h));
91
+ else
92
+ byWidth.push(...(await Promise.all(VIEWPORTS.map(([disp, w, h]) => timed(disp, w, h)))));
82
93
  for (const textos of byWidth)
83
94
  seen.push(...textos);
84
95
  }
85
96
  const reading = classify(seen);
86
- reading.contraste = await confirmByPixels(reading.contraste, (t) => pages.get(`${t.rota}|${t.disp}`));
97
+ /**
98
+ * Sem a foto, o par cuja pintura o estilo não conta (`mistura`) sai: o número dele seria da cor declarada, e não da
99
+ * que se vê. Medido em 22/10 em stripe.com - o título acusado a 2,39 : 1 era um azul translúcido misturado sobre um
100
+ * degradê, e o "depois" o pintou de verde. Com a foto (o CLI), quem decide é o pixel, como sempre.
101
+ */
102
+ if (opts.skipPixels)
103
+ reading.contraste = reading.contraste.filter((c) => c.razao >= 1.2 && !c.sample.mistura);
104
+ else {
105
+ const t = performance.now();
106
+ reading.contraste = await confirmByPixels(reading.contraste, (t) => pages.get(`${t.rota}|${t.disp}`));
107
+ opts.onWidth?.("pixels", Math.round(performance.now() - t));
108
+ }
87
109
  return { reading, seen, pages, statuses, close };
88
110
  }
89
111
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.505",
3
+ "version": "0.16.506",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {