@rojaostudio/ds-core 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rojão Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @rojaostudio/ds-core
2
+
3
+ The engine behind [Rojão DS](https://ds.rojao.ai): design tokens, theme derivation, emitters.
4
+
5
+ **No React. No Tailwind. No framework-coupled CSS** — `emitCss` returns plain custom properties that work anywhere CSS does.
6
+
7
+ ```bash
8
+ npm i @rojaostudio/ds-core
9
+ ```
10
+
11
+ ```ts
12
+ import { generateTheme, emitCss, emitClaudeMd } from '@rojaostudio/ds-core/generate';
13
+
14
+ const brand = {
15
+ name: 'acme',
16
+ brand: { primary: '#7C3AED' },
17
+ surface: 'neutral',
18
+ text: 'neutral',
19
+ fonts: { body: 'inter' },
20
+ };
21
+
22
+ emitCss(brand); // theme.css — light and dark, derived from one color
23
+ emitClaudeMd(brand); // the same brand as rules for Claude Code, Cursor or AGENTS.md
24
+ ```
25
+
26
+ One color goes in. What comes out is an OKLCH scale with an adaptive contrast floor, semantic
27
+ roles for surface, text, border and brand, and the full light/dark pair — verified against
28
+ WCAG AA on all three surfaces, by test, on every build.
29
+
30
+ ## Why this is a separate package
31
+
32
+ It used to be one package, and its `peerDependencies` forced React 19 and Tailwind 4 on anyone
33
+ who only wanted tokens — a consumer depending on what it doesn't use. The boundary already
34
+ existed in the code: this directory has never imported React. It just didn't exist in the
35
+ packaging.
36
+
37
+ Components, styles and the React Native target live in
38
+ [`@rojaostudio/ds`](https://www.npmjs.com/package/@rojaostudio/ds).
39
+
40
+ ## API
41
+
42
+ | Entry | What's in it |
43
+ |---|---|
44
+ | `@rojaostudio/ds-core/generate` | `generateTheme`, `emitCss`, `emitClaudeMd`, `buildScale`, `contrastRatio` |
45
+ | `@rojaostudio/ds-core/tokens` | `primitives` — the raw palettes and scales |
46
+ | `@rojaostudio/ds-core/recipes` | the reference brand definition |
47
+ | `@rojaostudio/ds-core/themes` | the preset catalogue: a starting palette per niche |
48
+
49
+ ## Em português
50
+
51
+ O site é em português e faz isso sem escrever código: **[ds.rojao.ai](https://ds.rojao.ai)** —
52
+ você joga sua logo, ele tira a cor e devolve o tema pronto para colar no projeto.
53
+
54
+ ## License
55
+
56
+ MIT — see [LICENSE](./LICENSE). The **name and logo are not**: see
57
+ [TRADEMARK.md](https://github.com/rojaostudio/ds/blob/main/TRADEMARK.md).
58
+
59
+ **What comes out of the engine is yours.** The `theme.css` and `CLAUDE.md` generated from your
60
+ brand carry no license obligation at all. Use them commercially, ship them to clients, sell
61
+ them. The engine is MIT; the output isn't ours.
@@ -0,0 +1,405 @@
1
+ // tokens/index.ts
2
+ var primitives = {
3
+ color: {
4
+ white: "#ffffff",
5
+ black: "#000000",
6
+ // Black/White com alpha — escala mesma das paletas (50-900 = % opacity).
7
+ // Uso: estados disabled, overlays, scrim, hover sutil sobre qualquer fundo.
8
+ // Hex 8 chars (#RRGGBBAA): 50=5% (0D) → 900=90% (E6).
9
+ blackAlpha: {
10
+ 50: "#0000000D",
11
+ 100: "#0000001A",
12
+ 200: "#00000033",
13
+ 300: "#0000004D",
14
+ 400: "#00000066",
15
+ 500: "#00000080",
16
+ 600: "#00000099",
17
+ 700: "#000000B3",
18
+ 800: "#000000CC",
19
+ 900: "#000000E6"
20
+ },
21
+ whiteAlpha: {
22
+ 50: "#FFFFFF0D",
23
+ 100: "#FFFFFF1A",
24
+ 200: "#FFFFFF33",
25
+ 300: "#FFFFFF4D",
26
+ 400: "#FFFFFF66",
27
+ 500: "#FFFFFF80",
28
+ 600: "#FFFFFF99",
29
+ 700: "#FFFFFFB3",
30
+ 800: "#FFFFFFCC",
31
+ 900: "#FFFFFFE6"
32
+ },
33
+ neutral: {
34
+ 50: "#fafafa",
35
+ 100: "#ededed",
36
+ 200: "#d6d6d6",
37
+ 300: "#b9b9b9",
38
+ 400: "#979797",
39
+ 500: "#737373",
40
+ 600: "#575757",
41
+ 700: "#3c3c3c",
42
+ 800: "#232323",
43
+ 900: "#0d0d0d"
44
+ },
45
+ red: {
46
+ 50: "#fff8f7",
47
+ 100: "#ffe8e5",
48
+ 200: "#ffccc4",
49
+ 300: "#ffa598",
50
+ 400: "#ff6c5c",
51
+ 500: "#f44336",
52
+ 600: "#c50003",
53
+ 700: "#990001",
54
+ 800: "#6f0000",
55
+ 900: "#4b0000"
56
+ },
57
+ green: {
58
+ 50: "#f0ffef",
59
+ 100: "#caffc8",
60
+ 200: "#a5f0a4",
61
+ 300: "#82d982",
62
+ 400: "#4caf50",
63
+ 500: "#379c3d",
64
+ 600: "#0a7e1c",
65
+ 700: "#00610f",
66
+ 800: "#004507",
67
+ 900: "#002d01"
68
+ },
69
+ amber: {
70
+ 50: "#fffaee",
71
+ 100: "#fff2d6",
72
+ 200: "#ffe3ab",
73
+ 300: "#ffd068",
74
+ 400: "#ffc107",
75
+ 500: "#e5a500",
76
+ 600: "#d29100",
77
+ 700: "#c08000",
78
+ 800: "#ae7000",
79
+ 900: "#9d6100"
80
+ },
81
+ yellow: {
82
+ 50: "#fffcdc",
83
+ 100: "#fff7b2",
84
+ 200: "#ffeb3b",
85
+ 300: "#f6df4b",
86
+ 400: "#eecb00",
87
+ 500: "#e2b900",
88
+ 600: "#d5a800",
89
+ 700: "#c89800",
90
+ 800: "#bb8900",
91
+ 900: "#af7b00"
92
+ },
93
+ blue: {
94
+ 50: "#f6fbff",
95
+ 100: "#e2f0ff",
96
+ 200: "#bddeff",
97
+ 300: "#8cc6ff",
98
+ 400: "#44a7ff",
99
+ 500: "#2196f3",
100
+ 600: "#006bb5",
101
+ 700: "#00518d",
102
+ 800: "#003966",
103
+ 900: "#002445"
104
+ },
105
+ purple: {
106
+ 50: "#fef8ff",
107
+ 100: "#fbe6ff",
108
+ 200: "#f7c6ff",
109
+ 300: "#f09bff",
110
+ 400: "#db6def",
111
+ 500: "#bc4ad0",
112
+ 600: "#9c27b0",
113
+ 700: "#7e0090",
114
+ 800: "#5b0069",
115
+ 900: "#3c0046"
116
+ },
117
+ teal: {
118
+ 50: "#ecfffb",
119
+ 100: "#c0fdf2",
120
+ 200: "#99eddf",
121
+ 300: "#71d5c6",
122
+ 400: "#45b7a8",
123
+ 500: "#009688",
124
+ 600: "#00796d",
125
+ 700: "#005d53",
126
+ 800: "#00423b",
127
+ 900: "#002a25"
128
+ },
129
+ cyan: {
130
+ 50: "#f1fdff",
131
+ 100: "#cef7ff",
132
+ 200: "#86ecff",
133
+ 300: "#56d4ea",
134
+ 400: "#00bcd4",
135
+ 500: "#0095a8",
136
+ 600: "#007585",
137
+ 700: "#005a66",
138
+ 800: "#004049",
139
+ 900: "#002930"
140
+ },
141
+ orange: {
142
+ 50: "#fff9f3",
143
+ 100: "#ffeddd",
144
+ 200: "#ffd8b4",
145
+ 300: "#ffbb7a",
146
+ 400: "#ff9800",
147
+ 500: "#dc7f00",
148
+ 600: "#bd6a00",
149
+ 700: "#a15700",
150
+ 800: "#854500",
151
+ 900: "#6c3500"
152
+ },
153
+ pink: {
154
+ 50: "#fff8f9",
155
+ 100: "#ffe8ea",
156
+ 200: "#ffcad1",
157
+ 300: "#ffa1b0",
158
+ 400: "#ff6587",
159
+ 500: "#e91e63",
160
+ 600: "#c0004d",
161
+ 700: "#95003a",
162
+ 800: "#6d0027",
163
+ 900: "#490017"
164
+ },
165
+ indigo: {
166
+ 50: "#f8faff",
167
+ 100: "#e8eeff",
168
+ 200: "#cdd9ff",
169
+ 300: "#aabdff",
170
+ 400: "#809aff",
171
+ 500: "#637ae3",
172
+ 600: "#3f51b5",
173
+ 700: "#3342a4",
174
+ 800: "#202885",
175
+ 900: "#110e66"
176
+ },
177
+ // ── DS Custom palettes — universais. Primitivos disponíveis para qualquer projeto ──
178
+ // Cobalt — azul profundo de marca (700 = canônico). Mais saturado/escuro
179
+ // que blue. Uso típico: accent, link, focus, badge selecionado.
180
+ cobalt: {
181
+ 50: "#f8faff",
182
+ 100: "#e7eeff",
183
+ 200: "#c9daff",
184
+ 300: "#a3bfff",
185
+ 400: "#769dff",
186
+ 500: "#4b79fa",
187
+ 600: "#3259d8",
188
+ 700: "#1e3fbc",
189
+ 800: "#0c1f98",
190
+ 900: "#060076"
191
+ },
192
+ // Brick — laranja-tijolo, alerta de marca. Uso < 5% (badges críticos,
193
+ // pre-order, destaque de alta tensão).
194
+ brick: {
195
+ 50: "#fff8f7",
196
+ 100: "#ffe9e4",
197
+ 200: "#ffccc2",
198
+ 300: "#ffa594",
199
+ 400: "#ff6d55",
200
+ 500: "#e5462e",
201
+ 600: "#c01b00",
202
+ 700: "#950f00",
203
+ 800: "#6d0500",
204
+ 900: "#4a0000"
205
+ },
206
+ // Cream — off-white amadeirado. Superfícies quentes e near-white sobre dark.
207
+ // 50 = surface-page light; 100 = surface-raised light + text/borders sobre dark.
208
+ cream: {
209
+ 50: "#fdfaf3",
210
+ 100: "#f1ece3",
211
+ 200: "#dcd5c8",
212
+ 300: "#b8b0a0",
213
+ 400: "#9f9587",
214
+ 500: "#7f7467",
215
+ 600: "#605449",
216
+ 700: "#46392f",
217
+ 800: "#2b2018",
218
+ 900: "#140b05"
219
+ },
220
+ // Moss — verde-musgo dessaturado, de galeria/ateliê. Recua (frio, sóbrio),
221
+ // o oposto do laranja que avança. Uso: accent autoral, links, detalhes. 500 = canônico.
222
+ moss: {
223
+ 50: "#f3fdf6",
224
+ 100: "#e3f1e7",
225
+ 200: "#c9dbce",
226
+ 300: "#aabfb0",
227
+ 400: "#889d8d",
228
+ 500: "#677c6d",
229
+ 600: "#566b5c",
230
+ 700: "#2e4134",
231
+ 800: "#16281b",
232
+ 900: "#031107"
233
+ },
234
+ // Clay — terracota queimada, tátil. Calor de tinta a óleo. Uso pontual (< 10%):
235
+ // o gesto quente único (carimbo, sublinhado à mão, warm accent). 500 = canônico.
236
+ clay: {
237
+ 50: "#fff8f6",
238
+ 100: "#ffe9e2",
239
+ 200: "#ffcdbc",
240
+ 300: "#ffa789",
241
+ 400: "#e8825f",
242
+ 500: "#c86240",
243
+ 600: "#b5512e",
244
+ 700: "#8a2900",
245
+ 800: "#651a00",
246
+ 900: "#440c00"
247
+ }
248
+ // Paletas Project (coal/flare/zinc/ash/ember) saíram do core → viraram
249
+ // custom por marca em recipes/index.ts (palettes), geradas no theme.css.
250
+ },
251
+ space: {
252
+ 0: 0,
253
+ 1: 1,
254
+ 2: 2,
255
+ 4: 4,
256
+ 8: 8,
257
+ 12: 12,
258
+ 16: 16,
259
+ 20: 20,
260
+ 24: 24,
261
+ 28: 28,
262
+ 32: 32,
263
+ 40: 40,
264
+ 48: 48,
265
+ 56: 56,
266
+ 64: 64,
267
+ 80: 80,
268
+ 96: 96
269
+ },
270
+ radius: { square: 0, sm: 4, md: 8, lg: 12, xl: 16, full: 9999 },
271
+ motion: { fast: 100, base: 150, slow: 300 },
272
+ fontSize: {
273
+ xs: 12,
274
+ sm: 14,
275
+ base: 16,
276
+ lg: 18,
277
+ xl: 20,
278
+ "2xl": 24,
279
+ "3xl": 30,
280
+ "4xl": 36,
281
+ "5xl": 48,
282
+ "6xl": 56,
283
+ "7xl": 64,
284
+ "8xl": 72,
285
+ "9xl": 88
286
+ // display (landing/hero)
287
+ },
288
+ fontWeight: { regular: 400, medium: 500, semibold: 600, bold: 700 },
289
+ // Unitless para escalar corretamente com o font-size do elemento.
290
+ // Valores derivados das proporções originais (ex: 20/14 ≈ 1.43).
291
+ lineHeight: { xs: 1.33, sm: 1.43, base: 1.5, lg: 1.56, xl: 1.6, "2xl": 1.67 },
292
+ letterSpacing: { tight: -0.5, normal: 0, wide: 0.5 },
293
+ // Control-specific semantic scale (System tokens)
294
+ controlHeight: { sm: 32, md: 40, lg: 48 },
295
+ controlPaddingX: { sm: 12, md: 16, lg: 20 },
296
+ controlPaddingY: { sm: 4, md: 8, lg: 12 },
297
+ controlGap: { sm: 4, md: 8, lg: 8 },
298
+ icon: { size: 24 },
299
+ borderWidth: { default: 1 }
300
+ };
301
+ var tokens = {
302
+ color: {
303
+ surface: {
304
+ page: primitives.color.neutral[50],
305
+ default: primitives.color.white,
306
+ raised: primitives.color.neutral[100],
307
+ overlay: primitives.color.neutral[900]
308
+ },
309
+ border: {
310
+ subtle: primitives.color.neutral[100],
311
+ default: primitives.color.neutral[200],
312
+ strong: primitives.color.neutral[300],
313
+ focus: primitives.color.black
314
+ },
315
+ text: {
316
+ primary: primitives.color.neutral[900],
317
+ secondary: primitives.color.neutral[700],
318
+ muted: primitives.color.neutral[500],
319
+ disabled: primitives.color.neutral[400],
320
+ inverse: primitives.color.white,
321
+ placeholder: primitives.color.neutral[400]
322
+ },
323
+ icon: { default: primitives.color.neutral[800] },
324
+ action: {
325
+ primary: { default: primitives.color.neutral[900], hover: primitives.color.neutral[700], text: primitives.color.white },
326
+ secondary: { default: primitives.color.neutral[100], hover: primitives.color.neutral[200], text: primitives.color.neutral[700] },
327
+ ghost: { hover: primitives.color.neutral[100], text: primitives.color.neutral[700] },
328
+ destructive: { default: primitives.color.red[600], hover: primitives.color.red[700], text: primitives.color.white }
329
+ },
330
+ feedback: {
331
+ danger: { base: primitives.color.red[500], badge: primitives.color.red[100], border: primitives.color.red[200], text: primitives.color.red[700] },
332
+ success: { base: primitives.color.green[500], badge: primitives.color.green[100], border: primitives.color.green[500], text: primitives.color.green[700] },
333
+ warning: { base: primitives.color.amber[500], badge: primitives.color.amber[100], border: primitives.color.amber[200], text: primitives.color.amber[700] },
334
+ info: { base: primitives.color.blue[500], badge: primitives.color.blue[100], border: primitives.color.blue[200], text: primitives.color.blue[700] }
335
+ },
336
+ // Attention — escala de urgência temporal (countdown/trial), 3 degraus crescentes
337
+ // de saturação do amarelo Rojão (yellow-50 → 100 → 200) + texto quase-preto fixo
338
+ // (amarelo+preto = máxima legibilidade). NÃO flipa no dark. Ver base.css p/ contraste.
339
+ attention: {
340
+ low: { soft: primitives.color.yellow[50], border: primitives.color.yellow[100], text: primitives.color.neutral[900] },
341
+ medium: { soft: primitives.color.yellow[100], border: primitives.color.yellow[200], text: primitives.color.neutral[900] },
342
+ high: { soft: primitives.color.yellow[200], border: primitives.color.yellow[300], text: primitives.color.neutral[900] },
343
+ // CTA fixo (não flipa no dark, pois o fundo amarelo é fixo).
344
+ action: primitives.color.neutral[900],
345
+ onAction: primitives.color.white
346
+ },
347
+ // Avatar/monograma — paleta categórica decorativa para placeholder sem foto.
348
+ // Indexado por hash do nome (1-6) → cor estável por produto/cliente.
349
+ avatar: {
350
+ 1: { bg: primitives.color.blue[100], fg: primitives.color.blue[800] },
351
+ 2: { bg: primitives.color.purple[100], fg: primitives.color.purple[800] },
352
+ 3: { bg: primitives.color.teal[100], fg: primitives.color.teal[800] },
353
+ 4: { bg: primitives.color.orange[100], fg: primitives.color.orange[800] },
354
+ 5: { bg: primitives.color.pink[100], fg: primitives.color.pink[800] },
355
+ 6: { bg: primitives.color.indigo[100], fg: primitives.color.indigo[800] }
356
+ },
357
+ status: {
358
+ active: { base: primitives.color.green[500], text: primitives.color.green[700] },
359
+ pending: { base: primitives.color.amber[400], text: primitives.color.amber[700] },
360
+ error: { base: primitives.color.red[500], text: primitives.color.red[700] },
361
+ inactive: { base: primitives.color.neutral[400], text: primitives.color.neutral[500] }
362
+ }
363
+ }
364
+ };
365
+ var darkTokens = {
366
+ color: {
367
+ surface: {
368
+ page: "#111111",
369
+ // between neutral[900] and black
370
+ default: "#1e1e1e",
371
+ // between neutral[800] and neutral[900]
372
+ raised: primitives.color.neutral[800],
373
+ // #262626
374
+ overlay: primitives.color.neutral[100]
375
+ // #f5f5f5
376
+ },
377
+ border: {
378
+ subtle: primitives.color.whiteAlpha[100],
379
+ // base.css: color-mix(white 8%, transparent)
380
+ default: primitives.color.whiteAlpha[100],
381
+ // intentionally same as subtle
382
+ strong: primitives.color.whiteAlpha[200],
383
+ // base.css: color-mix(white 22%, transparent)
384
+ focus: primitives.color.white
385
+ },
386
+ text: {
387
+ primary: primitives.color.neutral[50],
388
+ // #fafafa
389
+ secondary: primitives.color.neutral[400],
390
+ // #a3a3a3
391
+ muted: primitives.color.neutral[500],
392
+ // #737373
393
+ disabled: primitives.color.neutral[600],
394
+ // #525252
395
+ inverse: primitives.color.neutral[900],
396
+ // #171717
397
+ placeholder: primitives.color.neutral[500]
398
+ // #737373
399
+ },
400
+ icon: { default: primitives.color.neutral[400] }
401
+ // #a3a3a3
402
+ }
403
+ };
404
+
405
+ export { darkTokens, primitives, tokens };