@pipelex/mthds-form 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brand/index.d.ts +170 -5
- package/dist/brand/index.js +274 -11
- package/dist/brand/index.js.map +1 -1
- package/dist/chunk-GHVVULHY.js +42 -0
- package/dist/chunk-GHVVULHY.js.map +1 -0
- package/dist/{chunk-4DPYGNJ3.js → chunk-IBBAJXIO.js} +3 -3
- package/dist/{chunk-4DPYGNJ3.js.map → chunk-IBBAJXIO.js.map} +1 -1
- package/dist/{chunk-II5TOORN.js → chunk-JCFALTTY.js} +285 -142
- package/dist/chunk-JCFALTTY.js.map +1 -0
- package/dist/core/gate.js +2 -2
- package/dist/core/humanize.d.ts +77 -0
- package/dist/core/humanize.js +3 -0
- package/dist/core/humanize.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +3 -2
- package/dist/generative/index.js +73 -35
- package/dist/generative/index.js.map +1 -1
- package/dist/react/index.d.ts +16 -18
- package/dist/react/index.js +2 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/chunk-II5TOORN.js.map +0 -1
package/dist/brand/index.d.ts
CHANGED
|
@@ -361,7 +361,10 @@ interface AccentRoles {
|
|
|
361
361
|
accent: string;
|
|
362
362
|
/** A pale accent fill (deep on a dark page), behind accent ink: a pill, a badge. */
|
|
363
363
|
accentFill: string;
|
|
364
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* The accent as text, reading at 4.5:1 or more on the page, on `accentFill`
|
|
366
|
+
* and on every other surface the host named (`deriveBrandRoles`' `surfaces`).
|
|
367
|
+
*/
|
|
365
368
|
accentInk: string;
|
|
366
369
|
}
|
|
367
370
|
/** The header band's roles, present when the header is set. */
|
|
@@ -385,8 +388,17 @@ type BrandRoles = (ActionRoles | Absent<ActionRoles>) & (AccentRoles | Absent<Ac
|
|
|
385
388
|
* The roles a page paints with, from up to three seeds and the colour of the
|
|
386
389
|
* page they render on. Throws when a seed or the page is not a `#rrggbb`
|
|
387
390
|
* colour: a host validates what a person typed before it reaches here.
|
|
391
|
+
*
|
|
392
|
+
* `surfaces` are the other colours accent text sits on besides the page and
|
|
393
|
+
* the pale fill, each `#rrggbb`: a style's panel and the fields inside it
|
|
394
|
+
* (`resolveRoleColor` turns a style's role colour into one), or a canvas
|
|
395
|
+
* behind the page. The ink is moved until it reads on all of them. When
|
|
396
|
+
* there is an accent to derive, a surface on which not even the page's own
|
|
397
|
+
* text extreme, pure black on a light page or pure white on a dark one,
|
|
398
|
+
* reaches 4.5:1 throws, since no ink moving the page's way could read there.
|
|
399
|
+
* With no accent, no ink is derived, and the surfaces are only parsed.
|
|
388
400
|
*/
|
|
389
|
-
declare function deriveBrandRoles(seeds: BrandSeeds | null | undefined, page: string): BrandRoles;
|
|
401
|
+
declare function deriveBrandRoles(seeds: BrandSeeds | null | undefined, page: string, surfaces?: readonly string[]): BrandRoles;
|
|
390
402
|
/**
|
|
391
403
|
* Whether the button colour sits below 3:1 against the page, so a button may
|
|
392
404
|
* be hard to see in that theme. Only a colour the person set is judged: with
|
|
@@ -395,11 +407,12 @@ declare function deriveBrandRoles(seeds: BrandSeeds | null | undefined, page: st
|
|
|
395
407
|
declare function isActionFaint(seeds: BrandSeeds | null | undefined, page: string): boolean;
|
|
396
408
|
/**
|
|
397
409
|
* Whether the accent had to be darkened or lightened to become ink on the
|
|
398
|
-
* page
|
|
410
|
+
* page (and on the `surfaces`, as `deriveBrandRoles` takes them), so links read
|
|
411
|
+
* in a shade of it rather than in the colour itself. Only
|
|
399
412
|
* a colour the person set is judged, and the accent counts as set when it
|
|
400
413
|
* follows a button colour the person set: with neither, the answer is `false`.
|
|
401
414
|
*/
|
|
402
|
-
declare function isAccentAdjusted(seeds: BrandSeeds | null | undefined, page: string): boolean;
|
|
415
|
+
declare function isAccentAdjusted(seeds: BrandSeeds | null | undefined, page: string, surfaces?: readonly string[]): boolean;
|
|
403
416
|
/**
|
|
404
417
|
* The seeds a dark theme uses: each dark colour where one is set, and the
|
|
405
418
|
* light one where it is not. Colour by colour, before the accent follows the
|
|
@@ -411,4 +424,156 @@ declare function resolveDarkSeeds(colors: BrandSeeds | null | undefined, darkCol
|
|
|
411
424
|
/** Pure black or pure white, whichever reads better on a `#rrggbb` fill. */
|
|
412
425
|
declare function readableTextOn(fill: string): TextOnFill;
|
|
413
426
|
|
|
414
|
-
|
|
427
|
+
/**
|
|
428
|
+
* The app styles and the typefaces, as data, and the roles a team's choice of
|
|
429
|
+
* the two sets.
|
|
430
|
+
*
|
|
431
|
+
* A style is a small set of design values a team picks from a closed list, on
|
|
432
|
+
* top of its own colours and logo: how its headings, its panels, its corners,
|
|
433
|
+
* its call to action and its rhythm look. A typeface is picked from a short
|
|
434
|
+
* list beside it, and replaces the style's body face, and its heading face
|
|
435
|
+
* wherever the style's headings follow the body. Neither adds a colour: every
|
|
436
|
+
* fill, edge and ink a style uses is a role the theme contract already has,
|
|
437
|
+
* named here by that role (`RoleColor`), or a shadow.
|
|
438
|
+
*
|
|
439
|
+
* `deriveStyleRoles` returns roles, never custom property names, the way
|
|
440
|
+
* `deriveBrandRoles` does for the seed colours: each host maps them onto its
|
|
441
|
+
* own tokens, and the kernel's controls and the generative layer read the few
|
|
442
|
+
* optional tokens docs/theming.md lists, each with today's look as its
|
|
443
|
+
* fallback. A role a choice does not set is not a key, so a host keeps its own
|
|
444
|
+
* value there; `quiet` with no typeface sets nothing at all, which is what
|
|
445
|
+
* makes "no style" and "the default style" one state.
|
|
446
|
+
*
|
|
447
|
+
* See docs/brand.md § "App styles and typefaces".
|
|
448
|
+
*/
|
|
449
|
+
/** The faces of the closed list, all on Google Fonts under the SIL Open Font License. */
|
|
450
|
+
type TypefaceId = 'inter' | 'dm-sans' | 'plus-jakarta-sans' | 'manrope' | 'ibm-plex-sans' | 'source-sans-3' | 'work-sans' | 'space-grotesk' | 'newsreader';
|
|
451
|
+
/** The faces a team may pick as its typeface: every face but a style's own heading face. */
|
|
452
|
+
type BodyTypefaceId = Exclude<TypefaceId, 'newsreader'>;
|
|
453
|
+
interface Typeface {
|
|
454
|
+
id: TypefaceId;
|
|
455
|
+
/** The family name, as a stylesheet and a font service name it. */
|
|
456
|
+
family: string;
|
|
457
|
+
category: 'sans-serif' | 'serif';
|
|
458
|
+
/** The weights a host loads it in: from the host's own light titles (300) to the boldest a style sets (700). */
|
|
459
|
+
weights: readonly number[];
|
|
460
|
+
/** The generic stack that renders while the face loads, or when it never does. */
|
|
461
|
+
fallback: string;
|
|
462
|
+
/** The face first, then its fallback: a whole `font-family` value. */
|
|
463
|
+
stack: string;
|
|
464
|
+
/** Whether a team may pick it (`body`), or a style uses it for its headings only (`heading`). */
|
|
465
|
+
use: 'body' | 'heading';
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Every face a style or a team can set. The body faces are the list a team
|
|
469
|
+
* picks from; Newsreader is the `editorial` style's heading face, listed so a
|
|
470
|
+
* host loads it, and is not a team's choice. Frozen, like `APP_STYLES`: a
|
|
471
|
+
* process serves many teams, so the catalogue one of them reads cannot be
|
|
472
|
+
* one another has edited.
|
|
473
|
+
*/
|
|
474
|
+
declare const TYPEFACES: readonly Typeface[];
|
|
475
|
+
/** Whether `value` names a face a team may pick: a body face of `TYPEFACES`. */
|
|
476
|
+
declare function isTypeface(value: unknown): value is BodyTypefaceId;
|
|
477
|
+
/**
|
|
478
|
+
* A colour a style paints with, named by the role of the theme contract it
|
|
479
|
+
* is: never a colour of its own, so a team's palette and the host's theme
|
|
480
|
+
* stay the only sources of colour, and their readability guarantees hold.
|
|
481
|
+
*/
|
|
482
|
+
interface RoleColor {
|
|
483
|
+
role: ColorTokenName;
|
|
484
|
+
/** Mixed toward another role, in Oklab, by `share` (0 to 1): the card a little toward the foreground. */
|
|
485
|
+
toward?: {
|
|
486
|
+
role: ColorTokenName;
|
|
487
|
+
share: number;
|
|
488
|
+
};
|
|
489
|
+
/** Its opacity over what lies behind it, 0 to 1; absent, opaque. */
|
|
490
|
+
alpha?: number;
|
|
491
|
+
}
|
|
492
|
+
/** How a style draws a panel - a host's composer, a designed page's rail or card - in one theme. */
|
|
493
|
+
interface PanelRoles {
|
|
494
|
+
fill: RoleColor;
|
|
495
|
+
/** The edge's colour, or `null` for no visible edge (the width is kept, so a panel's box does not move). */
|
|
496
|
+
edge: RoleColor | null;
|
|
497
|
+
/** The edge's width, in CSS pixels. */
|
|
498
|
+
edgeWidth: number;
|
|
499
|
+
/** A CSS `box-shadow` in a neutral ink, or `null` for none. */
|
|
500
|
+
shadow: string | null;
|
|
501
|
+
/**
|
|
502
|
+
* The fill of the fields set into the panel, where the host's field fill
|
|
503
|
+
* would sink into it; absent, the host's own.
|
|
504
|
+
*/
|
|
505
|
+
field?: RoleColor;
|
|
506
|
+
}
|
|
507
|
+
/** The call to action's shadow: a CSS shadow's geometry, in pixels, tinted with a role. */
|
|
508
|
+
interface ActionShadow {
|
|
509
|
+
x: number;
|
|
510
|
+
y: number;
|
|
511
|
+
blur: number;
|
|
512
|
+
spread: number;
|
|
513
|
+
color: RoleColor;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The roles a style and a typeface set. An absent role is not a key: a host
|
|
517
|
+
* keeps its own value there.
|
|
518
|
+
*/
|
|
519
|
+
interface StyleRoles {
|
|
520
|
+
/** The face of everything on the page. */
|
|
521
|
+
bodyFace?: Typeface;
|
|
522
|
+
/** The face of the page's titles and its sections' and panels' headings. */
|
|
523
|
+
headingFace?: Typeface;
|
|
524
|
+
headingWeight?: number;
|
|
525
|
+
/** The headings' letter spacing, in `em`. */
|
|
526
|
+
headingTracking?: string;
|
|
527
|
+
/** A control's corner radius, in CSS pixels; a panel takes one and a half of it. */
|
|
528
|
+
radius?: number;
|
|
529
|
+
/** The panel treatment in each theme: a shadow that reads on a light page is lost on a dark one. */
|
|
530
|
+
panel?: {
|
|
531
|
+
light: PanelRoles;
|
|
532
|
+
dark: PanelRoles;
|
|
533
|
+
};
|
|
534
|
+
/**
|
|
535
|
+
* The call to action's shadow in each theme, or `none` for a plain fill: a
|
|
536
|
+
* shadow in the button colour that lifts a button on a light page smears
|
|
537
|
+
* under it on a dark one.
|
|
538
|
+
*/
|
|
539
|
+
actionShadow?: {
|
|
540
|
+
light: ActionShadow | 'none';
|
|
541
|
+
dark: ActionShadow | 'none';
|
|
542
|
+
};
|
|
543
|
+
/** The rhythm: a multiple of the host's own margins, paddings and gaps around the form. Never a control's size. */
|
|
544
|
+
density?: number;
|
|
545
|
+
}
|
|
546
|
+
type AppStyleId = 'quiet' | 'soft' | 'editorial' | 'bold';
|
|
547
|
+
interface AppStyle {
|
|
548
|
+
id: AppStyleId;
|
|
549
|
+
name: string;
|
|
550
|
+
/** The direction, in one sentence, as a designer reads it. */
|
|
551
|
+
direction: string;
|
|
552
|
+
/** The roles the style sets when the team picks no typeface. */
|
|
553
|
+
roles: StyleRoles;
|
|
554
|
+
}
|
|
555
|
+
declare const APP_STYLE_IDS: readonly AppStyleId[];
|
|
556
|
+
/** Whether `value` names one of the app styles. */
|
|
557
|
+
declare function isAppStyle(value: unknown): value is AppStyleId;
|
|
558
|
+
/**
|
|
559
|
+
* The roles a team's choice sets: its style and its typeface, each a stored
|
|
560
|
+
* id or nothing. An unknown or absent style is `quiet`, which sets nothing;
|
|
561
|
+
* an unknown typeface, or one a team may not pick, is no typeface, so the
|
|
562
|
+
* style's own face (or the host's) stays. The team's typeface replaces the
|
|
563
|
+
* style's body face, and its heading face wherever the style's headings
|
|
564
|
+
* follow the body.
|
|
565
|
+
*/
|
|
566
|
+
declare function deriveStyleRoles(style: string | null | undefined, typeface?: string | null): StyleRoles;
|
|
567
|
+
/** The styles, each with its own roles: what it sets for a team that picked no typeface. */
|
|
568
|
+
declare const APP_STYLES: Readonly<Record<AppStyleId, AppStyle>>;
|
|
569
|
+
/**
|
|
570
|
+
* A role colour as a `#rrggbb` hex over the palette it names, to measure a
|
|
571
|
+
* surface a style draws: the ink a team's accent needs on a style's panel
|
|
572
|
+
* (`deriveBrandRoles`' `surfaces`). The mix is Oklab's, as CSS `color-mix()`
|
|
573
|
+
* computes it. A translucent colour is laid over `behind`; without it, it
|
|
574
|
+
* throws, since a translucent colour has no one value. Throws, too, for a
|
|
575
|
+
* role the palette does not hold or a palette colour that is not a hex.
|
|
576
|
+
*/
|
|
577
|
+
declare function resolveRoleColor(color: RoleColor, palette: Partial<Record<ColorTokenName, string>>, behind?: string): string;
|
|
578
|
+
|
|
579
|
+
export { APP_STYLES, APP_STYLE_IDS, type ActionShadow, type AppStyle, type AppStyleId, type AssembleResult, type AssembledBrand, BRAND_CONTRACT, BRAND_PRODUCERS, BRAND_VARIABLES, type BodyTypefaceId, BrandManifest, type BrandProducer, type BrandProvenance, type BrandRoles, type BrandSeeds, type BrandSource, type BrandTokens, COLOR_TOKEN_NAMES, CONTRAST_PAIRS, type ColorMode, type ColorToken, type ColorTokenName, type ColorValue, type ContractToken, type ContractTokenType, type DimensionToken, type FontFamilyToken, MIN_CONTRAST, MIN_CONTROL_CONTRAST, type PanelRoles, type RoleColor, type SrgbColor, type StatedFacts, type StyleRoles, TYPEFACES, type TextOnFill, type TokensValidation, type Typeface, type TypefaceId, assembleBrand, brandProducerId, brandProvenanceSchema, brandScope, colorHex, colorIsHex, compileBrand, contractVariable, contrastRatio, deriveBrandRoles, deriveStyleRoles, isAccentAdjusted, isActionFaint, isAppStyle, isColorTokenName, isTypeface, readableTextOn, resolveColor, resolveDarkSeeds, resolveRoleColor, srgbColorSchema, statedFactProblems, statedFactsSchema, validateBrandTokens };
|
package/dist/brand/index.js
CHANGED
|
@@ -201,10 +201,11 @@ function resolveColor(tokens, name, mode) {
|
|
|
201
201
|
current = target;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
+
function linearChannel(channel) {
|
|
205
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
206
|
+
}
|
|
204
207
|
function luminance(color) {
|
|
205
|
-
const [r, g, b] = color.components.map(
|
|
206
|
-
(channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
|
|
207
|
-
);
|
|
208
|
+
const [r, g, b] = color.components.map(linearChannel);
|
|
208
209
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
209
210
|
}
|
|
210
211
|
function contrastRatio(a, b) {
|
|
@@ -486,20 +487,28 @@ function assembleBrand(source) {
|
|
|
486
487
|
var MIN_CONTROL_CONTRAST = 3;
|
|
487
488
|
var ACCENT_FILL_STRENGTH = 0.12;
|
|
488
489
|
var SEARCH_STEPS = 32;
|
|
489
|
-
function deriveBrandRoles(seeds, page) {
|
|
490
|
+
function deriveBrandRoles(seeds, page, surfaces = []) {
|
|
490
491
|
const canvas = parseHex(page, "The page colour");
|
|
492
|
+
const extreme = textOn(canvas) === "#000000" ? BLACK : WHITE;
|
|
493
|
+
const others = surfaces.map((surface, index) => parseHex(surface, `Surface ${index + 1}`));
|
|
491
494
|
const action = seeds?.action == null ? null : parseHex(seeds.action, "The action seed");
|
|
492
495
|
const accent = seeds?.accent == null ? action : parseHex(seeds.accent, "The accent seed");
|
|
493
496
|
const header = seeds?.header == null ? null : parseHex(seeds.header, "The header seed");
|
|
494
497
|
const actionRoles = action === null ? {} : { action: colorHex(action), onAction: textOn(action) };
|
|
495
|
-
const accentRoles = accent === null ? {} : accentRolesOf(accent, canvas);
|
|
498
|
+
const accentRoles = accent === null ? {} : accentRolesOf(accent, canvas, extreme, others);
|
|
496
499
|
const headerRoles = header === null ? {} : { header: colorHex(header), onHeader: textOn(header) };
|
|
497
500
|
return { ...actionRoles, ...accentRoles, ...headerRoles };
|
|
498
501
|
}
|
|
499
|
-
function accentRolesOf(accent, canvas) {
|
|
500
|
-
const
|
|
502
|
+
function accentRolesOf(accent, canvas, extreme, surfaces) {
|
|
503
|
+
for (const surface of surfaces) {
|
|
504
|
+
if (contrastRatio(extreme, surface) < MIN_CONTRAST) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`No accent ink can read on ${colorHex(surface)}: not even ${colorHex(extreme)}, the page's own text extreme, reaches ${MIN_CONTRAST}:1 there.`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
501
510
|
const accentFill = paleFill(accent, canvas, extreme);
|
|
502
|
-
const accentInk = inkFor(accent, [canvas, accentFill], extreme);
|
|
511
|
+
const accentInk = inkFor(accent, [canvas, accentFill, ...surfaces], extreme);
|
|
503
512
|
return {
|
|
504
513
|
accent: colorHex(accent),
|
|
505
514
|
accentFill: colorHex(accentFill),
|
|
@@ -511,8 +520,8 @@ function isActionFaint(seeds, page) {
|
|
|
511
520
|
if (seeds?.action == null) return false;
|
|
512
521
|
return contrastRatio(parseHex(seeds.action, "The action seed"), canvas) < MIN_CONTROL_CONTRAST;
|
|
513
522
|
}
|
|
514
|
-
function isAccentAdjusted(seeds, page) {
|
|
515
|
-
const roles = deriveBrandRoles(seeds, page);
|
|
523
|
+
function isAccentAdjusted(seeds, page, surfaces = []) {
|
|
524
|
+
const roles = deriveBrandRoles(seeds, page, surfaces);
|
|
516
525
|
return roles.accentInk !== void 0 && roles.accentInk !== roles.accent;
|
|
517
526
|
}
|
|
518
527
|
function resolveDarkSeeds(colors, darkColors) {
|
|
@@ -594,6 +603,260 @@ function fromHsl(hue, saturation, lightness) {
|
|
|
594
603
|
return [r + offset, g + offset, b + offset];
|
|
595
604
|
}
|
|
596
605
|
|
|
597
|
-
|
|
606
|
+
// src/brand/styles.ts
|
|
607
|
+
var SANS_FALLBACK = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
|
|
608
|
+
var SERIF_FALLBACK = 'ui-serif, Georgia, Cambria, "Times New Roman", serif';
|
|
609
|
+
var WEIGHTS = [300, 400, 500, 600, 700];
|
|
610
|
+
function face(id, family, category, use) {
|
|
611
|
+
const fallback = category === "serif" ? SERIF_FALLBACK : SANS_FALLBACK;
|
|
612
|
+
return {
|
|
613
|
+
id,
|
|
614
|
+
family,
|
|
615
|
+
category,
|
|
616
|
+
weights: WEIGHTS,
|
|
617
|
+
fallback,
|
|
618
|
+
stack: `"${family}", ${fallback}`,
|
|
619
|
+
use
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
var TYPEFACES = deepFreeze([
|
|
623
|
+
face("inter", "Inter", "sans-serif", "body"),
|
|
624
|
+
face("dm-sans", "DM Sans", "sans-serif", "body"),
|
|
625
|
+
face("plus-jakarta-sans", "Plus Jakarta Sans", "sans-serif", "body"),
|
|
626
|
+
face("manrope", "Manrope", "sans-serif", "body"),
|
|
627
|
+
face("ibm-plex-sans", "IBM Plex Sans", "sans-serif", "body"),
|
|
628
|
+
face("source-sans-3", "Source Sans 3", "sans-serif", "body"),
|
|
629
|
+
face("work-sans", "Work Sans", "sans-serif", "body"),
|
|
630
|
+
face("space-grotesk", "Space Grotesk", "sans-serif", "body"),
|
|
631
|
+
face("newsreader", "Newsreader", "serif", "heading")
|
|
632
|
+
]);
|
|
633
|
+
var FACES = new Map(TYPEFACES.map((candidate) => [candidate.id, candidate]));
|
|
634
|
+
function isTypeface(value) {
|
|
635
|
+
return typeof value === "string" && FACES.get(value)?.use === "body";
|
|
636
|
+
}
|
|
637
|
+
function faceOf(id) {
|
|
638
|
+
return FACES.get(id);
|
|
639
|
+
}
|
|
640
|
+
var role = (name, more = {}) => ({
|
|
641
|
+
role: name,
|
|
642
|
+
...more
|
|
643
|
+
});
|
|
644
|
+
var DEFINITIONS = {
|
|
645
|
+
// The host's own look, named: it sets nothing, and a team's typeface is its
|
|
646
|
+
// body and its headings.
|
|
647
|
+
quiet: {
|
|
648
|
+
name: "Quiet",
|
|
649
|
+
direction: "Calm and precise: light titles, hairline panels, generous air, nothing decorated.",
|
|
650
|
+
bodyFace: null,
|
|
651
|
+
headingFace: "body",
|
|
652
|
+
roles: {}
|
|
653
|
+
},
|
|
654
|
+
soft: {
|
|
655
|
+
name: "Soft",
|
|
656
|
+
direction: "Friendly and rounded: generous corners, panels that float on a soft shadow with no outline, warm medium-weight titles.",
|
|
657
|
+
bodyFace: "dm-sans",
|
|
658
|
+
headingFace: "body",
|
|
659
|
+
roles: {
|
|
660
|
+
headingWeight: 500,
|
|
661
|
+
headingTracking: "-0.015em",
|
|
662
|
+
radius: 12,
|
|
663
|
+
panel: {
|
|
664
|
+
light: {
|
|
665
|
+
fill: role("card"),
|
|
666
|
+
edge: null,
|
|
667
|
+
edgeWidth: 1,
|
|
668
|
+
shadow: "0 1px 2px rgb(16 24 40 / 0.05), 0 12px 32px -10px rgb(16 24 40 / 0.16)",
|
|
669
|
+
field: role("card")
|
|
670
|
+
},
|
|
671
|
+
// On a dark page a shadow barely shows, so the panel is lifted by its
|
|
672
|
+
// fill instead, the card a little toward the foreground, with a faint
|
|
673
|
+
// highlight along its top edge where light would catch it, and its
|
|
674
|
+
// fields sink back to the page's own background.
|
|
675
|
+
dark: {
|
|
676
|
+
fill: role("card", { toward: { role: "foreground", share: 0.07 } }),
|
|
677
|
+
edge: null,
|
|
678
|
+
edgeWidth: 1,
|
|
679
|
+
shadow: "inset 0 1px 0 rgb(255 255 255 / 0.07), 0 2px 4px rgb(0 0 0 / 0.55), 0 24px 56px -12px rgb(0 0 0 / 0.95)",
|
|
680
|
+
field: role("background")
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
// A short shadow in the button colour on a light page, kept within the
|
|
684
|
+
// gap a host leaves under its button. On a dark page the panel is lifted
|
|
685
|
+
// by its fill, and a glow in a light button colour reads as a smear.
|
|
686
|
+
actionShadow: {
|
|
687
|
+
light: { x: 0, y: 6, blur: 14, spread: -6, color: role("primary", { alpha: 0.5 }) },
|
|
688
|
+
dark: "none"
|
|
689
|
+
},
|
|
690
|
+
density: 1.08
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
editorial: {
|
|
694
|
+
name: "Editorial",
|
|
695
|
+
direction: "Considered and literary: serif display titles, fine hairlines, wide margins and a reading rhythm.",
|
|
696
|
+
bodyFace: "source-sans-3",
|
|
697
|
+
headingFace: "newsreader",
|
|
698
|
+
roles: {
|
|
699
|
+
headingWeight: 500,
|
|
700
|
+
headingTracking: "-0.01em",
|
|
701
|
+
radius: 2,
|
|
702
|
+
// Hairlines all the way in: the fields inside the panel take its own
|
|
703
|
+
// fill, so each reads as its fine edge rather than as a grey block.
|
|
704
|
+
panel: {
|
|
705
|
+
light: {
|
|
706
|
+
fill: role("card"),
|
|
707
|
+
edge: role("border"),
|
|
708
|
+
edgeWidth: 1,
|
|
709
|
+
shadow: null,
|
|
710
|
+
field: role("card")
|
|
711
|
+
},
|
|
712
|
+
dark: {
|
|
713
|
+
fill: role("card"),
|
|
714
|
+
edge: role("border"),
|
|
715
|
+
edgeWidth: 1,
|
|
716
|
+
shadow: null,
|
|
717
|
+
field: role("card")
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
actionShadow: { light: "none", dark: "none" },
|
|
721
|
+
density: 1.25
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
bold: {
|
|
725
|
+
name: "Bold",
|
|
726
|
+
direction: "Confident and graphic: heavy, tightly set titles, solid panels with firm edges, a compact rhythm.",
|
|
727
|
+
bodyFace: "space-grotesk",
|
|
728
|
+
headingFace: "body",
|
|
729
|
+
roles: {
|
|
730
|
+
headingWeight: 700,
|
|
731
|
+
headingTracking: "-0.03em",
|
|
732
|
+
radius: 6,
|
|
733
|
+
// One firm edge around a solid panel, softer on a dark page. The panel
|
|
734
|
+
// is the muted fill, which the host's fields all but match, so fields
|
|
735
|
+
// set into it take the card on a light page and the page's background
|
|
736
|
+
// on a dark one.
|
|
737
|
+
panel: {
|
|
738
|
+
light: {
|
|
739
|
+
fill: role("muted"),
|
|
740
|
+
edge: role("foreground", { alpha: 0.62 }),
|
|
741
|
+
edgeWidth: 2,
|
|
742
|
+
shadow: null,
|
|
743
|
+
field: role("card")
|
|
744
|
+
},
|
|
745
|
+
dark: {
|
|
746
|
+
fill: role("muted"),
|
|
747
|
+
edge: role("foreground", { alpha: 0.3 }),
|
|
748
|
+
edgeWidth: 2,
|
|
749
|
+
shadow: null,
|
|
750
|
+
field: role("background")
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
actionShadow: { light: "none", dark: "none" },
|
|
754
|
+
density: 0.85
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
var APP_STYLE_IDS = Object.freeze([
|
|
759
|
+
"quiet",
|
|
760
|
+
"soft",
|
|
761
|
+
"editorial",
|
|
762
|
+
"bold"
|
|
763
|
+
]);
|
|
764
|
+
function isAppStyle(value) {
|
|
765
|
+
return typeof value === "string" && Object.prototype.hasOwnProperty.call(DEFINITIONS, value);
|
|
766
|
+
}
|
|
767
|
+
function deriveStyleRoles(style, typeface) {
|
|
768
|
+
const definition = DEFINITIONS[isAppStyle(style) ? style : "quiet"];
|
|
769
|
+
const roles = {};
|
|
770
|
+
const body = isTypeface(typeface) ? faceOf(typeface) : definition.bodyFace === null ? void 0 : faceOf(definition.bodyFace);
|
|
771
|
+
if (body) roles.bodyFace = body;
|
|
772
|
+
const heading = definition.headingFace === "body" ? body : faceOf(definition.headingFace);
|
|
773
|
+
if (heading) roles.headingFace = heading;
|
|
774
|
+
return copyData({ ...roles, ...definition.roles });
|
|
775
|
+
}
|
|
776
|
+
var APP_STYLES = deepFreeze(
|
|
777
|
+
Object.fromEntries(
|
|
778
|
+
APP_STYLE_IDS.map((id) => [
|
|
779
|
+
id,
|
|
780
|
+
{
|
|
781
|
+
id,
|
|
782
|
+
name: DEFINITIONS[id].name,
|
|
783
|
+
direction: DEFINITIONS[id].direction,
|
|
784
|
+
roles: deriveStyleRoles(id)
|
|
785
|
+
}
|
|
786
|
+
])
|
|
787
|
+
)
|
|
788
|
+
);
|
|
789
|
+
function copyData(value) {
|
|
790
|
+
if (Array.isArray(value)) return value.map(copyData);
|
|
791
|
+
if (value !== null && typeof value === "object") {
|
|
792
|
+
return Object.fromEntries(
|
|
793
|
+
Object.entries(value).map(([key, inner]) => [key, copyData(inner)])
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
return value;
|
|
797
|
+
}
|
|
798
|
+
function deepFreeze(value) {
|
|
799
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
800
|
+
for (const inner of Object.values(value)) deepFreeze(inner);
|
|
801
|
+
Object.freeze(value);
|
|
802
|
+
}
|
|
803
|
+
return value;
|
|
804
|
+
}
|
|
805
|
+
function resolveRoleColor(color, palette, behind) {
|
|
806
|
+
const of = (name) => {
|
|
807
|
+
const value = palette[name];
|
|
808
|
+
if (!isColorTokenName(name) || value === void 0) {
|
|
809
|
+
throw new Error(`The palette has no ${name} to resolve a style's colour against.`);
|
|
810
|
+
}
|
|
811
|
+
return parseHex(value, `The palette's ${name}`);
|
|
812
|
+
};
|
|
813
|
+
let rgb = of(color.role).components;
|
|
814
|
+
if (color.toward) {
|
|
815
|
+
const share = clampUnit(color.toward.share);
|
|
816
|
+
rgb = mixOklab(rgb, of(color.toward.role).components, share);
|
|
817
|
+
}
|
|
818
|
+
if (color.alpha !== void 0 && color.alpha < 1) {
|
|
819
|
+
if (behind === void 0) {
|
|
820
|
+
throw new Error(`A translucent ${color.role} has no one colour: say what it lies over.`);
|
|
821
|
+
}
|
|
822
|
+
const under = parseHex(behind, "The colour behind").components;
|
|
823
|
+
const alpha = clampUnit(color.alpha);
|
|
824
|
+
rgb = rgb.map((channel, index) => under[index] + (channel - under[index]) * alpha);
|
|
825
|
+
}
|
|
826
|
+
return colorHex({ components: rgb});
|
|
827
|
+
}
|
|
828
|
+
function clampUnit(value) {
|
|
829
|
+
return Math.min(1, Math.max(0, value));
|
|
830
|
+
}
|
|
831
|
+
function mixOklab(a, b, share) {
|
|
832
|
+
const [l1, a1, b1] = toOklab(a);
|
|
833
|
+
const [l2, a2, b2] = toOklab(b);
|
|
834
|
+
const at = (from, to) => from + (to - from) * share;
|
|
835
|
+
return fromOklab([at(l1, l2), at(a1, a2), at(b1, b2)]).map(clampUnit);
|
|
836
|
+
}
|
|
837
|
+
var fromLinear = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
838
|
+
function toOklab(rgb) {
|
|
839
|
+
const [r, g, b] = rgb.map(linearChannel);
|
|
840
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
841
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
842
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
843
|
+
return [
|
|
844
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
845
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
846
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
847
|
+
];
|
|
848
|
+
}
|
|
849
|
+
function fromOklab([lightness, a, b]) {
|
|
850
|
+
const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
851
|
+
const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
852
|
+
const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
853
|
+
return [
|
|
854
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
855
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
856
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
857
|
+
].map((channel) => fromLinear(clampUnit(channel)));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export { APP_STYLES, APP_STYLE_IDS, BRAND_CONTRACT, BRAND_PRODUCERS, BRAND_VARIABLES, COLOR_TOKEN_NAMES, CONTRAST_PAIRS, MIN_CONTRAST, MIN_CONTROL_CONTRAST, TYPEFACES, assembleBrand, brandProducerId, brandProvenanceSchema, brandScope, colorHex, colorIsHex, compileBrand, contractVariable, contrastRatio, deriveBrandRoles, deriveStyleRoles, isAccentAdjusted, isActionFaint, isAppStyle, isColorTokenName, isTypeface, readableTextOn, resolveColor, resolveDarkSeeds, resolveRoleColor, srgbColorSchema, statedFactProblems, statedFactsSchema, validateBrandTokens };
|
|
598
861
|
//# sourceMappingURL=index.js.map
|
|
599
862
|
//# sourceMappingURL=index.js.map
|