@quo-systems/ui 0.1.0 → 0.1.1
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/elements/theme.d.ts +1 -7
- package/dist/elements/theme.js +77 -42
- package/dist/tokens/livery.js +36 -6
- package/elements/theme.ts +79 -40
- package/package.json +1 -1
- package/tokens/livery.ts +41 -6
package/dist/elements/theme.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const THEME_COOKIE = "nv.theme";
|
|
2
2
|
export declare const prepaint: string;
|
|
3
3
|
export declare function applyTheme(value: string, root: HTMLElement): void;
|
|
4
|
-
export declare class NvTheme extends HTMLElement {
|
|
5
|
-
connectedCallback(): void;
|
|
6
|
-
disconnectedCallback(): void;
|
|
7
|
-
handleEvent(event: Event): void;
|
|
8
|
-
private mark;
|
|
9
|
-
}
|
package/dist/elements/theme.js
CHANGED
|
@@ -8,32 +8,58 @@
|
|
|
8
8
|
// still labelled, and the page still paints, because `auto` is the state a
|
|
9
9
|
// document in no theme is already in.
|
|
10
10
|
//
|
|
11
|
-
// The
|
|
12
|
-
//
|
|
13
|
-
|
|
11
|
+
// The choice is a cookie and not browser storage, and that is the whole
|
|
12
|
+
// reason this element exists rather than three lines in a page. A cookie
|
|
13
|
+
// carries a Domain, so a person who picks dark on a world's website has
|
|
14
|
+
// picked dark on its quo subdomain and in its app: one choice, one surface
|
|
15
|
+
// to them, however many origins it is to us. Browser storage is per origin
|
|
16
|
+
// and would make each of those a stranger to the others.
|
|
17
|
+
//
|
|
18
|
+
// A server may read it too, since a cookie is sent with the request, but
|
|
19
|
+
// nothing here needs that: the snippet below runs before the first paint
|
|
20
|
+
// and no page waits on it.
|
|
21
|
+
export const THEME_COOKIE = 'nv.theme';
|
|
14
22
|
// Inline this in <head>, before any stylesheet, or the first paint is the
|
|
15
23
|
// wrong theme and the second one is a flash. It is the only script the kit
|
|
16
24
|
// asks a page to inline, and it is this short on purpose.
|
|
17
|
-
export const prepaint = `try{var
|
|
18
|
-
`if(
|
|
19
|
-
|
|
20
|
-
// data, and throws outright in some embeddings. A theme control is a
|
|
21
|
-
// convenience, so every read and write fails quietly and the surface stays
|
|
22
|
-
// on `auto`.
|
|
23
|
-
function stored() {
|
|
25
|
+
export const prepaint = `try{var m=document.cookie.match(/(?:^|; )${THEME_COOKIE.replace('.', '\\.')}=([^;]*)/);` +
|
|
26
|
+
`if(m&&m[1]!=='auto')document.documentElement.dataset.theme=decodeURIComponent(m[1])}catch(e){}`;
|
|
27
|
+
function stored(doc) {
|
|
24
28
|
try {
|
|
25
|
-
|
|
29
|
+
const m = doc.cookie.match(new RegExp(`(?:^|; )${THEME_COOKIE.replace('.', '\\.')}=([^;]*)`));
|
|
30
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
26
31
|
}
|
|
27
32
|
catch {
|
|
28
33
|
return null;
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
|
-
|
|
36
|
+
/*
|
|
37
|
+
* The domain a choice is remembered for: the registrable one, so every
|
|
38
|
+
* subdomain of a world shares it. Two labels, which is right for every name
|
|
39
|
+
* this repository serves and wrong for a two-label public suffix such as
|
|
40
|
+
* `co.uk`; a world under one of those sets its own Domain and this is where
|
|
41
|
+
* that would be read from.
|
|
42
|
+
*
|
|
43
|
+
* A bare host or an address gets no Domain at all, because a browser rejects
|
|
44
|
+
* one there and would drop the cookie entirely.
|
|
45
|
+
*/
|
|
46
|
+
function domainOf(host) {
|
|
47
|
+
if (!host || host === 'localhost' || /^[\d.]+$/.test(host) || !host.includes('.'))
|
|
48
|
+
return null;
|
|
49
|
+
return `.${host.split('.').slice(-2).join('.')}`;
|
|
50
|
+
}
|
|
51
|
+
function store(value, doc) {
|
|
32
52
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
const parts = [
|
|
54
|
+
`${THEME_COOKIE}=${encodeURIComponent(value)}`,
|
|
55
|
+
'Path=/',
|
|
56
|
+
'SameSite=Lax',
|
|
57
|
+
'Max-Age=31536000',
|
|
58
|
+
];
|
|
59
|
+
const domain = domainOf(doc.location?.hostname ?? '');
|
|
60
|
+
if (domain)
|
|
61
|
+
parts.push(`Domain=${domain}`);
|
|
62
|
+
doc.cookie = parts.join('; ');
|
|
37
63
|
}
|
|
38
64
|
catch {
|
|
39
65
|
/* a choice that cannot be remembered still applies to this page */
|
|
@@ -45,31 +71,40 @@ export function applyTheme(value, root) {
|
|
|
45
71
|
else
|
|
46
72
|
root.dataset.theme = value;
|
|
47
73
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.mark(chosen);
|
|
63
|
-
}
|
|
64
|
-
// The document and the buttons say the same thing, always, and the element
|
|
65
|
-
// is the only writer of either.
|
|
66
|
-
mark(chosen) {
|
|
67
|
-
applyTheme(chosen, this.ownerDocument.documentElement);
|
|
68
|
-
for (const button of this.querySelectorAll('[data-theme]')) {
|
|
69
|
-
button.setAttribute('aria-pressed', String(button.dataset.theme === chosen));
|
|
74
|
+
/*
|
|
75
|
+
* The class is built inside this function and not at the top of the file,
|
|
76
|
+
* so importing this module where there is no DOM is safe. A site that
|
|
77
|
+
* renders on request imports it on the server to inline `prepaint`, and a
|
|
78
|
+
* class extending HTMLElement at module scope would end that render with a
|
|
79
|
+
* ReferenceError before a page ever reached a browser.
|
|
80
|
+
*/
|
|
81
|
+
function define() {
|
|
82
|
+
if (typeof customElements === 'undefined' || customElements.get('nv-theme'))
|
|
83
|
+
return;
|
|
84
|
+
customElements.define('nv-theme', class extends HTMLElement {
|
|
85
|
+
connectedCallback() {
|
|
86
|
+
this.addEventListener('click', this);
|
|
87
|
+
this.mark(stored(this.ownerDocument) ?? 'auto');
|
|
70
88
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
disconnectedCallback() {
|
|
90
|
+
this.removeEventListener('click', this);
|
|
91
|
+
}
|
|
92
|
+
handleEvent(event) {
|
|
93
|
+
const button = event.target?.closest('[data-theme]');
|
|
94
|
+
if (!button || !this.contains(button))
|
|
95
|
+
return;
|
|
96
|
+
const chosen = button.dataset.theme ?? 'auto';
|
|
97
|
+
store(chosen, this.ownerDocument);
|
|
98
|
+
this.mark(chosen);
|
|
99
|
+
}
|
|
100
|
+
// The document and the buttons say the same thing, always, and the
|
|
101
|
+
// element is the only writer of either.
|
|
102
|
+
mark(chosen) {
|
|
103
|
+
applyTheme(chosen, this.ownerDocument.documentElement);
|
|
104
|
+
for (const button of this.querySelectorAll('[data-theme]')) {
|
|
105
|
+
button.setAttribute('aria-pressed', String(button.dataset.theme === chosen));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
75
109
|
}
|
|
110
|
+
define();
|
package/dist/tokens/livery.js
CHANGED
|
@@ -12,6 +12,37 @@ export function missingSlots(slots) {
|
|
|
12
12
|
export function forbiddenSlots(slots) {
|
|
13
13
|
return Object.keys(slots).filter((slot) => !designMaySet(slot));
|
|
14
14
|
}
|
|
15
|
+
const declare = (slots, pad) => Object.entries(slots)
|
|
16
|
+
.map(([slot, value]) => `${pad}${slot}: ${value};`)
|
|
17
|
+
.join('\n');
|
|
18
|
+
/*
|
|
19
|
+
* What a document in no theme wears.
|
|
20
|
+
*
|
|
21
|
+
* `auto` is not a theme, it is the absence of a choice, and a design whose
|
|
22
|
+
* every block keys on data-theme paints nothing at all when nobody has
|
|
23
|
+
* chosen. So the first theme declared is also written at the bare root, and
|
|
24
|
+
* where a design has both light and dark the system preference picks between
|
|
25
|
+
* them for a reader who has told their system and not this page.
|
|
26
|
+
*
|
|
27
|
+
* The dark block is guarded against an explicit light, or a reader who chose
|
|
28
|
+
* light on a dark-preferring system would be overruled by their own machine.
|
|
29
|
+
*/
|
|
30
|
+
function systemBlocks(livery) {
|
|
31
|
+
const themes = Object.entries(livery.themes);
|
|
32
|
+
const [firstName, first] = themes[0];
|
|
33
|
+
const out = [
|
|
34
|
+
`:root {\n color-scheme: ${first.colorScheme};\n\n${declare(first.slots, ' ')}\n}`,
|
|
35
|
+
];
|
|
36
|
+
const other = themes.find(([name]) => name !== firstName && name === 'dark') ??
|
|
37
|
+
themes.find(([name]) => name !== firstName);
|
|
38
|
+
if (themes.length > 1 && other && first.colorScheme !== other[1].colorScheme) {
|
|
39
|
+
const [, o] = other;
|
|
40
|
+
out.push(`@media (prefers-color-scheme: ${o.colorScheme}) {\n` +
|
|
41
|
+
` :root:not([data-theme='${firstName}']) {\n` +
|
|
42
|
+
` color-scheme: ${o.colorScheme};\n\n${declare(o.slots, ' ')}\n }\n}`);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
15
46
|
// Throws on the first theme that leaves a required slot undefined or sets one
|
|
16
47
|
// that belongs to the kit. A design either paints a whole face or it does not
|
|
17
48
|
// compile.
|
|
@@ -26,14 +57,13 @@ export function renderTokensCss(livery) {
|
|
|
26
57
|
if (forbidden.length > 0) {
|
|
27
58
|
throw new Error(`${theme}: structure belongs to the kit, and livery.json sets:\n ${forbidden.join('\n ')}`);
|
|
28
59
|
}
|
|
29
|
-
|
|
30
|
-
.map(([slot, value]) => ` ${slot}: ${value};`)
|
|
31
|
-
.join('\n');
|
|
32
|
-
blocks.push(`:root[data-theme='${theme}'] {\n color-scheme: ${colorScheme};\n\n${declared}\n}`);
|
|
60
|
+
blocks.push(`:root[data-theme='${theme}'] {\n color-scheme: ${colorScheme};\n\n${declare(slots, ' ')}\n}`);
|
|
33
61
|
}
|
|
34
62
|
return (`/*\n * Generated from livery.json. Never edit: run nv-livery.\n *\n` +
|
|
35
63
|
` * The ${livery.name} palette, colour and shadow only, worn by whichever\n` +
|
|
36
64
|
` * estate wears the ${livery.name} design. Declared unlayered, so it wins\n` +
|
|
37
|
-
` * over the kit's layered defaults without a specificity trick.\n
|
|
38
|
-
|
|
65
|
+
` * over the kit's layered defaults without a specificity trick.\n *\n` +
|
|
66
|
+
` * The first block is what a document in no theme wears, since auto is\n` +
|
|
67
|
+
` * the absence of a choice and not a theme of its own.\n */\n\n` +
|
|
68
|
+
`${[...systemBlocks(livery), ...blocks].join('\n\n')}\n`);
|
|
39
69
|
}
|
package/elements/theme.ts
CHANGED
|
@@ -8,34 +8,61 @@
|
|
|
8
8
|
// still labelled, and the page still paints, because `auto` is the state a
|
|
9
9
|
// document in no theme is already in.
|
|
10
10
|
//
|
|
11
|
-
// The
|
|
12
|
-
//
|
|
11
|
+
// The choice is a cookie and not browser storage, and that is the whole
|
|
12
|
+
// reason this element exists rather than three lines in a page. A cookie
|
|
13
|
+
// carries a Domain, so a person who picks dark on a world's website has
|
|
14
|
+
// picked dark on its quo subdomain and in its app: one choice, one surface
|
|
15
|
+
// to them, however many origins it is to us. Browser storage is per origin
|
|
16
|
+
// and would make each of those a stranger to the others.
|
|
17
|
+
//
|
|
18
|
+
// A server may read it too, since a cookie is sent with the request, but
|
|
19
|
+
// nothing here needs that: the snippet below runs before the first paint
|
|
20
|
+
// and no page waits on it.
|
|
13
21
|
|
|
14
|
-
export const
|
|
22
|
+
export const THEME_COOKIE = 'nv.theme';
|
|
15
23
|
|
|
16
24
|
// Inline this in <head>, before any stylesheet, or the first paint is the
|
|
17
25
|
// wrong theme and the second one is a flash. It is the only script the kit
|
|
18
26
|
// asks a page to inline, and it is this short on purpose.
|
|
19
27
|
export const prepaint =
|
|
20
|
-
`try{var
|
|
21
|
-
`if(
|
|
28
|
+
`try{var m=document.cookie.match(/(?:^|; )${THEME_COOKIE.replace('.', '\\.')}=([^;]*)/);` +
|
|
29
|
+
`if(m&&m[1]!=='auto')document.documentElement.dataset.theme=decodeURIComponent(m[1])}catch(e){}`;
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
// data, and throws outright in some embeddings. A theme control is a
|
|
25
|
-
// convenience, so every read and write fails quietly and the surface stays
|
|
26
|
-
// on `auto`.
|
|
27
|
-
function stored(): string | null {
|
|
31
|
+
function stored(doc: Document): string | null {
|
|
28
32
|
try {
|
|
29
|
-
|
|
33
|
+
const m = doc.cookie.match(new RegExp(`(?:^|; )${THEME_COOKIE.replace('.', '\\.')}=([^;]*)`));
|
|
34
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
30
35
|
} catch {
|
|
31
36
|
return null;
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
/*
|
|
41
|
+
* The domain a choice is remembered for: the registrable one, so every
|
|
42
|
+
* subdomain of a world shares it. Two labels, which is right for every name
|
|
43
|
+
* this repository serves and wrong for a two-label public suffix such as
|
|
44
|
+
* `co.uk`; a world under one of those sets its own Domain and this is where
|
|
45
|
+
* that would be read from.
|
|
46
|
+
*
|
|
47
|
+
* A bare host or an address gets no Domain at all, because a browser rejects
|
|
48
|
+
* one there and would drop the cookie entirely.
|
|
49
|
+
*/
|
|
50
|
+
function domainOf(host: string): string | null {
|
|
51
|
+
if (!host || host === 'localhost' || /^[\d.]+$/.test(host) || !host.includes('.')) return null;
|
|
52
|
+
return `.${host.split('.').slice(-2).join('.')}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function store(value: string, doc: Document): void {
|
|
36
56
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
const parts = [
|
|
58
|
+
`${THEME_COOKIE}=${encodeURIComponent(value)}`,
|
|
59
|
+
'Path=/',
|
|
60
|
+
'SameSite=Lax',
|
|
61
|
+
'Max-Age=31536000',
|
|
62
|
+
];
|
|
63
|
+
const domain = domainOf(doc.location?.hostname ?? '');
|
|
64
|
+
if (domain) parts.push(`Domain=${domain}`);
|
|
65
|
+
doc.cookie = parts.join('; ');
|
|
39
66
|
} catch {
|
|
40
67
|
/* a choice that cannot be remembered still applies to this page */
|
|
41
68
|
}
|
|
@@ -46,34 +73,46 @@ export function applyTheme(value: string, root: HTMLElement): void {
|
|
|
46
73
|
else root.dataset.theme = value;
|
|
47
74
|
}
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
/*
|
|
77
|
+
* The class is built inside this function and not at the top of the file,
|
|
78
|
+
* so importing this module where there is no DOM is safe. A site that
|
|
79
|
+
* renders on request imports it on the server to inline `prepaint`, and a
|
|
80
|
+
* class extending HTMLElement at module scope would end that render with a
|
|
81
|
+
* ReferenceError before a page ever reached a browser.
|
|
82
|
+
*/
|
|
83
|
+
function define(): void {
|
|
84
|
+
if (typeof customElements === 'undefined' || customElements.get('nv-theme')) return;
|
|
54
85
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
customElements.define(
|
|
87
|
+
'nv-theme',
|
|
88
|
+
class extends HTMLElement {
|
|
89
|
+
connectedCallback(): void {
|
|
90
|
+
this.addEventListener('click', this);
|
|
91
|
+
this.mark(stored(this.ownerDocument) ?? 'auto');
|
|
92
|
+
}
|
|
58
93
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const chosen = button.dataset.theme ?? 'auto';
|
|
63
|
-
store(chosen);
|
|
64
|
-
this.mark(chosen);
|
|
65
|
-
}
|
|
94
|
+
disconnectedCallback(): void {
|
|
95
|
+
this.removeEventListener('click', this);
|
|
96
|
+
}
|
|
66
97
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
}
|
|
98
|
+
handleEvent(event: Event): void {
|
|
99
|
+
const button = (event.target as Element | null)?.closest<HTMLElement>('[data-theme]');
|
|
100
|
+
if (!button || !this.contains(button)) return;
|
|
101
|
+
const chosen = button.dataset.theme ?? 'auto';
|
|
102
|
+
store(chosen, this.ownerDocument);
|
|
103
|
+
this.mark(chosen);
|
|
104
|
+
}
|
|
76
105
|
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
// The document and the buttons say the same thing, always, and the
|
|
107
|
+
// element is the only writer of either.
|
|
108
|
+
mark(chosen: string): void {
|
|
109
|
+
applyTheme(chosen, this.ownerDocument.documentElement);
|
|
110
|
+
for (const button of this.querySelectorAll<HTMLElement>('[data-theme]')) {
|
|
111
|
+
button.setAttribute('aria-pressed', String(button.dataset.theme === chosen));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
);
|
|
79
116
|
}
|
|
117
|
+
|
|
118
|
+
define();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quo-systems/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The kit: one token contract, one baseline, and the primitives a screen and a website both need. No framework, no dependency, and no word about harbor, ward or being.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-tokens",
|
package/tokens/livery.ts
CHANGED
|
@@ -25,6 +25,42 @@ export function forbiddenSlots(slots: Record<string, string>): string[] {
|
|
|
25
25
|
return Object.keys(slots).filter((slot) => !designMaySet(slot));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
const declare = (slots: Record<string, string>, pad: string) =>
|
|
29
|
+
Object.entries(slots)
|
|
30
|
+
.map(([slot, value]) => `${pad}${slot}: ${value};`)
|
|
31
|
+
.join('\n');
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* What a document in no theme wears.
|
|
35
|
+
*
|
|
36
|
+
* `auto` is not a theme, it is the absence of a choice, and a design whose
|
|
37
|
+
* every block keys on data-theme paints nothing at all when nobody has
|
|
38
|
+
* chosen. So the first theme declared is also written at the bare root, and
|
|
39
|
+
* where a design has both light and dark the system preference picks between
|
|
40
|
+
* them for a reader who has told their system and not this page.
|
|
41
|
+
*
|
|
42
|
+
* The dark block is guarded against an explicit light, or a reader who chose
|
|
43
|
+
* light on a dark-preferring system would be overruled by their own machine.
|
|
44
|
+
*/
|
|
45
|
+
function systemBlocks(livery: Livery): string[] {
|
|
46
|
+
const themes = Object.entries(livery.themes);
|
|
47
|
+
const [firstName, first] = themes[0];
|
|
48
|
+
const out = [
|
|
49
|
+
`:root {\n color-scheme: ${first.colorScheme};\n\n${declare(first.slots, ' ')}\n}`,
|
|
50
|
+
];
|
|
51
|
+
const other = themes.find(([name]) => name !== firstName && name === 'dark') ??
|
|
52
|
+
themes.find(([name]) => name !== firstName);
|
|
53
|
+
if (themes.length > 1 && other && first.colorScheme !== other[1].colorScheme) {
|
|
54
|
+
const [, o] = other;
|
|
55
|
+
out.push(
|
|
56
|
+
`@media (prefers-color-scheme: ${o.colorScheme}) {\n` +
|
|
57
|
+
` :root:not([data-theme='${firstName}']) {\n` +
|
|
58
|
+
` color-scheme: ${o.colorScheme};\n\n${declare(o.slots, ' ')}\n }\n}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
28
64
|
// Throws on the first theme that leaves a required slot undefined or sets one
|
|
29
65
|
// that belongs to the kit. A design either paints a whole face or it does not
|
|
30
66
|
// compile.
|
|
@@ -41,18 +77,17 @@ export function renderTokensCss(livery: Livery): string {
|
|
|
41
77
|
`${theme}: structure belongs to the kit, and livery.json sets:\n ${forbidden.join('\n ')}`,
|
|
42
78
|
);
|
|
43
79
|
}
|
|
44
|
-
const declared = Object.entries(slots)
|
|
45
|
-
.map(([slot, value]) => ` ${slot}: ${value};`)
|
|
46
|
-
.join('\n');
|
|
47
80
|
blocks.push(
|
|
48
|
-
`:root[data-theme='${theme}'] {\n color-scheme: ${colorScheme};\n\n${
|
|
81
|
+
`:root[data-theme='${theme}'] {\n color-scheme: ${colorScheme};\n\n${declare(slots, ' ')}\n}`,
|
|
49
82
|
);
|
|
50
83
|
}
|
|
51
84
|
return (
|
|
52
85
|
`/*\n * Generated from livery.json. Never edit: run nv-livery.\n *\n` +
|
|
53
86
|
` * The ${livery.name} palette, colour and shadow only, worn by whichever\n` +
|
|
54
87
|
` * estate wears the ${livery.name} design. Declared unlayered, so it wins\n` +
|
|
55
|
-
` * over the kit's layered defaults without a specificity trick.\n
|
|
56
|
-
|
|
88
|
+
` * over the kit's layered defaults without a specificity trick.\n *\n` +
|
|
89
|
+
` * The first block is what a document in no theme wears, since auto is\n` +
|
|
90
|
+
` * the absence of a choice and not a theme of its own.\n */\n\n` +
|
|
91
|
+
`${[...systemBlocks(livery), ...blocks].join('\n\n')}\n`
|
|
57
92
|
);
|
|
58
93
|
}
|