@allons-y/webc-patterns 0.0.0-stage → 0.1.0-beta.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/LICENSE +385 -0
- package/README.md +536 -2
- package/components/norn-accordion.webc +124 -0
- package/components/norn-breadcrumbs.webc +71 -0
- package/components/norn-callout.webc +97 -0
- package/components/norn-card-grid.webc +60 -0
- package/components/norn-card.webc +143 -0
- package/components/norn-code-tabs.webc +270 -0
- package/components/norn-copy-button.webc +220 -0
- package/components/norn-fact-list.webc +52 -0
- package/components/norn-footer.webc +122 -0
- package/components/norn-head.webc +59 -0
- package/components/norn-header.webc +238 -0
- package/components/norn-heading-anchor.webc +71 -0
- package/components/norn-hero.webc +81 -0
- package/components/norn-icon.webc +140 -0
- package/components/norn-link-button.webc +96 -0
- package/components/norn-notice.webc +94 -0
- package/components/norn-pager.webc +67 -0
- package/components/norn-rich-text.webc +105 -0
- package/components/norn-search.webc +303 -0
- package/components/norn-segmented-control.webc +143 -0
- package/components/norn-side-nav.webc +149 -0
- package/components/norn-sidebar-layout.webc +185 -0
- package/components/norn-simple-table.webc +120 -0
- package/components/norn-skip-link.webc +41 -0
- package/components/norn-theme-toggle.webc +127 -0
- package/components/norn-toc.webc +260 -0
- package/css/base.css +75 -0
- package/css/print.css +8 -0
- package/css/reset.css +45 -0
- package/fallback/norn-code-block.webc +13 -0
- package/index.js +177 -0
- package/layouts/norn-base.webc +137 -0
- package/layouts/norn-docs-home.webc +109 -0
- package/layouts/norn-docs.webc +77 -0
- package/layouts/norn-landing.webc +73 -0
- package/layouts/norn-workspace.webc +25 -0
- package/layouts/norn-workspaces-home.webc +44 -0
- package/lib/icons.js +166 -0
- package/lib/npm.js +49 -0
- package/lib/optional.js +25 -0
- package/lib/site.js +87 -0
- package/lib/workspaces.js +71 -0
- package/markdown.js +278 -0
- package/package.json +71 -5
- package/plugin.js +279 -0
- package/project.js +15 -0
- package/schema/project.schema.json +112 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!---
|
|
2
|
+
/*
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
--->
|
|
9
|
+
<norn-segmented-control
|
|
10
|
+
:@icon-only="true"
|
|
11
|
+
@name="color-scheme"
|
|
12
|
+
:@options="[
|
|
13
|
+
{ value: 'light', label: '', icon: 'sun' },
|
|
14
|
+
{ value: '', label: '', icon: 'system', checked: true },
|
|
15
|
+
{ value: 'dark', label: '', icon: 'moon' }
|
|
16
|
+
]"
|
|
17
|
+
>
|
|
18
|
+
<span slot="legend" @text="nornComponentStrings?.('norn-theme-toggle', 'label', $data.page) || 'Color scheme'"></span>
|
|
19
|
+
</norn-segmented-control>
|
|
20
|
+
<span hidden>
|
|
21
|
+
<span data-string="label"><slot name="label"><span webc:nokeep @text="nornComponentStrings?.('norn-theme-toggle', 'label', $data.page) || 'Color scheme'"></span></slot></span>
|
|
22
|
+
<span data-string="light"><slot name="light"><span webc:nokeep @text="nornComponentStrings?.('norn-theme-toggle', 'light', $data.page) || 'Light'"></span></slot></span>
|
|
23
|
+
<span data-string="system"><slot name="system"><span webc:nokeep @text="nornComponentStrings?.('norn-theme-toggle', 'system', $data.page) || 'System'"></span></slot></span>
|
|
24
|
+
<span data-string="dark"><slot name="dark"><span webc:nokeep @text="nornComponentStrings?.('norn-theme-toggle', 'dark', $data.page) || 'Dark'"></span></slot></span>
|
|
25
|
+
</span>
|
|
26
|
+
<style>
|
|
27
|
+
@layer norn.components {
|
|
28
|
+
norn-theme-toggle {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
|
|
31
|
+
&:not(:defined) {
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
<script>
|
|
38
|
+
{
|
|
39
|
+
const KEY = "color-scheme";
|
|
40
|
+
const SCHEMES = ["light", "dark"];
|
|
41
|
+
let toggles = 0;
|
|
42
|
+
|
|
43
|
+
/** The saved choice, or "" for the system. Storage can be blocked: then there is no saved choice. */
|
|
44
|
+
const saved = () => {
|
|
45
|
+
try {
|
|
46
|
+
const value = localStorage.getItem(KEY);
|
|
47
|
+
return SCHEMES.includes(value) ? value : "";
|
|
48
|
+
} catch {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const save = (value) => {
|
|
54
|
+
try {
|
|
55
|
+
if (value) localStorage.setItem(KEY, value);
|
|
56
|
+
else localStorage.removeItem(KEY);
|
|
57
|
+
} catch {
|
|
58
|
+
// Not saved: the choice holds for this page only.
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const apply = (value) => {
|
|
63
|
+
if (value) document.documentElement.style.colorScheme = value;
|
|
64
|
+
else document.documentElement.style.removeProperty("color-scheme");
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** The option each input stands for, by value. */
|
|
68
|
+
const OPTIONS = { light: "light", "": "system", dark: "dark" };
|
|
69
|
+
|
|
70
|
+
/** Keeps the page's color scheme and every toggle in step with the visitor's choice. */
|
|
71
|
+
class ThemeToggle extends HTMLElement {
|
|
72
|
+
#wired = false;
|
|
73
|
+
#sync = () => this.#show(saved());
|
|
74
|
+
#onStorage = (event) => {
|
|
75
|
+
if (event.key !== KEY && event.key !== null) return;
|
|
76
|
+
apply(saved());
|
|
77
|
+
this.#show(saved());
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
document.addEventListener("themetogglechange", this.#sync);
|
|
82
|
+
window.addEventListener("storage", this.#onStorage);
|
|
83
|
+
if (!this.#wired) {
|
|
84
|
+
this.#wired = true;
|
|
85
|
+
// A radio group is shared by name across the page, so each toggle names its own.
|
|
86
|
+
const name = `${KEY}-${++toggles}`;
|
|
87
|
+
for (const input of this.querySelectorAll("input")) input.name = name;
|
|
88
|
+
this.#name();
|
|
89
|
+
this.addEventListener("change", (event) => {
|
|
90
|
+
const value = SCHEMES.includes(event.target.value) ? event.target.value : "";
|
|
91
|
+
save(value);
|
|
92
|
+
apply(value);
|
|
93
|
+
document.dispatchEvent(new CustomEvent("themetogglechange"));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
apply(saved());
|
|
97
|
+
this.#show(saved());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
disconnectedCallback() {
|
|
101
|
+
document.removeEventListener("themetogglechange", this.#sync);
|
|
102
|
+
window.removeEventListener("storage", this.#onStorage);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Show the names the page holds: the group's in the legend, each option's as its text and title. */
|
|
106
|
+
#name() {
|
|
107
|
+
const text = (key) => this.querySelector(`:scope > [hidden] > [data-string="${key}"]`)?.textContent.trim() ?? "";
|
|
108
|
+
const legend = this.querySelector("legend");
|
|
109
|
+
if (legend) legend.textContent = text("label");
|
|
110
|
+
for (const input of this.querySelectorAll("input")) {
|
|
111
|
+
const label = input.closest("label");
|
|
112
|
+
const words = text(OPTIONS[input.value]);
|
|
113
|
+
if (!label || !words) continue;
|
|
114
|
+
label.title = words;
|
|
115
|
+
const span = label.querySelector(":scope > span");
|
|
116
|
+
if (span) span.textContent = words;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#show(value) {
|
|
121
|
+
for (const input of this.querySelectorAll("input")) input.checked = input.value === value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!customElements.get("norn-theme-toggle")) customElements.define("norn-theme-toggle", ThemeToggle);
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<!---
|
|
2
|
+
/*
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
--->
|
|
9
|
+
<script webc:setup>
|
|
10
|
+
const escape = (text) => String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
11
|
+
|
|
12
|
+
/** Renders the flat headings as nested `<li>` elements, one inner `<ol>` per level of depth. */
|
|
13
|
+
function outline(items) {
|
|
14
|
+
const root = { level: 0, nodes: [] };
|
|
15
|
+
const trail = [root];
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
while (trail.length > 1 && trail.at(-1).level >= item.level) trail.pop();
|
|
18
|
+
const node = { level: item.level, item, nodes: [] };
|
|
19
|
+
trail.at(-1).nodes.push(node);
|
|
20
|
+
trail.push(node);
|
|
21
|
+
}
|
|
22
|
+
const list = (nodes) => '<ol>' + nodes.map((node) => '<li><a href="#' + escape(node.item.id) + '">' + escape(node.item.text) + '</a>' + (node.nodes.length ? list(node.nodes) : '') + '</li>').join('') + '</ol>';
|
|
23
|
+
return list(root.nodes);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
<nav webc:if="items && items.length > 1" :aria-labelledby="'norn-toc-' + uid" :data-fold="['always', 'narrow'].includes(fold) ? fold : undefined">
|
|
27
|
+
<details>
|
|
28
|
+
<summary><span :id="'norn-toc-' + uid"><slot name="label"><span webc:nokeep @text="nornComponentStrings?.('norn-toc', 'label', $data.page) || 'On this page'"></span></slot></span></summary>
|
|
29
|
+
<template webc:nokeep @raw="outline(items)"></template>
|
|
30
|
+
</details>
|
|
31
|
+
</nav>
|
|
32
|
+
<style>
|
|
33
|
+
@layer norn.components {
|
|
34
|
+
norn-toc {
|
|
35
|
+
display: block;
|
|
36
|
+
font-size: var(--norn-font-size-s, 0.875rem);
|
|
37
|
+
|
|
38
|
+
& > nav > details > summary {
|
|
39
|
+
--_state: 0%;
|
|
40
|
+
--_fold: none;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
gap: var(--norn-space-xs, 0.5rem);
|
|
46
|
+
margin-block-end: var(--norn-space-xs, 0.5rem);
|
|
47
|
+
color: var(--norn-text-base, light-dark(#16181d, #eceef3));
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
list-style: none;
|
|
50
|
+
|
|
51
|
+
&::-webkit-details-marker {
|
|
52
|
+
display: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* A chevron drawn with two borders, shown only while the list folds. */
|
|
56
|
+
&::after {
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
content: "";
|
|
59
|
+
display: var(--_fold);
|
|
60
|
+
flex: none;
|
|
61
|
+
inline-size: 0.5em;
|
|
62
|
+
block-size: 0.5em;
|
|
63
|
+
border-block-end: 2px solid var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
64
|
+
border-inline-end: 2px solid var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
65
|
+
rotate: 45deg;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
& > nav > details[open] > summary::after {
|
|
70
|
+
rotate: 225deg;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Open or not, the list shows, so the title is only a title. */
|
|
74
|
+
@supports selector(::details-content) {
|
|
75
|
+
& > nav > details::details-content {
|
|
76
|
+
content-visibility: visible;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
& > nav > details > ol,
|
|
81
|
+
& > nav > details > ol ol {
|
|
82
|
+
display: grid;
|
|
83
|
+
gap: var(--norn-space-2xs, 0.25rem);
|
|
84
|
+
margin: 0;
|
|
85
|
+
padding: 0;
|
|
86
|
+
list-style: none;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
& > nav > details > ol {
|
|
90
|
+
border-inline-start: 1px solid var(--norn-ui-border-color, light-dark(#d3d8e2, #2d323e));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
& > nav > details > ol ol {
|
|
94
|
+
padding-inline-start: var(--norn-space-s, 0.75rem);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
& > nav > details > ol li > a {
|
|
98
|
+
--_state: 0%;
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
min-block-size: 1.5rem;
|
|
103
|
+
margin-inline-start: -1px;
|
|
104
|
+
padding: var(--norn-space-2xs, 0.25rem) var(--norn-space-s, 0.75rem);
|
|
105
|
+
border-inline-start: 2px solid transparent;
|
|
106
|
+
border-start-end-radius: var(--norn-ui-radius-s, 4px);
|
|
107
|
+
border-end-end-radius: var(--norn-ui-radius-s, 4px);
|
|
108
|
+
background-color: color-mix(in oklab, currentcolor var(--_state), transparent);
|
|
109
|
+
color: var(--norn-text-muted, light-dark(#4b5160, #a4abba));
|
|
110
|
+
text-decoration: none;
|
|
111
|
+
|
|
112
|
+
&:hover {
|
|
113
|
+
--_state: var(--norn-ui-state-hover, 8%);
|
|
114
|
+
color: var(--norn-text-base, light-dark(#16181d, #eceef3));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&:active {
|
|
118
|
+
--_state: var(--norn-ui-state-press, 12%);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
&[aria-current] {
|
|
122
|
+
border-inline-start-color: var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
123
|
+
color: var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* The fold prop turns the fold on, always or while the nearest container is narrow. */
|
|
128
|
+
& > nav[data-fold="always"] {
|
|
129
|
+
--_folded: on;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@container (width < 60rem) {
|
|
133
|
+
& > nav[data-fold="narrow"] {
|
|
134
|
+
--_folded: on;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Folded, the title is a control that opens the list. */
|
|
139
|
+
@container style(--_folded: on) {
|
|
140
|
+
& > nav > details > summary {
|
|
141
|
+
--_fold: block;
|
|
142
|
+
padding: var(--norn-space-xs, 0.5rem) var(--norn-space-s, 0.75rem);
|
|
143
|
+
border-radius: var(--norn-ui-radius-s, 4px);
|
|
144
|
+
background-color: color-mix(in oklab, currentcolor var(--_state), var(--norn-surface-raised, light-dark(hsl(40, 14%, 96%), #242932)));
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
|
|
147
|
+
&:hover {
|
|
148
|
+
--_state: var(--norn-ui-state-hover, 8%);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&:active {
|
|
152
|
+
--_state: var(--norn-ui-state-press, 12%);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
&:focus-visible {
|
|
156
|
+
outline-offset: calc(-1 * var(--norn-ui-focus-offset, 2px));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@supports selector(::details-content) {
|
|
161
|
+
& > nav > details:not([open])::details-content {
|
|
162
|
+
content-visibility: hidden;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Without ::details-content a closed fold can't show its list, so the title always works as a control. */
|
|
168
|
+
@supports not selector(::details-content) {
|
|
169
|
+
& > nav > details > summary {
|
|
170
|
+
--_fold: block;
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (pointer: coarse) {
|
|
176
|
+
& > nav > details > ol li > a {
|
|
177
|
+
min-block-size: 2.75rem;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
182
|
+
& > nav > details > ol li > a,
|
|
183
|
+
& > nav > details > summary {
|
|
184
|
+
transition: background-color var(--norn-ui-motion-duration, 150ms) var(--norn-ui-motion-easing, cubic-bezier(0.2, 0, 0, 1));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@media (forced-colors: active) {
|
|
189
|
+
& > nav > details > summary::after {
|
|
190
|
+
border-color: CanvasText;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* A clear border would be drawn in the text color, so only the current link has one. */
|
|
194
|
+
& > nav > details > ol li > a {
|
|
195
|
+
border-inline-start-width: 0;
|
|
196
|
+
padding-inline-start: calc(var(--norn-space-s, 0.75rem) + 2px);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
& > nav > details > ol li > a[aria-current] {
|
|
200
|
+
border-inline-start-width: 2px;
|
|
201
|
+
border-inline-start-color: Highlight;
|
|
202
|
+
padding-inline-start: var(--norn-space-s, 0.75rem);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@media print {
|
|
207
|
+
display: none;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
<script>
|
|
213
|
+
{
|
|
214
|
+
/** Marks the link to the last section heading scrolled past, or the first one. */
|
|
215
|
+
class Toc extends HTMLElement {
|
|
216
|
+
#observer;
|
|
217
|
+
#onScroll;
|
|
218
|
+
|
|
219
|
+
connectedCallback() {
|
|
220
|
+
const links = [...this.querySelectorAll('a[href^="#"]')];
|
|
221
|
+
// A link's hash is percent-encoded and an id is not, but an id may hold a literal `%`: try the decoded hash, then the hash as written.
|
|
222
|
+
const heading = (link) => {
|
|
223
|
+
const hash = link.hash.slice(1);
|
|
224
|
+
try {
|
|
225
|
+
return document.getElementById(decodeURIComponent(hash)) ?? document.getElementById(hash);
|
|
226
|
+
} catch {
|
|
227
|
+
return document.getElementById(hash);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const sections = links.map((link) => [link, heading(link)]).filter(([, target]) => target);
|
|
231
|
+
if (!sections.length) return;
|
|
232
|
+
const mark = () => {
|
|
233
|
+
const line = innerHeight * 0.25;
|
|
234
|
+
const [current] = sections.findLast(([, target]) => target.getBoundingClientRect().top <= line) ?? sections[0];
|
|
235
|
+
for (const link of links) {
|
|
236
|
+
if (link === current) link.setAttribute("aria-current", "location");
|
|
237
|
+
else link.removeAttribute("aria-current");
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
this.#teardown();
|
|
241
|
+
this.#onScroll = () => requestAnimationFrame(mark);
|
|
242
|
+
addEventListener("scroll", this.#onScroll, { passive: true });
|
|
243
|
+
this.#observer = new ResizeObserver(this.#onScroll);
|
|
244
|
+
this.#observer.observe(document.documentElement);
|
|
245
|
+
mark();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
disconnectedCallback() {
|
|
249
|
+
this.#teardown();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#teardown() {
|
|
253
|
+
if (this.#onScroll) removeEventListener("scroll", this.#onScroll);
|
|
254
|
+
this.#observer?.disconnect();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!customElements.get("norn-toc")) customElements.define("norn-toc", Toc);
|
|
259
|
+
}
|
|
260
|
+
</script>
|
package/css/base.css
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Base styles for the page, in the norn.base layer: color-scheme and base typography.
|
|
3
|
+
* It defines no design tokens: every component carries each shared token as the fallback of its custom property, compiled from `tokens.json`.
|
|
4
|
+
* Adds one utility class, `.visually-hidden`: text that is read but not shown, and shows while it or a child has focus.
|
|
5
|
+
*/
|
|
6
|
+
@layer norn.base {
|
|
7
|
+
:root {
|
|
8
|
+
color-scheme: light dark;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
12
|
+
html {
|
|
13
|
+
scroll-behavior: smooth;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
min-block-size: 100dvh;
|
|
19
|
+
margin: 0;
|
|
20
|
+
background: var(--norn-surface-base, light-dark(#ffffff, #101216));
|
|
21
|
+
color: var(--norn-text-base, light-dark(#16181d, #eceef3));
|
|
22
|
+
font-family: var(--norn-font-body, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
23
|
+
font-size: var(--norn-font-size-m, 1rem);
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1,
|
|
28
|
+
h2,
|
|
29
|
+
h3,
|
|
30
|
+
h4,
|
|
31
|
+
h5,
|
|
32
|
+
h6 {
|
|
33
|
+
font-family: var(--norn-font-heading, var(--norn-font-body, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif));
|
|
34
|
+
line-height: 1.15;
|
|
35
|
+
text-wrap: balance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
p,
|
|
39
|
+
li {
|
|
40
|
+
text-wrap: pretty;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
a {
|
|
44
|
+
color: var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
45
|
+
text-decoration: underline dotted;
|
|
46
|
+
text-decoration-color: currentcolor;
|
|
47
|
+
text-decoration-thickness: 1px;
|
|
48
|
+
text-underline-offset: 0.2em;
|
|
49
|
+
overflow-wrap: break-word;
|
|
50
|
+
|
|
51
|
+
&:is(:hover, :focus-visible) {
|
|
52
|
+
text-decoration-style: solid;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* The one focus ring. A component that the ring would clip draws it inset, at the negative of the offset. */
|
|
57
|
+
:focus-visible {
|
|
58
|
+
outline: var(--norn-ui-focus-width, 2px) solid var(--norn-text-accent, light-dark(oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 82%, black) min(l, 0.5) c h), oklch(from color-mix(in oklch, var(--norn-ui-accent, #2346d4) 55%, white) max(l, 0.72) c h)));
|
|
59
|
+
outline-offset: var(--norn-ui-focus-offset, 2px);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main:focus {
|
|
63
|
+
outline: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Off screen but still read. Shows while it, or a child, has focus. */
|
|
67
|
+
.visually-hidden:not(:focus-within, :active) {
|
|
68
|
+
position: absolute;
|
|
69
|
+
inline-size: 1px;
|
|
70
|
+
block-size: 1px;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
clip-path: inset(50%);
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/css/print.css
ADDED
package/css/reset.css
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* An optional reset, in the norn.reset layer. Norn's components and layouts look the same without it.
|
|
3
|
+
* Copy or link it from `@allons-y/webc-patterns/css/reset.css`, or read its path from the plugin's `reset` export.
|
|
4
|
+
*/
|
|
5
|
+
@layer norn.reset {
|
|
6
|
+
*,
|
|
7
|
+
*::before,
|
|
8
|
+
*::after {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html {
|
|
13
|
+
-webkit-text-size-adjust: 100%;
|
|
14
|
+
text-size-adjust: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body,
|
|
18
|
+
h1,
|
|
19
|
+
h2,
|
|
20
|
+
h3,
|
|
21
|
+
p,
|
|
22
|
+
figure,
|
|
23
|
+
pre,
|
|
24
|
+
ul {
|
|
25
|
+
margin: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ul {
|
|
29
|
+
padding: 0;
|
|
30
|
+
list-style: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
img,
|
|
34
|
+
picture,
|
|
35
|
+
svg {
|
|
36
|
+
display: block;
|
|
37
|
+
max-inline-size: 100%;
|
|
38
|
+
block-size: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
button {
|
|
42
|
+
font: inherit;
|
|
43
|
+
color: inherit;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!---
|
|
2
|
+
/*
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
--->
|
|
9
|
+
<figure webc:if="typeof caption === 'string' && caption" webc:root="override"
|
|
10
|
+
><pre dir="ltr" :data-language="typeof language === 'string' && language ? language : undefined"><code @text="typeof code === 'string' ? code : ''"></code></pre
|
|
11
|
+
><figcaption @text="caption"></figcaption
|
|
12
|
+
></figure>
|
|
13
|
+
<pre webc:else webc:root="override" dir="ltr" :data-language="typeof language === 'string' && language ? language : undefined"><code @text="typeof code === 'string' ? code : ''"></code></pre>
|