@codedartdev/coinsentry-sdk 0.1.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 (66) hide show
  1. package/README.md +271 -0
  2. package/dist/brand/config.d.ts +78 -0
  3. package/dist/brand/config.d.ts.map +1 -0
  4. package/dist/brand/config.js +274 -0
  5. package/dist/brand/config.js.map +1 -0
  6. package/dist/brand/index.d.ts +4 -0
  7. package/dist/brand/index.d.ts.map +1 -0
  8. package/dist/brand/index.js +2 -0
  9. package/dist/brand/index.js.map +1 -0
  10. package/dist/brand/types.d.ts +122 -0
  11. package/dist/brand/types.d.ts.map +1 -0
  12. package/dist/brand/types.js +2 -0
  13. package/dist/brand/types.js.map +1 -0
  14. package/dist/client.d.ts +169 -0
  15. package/dist/client.d.ts.map +1 -0
  16. package/dist/client.js +239 -0
  17. package/dist/client.js.map +1 -0
  18. package/dist/errors.d.ts +32 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +143 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/guards.d.ts +12 -0
  23. package/dist/guards.d.ts.map +1 -0
  24. package/dist/guards.js +76 -0
  25. package/dist/guards.js.map +1 -0
  26. package/dist/http.d.ts +38 -0
  27. package/dist/http.d.ts.map +1 -0
  28. package/dist/http.js +175 -0
  29. package/dist/http.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +8 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/kyc/config.d.ts +20 -0
  35. package/dist/kyc/config.d.ts.map +1 -0
  36. package/dist/kyc/config.js +224 -0
  37. package/dist/kyc/config.js.map +1 -0
  38. package/dist/kyc/flow.d.ts +37 -0
  39. package/dist/kyc/flow.d.ts.map +1 -0
  40. package/dist/kyc/flow.js +329 -0
  41. package/dist/kyc/flow.js.map +1 -0
  42. package/dist/kyc/index.d.ts +6 -0
  43. package/dist/kyc/index.d.ts.map +1 -0
  44. package/dist/kyc/index.js +4 -0
  45. package/dist/kyc/index.js.map +1 -0
  46. package/dist/kyc/locale.d.ts +17 -0
  47. package/dist/kyc/locale.d.ts.map +1 -0
  48. package/dist/kyc/locale.js +40 -0
  49. package/dist/kyc/locale.js.map +1 -0
  50. package/dist/kyc/types.d.ts +289 -0
  51. package/dist/kyc/types.d.ts.map +1 -0
  52. package/dist/kyc/types.js +2 -0
  53. package/dist/kyc/types.js.map +1 -0
  54. package/dist/products.d.ts +280 -0
  55. package/dist/products.d.ts.map +1 -0
  56. package/dist/products.js +302 -0
  57. package/dist/products.js.map +1 -0
  58. package/dist/realtime.d.ts +33 -0
  59. package/dist/realtime.d.ts.map +1 -0
  60. package/dist/realtime.js +28 -0
  61. package/dist/realtime.js.map +1 -0
  62. package/dist/types.d.ts +145 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +2 -0
  65. package/dist/types.js.map +1 -0
  66. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @codedartdev/coinsentry-sdk
2
+
3
+ Core headless do SDK CoinSentry. Este pacote define contratos publicos para
4
+ frontends de clientes consumirem Auth, Session, Entity, KYC, Brand, HTTP,
5
+ erros e Realtime sem depender de React, Next.js ou componentes da whitelabel.
6
+
7
+ ## Responsabilidade
8
+
9
+ - Cliente HTTP same-origin com `fetch`, cookies, CSRF retry, FormData seguro e
10
+ corpos `URLSearchParams` para endpoints form-urlencoded.
11
+ - Servicos headless: `auth`, `session`, `entity`, `kyc`, `brand` e modulos
12
+ de produto.
13
+ - Tipos publicos, guards e codigos de erro estaveis.
14
+ - Resolucao pura do KYC universal via `@codedartdev/coinsentry-sdk/kyc`.
15
+ - Interface de realtime desacoplada do client WebSocket interno.
16
+
17
+ ## Fora Do Escopo
18
+
19
+ - Segredos de assinatura do BFF.
20
+ - Headers upstream `X-Frontend-*`.
21
+ - React providers, hooks, modais, toasts ou redirects.
22
+ - Chamada browser para `/api/v1/kyc/requirements`.
23
+ - Decisao local de `client_flow`, jurisdicao, customer type ou proxima tela.
24
+
25
+ `createCoinSentryClient()` e browser-safe e nunca aceita segredo de assinatura.
26
+ O segredo do proxy deve existir apenas em APIs server-only de
27
+ `@codedartdev/coinsentry-next`.
28
+
29
+ ## Uso Basico
30
+
31
+ ```ts
32
+ import { createCoinSentryClient } from '@codedartdev/coinsentry-sdk';
33
+
34
+ const cs = createCoinSentryClient({
35
+ baseUrl: '/',
36
+ locale: 'pt-BR',
37
+ onError(error) {
38
+ console.warn(error.code, error.status, error.reason);
39
+ },
40
+ });
41
+
42
+ await cs.auth.login({ email, password, slideCaptchaToken });
43
+ const session = await cs.session.refresh();
44
+ const entities = await cs.entity.list();
45
+ const selected = await cs.entity.select({ entityUuid: entities[0].uuid });
46
+ const overview = await cs.kyc.getOverview();
47
+ ```
48
+
49
+ O browser deve usar `baseUrl: '/'` para chamar somente o BFF same-origin.
50
+ Nao passe `WHITELABEL_PROXY_SIGNATURE_SECRET`,
51
+ `FRONTEND_PROXY_SIGNATURE_SECRET` ou qualquer segredo equivalente para este
52
+ cliente.
53
+
54
+ ## Locale
55
+
56
+ O adapter `fetch` envia `Accept-Language` automaticamente.
57
+
58
+ ```ts
59
+ cs.locale.set('pt-BR');
60
+ cs.locale.setKyc('es-AR');
61
+ await cs.kyc.getOverview();
62
+ cs.locale.clearKyc();
63
+ ```
64
+
65
+ `locale.setKyc()` afeta apenas requests para `/api/v1/kyc*`. Isso preserva o
66
+ comportamento da whitelabel atual sem colocar fallback de fluxo no browser.
67
+
68
+ ## Transporte HTTP
69
+
70
+ `client.request<T>()` e o transporte headless oficial. Use-o para endpoints
71
+ same-origin do BFF em vez de `fetch` manual no browser. O adapter preserva
72
+ `FormData` sem definir `Content-Type` e aceita `URLSearchParams` para casos
73
+ form-urlencoded como autorizacao de canais realtime.
74
+
75
+ Para compatibilidade com Sanctum/Fortify, requests mutaveis same-origin leem o
76
+ ultimo cookie `XSRF-TOKEN` disponivel e enviam `X-XSRF-TOKEN` decodificado
77
+ automaticamente. O token nao e anexado a requests cross-origin.
78
+
79
+ O retry automatico de `419` continua ativo por padrao em `client.request<T>()`.
80
+ Para endpoints sensiveis que ja inicializam CSRF antes do POST, use
81
+ `retryOnCsrf: false` para evitar repetir a mesma acao:
82
+
83
+ ```ts
84
+ await cs.request({
85
+ method: 'POST',
86
+ path: '/auth/login',
87
+ retryOnCsrf: false,
88
+ body: payload,
89
+ });
90
+ ```
91
+
92
+ `csrf.init()` e deduplicado: chamadas concorrentes compartilham o mesmo request
93
+ e chamadas recentes reaproveitam uma janela curta de inicializacao. Metodos de
94
+ auth, como login, register e 2FA, continuam forçando CSRF fresco antes do POST
95
+ para preservar a seguranca do fluxo Fortify/Sanctum.
96
+
97
+ ## Auth
98
+
99
+ `auth.login()` e `auth.twoFactorChallenge()` preservam os envelopes reais do
100
+ backend:
101
+
102
+ - `CoinSentryLoginTwoFactor`: `{ two_factor: true }`;
103
+ - `CoinSentryLoginIncomplete`: `{ success: true, two_factor: false, needs_refresh: true }`;
104
+ - `CoinSentryLoginMultipleEntities`: login autenticado sem entidade ativa;
105
+ - `CoinSentryLoginSingleEntity`: login com entidade selecionada;
106
+ - `CoinSentryLoginSuccess`: sucesso simples.
107
+
108
+ Os metodos de auth que chamam `csrf.init()` antes do POST nao repetem
109
+ automaticamente o POST em `419`. Isso evita duplicar a mesma tentativa de login,
110
+ registro ou desafio 2FA, especialmente quando ha CAPTCHA envolvido.
111
+
112
+ Use guards publicos para reduzir branching inseguro:
113
+
114
+ ```ts
115
+ import {
116
+ isCoinSentryLoginIncomplete,
117
+ isCoinSentryLoginMultipleEntities,
118
+ isCoinSentryLoginTwoFactor,
119
+ } from '@codedartdev/coinsentry-sdk';
120
+
121
+ const response = await cs.auth.login({ email, password, slideCaptchaToken });
122
+
123
+ if (isCoinSentryLoginTwoFactor(response)) {
124
+ showTwoFactorChallenge();
125
+ }
126
+
127
+ if (isCoinSentryLoginIncomplete(response)) {
128
+ await cs.session.refresh();
129
+ }
130
+
131
+ if (isCoinSentryLoginMultipleEntities(response)) {
132
+ openEntitySelector(response.entities);
133
+ }
134
+ ```
135
+
136
+ ## Session E Entity
137
+
138
+ `session.refresh()` retorna a sessao autenticada. `entity.select()` retorna um
139
+ contrato tipado com usuario, balances, entidades e `kyc_required` quando o BFF
140
+ responde com esse envelope.
141
+
142
+ Guards uteis:
143
+
144
+ - `isCoinSentrySession(value)`;
145
+ - `hasCoinSentryActiveEntity(session)`;
146
+ - `isCoinSentryEntityMinimal(value)`;
147
+ - `requiresCoinSentryEntitySelection(value)`.
148
+
149
+ ## Modulos De Produto
150
+
151
+ Alem de Auth, Session, Entity e KYC, o client expoe dominios usados pela
152
+ whitelabel atual. Todos usam `client.request<T>()`, retornam payload de dominio
153
+ normalizado e aceitam `{ signal }` quando aplicavel.
154
+
155
+ - `account.twoFactor`: enable, QR code, confirmacao, recovery codes,
156
+ regeneracao, disable e status.
157
+ - `contacts`: contatos bancarios, bancos e contatos de wallet.
158
+ - `deposits`: dados de deposito e intent de deposito.
159
+ - `withdrawals`: dados de saque, saque fiat, saque crypto e taxa de rede.
160
+ - `payments`: moedas, historico, PIX receive/status/lookup/pay.
161
+ - `affiliate`: codigo, status, referrals, payouts e rankings.
162
+ - `market`: market data, orderbook, criar/cancelar ordens e cancelar todas.
163
+ - `otc`: pares, quote/execute/history, ativar/desativar quote e planos
164
+ recorrentes.
165
+
166
+ ```ts
167
+ const depositData = await cs.deposits.get('BRL');
168
+ const fee = await cs.withdrawals.networkFee({ currency: 'BTC', network: 'BTC' });
169
+ const quote = await cs.otc.createQuote({
170
+ symbol: 'USDT_BRL',
171
+ from_currency: 'BRL',
172
+ to_currency: 'USDT',
173
+ from_amount: '100',
174
+ });
175
+ ```
176
+
177
+ Esses modulos continuam headless: nao exibem toast, nao redirecionam e nao
178
+ conhecem componentes da whitelabel.
179
+
180
+ ## KYC Headless
181
+
182
+ ```ts
183
+ import {
184
+ hasKycClientFlow,
185
+ isKycFlowSnapshotConfigured,
186
+ resolveKycFlowSnapshot,
187
+ } from '@codedartdev/coinsentry-sdk/kyc';
188
+
189
+ const overview = await cs.kyc.getOverview();
190
+ const flow = resolveKycFlowSnapshot({ overview });
191
+
192
+ if (!isKycFlowSnapshotConfigured(flow)) {
193
+ throw new Error('KYC flow is not configured.');
194
+ }
195
+
196
+ if (flow.activeStep?.screen === 'document_upload') {
197
+ await cs.kyc.upload({ step: flow.activeStep, formData });
198
+ }
199
+ ```
200
+
201
+ O core falha fechado quando nao existe `client_flow`. Ele nao cria fallback local
202
+ para fluxo legado, jurisdicao, customer type ou proxima tela.
203
+
204
+ Guards KYC publicos:
205
+
206
+ - `hasKycClientFlow(requirements)`;
207
+ - `hasResolvedKycRequirements(requirements)`;
208
+ - `isKycFlowSnapshotConfigured(snapshot)`;
209
+ - `isKycOverviewFlowConfigured(overview)`.
210
+
211
+ ## Erros Publicos
212
+
213
+ O SDK normaliza falhas em `CoinSentryApiError`:
214
+
215
+ ```ts
216
+ import { normalizeCoinSentryError } from '@codedartdev/coinsentry-sdk';
217
+
218
+ try {
219
+ await cs.kyc.getOverview();
220
+ } catch (error) {
221
+ const normalized = normalizeCoinSentryError(error);
222
+ if (normalized.code === 'kyc_required') {
223
+ openKyc();
224
+ }
225
+ }
226
+ ```
227
+
228
+ Codigos estaveis:
229
+
230
+ - `unauthenticated`
231
+ - `csrf_expired`
232
+ - `kyc_required`
233
+ - `entity_required`
234
+ - `invalid_signature`
235
+ - `origin_not_allowed`
236
+ - `missing_signature_headers`
237
+ - `validation_failed`
238
+ - `rate_limited`
239
+ - `forbidden`
240
+ - `network_error`
241
+ - `server_error`
242
+ - `unknown_error`
243
+
244
+ ## Realtime
245
+
246
+ `createCoinSentryRealtime(adapter)` define a interface publica. A implementacao
247
+ concreta pode ser o Reverb/Pusher client interno da whitelabel ou outro adapter
248
+ compativel.
249
+
250
+ ```ts
251
+ const realtime = createCoinSentryRealtime(adapter);
252
+ realtime.connect();
253
+ realtime.subscribeToEntity(entityUuid, 'balance.updated', onBalance);
254
+ ```
255
+
256
+ O payload recomendado para saldo e `CoinSentryBalanceUpdate`:
257
+
258
+ ```ts
259
+ type CoinSentryBalanceUpdate = {
260
+ currency: string;
261
+ available: string | number;
262
+ locked: string | number;
263
+ total?: string | number;
264
+ };
265
+ ```
266
+
267
+ ## Estado Atual
268
+
269
+ Beta npm (`0.1.0`, dist-tag `next`). O pacote gera `dist`, `.d.ts`,
270
+ `.d.ts.map` e sourcemaps. O pacote permanece `UNLICENSED` enquanto os contratos
271
+ publicos seguem em beta.
@@ -0,0 +1,78 @@
1
+ import type { BrandConfig, BackendBrandLogoType, BrandLogoType } from './types.js';
2
+ export type BrandMode = 'light' | 'dark';
3
+ export interface BrandManifestIcon {
4
+ src: string;
5
+ sizes: string;
6
+ type?: string;
7
+ purpose?: 'any' | 'maskable' | 'monochrome';
8
+ }
9
+ export interface BrandWebManifest {
10
+ name: string;
11
+ short_name: string;
12
+ description: string;
13
+ icons: BrandManifestIcon[];
14
+ theme_color: string;
15
+ background_color: string;
16
+ display: 'standalone';
17
+ start_url: string;
18
+ }
19
+ export interface BrandIconDescriptor {
20
+ url: string;
21
+ type?: string;
22
+ media?: string;
23
+ }
24
+ export interface NormalizedLogoLayout {
25
+ width: string;
26
+ height: string;
27
+ }
28
+ export declare function normalizeLogoType(type: BackendBrandLogoType | string | null | undefined): BrandLogoType;
29
+ export declare function isImageLogoType(type: BackendBrandLogoType | string | null | undefined): boolean;
30
+ export declare function normalizeLogoDimension(value: string | number | null | undefined, fallback: string): string;
31
+ export declare function normalizeLogoLayout(layout: BrandConfig['brand']['logo']['layout'] | null | undefined): NormalizedLogoLayout;
32
+ export declare function normalizeFontSans(value: string | null | undefined): string;
33
+ export declare function fontSansToCssFamily(value: string | null | undefined): string;
34
+ export declare function normalizeBrandConfig(config: BrandConfig): BrandConfig;
35
+ export declare function getLogoImageUrl(logo: BrandConfig['brand']['logo'], mode?: BrandMode): string | null;
36
+ export declare function getLogoTextFontFamily(config: BrandConfig): string;
37
+ export declare function getLogoTextColors(config: BrandConfig, mode: BrandMode): {
38
+ base?: string | null;
39
+ accent?: string | null;
40
+ };
41
+ export declare function getBrandCssVariables(config: BrandConfig, mode: BrandMode): {
42
+ '--radius': string;
43
+ '--primary': string;
44
+ '--primary-foreground': string;
45
+ '--background': string;
46
+ '--foreground': string;
47
+ '--card': string;
48
+ '--card-foreground': string;
49
+ '--secondary': string;
50
+ '--secondary-foreground': string;
51
+ '--muted': string;
52
+ '--muted-foreground': string;
53
+ '--accent': string;
54
+ '--accent-foreground': string;
55
+ '--border': string;
56
+ '--ring': string;
57
+ '--sidebar': string;
58
+ '--sidebar-foreground': string;
59
+ '--sidebar-primary': string;
60
+ '--sidebar-primary-foreground': string;
61
+ '--sidebar-accent': string;
62
+ '--sidebar-accent-foreground': string;
63
+ '--sidebar-border': string;
64
+ };
65
+ export declare function buildBrandCss(config: BrandConfig): string;
66
+ export declare function resolveBrandThemeMode(config: BrandConfig, preferredMode?: string | null): BrandMode;
67
+ export declare function inferImageMimeType(url: string): string | undefined;
68
+ export declare function buildBrandManifest(config: BrandConfig): BrandWebManifest;
69
+ export declare function getBrandFaviconUrl(favicon: BrandConfig['favicon'], mode?: BrandMode): string;
70
+ export declare function buildBrandIconsMetadata(favicon: BrandConfig['favicon'], mode?: BrandMode): {
71
+ icon: {
72
+ url: string;
73
+ type: string | undefined;
74
+ media: string;
75
+ }[];
76
+ shortcut: string;
77
+ };
78
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/brand/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,aAAa,EAAY,MAAM,YAAY,CAAC;AAE7F,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,YAAY,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,CAEvG;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE/F;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,QAAQ,EAAE,MAAM,GACf,MAAM,CAkBR;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,IAAI,GAAG,SAAS,GAChE,oBAAoB,CAKtB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1E;AAWD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE5E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAkCrE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,EAClC,IAAI,GAAE,SAAmB,GACxB,MAAM,GAAG,IAAI,CAOf;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEjE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS;;;EAErE;AAyED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;EAMxE;AAQD,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAmBzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAOnG;AAMD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQlE;AAoBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,CAsBxE;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,EAC/B,IAAI,GAAE,SAAmB,GACxB,MAAM,CAIR;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,EAC/B,IAAI,GAAE,SAAmB;;;;;;;EAY1B"}
@@ -0,0 +1,274 @@
1
+ const FALLBACK_FAVICON = '/favicon.ico';
2
+ const FALLBACK_FONT = 'Inter';
3
+ const FALLBACK_LOGO_WIDTH = '160px';
4
+ const FALLBACK_LOGO_HEIGHT = '40px';
5
+ const SAFE_LOGO_KEYWORDS = new Set(['auto', 'fit-content']);
6
+ const SAFE_LOGO_DIMENSION_PATTERN = /^(\d+(?:\.\d+)?)(px|rem|em|%|vw|vh)$/i;
7
+ export function normalizeLogoType(type) {
8
+ return type === 'text' ? 'text' : 'image';
9
+ }
10
+ export function isImageLogoType(type) {
11
+ return normalizeLogoType(type) === 'image';
12
+ }
13
+ export function normalizeLogoDimension(value, fallback) {
14
+ if (typeof value === 'number') {
15
+ return Number.isFinite(value) && value >= 0 ? `${value}px` : fallback;
16
+ }
17
+ const raw = value?.trim();
18
+ if (!raw)
19
+ return fallback;
20
+ const lower = raw.toLowerCase();
21
+ if (SAFE_LOGO_KEYWORDS.has(lower))
22
+ return lower;
23
+ const match = lower.match(SAFE_LOGO_DIMENSION_PATTERN);
24
+ if (!match)
25
+ return fallback;
26
+ const amount = Number(match[1]);
27
+ if (!Number.isFinite(amount) || amount < 0)
28
+ return fallback;
29
+ return `${match[1]}${match[2].toLowerCase()}`;
30
+ }
31
+ export function normalizeLogoLayout(layout) {
32
+ return {
33
+ width: normalizeLogoDimension(layout?.width, FALLBACK_LOGO_WIDTH),
34
+ height: normalizeLogoDimension(layout?.height, FALLBACK_LOGO_HEIGHT),
35
+ };
36
+ }
37
+ export function normalizeFontSans(value) {
38
+ const raw = value?.trim();
39
+ if (!raw)
40
+ return FALLBACK_FONT;
41
+ if (!raw.includes(',')) {
42
+ return raw.replace(/['"`]/g, '').trim() || FALLBACK_FONT;
43
+ }
44
+ return raw.split(',')[0]?.replace(/['"`]/g, '').trim() || FALLBACK_FONT;
45
+ }
46
+ function quoteFontFamily(name) {
47
+ const clean = name.replace(/['"`]/g, '').trim();
48
+ if (!clean || clean === 'System') {
49
+ return 'ui-sans-serif, system-ui, sans-serif';
50
+ }
51
+ return `'${clean}', ui-sans-serif, system-ui, sans-serif`;
52
+ }
53
+ export function fontSansToCssFamily(value) {
54
+ return quoteFontFamily(normalizeFontSans(value));
55
+ }
56
+ export function normalizeBrandConfig(config) {
57
+ const logo = config.brand.logo;
58
+ const text = logo.text ?? { base: config.brand.name, accent: '' };
59
+ return {
60
+ ...config,
61
+ brand: {
62
+ ...config.brand,
63
+ logo: {
64
+ ...logo,
65
+ type: normalizeLogoType(logo.type),
66
+ imageUrl: logo.imageUrl ?? '',
67
+ darkImageUrl: logo.darkImageUrl ?? null,
68
+ layout: normalizeLogoLayout(logo.layout),
69
+ text: {
70
+ ...text,
71
+ base: text.base ?? config.brand.name,
72
+ accent: text.accent ?? '',
73
+ fontFamily: text.fontFamily ? normalizeFontSans(text.fontFamily) : text.fontFamily,
74
+ },
75
+ },
76
+ },
77
+ favicon: {
78
+ default: config.favicon?.default ?? FALLBACK_FAVICON,
79
+ dark: config.favicon?.dark ?? null,
80
+ },
81
+ theme: {
82
+ ...config.theme,
83
+ font: {
84
+ ...config.theme.font,
85
+ sans: normalizeFontSans(config.theme.font?.sans),
86
+ },
87
+ },
88
+ };
89
+ }
90
+ export function getLogoImageUrl(logo, mode = 'light') {
91
+ if (!isImageLogoType(logo.type))
92
+ return null;
93
+ const darkUrl = logo.darkImageUrl?.trim();
94
+ const defaultUrl = logo.imageUrl?.trim();
95
+ return (mode === 'dark' && darkUrl ? darkUrl : defaultUrl) || null;
96
+ }
97
+ export function getLogoTextFontFamily(config) {
98
+ return fontSansToCssFamily(config.brand.logo.text.fontFamily || config.theme.font.sans);
99
+ }
100
+ export function getLogoTextColors(config, mode) {
101
+ return config.brand.logo.text.colors?.[mode] ?? {};
102
+ }
103
+ function colorMixHex(a, b, t) {
104
+ const pa = parseHex(a);
105
+ const pb = parseHex(b);
106
+ if (!pa || !pb)
107
+ return a;
108
+ const r = Math.round(pa[0] + (pb[0] - pa[0]) * t);
109
+ const g = Math.round(pa[1] + (pb[1] - pa[1]) * t);
110
+ const bl = Math.round(pa[2] + (pb[2] - pa[2]) * t);
111
+ return `#${hex2(r)}${hex2(g)}${hex2(bl)}`;
112
+ }
113
+ function hexToRgbaValue(hex, alpha) {
114
+ const parsed = parseHex(hex);
115
+ if (!parsed)
116
+ return `rgba(128,128,128,${alpha})`;
117
+ return `rgba(${parsed[0]},${parsed[1]},${parsed[2]},${alpha})`;
118
+ }
119
+ function parseHex(hex) {
120
+ const clean = hex.replace('#', '');
121
+ const full = clean.length === 3
122
+ ? clean.split('').map((c) => c + c).join('')
123
+ : clean;
124
+ if (!/^[0-9a-fA-F]{6}$/.test(full))
125
+ return null;
126
+ return [
127
+ parseInt(full.slice(0, 2), 16),
128
+ parseInt(full.slice(2, 4), 16),
129
+ parseInt(full.slice(4, 6), 16),
130
+ ];
131
+ }
132
+ function hex2(n) {
133
+ return n.toString(16).padStart(2, '0');
134
+ }
135
+ function buildModeVars(colors, mode) {
136
+ const isDark = mode === 'dark';
137
+ const secondary = colorMixHex(colors.background, isDark ? '#ffffff' : '#000000', isDark ? 0.12 : 0.05);
138
+ const muted = colorMixHex(colors.background, isDark ? '#ffffff' : '#888888', isDark ? 0.08 : 0.07);
139
+ const accent = colorMixHex(colors.background, isDark ? '#ffffff' : '#000000', isDark ? 0.16 : 0.08);
140
+ const border = hexToRgbaValue(colors.foreground, isDark ? 0.12 : 0.1);
141
+ const mutedForeground = hexToRgbaValue(colors.foreground, isDark ? 0.68 : 0.62);
142
+ return {
143
+ '--primary': colors.primary,
144
+ '--primary-foreground': colors.primaryForeground,
145
+ '--background': colors.background,
146
+ '--foreground': colors.foreground,
147
+ '--card': colors.background,
148
+ '--card-foreground': colors.foreground,
149
+ '--secondary': secondary,
150
+ '--secondary-foreground': colors.foreground,
151
+ '--muted': muted,
152
+ '--muted-foreground': mutedForeground,
153
+ '--accent': accent,
154
+ '--accent-foreground': colors.foreground,
155
+ '--border': border,
156
+ '--ring': colors.primary,
157
+ '--sidebar': colors.background,
158
+ '--sidebar-foreground': colors.foreground,
159
+ '--sidebar-primary': colors.primary,
160
+ '--sidebar-primary-foreground': colors.primaryForeground,
161
+ '--sidebar-accent': accent,
162
+ '--sidebar-accent-foreground': colors.foreground,
163
+ '--sidebar-border': border,
164
+ };
165
+ }
166
+ export function getBrandCssVariables(config, mode) {
167
+ const colors = mode === 'dark' ? config.theme.colors.dark : config.theme.colors.light;
168
+ return {
169
+ ...buildModeVars(colors, mode),
170
+ '--radius': config.theme.radius,
171
+ };
172
+ }
173
+ function formatCssVars(vars) {
174
+ return Object.entries(vars)
175
+ .map(([key, value]) => ` ${key}: ${value};`)
176
+ .join('\n');
177
+ }
178
+ export function buildBrandCss(config) {
179
+ const normalized = normalizeBrandConfig(config);
180
+ const lightVars = getBrandCssVariables(normalized, 'light');
181
+ const darkVars = getBrandCssVariables(normalized, 'dark');
182
+ const fontFamily = fontSansToCssFamily(normalized.theme.font.sans);
183
+ return `
184
+ :root {
185
+ ${formatCssVars(lightVars)}
186
+ }
187
+
188
+ html[data-theme="dark"] {
189
+ ${formatCssVars(darkVars)}
190
+ }
191
+
192
+ html, body, button, input, select, textarea {
193
+ font-family: ${fontFamily};
194
+ }
195
+ `.trim();
196
+ }
197
+ export function resolveBrandThemeMode(config, preferredMode) {
198
+ if (preferredMode === 'light' || preferredMode === 'dark') {
199
+ return preferredMode;
200
+ }
201
+ const defaultMode = config.theme.defaultMode;
202
+ return defaultMode === 'dark' ? 'dark' : 'light';
203
+ }
204
+ function stripQueryAndHash(url) {
205
+ return url.split(/[?#]/)[0] ?? url;
206
+ }
207
+ export function inferImageMimeType(url) {
208
+ const lower = stripQueryAndHash(url).toLowerCase();
209
+ if (lower.endsWith('.svg'))
210
+ return 'image/svg+xml';
211
+ if (lower.endsWith('.png'))
212
+ return 'image/png';
213
+ if (lower.endsWith('.webp'))
214
+ return 'image/webp';
215
+ if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
216
+ return 'image/jpeg';
217
+ if (lower.endsWith('.ico'))
218
+ return 'image/x-icon';
219
+ return undefined;
220
+ }
221
+ function inferIconSizes(url) {
222
+ const path = stripQueryAndHash(url);
223
+ if (/\.(svg|ico)$/i.test(path))
224
+ return 'any';
225
+ const match = path.match(/(?:^|[^\d])(\d{2,4})x\1(?:[^\d]|$)|(?:^|[^\d])(\d{2,4})(?:[^\d]|$)/);
226
+ const size = match?.[1] ?? match?.[2];
227
+ return size ? `${size}x${size}` : 'any';
228
+ }
229
+ function buildManifestIcon(src) {
230
+ return {
231
+ src,
232
+ sizes: inferIconSizes(src),
233
+ type: inferImageMimeType(src),
234
+ purpose: 'any',
235
+ };
236
+ }
237
+ export function buildBrandManifest(config) {
238
+ const normalized = normalizeBrandConfig(config);
239
+ const mode = resolveBrandThemeMode(normalized);
240
+ const colors = mode === 'dark' ? normalized.theme.colors.dark : normalized.theme.colors.light;
241
+ const defaultFavicon = normalized.favicon.default || FALLBACK_FAVICON;
242
+ const darkFavicon = normalized.favicon.dark?.trim();
243
+ const icons = [buildManifestIcon(defaultFavicon)];
244
+ if (darkFavicon && darkFavicon !== defaultFavicon) {
245
+ icons.push(buildManifestIcon(darkFavicon));
246
+ }
247
+ return {
248
+ name: normalized.brand.name,
249
+ short_name: normalized.brand.name,
250
+ description: normalized.brand.description || normalized.brand.tagline || normalized.brand.name,
251
+ icons,
252
+ theme_color: colors.primary,
253
+ background_color: colors.background,
254
+ display: 'standalone',
255
+ start_url: '/',
256
+ };
257
+ }
258
+ export function getBrandFaviconUrl(favicon, mode = 'light') {
259
+ const defaultFavicon = favicon?.default || FALLBACK_FAVICON;
260
+ const darkFavicon = favicon?.dark?.trim();
261
+ return mode === 'dark' && darkFavicon ? darkFavicon : defaultFavicon;
262
+ }
263
+ export function buildBrandIconsMetadata(favicon, mode = 'light') {
264
+ const activeFavicon = getBrandFaviconUrl(favicon, mode);
265
+ return {
266
+ icon: [{
267
+ url: activeFavicon,
268
+ type: inferImageMimeType(activeFavicon),
269
+ media: 'all',
270
+ }],
271
+ shortcut: activeFavicon,
272
+ };
273
+ }
274
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/brand/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandConfig, BackendBrandLogoType, BrandLogoType, ColorSet } from './types.js';\n\nexport type BrandMode = 'light' | 'dark';\n\nexport interface BrandManifestIcon {\n src: string;\n sizes: string;\n type?: string;\n purpose?: 'any' | 'maskable' | 'monochrome';\n}\n\nexport interface BrandWebManifest {\n name: string;\n short_name: string;\n description: string;\n icons: BrandManifestIcon[];\n theme_color: string;\n background_color: string;\n display: 'standalone';\n start_url: string;\n}\n\nexport interface BrandIconDescriptor {\n url: string;\n type?: string;\n media?: string;\n}\n\nconst FALLBACK_FAVICON = '/favicon.ico';\nconst FALLBACK_FONT = 'Inter';\nconst FALLBACK_LOGO_WIDTH = '160px';\nconst FALLBACK_LOGO_HEIGHT = '40px';\nconst SAFE_LOGO_KEYWORDS = new Set(['auto', 'fit-content']);\nconst SAFE_LOGO_DIMENSION_PATTERN = /^(\\d+(?:\\.\\d+)?)(px|rem|em|%|vw|vh)$/i;\n\nexport interface NormalizedLogoLayout {\n width: string;\n height: string;\n}\n\nexport function normalizeLogoType(type: BackendBrandLogoType | string | null | undefined): BrandLogoType {\n return type === 'text' ? 'text' : 'image';\n}\n\nexport function isImageLogoType(type: BackendBrandLogoType | string | null | undefined): boolean {\n return normalizeLogoType(type) === 'image';\n}\n\nexport function normalizeLogoDimension(\n value: string | number | null | undefined,\n fallback: string,\n): string {\n if (typeof value === 'number') {\n return Number.isFinite(value) && value >= 0 ? `${value}px` : fallback;\n }\n\n const raw = value?.trim();\n if (!raw) return fallback;\n\n const lower = raw.toLowerCase();\n if (SAFE_LOGO_KEYWORDS.has(lower)) return lower;\n\n const match = lower.match(SAFE_LOGO_DIMENSION_PATTERN);\n if (!match) return fallback;\n\n const amount = Number(match[1]);\n if (!Number.isFinite(amount) || amount < 0) return fallback;\n\n return `${match[1]}${match[2].toLowerCase()}`;\n}\n\nexport function normalizeLogoLayout(\n layout: BrandConfig['brand']['logo']['layout'] | null | undefined,\n): NormalizedLogoLayout {\n return {\n width: normalizeLogoDimension(layout?.width, FALLBACK_LOGO_WIDTH),\n height: normalizeLogoDimension(layout?.height, FALLBACK_LOGO_HEIGHT),\n };\n}\n\nexport function normalizeFontSans(value: string | null | undefined): string {\n const raw = value?.trim();\n if (!raw) return FALLBACK_FONT;\n\n if (!raw.includes(',')) {\n return raw.replace(/['\"`]/g, '').trim() || FALLBACK_FONT;\n }\n\n return raw.split(',')[0]?.replace(/['\"`]/g, '').trim() || FALLBACK_FONT;\n}\n\nfunction quoteFontFamily(name: string): string {\n const clean = name.replace(/['\"`]/g, '').trim();\n if (!clean || clean === 'System') {\n return 'ui-sans-serif, system-ui, sans-serif';\n }\n\n return `'${clean}', ui-sans-serif, system-ui, sans-serif`;\n}\n\nexport function fontSansToCssFamily(value: string | null | undefined): string {\n return quoteFontFamily(normalizeFontSans(value));\n}\n\nexport function normalizeBrandConfig(config: BrandConfig): BrandConfig {\n const logo = config.brand.logo;\n const text = logo.text ?? { base: config.brand.name, accent: '' };\n\n return {\n ...config,\n brand: {\n ...config.brand,\n logo: {\n ...logo,\n type: normalizeLogoType(logo.type),\n imageUrl: logo.imageUrl ?? '',\n darkImageUrl: logo.darkImageUrl ?? null,\n layout: normalizeLogoLayout(logo.layout),\n text: {\n ...text,\n base: text.base ?? config.brand.name,\n accent: text.accent ?? '',\n fontFamily: text.fontFamily ? normalizeFontSans(text.fontFamily) : text.fontFamily,\n },\n },\n },\n favicon: {\n default: config.favicon?.default ?? FALLBACK_FAVICON,\n dark: config.favicon?.dark ?? null,\n },\n theme: {\n ...config.theme,\n font: {\n ...config.theme.font,\n sans: normalizeFontSans(config.theme.font?.sans),\n },\n },\n };\n}\n\nexport function getLogoImageUrl(\n logo: BrandConfig['brand']['logo'],\n mode: BrandMode = 'light',\n): string | null {\n if (!isImageLogoType(logo.type)) return null;\n\n const darkUrl = logo.darkImageUrl?.trim();\n const defaultUrl = logo.imageUrl?.trim();\n\n return (mode === 'dark' && darkUrl ? darkUrl : defaultUrl) || null;\n}\n\nexport function getLogoTextFontFamily(config: BrandConfig): string {\n return fontSansToCssFamily(config.brand.logo.text.fontFamily || config.theme.font.sans);\n}\n\nexport function getLogoTextColors(config: BrandConfig, mode: BrandMode) {\n return config.brand.logo.text.colors?.[mode] ?? {};\n}\n\nfunction colorMixHex(a: string, b: string, t: number): string {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return a;\n\n const r = Math.round(pa[0] + (pb[0] - pa[0]) * t);\n const g = Math.round(pa[1] + (pb[1] - pa[1]) * t);\n const bl = Math.round(pa[2] + (pb[2] - pa[2]) * t);\n\n return `#${hex2(r)}${hex2(g)}${hex2(bl)}`;\n}\n\nfunction hexToRgbaValue(hex: string, alpha: number): string {\n const parsed = parseHex(hex);\n if (!parsed) return `rgba(128,128,128,${alpha})`;\n\n return `rgba(${parsed[0]},${parsed[1]},${parsed[2]},${alpha})`;\n}\n\nfunction parseHex(hex: string): [number, number, number] | null {\n const clean = hex.replace('#', '');\n const full = clean.length === 3\n ? clean.split('').map((c) => c + c).join('')\n : clean;\n\n if (!/^[0-9a-fA-F]{6}$/.test(full)) return null;\n\n return [\n parseInt(full.slice(0, 2), 16),\n parseInt(full.slice(2, 4), 16),\n parseInt(full.slice(4, 6), 16),\n ];\n}\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, '0');\n}\n\nfunction buildModeVars(colors: ColorSet, mode: BrandMode) {\n const isDark = mode === 'dark';\n const secondary = colorMixHex(colors.background, isDark ? '#ffffff' : '#000000', isDark ? 0.12 : 0.05);\n const muted = colorMixHex(colors.background, isDark ? '#ffffff' : '#888888', isDark ? 0.08 : 0.07);\n const accent = colorMixHex(colors.background, isDark ? '#ffffff' : '#000000', isDark ? 0.16 : 0.08);\n const border = hexToRgbaValue(colors.foreground, isDark ? 0.12 : 0.1);\n const mutedForeground = hexToRgbaValue(colors.foreground, isDark ? 0.68 : 0.62);\n\n return {\n '--primary': colors.primary,\n '--primary-foreground': colors.primaryForeground,\n '--background': colors.background,\n '--foreground': colors.foreground,\n '--card': colors.background,\n '--card-foreground': colors.foreground,\n '--secondary': secondary,\n '--secondary-foreground': colors.foreground,\n '--muted': muted,\n '--muted-foreground': mutedForeground,\n '--accent': accent,\n '--accent-foreground': colors.foreground,\n '--border': border,\n '--ring': colors.primary,\n '--sidebar': colors.background,\n '--sidebar-foreground': colors.foreground,\n '--sidebar-primary': colors.primary,\n '--sidebar-primary-foreground': colors.primaryForeground,\n '--sidebar-accent': accent,\n '--sidebar-accent-foreground': colors.foreground,\n '--sidebar-border': border,\n };\n}\n\nexport function getBrandCssVariables(config: BrandConfig, mode: BrandMode) {\n const colors = mode === 'dark' ? config.theme.colors.dark : config.theme.colors.light;\n return {\n ...buildModeVars(colors, mode),\n '--radius': config.theme.radius,\n };\n}\n\nfunction formatCssVars(vars: Record<string, string>): string {\n return Object.entries(vars)\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n}\n\nexport function buildBrandCss(config: BrandConfig): string {\n const normalized = normalizeBrandConfig(config);\n const lightVars = getBrandCssVariables(normalized, 'light');\n const darkVars = getBrandCssVariables(normalized, 'dark');\n const fontFamily = fontSansToCssFamily(normalized.theme.font.sans);\n\n return `\n :root {\n${formatCssVars(lightVars)}\n }\n\n html[data-theme=\"dark\"] {\n${formatCssVars(darkVars)}\n }\n\n html, body, button, input, select, textarea {\n font-family: ${fontFamily};\n }\n `.trim();\n}\n\nexport function resolveBrandThemeMode(config: BrandConfig, preferredMode?: string | null): BrandMode {\n if (preferredMode === 'light' || preferredMode === 'dark') {\n return preferredMode;\n }\n\n const defaultMode = config.theme.defaultMode;\n return defaultMode === 'dark' ? 'dark' : 'light';\n}\n\nfunction stripQueryAndHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\nexport function inferImageMimeType(url: string): string | undefined {\n const lower = stripQueryAndHash(url).toLowerCase();\n if (lower.endsWith('.svg')) return 'image/svg+xml';\n if (lower.endsWith('.png')) return 'image/png';\n if (lower.endsWith('.webp')) return 'image/webp';\n if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg';\n if (lower.endsWith('.ico')) return 'image/x-icon';\n return undefined;\n}\n\nfunction inferIconSizes(url: string): string {\n const path = stripQueryAndHash(url);\n if (/\\.(svg|ico)$/i.test(path)) return 'any';\n\n const match = path.match(/(?:^|[^\\d])(\\d{2,4})x\\1(?:[^\\d]|$)|(?:^|[^\\d])(\\d{2,4})(?:[^\\d]|$)/);\n const size = match?.[1] ?? match?.[2];\n return size ? `${size}x${size}` : 'any';\n}\n\nfunction buildManifestIcon(src: string): BrandManifestIcon {\n return {\n src,\n sizes: inferIconSizes(src),\n type: inferImageMimeType(src),\n purpose: 'any',\n };\n}\n\nexport function buildBrandManifest(config: BrandConfig): BrandWebManifest {\n const normalized = normalizeBrandConfig(config);\n const mode = resolveBrandThemeMode(normalized);\n const colors = mode === 'dark' ? normalized.theme.colors.dark : normalized.theme.colors.light;\n const defaultFavicon = normalized.favicon.default || FALLBACK_FAVICON;\n const darkFavicon = normalized.favicon.dark?.trim();\n const icons = [buildManifestIcon(defaultFavicon)];\n\n if (darkFavicon && darkFavicon !== defaultFavicon) {\n icons.push(buildManifestIcon(darkFavicon));\n }\n\n return {\n name: normalized.brand.name,\n short_name: normalized.brand.name,\n description: normalized.brand.description || normalized.brand.tagline || normalized.brand.name,\n icons,\n theme_color: colors.primary,\n background_color: colors.background,\n display: 'standalone',\n start_url: '/',\n };\n}\n\nexport function getBrandFaviconUrl(\n favicon: BrandConfig['favicon'],\n mode: BrandMode = 'light',\n): string {\n const defaultFavicon = favicon?.default || FALLBACK_FAVICON;\n const darkFavicon = favicon?.dark?.trim();\n return mode === 'dark' && darkFavicon ? darkFavicon : defaultFavicon;\n}\n\nexport function buildBrandIconsMetadata(\n favicon: BrandConfig['favicon'],\n mode: BrandMode = 'light',\n) {\n const activeFavicon = getBrandFaviconUrl(favicon, mode);\n\n return {\n icon: [{\n url: activeFavicon,\n type: inferImageMimeType(activeFavicon),\n media: 'all',\n }],\n shortcut: activeFavicon,\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export type { BackendBrandLogoType, BrandConfig, BrandLogoType, ColorSet, DeepPartial, SecurityTxtConfig, } from './types.js';
2
+ export { buildBrandCss, buildBrandIconsMetadata, buildBrandManifest, fontSansToCssFamily, getBrandCssVariables, getBrandFaviconUrl, getLogoImageUrl, getLogoTextColors, getLogoTextFontFamily, inferImageMimeType, isImageLogoType, normalizeBrandConfig, normalizeFontSans, normalizeLogoDimension, normalizeLogoLayout, normalizeLogoType, resolveBrandThemeMode, } from './config.js';
3
+ export type { BrandIconDescriptor, BrandManifestIcon, BrandMode, BrandWebManifest, NormalizedLogoLayout, } from './config.js';
4
+ //# sourceMappingURL=index.d.ts.map