@datatechsolutions/tympan-tokens 0.4.1 → 0.5.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.
Files changed (91) hide show
  1. package/THIRD_PARTY_NOTICES.md +5 -5
  2. package/dist/contrast-report.md +95 -95
  3. package/dist/dtcg/astrlabe.dark-high.tokens.json +99 -43
  4. package/dist/dtcg/astrlabe.dark.tokens.json +137 -81
  5. package/dist/dtcg/astrlabe.light-high.tokens.json +146 -33
  6. package/dist/dtcg/astrlabe.light.tokens.json +211 -98
  7. package/dist/fonts.css +80 -80
  8. package/dist/index.d.ts +257 -58
  9. package/dist/index.js +568 -36
  10. package/dist/index.js.map +1 -1
  11. package/dist/print-themes/print-aquarela.css +7 -7
  12. package/dist/print-themes/print-art-nouveau.css +2 -2
  13. package/dist/print-themes/print-atlas-oficial.css +7 -7
  14. package/dist/print-themes/print-azulejo-modernista.css +2 -2
  15. package/dist/print-themes/print-bauhaus.css +2 -2
  16. package/dist/print-themes/print-blocos-coloridos.css +5 -5
  17. package/dist/print-themes/print-brutalista.css +8 -8
  18. package/dist/print-themes/print-caderno.css +14 -14
  19. package/dist/print-themes/print-cartao-postal.css +5 -5
  20. package/dist/print-themes/print-cientifico.css +3 -3
  21. package/dist/print-themes/print-concretismo.css +8 -8
  22. package/dist/print-themes/print-construtivismo.css +6 -6
  23. package/dist/print-themes/print-cordel.css +6 -6
  24. package/dist/print-themes/print-dados-br.css +12 -12
  25. package/dist/print-themes/print-dashboard.css +18 -18
  26. package/dist/print-themes/print-diagrama-modernista.css +3 -3
  27. package/dist/print-themes/print-divulgacao.css +3 -3
  28. package/dist/print-themes/print-fluxo-historico.css +3 -3
  29. package/dist/print-themes/print-grade-holandesa.css +2 -2
  30. package/dist/print-themes/print-graficos-1900.css +6 -6
  31. package/dist/print-themes/print-infografico-ilustrado.css +6 -6
  32. package/dist/print-themes/print-isotype.css +2 -2
  33. package/dist/print-themes/print-jornal-1959.css +5 -5
  34. package/dist/print-themes/print-jornal.css +6 -6
  35. package/dist/print-themes/print-mapa-de-metro.css +1 -1
  36. package/dist/print-themes/print-memphis.css +5 -5
  37. package/dist/print-themes/print-minimo-de-tinta.css +4 -4
  38. package/dist/print-themes/print-papel-recortado.css +2 -2
  39. package/dist/print-themes/print-papel-salmao.css +6 -6
  40. package/dist/print-themes/print-pictogramas.css +2 -2
  41. package/dist/print-themes/print-pop-art.css +2 -2
  42. package/dist/print-themes/print-prancheta-clara.css +5 -5
  43. package/dist/print-themes/print-prancheta.css +5 -5
  44. package/dist/print-themes/print-proporcao-modular.css +2 -2
  45. package/dist/print-themes/print-riso.css +4 -4
  46. package/dist/print-themes/print-semanario.css +13 -13
  47. package/dist/print-themes/print-sinalizacao.css +8 -8
  48. package/dist/print-themes/print-suico.css +4 -4
  49. package/dist/print-themes/print-tropicalia.css +3 -3
  50. package/dist/print-themes.css +133 -133
  51. package/dist/tokens.css +247 -124
  52. package/dist/tokens.json +124 -66
  53. package/dist/values.js +124 -66
  54. package/fonts/ibmplexsans/ibmplexsans-italic-cyrillic-400_500_600.woff2 +0 -0
  55. package/fonts/ibmplexsans/ibmplexsans-italic-cyrillic-ext-400_500_600.woff2 +0 -0
  56. package/fonts/ibmplexsans/ibmplexsans-italic-greek-400_500_600.woff2 +0 -0
  57. package/fonts/ibmplexsans/ibmplexsans-italic-latin-400_500_600.woff2 +0 -0
  58. package/fonts/ibmplexsans/ibmplexsans-italic-latin-ext-400_500_600.woff2 +0 -0
  59. package/fonts/ibmplexsans/ibmplexsans-italic-vietnamese-400_500_600.woff2 +0 -0
  60. package/fonts/manifest.json +998 -820
  61. package/fonts/nunitosans/nunitosans-normal-cyrillic-400_600_700_800.woff2 +0 -0
  62. package/fonts/nunitosans/nunitosans-normal-cyrillic-ext-400_600_700_800.woff2 +0 -0
  63. package/fonts/nunitosans/nunitosans-normal-latin-400_600_700_800.woff2 +0 -0
  64. package/fonts/nunitosans/nunitosans-normal-latin-ext-400_600_700_800.woff2 +0 -0
  65. package/fonts/nunitosans/nunitosans-normal-vietnamese-400_600_700_800.woff2 +0 -0
  66. package/fonts/sourceserif4/sourceserif4-italic-cyrillic-400_600.woff2 +0 -0
  67. package/fonts/sourceserif4/sourceserif4-italic-cyrillic-ext-400_600.woff2 +0 -0
  68. package/fonts/sourceserif4/sourceserif4-italic-greek-400_600.woff2 +0 -0
  69. package/fonts/sourceserif4/sourceserif4-italic-latin-400_600.woff2 +0 -0
  70. package/fonts/sourceserif4/sourceserif4-italic-latin-ext-400_600.woff2 +0 -0
  71. package/fonts/sourceserif4/sourceserif4-italic-vietnamese-400_600.woff2 +0 -0
  72. package/package.json +1 -1
  73. package/src/fonts.ts +2 -2
  74. package/src/index.ts +1 -0
  75. package/src/presets.ts +115 -16
  76. package/src/print-book-themes.ts +239 -0
  77. package/src/print-presets.ts +220 -10
  78. package/src/stylesheet.ts +5 -0
  79. package/src/theme.ts +35 -5
  80. /package/fonts/ibmplexsans/{ibmplexsans-italic-cyrillic.woff2 → ibmplexsans-italic-cyrillic-400.woff2} +0 -0
  81. /package/fonts/ibmplexsans/{ibmplexsans-italic-cyrillic-ext.woff2 → ibmplexsans-italic-cyrillic-ext-400.woff2} +0 -0
  82. /package/fonts/ibmplexsans/{ibmplexsans-italic-greek.woff2 → ibmplexsans-italic-greek-400.woff2} +0 -0
  83. /package/fonts/ibmplexsans/{ibmplexsans-italic-latin.woff2 → ibmplexsans-italic-latin-400.woff2} +0 -0
  84. /package/fonts/ibmplexsans/{ibmplexsans-italic-latin-ext.woff2 → ibmplexsans-italic-latin-ext-400.woff2} +0 -0
  85. /package/fonts/ibmplexsans/{ibmplexsans-italic-vietnamese.woff2 → ibmplexsans-italic-vietnamese-400.woff2} +0 -0
  86. /package/fonts/sourceserif4/{sourceserif4-italic-cyrillic.woff2 → sourceserif4-italic-cyrillic-400.woff2} +0 -0
  87. /package/fonts/sourceserif4/{sourceserif4-italic-cyrillic-ext.woff2 → sourceserif4-italic-cyrillic-ext-400.woff2} +0 -0
  88. /package/fonts/sourceserif4/{sourceserif4-italic-greek.woff2 → sourceserif4-italic-greek-400.woff2} +0 -0
  89. /package/fonts/sourceserif4/{sourceserif4-italic-latin.woff2 → sourceserif4-italic-latin-400.woff2} +0 -0
  90. /package/fonts/sourceserif4/{sourceserif4-italic-latin-ext.woff2 → sourceserif4-italic-latin-ext-400.woff2} +0 -0
  91. /package/fonts/sourceserif4/{sourceserif4-italic-vietnamese.woff2 → sourceserif4-italic-vietnamese-400.woff2} +0 -0
@@ -11,7 +11,7 @@
11
11
  // white variant) into `--ty-print-*` custom properties on
12
12
  // `[data-ty-print-style="<name>"]`.
13
13
 
14
- import { luminance, parseColor, toHex, type Rgba } from './color.ts'
14
+ import { contrastRatio, luminance, oklchToRgb, parseColor, rgbToOklch, toHex, type Rgba } from './color.ts'
15
15
  import { PRINT_STYLE_ALIASES, warnDeprecatedPrintId, type PrintStyleAlias } from './print-aliases.ts'
16
16
 
17
17
  export type RenderizadorGrafico = 'limpo' | 'mao' | 'isotype' | 'gravura' | 'prancheta' | 'aquarela' | 'riso' | 'pontos'
@@ -20,7 +20,17 @@ export type EstadoProva = 'sustentada' | 'refutada' | 'nao-da-para-afirmar' | 'p
20
20
 
21
21
  export const ESTADOS_PROVA: readonly EstadoProva[] = ['sustentada', 'refutada', 'nao-da-para-afirmar', 'pendente', 'sem-dado', 'nao-testada']
22
22
 
23
- /** The word printed for each proof state (always visible, whatever the mark's shape). */
23
+ /** The word printed for each proof state in English (tympan-print picks by the book's `lang`). */
24
+ export const ROTULOS_PROVA_EN: Record<EstadoProva, string> = {
25
+ sustentada: 'Supported',
26
+ refutada: 'Refuted',
27
+ 'nao-da-para-afirmar': 'Cannot claim',
28
+ pendente: 'Pending',
29
+ 'sem-dado': 'No data',
30
+ 'nao-testada': 'Not tested',
31
+ }
32
+
33
+ /** The word printed for each proof state (always visible, whatever the mark's shape); pt-BR. */
24
34
  export const ROTULOS_PROVA: Record<EstadoProva, string> = {
25
35
  sustentada: 'Sustentada',
26
36
  refutada: 'Refutada',
@@ -52,6 +62,16 @@ export interface PrintCores {
52
62
  prova: Record<EstadoProva, string>
53
63
  /** Colours of page ornaments only (frames, tiles, waves); never data. Up to four. */
54
64
  ornamento?: string[]
65
+ /** Colours by category key (official party colours…): used by categorical maps, parliament and bars. */
66
+ categorias?: Record<string, string>
67
+ /** Colours of data themes (blocks of a panel page: economia, saude…); --ty-print-tema-<id>. */
68
+ temas?: Record<string, string>
69
+ /** The series of interest in maps and charts (default: `destaque`), when the accent of the page differs from the data ink. */
70
+ interesse?: string
71
+ /** Data palette, diverging: the low and the high side (default destaque2 × destaque). */
72
+ divergente?: [string, string]
73
+ /** Data palette, qualitative (party, category: no implied order), up to eight (default: a colour-blind-safe set). */
74
+ categorica?: string[]
55
75
  }
56
76
 
57
77
  /** Partial colours: any role, and any subset of the proof states. */
@@ -137,6 +157,16 @@ export interface PrintEstrutura {
137
157
  */
138
158
  realce?: 'serie' | 'linha'
139
159
  /** Head of each small multiple: plain title (default), a band in the highlighter colour with an arrow (Schiphol), a dark band (Vignelli), a band in the ornament colour (Aicher), each multiple on its own tinted card (Le Corbusier, Bayer). */
160
+ /**
161
+ * Long runs (one page per unit, thousands of pages): no page ornaments, no tinted panel fills, the 'bolso'
162
+ * panel as a rule instead of a filled box. The style's type, colours and frames stay.
163
+ */
164
+ economiaTinta?: boolean
165
+ /**
166
+ * Pages without a molde fill the type area: panels widen to close their row of the six columns and rows of
167
+ * panels share the free height (dashboard). A page can turn it on or off with `preencher`.
168
+ */
169
+ preencher?: boolean
140
170
  cabecaMultiplo?: 'titulo' | 'faixa' | 'faixa-escura' | 'faixa-cor' | 'caixa-cor'
141
171
  }
142
172
 
@@ -176,7 +206,7 @@ export type Moldura =
176
206
  | 'formas' // triangle, square and circle (Bauhaus)
177
207
  | 'quartos' // quarter circles in the page corners (diagrama-modernista)
178
208
 
179
- export interface PrintStyle {
209
+ export interface PrintStyle extends PrintTemaExtras {
180
210
  /** Stable id, e.g. 'jornal'. */
181
211
  name: string
182
212
  /** Human label, e.g. "Editorial de jornal". */
@@ -207,10 +237,94 @@ export interface PrintStyle {
207
237
  * has approved variations (ink, duotone, per style), see LogoDatatech.
208
238
  */
209
239
  logo?: 'cor' | 'mono'
240
+ /** Typesetting options a book may need against the style's idiom (see PrintTexto). */
241
+ texto?: PrintTexto
242
+ }
243
+
244
+ /**
245
+ * Typesetting switches for books whose content fights the style: a style in lower case (bauhaus,
246
+ * diagrama-modernista) would print "boa vista" for a proper name; a style with old-style figures would
247
+ * misalign table columns.
248
+ */
249
+ export interface PrintTexto {
250
+ /**
251
+ * 'estilo' (default): page titles and eyebrows follow the style's casing. 'preservar': the level-1 title,
252
+ * eyebrows and table cells keep the casing written in the content (proper names, codes), while panel labels
253
+ * and running heads keep the style's.
254
+ */
255
+ nomesProprios?: 'estilo' | 'preservar'
256
+ /** Figures in table cells: 'tabular' (default, lining and equal width, columns align) or 'proporcional'. */
257
+ numerosTabela?: 'tabular' | 'proporcional'
258
+ /** Font weight per role (titles, key numbers, labels, body, notes, mono), e.g. { titulo: 600, numero: 600 }. */
259
+ pesos?: Partial<Record<keyof PrintFontes, number>>
260
+ /** Figures per role: 'tabular' (equal width, lining) or 'proporcional'. Tables follow `numerosTabela`. */
261
+ algarismos?: Partial<Record<'numero' | 'rotulo' | 'corpo', 'tabular' | 'proporcional'>>
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Book-theme extras (see print-book-themes.ts)
266
+ // ---------------------------------------------------------------------------
267
+
268
+ /** A mark the theme prints (publisher logo, collection seal): an image URL (data: URIs work offline) and its text alternative. */
269
+ export interface PrintMarca {
270
+ src: string
271
+ alt: string
272
+ /** Width in mm where the mark is placed (default 18 for the logo, 12 for the seal). */
273
+ largura?: number
274
+ /** Image used in black and white print (default: the same `src`, greyed by CSS). */
275
+ srcPb?: string
276
+ }
277
+
278
+ /** Page format and print finishing. */
279
+ export interface PrintPagina {
280
+ /** Trim width in mm (default 170). */
281
+ largura?: number
282
+ /** Trim height in mm (default 240). */
283
+ altura?: number
284
+ /** Bleed in mm added around every page when the book prints for the press (default 3). */
285
+ sangria?: number
286
+ /** Crop marks in the slug, outside the bleed (default false). */
287
+ marcasCorte?: boolean
288
+ }
289
+
290
+ /** Running head and folio. */
291
+ export interface PrintFolio {
292
+ /** Where the folio sits: outer corner (default) or centred. */
293
+ folio?: 'externo' | 'centro'
294
+ /** Where the running head sits: outer corner (default), centred, or not printed. */
295
+ cabeco?: 'externo' | 'centro' | 'nenhum'
296
+ /** Text before the folio number, e.g. "p. ". */
297
+ prefixoFolio?: string
298
+ }
299
+
300
+ /** A local font file (offline, deterministic builds and EPUB): one @font-face. */
301
+ export interface PrintFonteLocal {
302
+ /** Family name as used in the theme's font stacks. */
303
+ familia: string
304
+ /** URL or path of the .woff2 (relative to the HTML, absolute, or a data: URI). */
305
+ src: string
306
+ peso?: number | string
307
+ estilo?: 'normal' | 'italic'
308
+ }
309
+
310
+ /** What a theme adds to a preset beyond the style tokens. */
311
+ export interface PrintTemaExtras {
312
+ marcas?: { editora?: PrintMarca; selo?: PrintMarca }
313
+ pagina?: PrintPagina
314
+ folio?: PrintFolio
315
+ /** Local font files; with them PrintBook can run without network (`fontes="locais"`). */
316
+ fontesLocais?: PrintFonteLocal[]
317
+ /**
318
+ * Trace of a number (Rastro): sign it with the theme's seal (`marcas.selo`) or the lakebrasil mark, and add
319
+ * the lake version seal when the version carries a date. Default: neutral (neither).
320
+ */
321
+ rastro?: { assinatura?: 'selo' | 'lakebrasil' | 'nenhuma'; seloLake?: boolean }
322
+ /** Texts the print components write by themselves, by key (see tympan-print i18n: TEXTOS_PT / TEXTOS_EN). */
323
+ textos?: Record<string, string>
210
324
  }
211
325
 
212
326
  /** Any token of a style may be overridden by the editor (partial, nested). */
213
- export interface PrintStyleOverrides {
327
+ export interface PrintStyleOverrides extends PrintTemaExtras {
214
328
  label?: string
215
329
  fontes?: Partial<PrintFontes>
216
330
  googleFonts?: string[]
@@ -224,6 +338,7 @@ export interface PrintStyleOverrides {
224
338
  pb?: PrintCoresParciais
225
339
  estrutura?: Partial<PrintEstrutura>
226
340
  logo?: 'cor' | 'mono'
341
+ texto?: PrintTexto
227
342
  }
228
343
 
229
344
  // ---------------------------------------------------------------------------
@@ -265,8 +380,9 @@ const dashboard: PrintStyle = {
265
380
  tinta2: '#3d3933',
266
381
  tinta3: '#5f594f',
267
382
  linha: '#d6cfc2',
268
- destaque: '#1c1a17',
269
- destaque2: '#9a4f2a',
383
+ // The highlight is not the ink (a highlighted bar must stand out): rust, AA on the paper, not a party colour.
384
+ destaque: '#9a4f2a',
385
+ destaque2: '#c4b8a8',
270
386
  marcaTexto: '#f3e6cf',
271
387
  contexto: '#8f877a',
272
388
  prova: {
@@ -285,7 +401,7 @@ const dashboard: PrintStyle = {
285
401
  raio: 1.6,
286
402
  caixaAlta: false,
287
403
  pb: {},
288
- estrutura: { painel: 'nenhum', rotulo: 'letra', rotuloCaixa: 'alta', notas: 'dentro', figura: 'simples', minusculas: false },
404
+ estrutura: { painel: 'nenhum', rotulo: 'letra', rotuloCaixa: 'alta', notas: 'dentro', figura: 'simples', minusculas: false, preencher: true },
289
405
  }
290
406
 
291
407
  const graficos1900: PrintStyle = {
@@ -1476,7 +1592,26 @@ function mergeCores(base: PrintCores, over?: PrintCoresParciais): PrintCores {
1476
1592
  if (!over) return base
1477
1593
  const { prova, ...rest } = over
1478
1594
  const clean = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)) as Partial<PrintCores>
1479
- return { ...base, ...clean, prova: { ...base.prova, ...(prova ?? {}) } }
1595
+ const temas = base.temas || clean.temas ? { temas: { ...base.temas, ...clean.temas } } : {}
1596
+ const categorias = base.categorias || clean.categorias ? { categorias: { ...base.categorias, ...clean.categorias } } : {}
1597
+ return { ...base, ...clean, ...temas, ...categorias, prova: { ...base.prova, ...(prova ?? {}) } }
1598
+ }
1599
+
1600
+ function mergeExtras(base: PrintTemaExtras, over: PrintTemaExtras): PrintTemaExtras {
1601
+ const out: PrintTemaExtras = {}
1602
+ const marcas = { ...base.marcas, ...over.marcas }
1603
+ if (Object.keys(marcas).length) out.marcas = marcas
1604
+ const pagina = { ...base.pagina, ...over.pagina }
1605
+ if (Object.keys(pagina).length) out.pagina = pagina
1606
+ const folio = { ...base.folio, ...over.folio }
1607
+ if (Object.keys(folio).length) out.folio = folio
1608
+ const fl = over.fontesLocais ?? base.fontesLocais
1609
+ if (fl) out.fontesLocais = fl
1610
+ const rs = { ...base.rastro, ...over.rastro }
1611
+ if (Object.keys(rs).length) out.rastro = rs
1612
+ const tx = { ...base.textos, ...over.textos }
1613
+ if (Object.keys(tx).length) out.textos = tx
1614
+ return out
1480
1615
  }
1481
1616
 
1482
1617
  /** A style with the editor's overrides applied (nested, partial). */
@@ -1494,9 +1629,12 @@ export function mergePrintStyle(style: PrintStyle, overrides?: PrintStyleOverrid
1494
1629
  marcaProva: overrides.marcaProva ?? style.marcaProva,
1495
1630
  raio: overrides.raio ?? style.raio,
1496
1631
  caixaAlta: overrides.caixaAlta ?? style.caixaAlta,
1497
- pb: overrides.pb ? mergeCores({ ...style.cor, ...style.pb, prova: { ...style.cor.prova, ...style.pb.prova } }, overrides.pb) : style.pb,
1632
+ // P&B adjustments stay partial: merging them over the full colour set would put colours back over the greys.
1633
+ pb: overrides.pb ? { ...style.pb, ...overrides.pb, ...(style.pb.prova || overrides.pb.prova ? { prova: { ...style.pb.prova, ...overrides.pb.prova } } : {}) } : style.pb,
1498
1634
  estrutura: { ...style.estrutura, ...overrides.estrutura },
1499
1635
  logo: overrides.logo ?? style.logo,
1636
+ ...(overrides.texto || style.texto ? { texto: { ...style.texto, ...overrides.texto } } : {}),
1637
+ ...mergeExtras(style, overrides),
1500
1638
  }
1501
1639
  }
1502
1640
 
@@ -1525,6 +1663,10 @@ export function printStyleCoresPb(style: PrintStyle): PrintCores {
1525
1663
  contexto: toGrey(g.contexto),
1526
1664
  prova: Object.fromEntries(ESTADOS_PROVA.map((e) => [e, toGrey(g.prova[e])])) as Record<EstadoProva, string>,
1527
1665
  ...(g.ornamento ? { ornamento: g.ornamento.map(toGrey) } : {}),
1666
+ ...(g.interesse ? { interesse: toGrey(g.interesse) } : {}),
1667
+ ...(g.temas ? { temas: Object.fromEntries(Object.entries(g.temas).map(([k, v]) => [k, toGrey(v)])) } : {}),
1668
+ ...(g.divergente ? { divergente: g.divergente.map(toGrey) as [string, string] } : {}),
1669
+ categorica: categoricaDoEstilo(style).map(toGrey),
1528
1670
  }
1529
1671
  return mergeCores(grey, style.pb)
1530
1672
  }
@@ -1550,7 +1692,7 @@ export function resolvePrintStyle(style: PrintStyle | PrintStyleId, options: Omi
1550
1692
  return { ...merged, cor: printStyleCoresPb(merged) }
1551
1693
  }
1552
1694
 
1553
- const kebab: Record<Exclude<keyof PrintCores, 'prova' | 'ornamento'>, string> = {
1695
+ const kebab: Record<Exclude<keyof PrintCores, 'prova' | 'ornamento' | 'divergente' | 'categorica' | 'interesse' | 'temas' | 'categorias'>, string> = {
1554
1696
  papel: 'papel',
1555
1697
  tinta: 'tinta',
1556
1698
  tinta2: 'tinta-2',
@@ -1564,6 +1706,58 @@ const kebab: Record<Exclude<keyof PrintCores, 'prova' | 'ornamento'>, string> =
1564
1706
 
1565
1707
  const num = (n: number) => String(Math.round(n * 1000) / 1000)
1566
1708
 
1709
+ /**
1710
+ * `cor` moved along OKLCH lightness (hue and chroma kept) until it reaches `min`:1 against `papel`: the
1711
+ * context grey of a theme stays visible on cream paper (areas ≥ 1.5:1, strokes ≥ 3:1).
1712
+ */
1713
+ export function contrasteMinimo(cor: string, papel: string, min: number): string {
1714
+ const p = parseColor(papel)
1715
+ if (contrastRatio(parseColor(cor), p) >= min) return toHex(parseColor(cor))
1716
+ const v = rgbToOklch(parseColor(cor))
1717
+ const passo = luminance(p) > 0.2 ? -0.005 : 0.005
1718
+ let out = parseColor(cor)
1719
+ for (let i = 0; i < 200 && contrastRatio(out, p) < min; i++) {
1720
+ v.l = Math.min(0.995, Math.max(0.02, v.l + passo))
1721
+ out = oklchToRgb(v)
1722
+ }
1723
+ return toHex(out)
1724
+ }
1725
+
1726
+ const SELETORES_PAPEL: Record<keyof PrintFontes, string> = {
1727
+ titulo: ':is(h1, h2, h3, h4, .ty-print-title, .ty-print-figure-title, [class*="-title"]:not(.ty-print-panel-title, .ty-print-block-title, .ty-print-margin-title))',
1728
+ numero: ':is(.ty-print-number-value, .ty-print-table td[data-numerica], .ty-print-parliament-total, [class*="-numero"], [class*="-number"])',
1729
+ rotulo: ':is(.ty-print-panel-title, .ty-print-block-title, .ty-print-margin-title, .ty-print-sobretitle, .ty-print-table th, .ty-print-map-legend, .ty-print-running-head, .ty-print-folio, .ty-print-letter)',
1730
+ corpo: '',
1731
+ anotacao: ':is(.ty-print-annotation, .ty-print-margin, .ty-print-callout-text)',
1732
+ mono: ':is(code, pre, .ty-print-mono, .ty-print-table td[data-mono])',
1733
+ }
1734
+
1735
+ /**
1736
+ * Unlayered rules (they win over `@layer tympan-print`) for a theme's per-role font weights and figures,
1737
+ * scoped to `seletor`; '' when the theme sets neither.
1738
+ */
1739
+ export function printTextoRolesCss(style: Pick<PrintStyle, 'texto'>, seletor: string): string {
1740
+ const out: string[] = []
1741
+ for (const [papel, peso] of Object.entries(style.texto?.pesos ?? {}) as Array<[keyof PrintFontes, number]>) {
1742
+ const s = SELETORES_PAPEL[papel]
1743
+ out.push(`${seletor}${s ? ` ${s}` : ''} { font-weight: ${peso}; }`)
1744
+ }
1745
+ for (const [papel, a] of Object.entries(style.texto?.algarismos ?? {}) as Array<[keyof PrintFontes, string]>) {
1746
+ const s = SELETORES_PAPEL[papel]
1747
+ out.push(`${seletor}${s ? ` ${s}` : ''} { font-variant-numeric: ${a === 'tabular' ? 'tabular-nums lining-nums' : 'proportional-nums'}; }`)
1748
+ }
1749
+ return out.join('\n')
1750
+ }
1751
+
1752
+ /** Qualitative palette (Okabe & Ito 2008, colour-blind safe) used when a style defines none. */
1753
+ export const CATEGORICA_PADRAO = ['#0072b2', '#e69f00', '#009e73', '#cc79a7', '#56b4e9', '#d55e00', '#f0e442', '#555555'] as const
1754
+
1755
+ /** The eight qualitative data colours of a style: its own `categorica`, completed by CATEGORICA_PADRAO. */
1756
+ export function categoricaDoEstilo(style: Pick<PrintStyle, 'cor'>): string[] {
1757
+ const own = style.cor.categorica ?? []
1758
+ return Array.from({ length: 8 }, (_, i) => own[i] ?? CATEGORICA_PADRAO[i]!)
1759
+ }
1760
+
1567
1761
  /** Custom properties of a resolved style, in a stable order. */
1568
1762
  export function printStyleVariables(style: PrintStyle): Array<[string, string]> {
1569
1763
  const vars: Array<[string, string]> = []
@@ -1581,6 +1775,22 @@ export function printStyleVariables(style: PrintStyle): Array<[string, string]>
1581
1775
  vars.push(['--ty-print-rotulo-caixa', minusculas ? 'lowercase' : rc === 'alta' ? 'uppercase' : 'none'])
1582
1776
  vars.push(['--ty-print-rotulo-variante', rc === 'versalete' ? 'small-caps' : 'normal'])
1583
1777
  vars.push(['--ty-print-rotulo-espaco', rc === 'normal' ? 'normal' : '0.07em'])
1778
+ vars.push(['--ty-print-pagina-largura', `${num(style.pagina?.largura ?? 170)}mm`])
1779
+ vars.push(['--ty-print-pagina-altura', `${num(style.pagina?.altura ?? 240)}mm`])
1780
+ const preservar = style.texto?.nomesProprios === 'preservar'
1781
+ vars.push(['--ty-print-titulo1-caixa', preservar ? 'none' : style.caixaAlta ? 'uppercase' : minusculas ? 'lowercase' : 'none'])
1782
+ vars.push(['--ty-print-sobretitulo-caixa', preservar ? 'none' : minusculas ? 'lowercase' : rc === 'alta' ? 'uppercase' : 'none'])
1783
+ vars.push(['--ty-print-celula-caixa', preservar ? 'none' : minusculas ? 'lowercase' : 'none'])
1784
+ vars.push(['--ty-print-tabela-numeros', style.texto?.numerosTabela === 'proporcional' ? 'proportional-nums lining-nums' : 'tabular-nums lining-nums'])
1785
+ for (const [k, v] of Object.entries(style.cor.temas ?? {})) vars.push([`--ty-print-tema-${k.replace(/[^a-z0-9-]/gi, '')}`, v])
1786
+ vars.push(['--ty-print-interesse', style.cor.interesse ?? style.cor.destaque])
1787
+ vars.push(['--ty-print-contexto-area', contrasteMinimo(style.cor.contexto, style.cor.papel, 1.5)])
1788
+ vars.push(['--ty-print-contexto-traco', contrasteMinimo(style.cor.contexto, style.cor.papel, 3)])
1789
+ const dv = style.cor.divergente ?? [style.cor.destaque2, style.cor.destaque]
1790
+ vars.push(['--ty-print-divergente-1', dv[0]])
1791
+ vars.push(['--ty-print-divergente-2', dv[1]])
1792
+ const cat = categoricaDoEstilo(style)
1793
+ cat.forEach((c, i) => vars.push([`--ty-print-categorica-${i + 1}`, c]))
1584
1794
  const orn = style.cor.ornamento ?? []
1585
1795
  const reserva = [style.cor.destaque, style.cor.destaque2, style.cor.tinta, style.cor.marcaTexto]
1586
1796
  for (let i = 0; i < 4; i++) vars.push([`--ty-print-ornamento-${i + 1}`, orn[i] ?? reserva[i]!])
package/src/stylesheet.ts CHANGED
@@ -120,6 +120,9 @@ export const FORCED_COLORS_VARS: VarList = [
120
120
  ['--ty-shadow-modal', 'none'],
121
121
  ['--ty-glass-blur-sheet', '0px'],
122
122
  ['--ty-glass-blur-floating', '0px'],
123
+ ['--ty-glass-edge', 'CanvasText'],
124
+ ['--ty-glass-fill', 'Canvas'],
125
+ ['--ty-glass-fill-raised', 'Canvas'],
123
126
  ]
124
127
 
125
128
  /** Reduced transparency (and no backdrop-filter support): opaque surfaces, no blur, no ambient. */
@@ -130,6 +133,8 @@ export const OPAQUE_VARS: VarList = [
130
133
  ['--ty-glass-blur-sheet', '0px !important'],
131
134
  ['--ty-glass-blur-floating', '0px !important'],
132
135
  ['--ty-glass-saturate', '1 !important'],
136
+ ['--ty-glass-fill', 'var(--ty-surface-solid) !important'],
137
+ ['--ty-glass-fill-raised', 'var(--ty-surface-raised-solid) !important'],
133
138
  ['--ty-ambient-1', 'transparent !important'],
134
139
  ['--ty-ambient-2', 'transparent !important'],
135
140
  ]
package/src/theme.ts CHANGED
@@ -70,6 +70,31 @@ export interface ThemeConfig {
70
70
  elevation?: Elevation
71
71
  /** Arbitrary string variables (e.g. for textures) */
72
72
  strings?: Record<string, string>
73
+ /**
74
+ * String variables per mode (`--ty-<name>`), for values that differ
75
+ * between light and dark: a glass edge, a fill gradient, a brand mark
76
+ * gradient. Like pins, they are exact design values: the generated
77
+ * high-contrast variant leaves them out, so components fall back.
78
+ */
79
+ modeStrings?: Partial<Record<Mode, Record<string, string>>>
80
+ /** Backdrop saturation of glass surfaces per mode (1.15 when omitted). */
81
+ glassSaturate?: Partial<Record<Mode, number>>
82
+ /** Exact shadow layers per mode, replacing the generated ones by name. */
83
+ shadowPins?: Partial<Record<Mode, Partial<Record<ShadowName, ShadowPin[]>>>>
84
+ /** Card and sheet radii in px, when they do not follow the control radius. */
85
+ radii?: Partial<Record<'card' | 'sheet', number>>
86
+ }
87
+
88
+ export type ShadowName = 'sheet' | 'raised' | 'floating' | 'modal' | 'sheet-inset'
89
+
90
+ /** One shadow layer of `shadowPins`, its colour as a CSS colour string. */
91
+ export interface ShadowPin {
92
+ x: number
93
+ y: number
94
+ blur: number
95
+ spread: number
96
+ color: string
97
+ inset?: boolean
73
98
  }
74
99
 
75
100
  export const RAMP_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const
@@ -288,7 +313,7 @@ export interface ResolvedTheme {
288
313
  colors: Record<string, Rgba>
289
314
  ramps: Record<SeedName, Record<RampStep, Rgba>>
290
315
  cta: { angle: number; stops: Rgba[] }
291
- shadows: Record<'sheet' | 'raised' | 'floating' | 'modal' | 'sheet-inset', ShadowLayer[]>
316
+ shadows: Record<ShadowName, ShadowLayer[]>
292
317
  /** Pixel dimensions (radius, glass blur, focus width). */
293
318
  dimensions: Record<string, number>
294
319
  /** Font stacks keyed by token name (`serif`, `sans`, `mono`); empty when the theme keeps the base stacks. */
@@ -471,19 +496,24 @@ export function resolveTheme(config: ThemeConfig, mode: Mode, contrastOverride?:
471
496
  }
472
497
  }
473
498
 
499
+ const shadowPins = config.shadowPins?.[mode] ?? {}
500
+ for (const [name, layers] of Object.entries(shadowPins) as Array<[ShadowName, ShadowPin[]]>) {
501
+ shadows[name] = layers.map((l) => ({ ...l, color: parseColor(l.color) }))
502
+ }
503
+
474
504
  const R = config.radius
475
505
  const dimensions: Record<string, number> = {
476
506
  radius: R,
477
507
  'radius-control': R,
478
- 'radius-card': Math.round(R * 1.6),
479
- 'radius-sheet': Math.round(R * 2.4),
508
+ 'radius-card': config.radii?.card ?? Math.round(R * 1.6),
509
+ 'radius-sheet': config.radii?.sheet ?? Math.round(R * 2.4),
480
510
  'radius-pill': 999,
481
511
  'radius-agent': Math.max(2, Math.round(R * 0.6)),
482
512
  'focus-width': contrast === 'high' ? 3 : 2,
483
513
  'glass-blur-sheet': config.glass ? 20 : 0,
484
514
  'glass-blur-floating': config.glass ? 24 : 0,
485
515
  }
486
- const numbers: Record<string, number> = { 'glass-saturate': config.glass ? 1.15 : 1 }
516
+ const numbers: Record<string, number> = { 'glass-saturate': config.glass ? (config.glassSaturate?.[mode] ?? 1.15) : 1 }
487
517
 
488
518
  const fonts: ResolvedTheme['fonts'] = {}
489
519
  for (const role of ['display', 'body', 'mono'] as const) {
@@ -491,7 +521,7 @@ export function resolveTheme(config: ThemeConfig, mode: Mode, contrastOverride?:
491
521
  if (list?.length) fonts[FONT_ROLE_TOKENS[role]] = [...list]
492
522
  }
493
523
 
494
- return { name: config.name, mode, contrast, colors, ramps, cta: { angle: 135, stops: ctaStops }, shadows, dimensions, numbers, fonts, strings: config.strings ?? {}, report }
524
+ return { name: config.name, mode, contrast, colors, ramps, cta: { angle: 135, stops: ctaStops }, shadows, dimensions, numbers, fonts, strings: { ...config.strings, ...(contrast === config.contrast ? config.modeStrings?.[mode] : undefined) }, report }
495
525
  }
496
526
 
497
527
  // ---------------------------------------------------------------------------