@augurworks/augur 0.15.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/CANVAS.md +858 -0
- package/INSTALL.md +603 -0
- package/LICENSE +27 -0
- package/README.md +150 -0
- package/TRADEMARKS.md +72 -0
- package/agents/README.md +65 -0
- package/agents/canon-extract.md +117 -0
- package/agents/canon.md +151 -0
- package/agents/canvas.md +88 -0
- package/agents/currency.md +74 -0
- package/agents/drafts.md +92 -0
- package/agents/identity.md +148 -0
- package/agents/prototype-contract.md +69 -0
- package/agents/publishing.md +324 -0
- package/agents/review-feedback.md +76 -0
- package/agents/space-json.md +132 -0
- package/agents/ui-skill.md +101 -0
- package/agents/working-marks.md +86 -0
- package/brand/augur-eye.svg +3 -0
- package/brand/augur-icon.svg +41 -0
- package/brand/augur-mark.svg +5 -0
- package/brand/lentianova-bulky.otf +0 -0
- package/brand/lentianova-median.otf +0 -0
- package/brand/lentianova.woff2 +0 -0
- package/build.js +8409 -0
- package/changelog.md +133 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/package.json +57 -0
- package/pitis/PETS.md +121 -0
- package/pitis/README.md +117 -0
- package/pitis/index.html +266 -0
- package/pitis/piti.build.js +81 -0
- package/pitis/piti.js +929 -0
- package/pitis/roast-agent.md +185 -0
- package/realtime/src/index.js +119 -0
- package/realtime/wrangler.example.toml +44 -0
- package/scripts/adopt.mjs +106 -0
- package/scripts/board-snapshot.mjs +197 -0
- package/scripts/build-seed-pack.mjs +33 -0
- package/scripts/bundle-rekey.mjs +112 -0
- package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
- package/scripts/canon-extract.mjs +578 -0
- package/scripts/canon.mjs +599 -0
- package/scripts/canvas-screen.mjs +333 -0
- package/scripts/check-local.mjs +136 -0
- package/scripts/clawd-art.mjs +75 -0
- package/scripts/clawd-canvas.mjs +741 -0
- package/scripts/cli.mjs +100 -0
- package/scripts/clone.mjs +422 -0
- package/scripts/close.mjs +30 -0
- package/scripts/connect.mjs +103 -0
- package/scripts/deploy.mjs +157 -0
- package/scripts/dev.mjs +54 -0
- package/scripts/doc-lint.mjs +143 -0
- package/scripts/export.mjs +300 -0
- package/scripts/fork.mjs +64 -0
- package/scripts/freeze.mjs +74 -0
- package/scripts/frontdoor-parity.mjs +180 -0
- package/scripts/hook.mjs +67 -0
- package/scripts/identity-rekey.mjs +112 -0
- package/scripts/init.mjs +122 -0
- package/scripts/land.mjs +40 -0
- package/scripts/lib/adapters.mjs +156 -0
- package/scripts/lib/board-snapshot.mjs +511 -0
- package/scripts/lib/draft.mjs +385 -0
- package/scripts/lib/graduate.mjs +276 -0
- package/scripts/lib/instance.mjs +74 -0
- package/scripts/lib/marks.mjs +107 -0
- package/scripts/lib/materialize.mjs +138 -0
- package/scripts/lib/merge3.mjs +124 -0
- package/scripts/lib/offline-posture.mjs +120 -0
- package/scripts/lib/offline-respawn.mjs +18 -0
- package/scripts/lib/offline-wrangler.mjs +78 -0
- package/scripts/lib/publish-compose.mjs +12 -0
- package/scripts/lib/publish-conflict.mjs +78 -0
- package/scripts/lib/publish-evidence.mjs +144 -0
- package/scripts/lib/refine-compare.mjs +140 -0
- package/scripts/lib/refine-ledger.mjs +150 -0
- package/scripts/lib/refine-png.mjs +149 -0
- package/scripts/lib/seed-pack-build.mjs +141 -0
- package/scripts/lib/state-compare.mjs +68 -0
- package/scripts/lib/store.mjs +122 -0
- package/scripts/login.mjs +84 -0
- package/scripts/mark.mjs +112 -0
- package/scripts/migrate-board-keys.mjs +164 -0
- package/scripts/migrate.mjs +265 -0
- package/scripts/no-foreign-vocabulary.mjs +216 -0
- package/scripts/no-product-names.mjs +157 -0
- package/scripts/no-tenant-globals.mjs +1134 -0
- package/scripts/offline.mjs +247 -0
- package/scripts/og.mjs +170 -0
- package/scripts/one-tenant-resolver.mjs +146 -0
- package/scripts/open.mjs +63 -0
- package/scripts/publish.mjs +1041 -0
- package/scripts/read.mjs +31 -0
- package/scripts/refine.mjs +455 -0
- package/scripts/release-drift.mjs +138 -0
- package/scripts/restore.mjs +322 -0
- package/scripts/review.mjs +125 -0
- package/scripts/save.mjs +21 -0
- package/scripts/shell-lint.mjs +189 -0
- package/scripts/ship.mjs +460 -0
- package/scripts/shoot.mjs +264 -0
- package/scripts/smoke-invite.mjs +113 -0
- package/scripts/state-inventory.mjs +223 -0
- package/scripts/status.mjs +177 -0
- package/scripts/sync.mjs +27 -0
- package/scripts/tenant-do-rehearsal.mjs +1314 -0
- package/scripts/ui-copy-lint.mjs +202 -0
- package/scripts/watch.mjs +32 -0
- package/scripts/wrangler-preflight.mjs +246 -0
- package/seed/CANON.md +43 -0
- package/seed/README.md +119 -0
- package/seed/prototype-status.json +9 -0
- package/seed/registry.json +88 -0
- package/seed/skills/starter-ui/SKILL.md +52 -0
- package/seed/skills/starter-ui/skill.json +11 -0
- package/seed/skills/starter-ui/starter-tokens.css +73 -0
- package/seed/skills/starter-ui/starter-ui.css +318 -0
- package/seed/skills/starter-ui/starter-ui.js +50 -0
- package/seed/space.json +5 -0
- package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
- package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
- package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
- package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
- package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
- package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
- package/seed/threads.json +52 -0
- package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
- package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
- package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
- package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
- package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
- package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
- package/src/_worker.js +12668 -0
- package/src/board-key.mjs +57 -0
- package/src/board-room.mjs +881 -0
- package/src/bundle-keys.mjs +138 -0
- package/src/canon/collect-in-browser.js +215 -0
- package/src/canon/emit.mjs +232 -0
- package/src/canon/observe.mjs +260 -0
- package/src/canon/schema.mjs +451 -0
- package/src/canvas/DSEG-LICENSE.txt +95 -0
- package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
- package/src/canvas/canvas.css +885 -0
- package/src/canvas/canvas.js +5780 -0
- package/src/canvas/capture.js +485 -0
- package/src/chrome/appchrome.mjs +568 -0
- package/src/currency.mjs +203 -0
- package/src/delete-confirmation.mjs +254 -0
- package/src/drafts/drafts.js +326 -0
- package/src/entry.js +58 -0
- package/src/galleries.mjs +400 -0
- package/src/health-cron.mjs +173 -0
- package/src/identity.json +1 -0
- package/src/kv-codec.mjs +135 -0
- package/src/kv-identity.mjs +259 -0
- package/src/mail.mjs +599 -0
- package/src/provenance.mjs +74 -0
- package/src/publish-compose.mjs +363 -0
- package/src/publish-fork.mjs +221 -0
- package/src/publish-units.mjs +44 -0
- package/src/purge.mjs +81 -0
- package/src/review/COMMENTING-UX.md +306 -0
- package/src/review/cat.png +0 -0
- package/src/review/comment-cursor.svg +7 -0
- package/src/review/comments.js +1738 -0
- package/src/room-ticket.mjs +96 -0
- package/src/roster-chip.mjs +32 -0
- package/src/seed-pack.mjs +261 -0
- package/src/state-inventory.mjs +249 -0
- package/src/sw-logic.mjs +48 -0
- package/src/tenant-cache.mjs +141 -0
- package/src/tenant-context.mjs +386 -0
- package/src/tenant-do.js +2940 -0
- package/src/tenant-host.mjs +144 -0
- package/src/tenant-quotas.mjs +140 -0
- package/src/unit-core.mjs +103 -0
- package/src/unit-object.mjs +338 -0
- package/templates/README.md +67 -0
- package/templates/shell/deploy.yml +145 -0
- package/templates/shell/engine-bump.yml +142 -0
- package/templates/shell/health.yml +233 -0
- package/templates/shell/kv-backup.yml +201 -0
- package/templates/shell/roster-update.yml +110 -0
- package/templates/shell/space-preflight.yml +35 -0
- package/templates/shell/space-rebake.yml +79 -0
- package/templates/shell/store-backup.yml +152 -0
- package/templates/shell/wrangler.example.toml +266 -0
- package/templates/space/publish.yml +91 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canon/schema — the roles a design system has to fill, and the grader for an answer.
|
|
3
|
+
*
|
|
4
|
+
* A workspace's design system is a skill folder (agents/ui-skill.md): a tokens
|
|
5
|
+
* stylesheet, a components stylesheet, a manifest. This module is the part of that
|
|
6
|
+
* shape a MACHINE can check — the list of token ROLES, what each one is for, which
|
|
7
|
+
* ones are observed and which are computed from the observed ones, and the rules a
|
|
8
|
+
* set of components has to obey to be worth shipping.
|
|
9
|
+
*
|
|
10
|
+
* IT IS ONE LIST, DELIBERATELY. The seed workspace's design system fills exactly
|
|
11
|
+
* these roles (`test/canon-schema.test.mjs` proves it against the seed's own token
|
|
12
|
+
* file, and against the token names the seed's design-system page reads live). So a
|
|
13
|
+
* canon extracted from a team's real product and the canon a workspace is born with
|
|
14
|
+
* are the same document with different values — a prototype written against one
|
|
15
|
+
* works against the other, and the page that teaches the system keeps teaching it.
|
|
16
|
+
* Adding a role here without adding it to the seed fails that test, which is what
|
|
17
|
+
* stops the two drifting into two formats.
|
|
18
|
+
*
|
|
19
|
+
* THERE IS NO MODEL IN HERE AND THERE MUST NEVER BE ONE. Deciding that a product's
|
|
20
|
+
* fourth-most-common grey is its hairline colour is judgement, and judgement is the
|
|
21
|
+
* user's own agent's job — this file only says what the slots are and refuses an
|
|
22
|
+
* answer that does not fill them. Everything below is arithmetic: contrast ratios,
|
|
23
|
+
* monotonic scales, and a check that a component's CSS names a token that exists.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const CANON_VERSION = 1;
|
|
27
|
+
|
|
28
|
+
/** Roles, in emission order. `derived` roles are computed when the answer omits them. */
|
|
29
|
+
export const ROLES = [
|
|
30
|
+
// ── Paper: the surfaces, from the desk up ──────────────────────────────────
|
|
31
|
+
{ role: "paper", group: "paper", kind: "color", what: "the desk a sheet lies on — the page background behind everything" },
|
|
32
|
+
{ role: "sheet", group: "paper", kind: "color", what: "the sheet itself: cards, panels, the surface text sits on" },
|
|
33
|
+
{ role: "inset", group: "paper", kind: "color", what: "a pressed, quieter fill — table stripes, wells, disabled surfaces", derived: "inset" },
|
|
34
|
+
{ role: "stage", group: "paper", kind: "color", what: "the dark inset media sits in: video, 3D, a terminal", derived: "stage" },
|
|
35
|
+
{ role: "stage-on", group: "paper", kind: "color", what: "what is written ON the stage", derived: "stage-on" },
|
|
36
|
+
|
|
37
|
+
// ── Ink: everything written on the paper ───────────────────────────────────
|
|
38
|
+
{ role: "ink", group: "ink", kind: "color", what: "every word that matters — body and heading text on the sheet" },
|
|
39
|
+
{ role: "ink-2", group: "ink", kind: "color", what: "secondary text: captions, metadata, help", derived: "ink-2" },
|
|
40
|
+
{ role: "ink-3", group: "ink", kind: "color", what: "placeholders and disabled text", derived: "ink-3" },
|
|
41
|
+
{ role: "rule", group: "ink", kind: "color", what: "hairlines: borders, dividers, table rules" },
|
|
42
|
+
|
|
43
|
+
// ── The one hot ink ────────────────────────────────────────────────────────
|
|
44
|
+
{ role: "mark", group: "mark", kind: "color", what: "the one colour that marks the thing a person is meant to act on — primary buttons, the current step, a pin" },
|
|
45
|
+
{ role: "mark-ink", group: "mark", kind: "color", what: "the mark, darkened until it is readable as TEXT on the sheet", derived: "mark-ink" },
|
|
46
|
+
{ role: "wash", group: "mark", kind: "color", what: "the mark's quietest form: highlights, selected rows, chips", derived: "wash" },
|
|
47
|
+
// Text ON the mark is a separate job from the mark as text, and forgetting it is how an
|
|
48
|
+
// extraction produces an illegible primary button: a product whose hot ink is a pale
|
|
49
|
+
// yellow gets white-on-yellow the moment a component writes `color: #fff`.
|
|
50
|
+
{ role: "mark-on", group: "mark", kind: "color", what: "what is written ON the mark — the label of a primary button", derived: "mark-on" },
|
|
51
|
+
|
|
52
|
+
// ── States ─────────────────────────────────────────────────────────────────
|
|
53
|
+
{ role: "ok", group: "state", kind: "color", what: "the success/positive colour", derived: "ok" },
|
|
54
|
+
{ role: "ok-wash", group: "state", kind: "color", what: "the success colour's quiet fill", derived: "ok-wash" },
|
|
55
|
+
|
|
56
|
+
// ── Type ───────────────────────────────────────────────────────────────────
|
|
57
|
+
{ role: "font-display", group: "type", kind: "font-stack", what: "the face headings and UI chrome are set in, as a full CSS font stack ending in a generic family" },
|
|
58
|
+
{ role: "font-body", group: "type", kind: "font-stack", what: "the face running text is set in, as a full CSS font stack ending in a generic family", derived: "font-body" },
|
|
59
|
+
{ role: "text-xs", group: "type", kind: "length", what: "the smallest step: labels, plate numbers", scale: "type" },
|
|
60
|
+
{ role: "text-sm", group: "type", kind: "length", what: "captions and chips", scale: "type" },
|
|
61
|
+
{ role: "text-md", group: "type", kind: "length", what: "body copy — most of everything", scale: "type" },
|
|
62
|
+
{ role: "text-lg", group: "type", kind: "length", what: "lede and step titles", scale: "type" },
|
|
63
|
+
{ role: "text-xl", group: "type", kind: "length", what: "section titles", scale: "type" },
|
|
64
|
+
{ role: "text-2xl", group: "type", kind: "length", what: "page titles", scale: "type" },
|
|
65
|
+
{ role: "text-3xl", group: "type", kind: "length", what: "the one big thing on a page", scale: "type" },
|
|
66
|
+
|
|
67
|
+
// ── Space ──────────────────────────────────────────────────────────────────
|
|
68
|
+
{ role: "s1", group: "space", kind: "length", what: "the tightest gap on the ramp", scale: "space" },
|
|
69
|
+
{ role: "s2", group: "space", kind: "length", what: "space step 2", scale: "space" },
|
|
70
|
+
{ role: "s3", group: "space", kind: "length", what: "space step 3", scale: "space" },
|
|
71
|
+
{ role: "s4", group: "space", kind: "length", what: "space step 4 — the one most gaps land on", scale: "space" },
|
|
72
|
+
{ role: "s5", group: "space", kind: "length", what: "space step 5", scale: "space" },
|
|
73
|
+
{ role: "s6", group: "space", kind: "length", what: "space step 6", scale: "space" },
|
|
74
|
+
{ role: "s7", group: "space", kind: "length", what: "space step 7", scale: "space" },
|
|
75
|
+
{ role: "s8", group: "space", kind: "length", what: "the widest gap on the ramp — separates whole sections", scale: "space" },
|
|
76
|
+
|
|
77
|
+
// ── Shape ──────────────────────────────────────────────────────────────────
|
|
78
|
+
{ role: "radius-1", group: "shape", kind: "length", what: "the small corner radius: fields, chips, buttons" },
|
|
79
|
+
{ role: "radius-2", group: "shape", kind: "length", what: "the large corner radius: cards, panels, sheets", derived: "radius-2" },
|
|
80
|
+
{ role: "radius-pill", group: "shape", kind: "length", what: "a fully rounded end — pills and avatars", derived: "radius-pill" },
|
|
81
|
+
{ role: "hair", group: "shape", kind: "border", what: "the hairline as a whole border shorthand", derived: "hair" },
|
|
82
|
+
{ role: "lift", group: "shape", kind: "shadow", what: "the one elevation in the system, as a box-shadow", derived: "lift" },
|
|
83
|
+
|
|
84
|
+
// ── Measure ────────────────────────────────────────────────────────────────
|
|
85
|
+
{ role: "sheet-w", group: "measure", kind: "length", what: "how wide the sheet gets before it stops growing", derived: "sheet-w" },
|
|
86
|
+
{ role: "rail-w", group: "measure", kind: "length", what: "the margin rail beside a block", derived: "rail-w" },
|
|
87
|
+
{ role: "measure", group: "measure", kind: "length", what: "the longest comfortable line of prose, in ch", derived: "measure" },
|
|
88
|
+
|
|
89
|
+
// ── Motion ─────────────────────────────────────────────────────────────────
|
|
90
|
+
{ role: "fast", group: "motion", kind: "motion", what: "the quick transition: hovers, presses", derived: "fast" },
|
|
91
|
+
{ role: "slow", group: "motion", kind: "motion", what: "the considered transition: panels, sheets", derived: "slow" },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export const ROLE_NAMES = ROLES.map((r) => r.role);
|
|
95
|
+
export const ROLE_BY_NAME = new Map(ROLES.map((r) => [r.role, r]));
|
|
96
|
+
/** The roles an answer must actually OBSERVE. Everything else has a computable default. */
|
|
97
|
+
export const OBSERVED_ROLES = ROLES.filter((r) => !r.derived).map((r) => r.role);
|
|
98
|
+
export const COMPONENT_TYPES = ["primitive", "component", "pattern", "page"];
|
|
99
|
+
|
|
100
|
+
/* ── Colour arithmetic (no dependencies, no judgement) ─────────────────────── */
|
|
101
|
+
|
|
102
|
+
const NAMED = {
|
|
103
|
+
black: "#000000", white: "#ffffff", red: "#ff0000", green: "#008000", blue: "#0000ff",
|
|
104
|
+
gray: "#808080", grey: "#808080", silver: "#c0c0c0", maroon: "#800000", navy: "#000080",
|
|
105
|
+
olive: "#808000", purple: "#800080", teal: "#008080", aqua: "#00ffff", fuchsia: "#ff00ff",
|
|
106
|
+
lime: "#00ff00", yellow: "#ffff00", orange: "#ffa500",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** A CSS colour → `#rrggbb`, or null when this module cannot resolve it arithmetically. */
|
|
110
|
+
export function toHex(value) {
|
|
111
|
+
if (typeof value !== "string") return null;
|
|
112
|
+
const v = value.trim().toLowerCase();
|
|
113
|
+
if (!v) return null;
|
|
114
|
+
if (NAMED[v]) return NAMED[v];
|
|
115
|
+
let m = /^#([0-9a-f]{3,8})$/.exec(v);
|
|
116
|
+
if (m) {
|
|
117
|
+
let h = m[1];
|
|
118
|
+
if (h.length === 3 || h.length === 4) h = h.slice(0, 3).split("").map((c) => c + c).join("");
|
|
119
|
+
if (h.length === 8) h = h.slice(0, 6);
|
|
120
|
+
if (h.length !== 6) return null;
|
|
121
|
+
return "#" + h;
|
|
122
|
+
}
|
|
123
|
+
m = /^rgba?\(([^)]+)\)$/.exec(v);
|
|
124
|
+
if (m) {
|
|
125
|
+
const p = m[1].split(/[,\s/]+/).filter(Boolean).slice(0, 3).map((n) =>
|
|
126
|
+
n.endsWith("%") ? Math.round(parseFloat(n) * 2.55) : Math.round(parseFloat(n)));
|
|
127
|
+
if (p.length !== 3 || p.some((n) => !Number.isFinite(n))) return null;
|
|
128
|
+
return rgbToHex(p);
|
|
129
|
+
}
|
|
130
|
+
m = /^hsla?\(([^)]+)\)$/.exec(v);
|
|
131
|
+
if (m) {
|
|
132
|
+
const p = m[1].split(/[,\s/]+/).filter(Boolean).slice(0, 3);
|
|
133
|
+
const h = parseFloat(p[0]), s = parseFloat(p[1]) / 100, l = parseFloat(p[2]) / 100;
|
|
134
|
+
if (![h, s, l].every(Number.isFinite)) return null;
|
|
135
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
136
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
137
|
+
const mm = l - c / 2;
|
|
138
|
+
const seg = [[c, x, 0], [x, c, 0], [0, c, x], [0, x, c], [x, 0, c], [c, 0, x]][Math.floor(((h % 360) + 360) % 360 / 60)];
|
|
139
|
+
return rgbToHex(seg.map((n) => (n + mm) * 255));
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function rgbToHex(c) {
|
|
145
|
+
return "#" + c.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
|
|
146
|
+
}
|
|
147
|
+
function hexToRgb(hex) {
|
|
148
|
+
const h = toHex(hex) || "#000000";
|
|
149
|
+
return [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
150
|
+
}
|
|
151
|
+
/** Blend `a` toward `b` by `t` (0→a, 1→b). */
|
|
152
|
+
export function mix(a, b, t) {
|
|
153
|
+
const x = hexToRgb(a), y = hexToRgb(b);
|
|
154
|
+
return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
|
|
155
|
+
}
|
|
156
|
+
export function luminance(hex) {
|
|
157
|
+
return hexToRgb(hex).map((v) => {
|
|
158
|
+
v /= 255;
|
|
159
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
160
|
+
}).reduce((l, v, i) => l + v * [0.2126, 0.7152, 0.0722][i], 0);
|
|
161
|
+
}
|
|
162
|
+
/** WCAG contrast ratio, 1–21. */
|
|
163
|
+
export function contrast(a, b) {
|
|
164
|
+
const la = luminance(a), lb = luminance(b);
|
|
165
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
166
|
+
}
|
|
167
|
+
/** Darken (or lighten) `hex` until it clears `ratio` against `on`. Same maths the seed's
|
|
168
|
+
* design-system page uses when a person picks a new hot ink, so a canon and a live pick
|
|
169
|
+
* land on the same value. */
|
|
170
|
+
export function readableOn(hex, on, ratio = 4.5) {
|
|
171
|
+
const towards = luminance(on) > 0.5 ? "#000000" : "#ffffff";
|
|
172
|
+
let out = mix(hex, towards, 0.18);
|
|
173
|
+
for (let t = 0.18; t <= 0.95 && contrast(out, on) < ratio; t += 0.04) out = mix(hex, towards, t);
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ── Lengths ───────────────────────────────────────────────────────────────── */
|
|
178
|
+
|
|
179
|
+
const ROOT_PX = 16;
|
|
180
|
+
/** A CSS length → px, for ordering a scale. `ch`/`%`/`vw` return null (not comparable). */
|
|
181
|
+
export function toPx(value) {
|
|
182
|
+
if (typeof value !== "string") return null;
|
|
183
|
+
const m = /^(-?[\d.]+)\s*(px|rem|em|pt)?$/.exec(value.trim());
|
|
184
|
+
if (!m) return null;
|
|
185
|
+
const n = parseFloat(m[1]);
|
|
186
|
+
if (!Number.isFinite(n)) return null;
|
|
187
|
+
switch (m[2]) {
|
|
188
|
+
case undefined: return n === 0 ? 0 : null;
|
|
189
|
+
case "px": return n;
|
|
190
|
+
case "rem": case "em": return n * ROOT_PX;
|
|
191
|
+
case "pt": return n * (96 / 72);
|
|
192
|
+
default: return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ── Deriving the roles an answer may leave out ────────────────────────────── */
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Fill every `derived` role the answer omitted, from the ones it did give.
|
|
200
|
+
* Pure arithmetic on the observed values — a canon that supplies only the roles it
|
|
201
|
+
* actually saw in the product still emits a complete, legible system.
|
|
202
|
+
* Returns `{ tokens, derived }`; `derived` names what this function supplied.
|
|
203
|
+
*/
|
|
204
|
+
export function deriveTokens(given) {
|
|
205
|
+
const t = { ...given };
|
|
206
|
+
const derived = [];
|
|
207
|
+
const need = (role, fn) => {
|
|
208
|
+
if (t[role] == null || t[role] === "") { const v = fn(); if (v != null) { t[role] = v; derived.push(role); } }
|
|
209
|
+
};
|
|
210
|
+
const sheet = toHex(t.sheet) || "#ffffff";
|
|
211
|
+
const ink = toHex(t.ink) || "#000000";
|
|
212
|
+
const paperDark = luminance(sheet) < 0.5;
|
|
213
|
+
|
|
214
|
+
need("inset", () => mix(sheet, ink, 0.05));
|
|
215
|
+
need("stage", () => (paperDark ? mix(sheet, "#000000", 0.4) : mix(ink, "#000000", 0.15)));
|
|
216
|
+
need("stage-on", () => readableOn(sheet, toHex(t.stage) || "#000000", 4.5));
|
|
217
|
+
need("mark-on", () => {
|
|
218
|
+
// Whichever of the two colours already in the system reads on the mark, preferring
|
|
219
|
+
// the sheet so a primary button still looks like it belongs to the page. Only when
|
|
220
|
+
// neither clears 4.5:1 does this reach for a colour the system does not own.
|
|
221
|
+
const m = toHex(t.mark);
|
|
222
|
+
if (!m) return null;
|
|
223
|
+
for (const c of [sheet, ink]) if (contrast(c, m) >= 4.5) return c;
|
|
224
|
+
return contrast("#ffffff", m) >= contrast("#000000", m) ? "#ffffff" : "#000000";
|
|
225
|
+
});
|
|
226
|
+
need("ink-2", () => readableOn(mix(ink, sheet, 0.35), sheet, 4.5));
|
|
227
|
+
need("ink-3", () => readableOn(mix(ink, sheet, 0.55), sheet, 3));
|
|
228
|
+
need("mark-ink", () => (t.mark ? readableOn(toHex(t.mark) || ink, sheet, 4.5) : null));
|
|
229
|
+
need("wash", () => (t.mark ? mix(toHex(t.mark) || ink, sheet, 0.86) : null));
|
|
230
|
+
need("ok", () => readableOn("#2e7d51", sheet, 4.5));
|
|
231
|
+
need("ok-wash", () => mix(toHex(t.ok) || "#2e7d51", sheet, 0.86));
|
|
232
|
+
need("font-body", () => t["font-display"] || null);
|
|
233
|
+
need("radius-2", () => scaleLength(t["radius-1"], 2.2));
|
|
234
|
+
need("radius-pill", () => "999px");
|
|
235
|
+
need("hair", () => (t.rule ? `1px solid ${t.rule}` : null));
|
|
236
|
+
need("lift", () => {
|
|
237
|
+
const rgb = hexToRgb(ink).join(", ");
|
|
238
|
+
return `0 1px 2px rgba(${rgb}, .05), 0 14px 34px rgba(${rgb}, .07)`;
|
|
239
|
+
});
|
|
240
|
+
need("sheet-w", () => "62rem");
|
|
241
|
+
need("rail-w", () => "5rem");
|
|
242
|
+
need("measure", () => "66ch");
|
|
243
|
+
need("fast", () => "120ms ease");
|
|
244
|
+
need("slow", () => "320ms cubic-bezier(.2, .7, .3, 1)");
|
|
245
|
+
return { tokens: t, derived };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function scaleLength(value, factor) {
|
|
249
|
+
if (typeof value !== "string") return null;
|
|
250
|
+
const m = /^(-?[\d.]+)\s*([a-z%]*)$/.exec(value.trim());
|
|
251
|
+
if (!m) return null;
|
|
252
|
+
const n = parseFloat(m[1]) * factor;
|
|
253
|
+
return `${Math.round(n * 100) / 100}${m[2] || ""}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* ── Grading an answer ─────────────────────────────────────────────────────── */
|
|
257
|
+
|
|
258
|
+
const IDENT = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
259
|
+
/** Colour keywords a component stylesheet may name without owning a token for it. */
|
|
260
|
+
const COLOR_KEYWORDS = new Set(["transparent", "currentcolor", "inherit", "initial", "unset", "none", "revert"]);
|
|
261
|
+
const LITERAL_COLOR = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|color|lab|lch|oklab|oklch)\s*\(/;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Grade a canon. Returns `{ ok, errors, warnings, tokens, derived, prefix }`.
|
|
265
|
+
* Errors are things that would ship a broken or illegible design system; warnings are
|
|
266
|
+
* things a person should look at. `strict` promotes warnings to errors.
|
|
267
|
+
*/
|
|
268
|
+
export function validateCanon(canon, { strict = false } = {}) {
|
|
269
|
+
const errors = [];
|
|
270
|
+
const warnings = [];
|
|
271
|
+
const err = (m) => errors.push(m);
|
|
272
|
+
const warn = (m) => (strict ? errors : warnings).push(m);
|
|
273
|
+
|
|
274
|
+
if (!canon || typeof canon !== "object") return { ok: false, errors: ["canon is not an object"], warnings, tokens: {}, derived: [], prefix: "" };
|
|
275
|
+
|
|
276
|
+
const prefix = String(canon.prefix || "").trim();
|
|
277
|
+
if (!IDENT.test(prefix)) err(`prefix: "${canon.prefix}" is not a css-safe name (lowercase, digits, single hyphens)`);
|
|
278
|
+
// Tokens and classes may use different prefixes — `skill.json` `cssPrefixes` is a list
|
|
279
|
+
// for exactly that reason, and a workspace whose tokens are `--acme-` and whose classes
|
|
280
|
+
// are `.a-` is not doing anything wrong. Default them to the same word.
|
|
281
|
+
const classPrefix = String(canon.classPrefix || canon.prefix || "").trim();
|
|
282
|
+
if (!IDENT.test(classPrefix)) err(`classPrefix: "${canon.classPrefix}" is not a css-safe name`);
|
|
283
|
+
|
|
284
|
+
const given = canon.tokens && typeof canon.tokens === "object" ? canon.tokens : {};
|
|
285
|
+
// A skeleton ships every role as null; naming them is the whole job, so say which.
|
|
286
|
+
const missing = OBSERVED_ROLES.filter((r) => given[r] == null || given[r] === "");
|
|
287
|
+
if (missing.length) err(`tokens: ${missing.length} observed role${missing.length > 1 ? "s" : ""} still unanswered — ${missing.join(", ")}`);
|
|
288
|
+
|
|
289
|
+
const unknown = Object.keys(given).filter((k) => !ROLE_BY_NAME.has(k) && !k.startsWith("x-"));
|
|
290
|
+
if (unknown.length) err(`tokens: ${unknown.join(", ")} — not a role. A value the product has and the roles do not name goes under an "x-" name (x-brand-blue), which emits as an extra token.`);
|
|
291
|
+
|
|
292
|
+
const { tokens, derived } = deriveTokens(given);
|
|
293
|
+
|
|
294
|
+
for (const r of ROLES) {
|
|
295
|
+
const v = tokens[r.role];
|
|
296
|
+
if (v == null || v === "") {
|
|
297
|
+
// A computed role is empty only because the role it computes FROM is unanswered,
|
|
298
|
+
// and that is already the finding above. Repeating it once per dependant turns one
|
|
299
|
+
// real problem into a wall of noise and buries the list of what to actually do.
|
|
300
|
+
if (!missing.includes(r.role) && !(r.derived && missing.length)) err(`tokens.${r.role}: empty`);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (typeof v !== "string") { err(`tokens.${r.role}: must be a string`); continue; }
|
|
304
|
+
if (r.kind === "color" && !toHex(v)) err(`tokens.${r.role}: "${v}" is not a colour this can read (hex, rgb(), hsl(), or a basic keyword)`);
|
|
305
|
+
if (r.kind === "length" && toPx(v) == null && !/^[\d.]+\s*(ch|ex|vw|vh|vmin|vmax|%)$/.test(v.trim())) err(`tokens.${r.role}: "${v}" is not a length`);
|
|
306
|
+
if (r.kind === "font-stack" && !/,|\b(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-monospace|ui-sans-serif|ui-serif)\b/.test(v)) warn(`tokens.${r.role}: "${v}" has no fallback — a font stack should end in a generic family so a screen still renders when the face does not load`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Scales must climb. A type ramp that is not ordered is not a ramp, and every page
|
|
310
|
+
// that uses it inherits the disorder.
|
|
311
|
+
for (const scale of ["type", "space"]) {
|
|
312
|
+
const steps = ROLES.filter((r) => r.scale === scale);
|
|
313
|
+
let prev = null, prevName = "";
|
|
314
|
+
for (const s of steps) {
|
|
315
|
+
const px = toPx(tokens[s.role]);
|
|
316
|
+
if (px == null) continue;
|
|
317
|
+
if (prev != null && px <= prev) err(`tokens.${s.role}: ${tokens[s.role]} is not larger than ${prevName} (${tokens[prevName]}) — the ${scale} scale has to climb`);
|
|
318
|
+
prev = px; prevName = s.role;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Legibility, computed and REPORTED. Every pair below is a place a real screen puts
|
|
323
|
+
// one of these colours on another; the grader prints all of them whether they pass or
|
|
324
|
+
// fail, because "it computes the contrast ratios" is only believable if you can see
|
|
325
|
+
// the numbers, and a ratio that passes today is what a person checks against when they
|
|
326
|
+
// change a value tomorrow.
|
|
327
|
+
const ratios = [];
|
|
328
|
+
const pair = (fg, bg, floor, level, why) => {
|
|
329
|
+
const a = toHex(tokens[fg]), b = toHex(tokens[bg]);
|
|
330
|
+
if (!a || !b) return;
|
|
331
|
+
const c = contrast(a, b);
|
|
332
|
+
ratios.push({ fg, bg, ratio: Math.round(c * 100) / 100, floor, ok: c >= floor });
|
|
333
|
+
if (c >= floor) return;
|
|
334
|
+
const msg = `contrast: ${fg} on ${bg} is ${c.toFixed(2)}:1, under ${floor}:1 — ${why}`;
|
|
335
|
+
if (level === "error") err(msg); else warn(msg);
|
|
336
|
+
};
|
|
337
|
+
pair("ink", "sheet", 4.5, "error", "body text is not readable");
|
|
338
|
+
pair("mark-ink", "sheet", 4.5, "error", "leave mark-ink out and it is computed from mark to clear it");
|
|
339
|
+
pair("mark-on", "mark", 4.5, "error", "the label of a primary button is not readable — leave mark-on out and it is computed");
|
|
340
|
+
pair("ink-2", "sheet", 4.5, "warn", "secondary text is thin");
|
|
341
|
+
pair("ink-3", "sheet", 3, "warn", "placeholders and disabled text disappear");
|
|
342
|
+
pair("stage-on", "stage", 4.5, "warn", "text on the dark inset is thin");
|
|
343
|
+
pair("mark", "sheet", 1.6, "warn", "the hot ink has to be visible as a fill");
|
|
344
|
+
const sheet = toHex(tokens.sheet);
|
|
345
|
+
if (toHex(tokens.mark) && toHex(tokens.ink) && contrast(toHex(tokens.mark), toHex(tokens.ink)) < 1.35)
|
|
346
|
+
warn(`mark and ink are nearly the same colour — nothing on a screen will read as "act on this"`);
|
|
347
|
+
|
|
348
|
+
// Components.
|
|
349
|
+
const comps = Array.isArray(canon.components) ? canon.components : [];
|
|
350
|
+
const seen = new Set();
|
|
351
|
+
const known = new Set([...ROLE_NAMES.map((r) => `--${prefix}-${r}`),
|
|
352
|
+
...Object.keys(given).filter((k) => k.startsWith("x-")).map((k) => `--${prefix}-${k.slice(2)}`)]);
|
|
353
|
+
for (const [i, c] of comps.entries()) {
|
|
354
|
+
const at = `components[${i}]${c && c.name ? ` (${c.name})` : ""}`;
|
|
355
|
+
if (!c || typeof c !== "object") { err(`${at}: not an object`); continue; }
|
|
356
|
+
if (!IDENT.test(String(c.name || ""))) { err(`${at}: name must be a css-safe word`); continue; }
|
|
357
|
+
if (seen.has(c.name)) err(`${at}: two components named ${c.name}`);
|
|
358
|
+
seen.add(c.name);
|
|
359
|
+
if (!COMPONENT_TYPES.includes(c.type)) err(`${at}: type must be one of ${COMPONENT_TYPES.join(", ")}`);
|
|
360
|
+
if (!c.label || !c.description) err(`${at}: needs a label and a one-line description — the overlay and the gallery print them`);
|
|
361
|
+
const classes = Array.isArray(c.classes) ? c.classes : [];
|
|
362
|
+
if (!classes.length) err(`${at}: no classes`);
|
|
363
|
+
for (const cl of classes) if (!String(cl).startsWith(`${classPrefix}-`)) err(`${at}: class "${cl}" does not start with "${classPrefix}-" — the overlay finds a family by its prefix`);
|
|
364
|
+
const css = typeof c.css === "string" ? c.css : "";
|
|
365
|
+
if (!css.trim()) { err(`${at}: no css`); continue; }
|
|
366
|
+
for (const cl of classes) if (!css.includes(`.${cl}`)) err(`${at}: css never defines .${cl}`);
|
|
367
|
+
const lit = LITERAL_COLOR.exec(stripVarFallbacks(css));
|
|
368
|
+
if (lit) err(`${at}: css hard-codes a colour (${lit[0]}) — a component drinks from tokens, or the whole system stops moving when a token changes. Add an x- token and use var(--${prefix}-…).`);
|
|
369
|
+
for (const m of css.matchAll(/var\(\s*(--[a-zA-Z0-9-]+)/g)) {
|
|
370
|
+
if (known.has(m[1])) continue;
|
|
371
|
+
// The commonest way to get this wrong is to write the `x-` into the variable name.
|
|
372
|
+
// It is a bookkeeping mark on the ANSWER, not part of the token, so say the real
|
|
373
|
+
// name rather than only that this one is unknown.
|
|
374
|
+
const unprefixed = m[1].replace(new RegExp(`^--${prefix}-x-`), `--${prefix}-`);
|
|
375
|
+
const hint = unprefixed !== m[1] && known.has(unprefixed)
|
|
376
|
+
? ` — an "x-" token emits WITHOUT the x-, so that one is ${unprefixed}`
|
|
377
|
+
: "";
|
|
378
|
+
err(`${at}: css reads ${m[1]}, which this canon does not define${hint}`);
|
|
379
|
+
}
|
|
380
|
+
for (const m of css.matchAll(/\.([a-zA-Z][\w-]*)/g)) {
|
|
381
|
+
if (!m[1].startsWith(`${classPrefix}-`) && !classes.includes(m[1])) warn(`${at}: css styles .${m[1]}, outside its own family`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (!comps.length) warn("components: none — the canon is tokens only. A prototype can wear it, but nothing is named yet.");
|
|
385
|
+
|
|
386
|
+
const src = canon.source && typeof canon.source === "object" ? canon.source : {};
|
|
387
|
+
if (!src.url) warn("source.url: not recorded — a canon should say what it was taken from");
|
|
388
|
+
|
|
389
|
+
return { ok: errors.length === 0, errors, warnings, tokens, derived, prefix, classPrefix, ratios, componentCount: comps.length };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** `rgba(…)` inside a var() fallback is the one honest place a literal colour appears. */
|
|
393
|
+
function stripVarFallbacks(css) {
|
|
394
|
+
return css.replace(/var\(\s*--[a-zA-Z0-9-]+\s*,[^)]*\)/g, "var(--x)");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The first hard-coded colour in a stylesheet, or null. Exported because the rule the
|
|
399
|
+
* grader holds an EXTRACTED component to is the same rule the workspace's own stylesheets
|
|
400
|
+
* have to obey — a shipped file that hard-codes the ink it was born with stays that
|
|
401
|
+
* colour after an extraction, on a page where everything around it moved.
|
|
402
|
+
*/
|
|
403
|
+
export function literalColorIn(css) {
|
|
404
|
+
const m = LITERAL_COLOR.exec(stripVarFallbacks(String(css)));
|
|
405
|
+
return m ? m[0] : null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* ── Reading a token stylesheet back ───────────────────────────────────────── */
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Parse a `<prefix>-tokens.css` into `{prefix, tokens, extras}`.
|
|
412
|
+
* Used by `augur canon check --space` (grade the design system a workspace is actually
|
|
413
|
+
* carrying) and by the test that holds the seed to this schema.
|
|
414
|
+
*/
|
|
415
|
+
export function parseTokensCss(text, prefixHint = "") {
|
|
416
|
+
const decls = [...String(text).replace(/\/\*[\s\S]*?\*\//g, "").matchAll(/--([a-zA-Z0-9-]+)\s*:\s*([^;}]+)/g)]
|
|
417
|
+
.map((m) => [m[1], m[2].trim()]);
|
|
418
|
+
let prefix = prefixHint;
|
|
419
|
+
if (!prefix) {
|
|
420
|
+
// The prefix is the first hyphen-segment shared by the most declarations.
|
|
421
|
+
const tally = new Map();
|
|
422
|
+
for (const [name] of decls) {
|
|
423
|
+
const head = name.split("-")[0];
|
|
424
|
+
if (head) tally.set(head, (tally.get(head) || 0) + 1);
|
|
425
|
+
}
|
|
426
|
+
prefix = [...tally.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] || "";
|
|
427
|
+
}
|
|
428
|
+
const tokens = {}, extras = {};
|
|
429
|
+
for (const [name, value] of decls) {
|
|
430
|
+
if (prefix && !name.startsWith(`${prefix}-`)) continue;
|
|
431
|
+
const role = prefix ? name.slice(prefix.length + 1) : name;
|
|
432
|
+
if (ROLE_BY_NAME.has(role)) tokens[role] = value;
|
|
433
|
+
else extras[role] = value;
|
|
434
|
+
}
|
|
435
|
+
return { prefix, tokens, extras };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** The skeleton `augur canon start` writes: every observed role, unanswered. */
|
|
439
|
+
export function blankCanon({ url = "", prefix = "canon", classPrefix = "" } = {}) {
|
|
440
|
+
const tokens = {};
|
|
441
|
+
for (const r of OBSERVED_ROLES) tokens[r] = null;
|
|
442
|
+
return {
|
|
443
|
+
canonVersion: CANON_VERSION,
|
|
444
|
+
prefix,
|
|
445
|
+
classPrefix: classPrefix || prefix,
|
|
446
|
+
source: { url, collectedAt: null, how: "" },
|
|
447
|
+
tokens,
|
|
448
|
+
components: [],
|
|
449
|
+
notes: "",
|
|
450
|
+
};
|
|
451
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Copyright (c) 2017, keshikan (http://www.keshikan.net),
|
|
2
|
+
with Reserved Font Name "DSEG".
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
6
|
+
This license is copied below, and is also available with a FAQ at:
|
|
7
|
+
http://scripts.sil.org/OFL
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
12
|
+
-----------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
PREAMBLE
|
|
15
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
16
|
+
development of collaborative font projects, to support the font creation
|
|
17
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
18
|
+
open framework in which fonts may be shared and improved in partnership
|
|
19
|
+
with others.
|
|
20
|
+
|
|
21
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
22
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
23
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
24
|
+
redistributed and/or sold with any software provided that any reserved
|
|
25
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
26
|
+
however, cannot be released under any other type of license. The
|
|
27
|
+
requirement for fonts to remain under this license does not apply
|
|
28
|
+
to any document created using the fonts or their derivatives.
|
|
29
|
+
|
|
30
|
+
DEFINITIONS
|
|
31
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
32
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
33
|
+
include source files, build scripts and documentation.
|
|
34
|
+
|
|
35
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
36
|
+
copyright statement(s).
|
|
37
|
+
|
|
38
|
+
"Original Version" refers to the collection of Font Software components as
|
|
39
|
+
distributed by the Copyright Holder(s).
|
|
40
|
+
|
|
41
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
42
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
43
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
44
|
+
new environment.
|
|
45
|
+
|
|
46
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
47
|
+
writer or other person who contributed to the Font Software.
|
|
48
|
+
|
|
49
|
+
PERMISSION & CONDITIONS
|
|
50
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
51
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
52
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
53
|
+
Software, subject to the following conditions:
|
|
54
|
+
|
|
55
|
+
1) Neither the Font Software nor any of its individual components,
|
|
56
|
+
in Original or Modified Versions, may be sold by itself.
|
|
57
|
+
|
|
58
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
59
|
+
redistributed and/or sold with any software, provided that each copy
|
|
60
|
+
contains the above copyright notice and this license. These can be
|
|
61
|
+
included either as stand-alone text files, human-readable headers or
|
|
62
|
+
in the appropriate machine-readable metadata fields within text or
|
|
63
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
64
|
+
|
|
65
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
66
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
67
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
68
|
+
presented to the users.
|
|
69
|
+
|
|
70
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
71
|
+
Software shall not be used to promote, endorse or advertise any
|
|
72
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
73
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
74
|
+
permission.
|
|
75
|
+
|
|
76
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
77
|
+
must be distributed entirely under this license, and must not be
|
|
78
|
+
distributed under any other license. The requirement for fonts to
|
|
79
|
+
remain under this license does not apply to any document created
|
|
80
|
+
using the Font Software.
|
|
81
|
+
|
|
82
|
+
TERMINATION
|
|
83
|
+
This license becomes null and void if any of the above conditions are
|
|
84
|
+
not met.
|
|
85
|
+
|
|
86
|
+
DISCLAIMER
|
|
87
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
88
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
89
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
90
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
91
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
92
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
93
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
94
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
95
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|