mevebu 1.2.0 → 1.4.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/mevebu/app/mevebu.js +260 -208
- package/mevebu/app/tailwind.css +1639 -2077
- package/mevebu/app/tailwind.src.css +155 -1120
- package/mevebu/index.html +2 -2
- package/mevebu/libs/js/core.js +236 -96
- package/mevebu/libs/js/registry.js +11 -11
- package/mevebu/libs/js/tools/abstract-checklist.js +5 -7
- package/mevebu/libs/js/tools/add-subtract-days.js +9 -11
- package/mevebu/libs/js/tools/age-calculator.js +7 -8
- package/mevebu/libs/js/tools/api-request-builder.js +3 -4
- package/mevebu/libs/js/tools/area-converter.js +7 -9
- package/mevebu/libs/js/tools/aspect-ratio.js +8 -9
- package/mevebu/libs/js/tools/average-calculator.js +4 -5
- package/mevebu/libs/js/tools/base64.js +31 -11
- package/mevebu/libs/js/tools/binary-calculator.js +6 -7
- package/mevebu/libs/js/tools/border-radius.js +7 -9
- package/mevebu/libs/js/tools/box-shadow.js +6 -7
- package/mevebu/libs/js/tools/break-even.js +4 -5
- package/mevebu/libs/js/tools/business-days.js +7 -8
- package/mevebu/libs/js/tools/canonical-tag.js +5 -6
- package/mevebu/libs/js/tools/caption-counter.js +5 -7
- package/mevebu/libs/js/tools/case-converter.js +11 -12
- package/mevebu/libs/js/tools/case-inspector.js +14 -16
- package/mevebu/libs/js/tools/cash-denomination.js +7 -8
- package/mevebu/libs/js/tools/cash-flow.js +7 -8
- package/mevebu/libs/js/tools/cert-expiry.js +6 -10
- package/mevebu/libs/js/tools/changelog-generator.js +4 -6
- package/mevebu/libs/js/tools/character-counter.js +10 -13
- package/mevebu/libs/js/tools/checklist.js +6 -7
- package/mevebu/libs/js/tools/citation-number-check.js +10 -13
- package/mevebu/libs/js/tools/coin-flip.js +9 -11
- package/mevebu/libs/js/tools/color-converter.js +23 -17
- package/mevebu/libs/js/tools/color-palette.js +6 -8
- package/mevebu/libs/js/tools/color-picker.js +4 -5
- package/mevebu/libs/js/tools/content-calendar.js +3 -4
- package/mevebu/libs/js/tools/contrast-checker.js +5 -6
- package/mevebu/libs/js/tools/cron-builder.js +4 -6
- package/mevebu/libs/js/tools/css-clamp.js +5 -6
- package/mevebu/libs/js/tools/css-formatter.js +4 -7
- package/mevebu/libs/js/tools/css-grid.js +6 -7
- package/mevebu/libs/js/tools/css-minifier.js +5 -8
- package/mevebu/libs/js/tools/csv-to-json.js +2 -3
- package/mevebu/libs/js/tools/csv-viewer.js +9 -11
- package/mevebu/libs/js/tools/currency-format.js +5 -7
- package/mevebu/libs/js/tools/daily-planner.js +9 -10
- package/mevebu/libs/js/tools/data-masker.js +7 -9
- package/mevebu/libs/js/tools/data-storage.js +7 -9
- package/mevebu/libs/js/tools/data-validator.js +9 -11
- package/mevebu/libs/js/tools/date-difference.js +6 -7
- package/mevebu/libs/js/tools/date-format.js +11 -13
- package/mevebu/libs/js/tools/delimiter-converter.js +3 -5
- package/mevebu/libs/js/tools/discount-calculator.js +4 -5
- package/mevebu/libs/js/tools/doi-resolver.js +4 -5
- package/mevebu/libs/js/tools/env-formatter.js +3 -6
- package/mevebu/libs/js/tools/equation-formatter.js +5 -9
- package/mevebu/libs/js/tools/expense-split.js +6 -7
- package/mevebu/libs/js/tools/fake-data.js +20 -13
- package/mevebu/libs/js/tools/find-replace.js +3 -5
- package/mevebu/libs/js/tools/flashcard-creator.js +9 -10
- package/mevebu/libs/js/tools/font-pairing.js +6 -8
- package/mevebu/libs/js/tools/fraction-calculator.js +10 -13
- package/mevebu/libs/js/tools/git-command-builder.js +11 -22
- package/mevebu/libs/js/tools/gpa-calculator.js +8 -10
- package/mevebu/libs/js/tools/grade-calculator.js +7 -8
- package/mevebu/libs/js/tools/gradient-generator.js +10 -12
- package/mevebu/libs/js/tools/habit-tracker.js +8 -9
- package/mevebu/libs/js/tools/hash-generator.js +3 -5
- package/mevebu/libs/js/tools/hashtag-formatter.js +6 -8
- package/mevebu/libs/js/tools/hmac-generator.js +3 -5
- package/mevebu/libs/js/tools/hpp-calculator.js +4 -5
- package/mevebu/libs/js/tools/html-entity.js +2 -3
- package/mevebu/libs/js/tools/html-formatter.js +3 -6
- package/mevebu/libs/js/tools/http-status.js +5 -7
- package/mevebu/libs/js/tools/inventory-quantity.js +7 -8
- package/mevebu/libs/js/tools/json-formatter.js +3 -6
- package/mevebu/libs/js/tools/json-minifier.js +5 -8
- package/mevebu/libs/js/tools/json-to-csv.js +3 -5
- package/mevebu/libs/js/tools/json-to-typescript.js +2 -5
- package/mevebu/libs/js/tools/json-validator.js +2 -3
- package/mevebu/libs/js/tools/jwt-decoder.js +6 -7
- package/mevebu/libs/js/tools/jwt-expiry.js +5 -6
- package/mevebu/libs/js/tools/jwt-generator.js +4 -6
- package/mevebu/libs/js/tools/length-converter.js +7 -9
- package/mevebu/libs/js/tools/license-reference.js +10 -13
- package/mevebu/libs/js/tools/line-counter.js +5 -7
- package/mevebu/libs/js/tools/list-difference.js +5 -7
- package/mevebu/libs/js/tools/list-merger.js +4 -5
- package/mevebu/libs/js/tools/locale-number.js +5 -8
- package/mevebu/libs/js/tools/lorem-ipsum.js +4 -2
- package/mevebu/libs/js/tools/markdown-preview.js +5 -8
- package/mevebu/libs/js/tools/markdown-table.js +5 -7
- package/mevebu/libs/js/tools/markup-calculator.js +4 -5
- package/mevebu/libs/js/tools/meeting-agenda.js +8 -9
- package/mevebu/libs/js/tools/meta-preview.js +12 -13
- package/mevebu/libs/js/tools/meta-tags.js +6 -7
- package/mevebu/libs/js/tools/mime-types.js +5 -7
- package/mevebu/libs/js/tools/name-picker.js +2 -3
- package/mevebu/libs/js/tools/number-base-converter.js +9 -12
- package/mevebu/libs/js/tools/password-generator.js +9 -12
- package/mevebu/libs/js/tools/password-strength.js +21 -25
- package/mevebu/libs/js/tools/percent-to-fraction.js +6 -7
- package/mevebu/libs/js/tools/percentage-calculator.js +6 -9
- package/mevebu/libs/js/tools/percentage-change.js +4 -5
- package/mevebu/libs/js/tools/permutation-combination.js +9 -12
- package/mevebu/libs/js/tools/prefix-suffix.js +2 -3
- package/mevebu/libs/js/tools/priority-matrix.js +6 -10
- package/mevebu/libs/js/tools/profit-margin.js +4 -5
- package/mevebu/libs/js/tools/qr-generator.js +3 -4
- package/mevebu/libs/js/tools/qr-scanner.js +11 -12
- package/mevebu/libs/js/tools/quadratic-solver.js +7 -8
- package/mevebu/libs/js/tools/query-builder.js +2 -3
- package/mevebu/libs/js/tools/quiz-formatter.js +4 -6
- package/mevebu/libs/js/tools/ratio-calculator.js +7 -8
- package/mevebu/libs/js/tools/readability.js +12 -15
- package/mevebu/libs/js/tools/reading-time.js +5 -7
- package/mevebu/libs/js/tools/reference-completeness.js +6 -8
- package/mevebu/libs/js/tools/regex-tester.js +7 -10
- package/mevebu/libs/js/tools/remove-duplicate-lines.js +2 -3
- package/mevebu/libs/js/tools/remove-extra-spaces.js +3 -5
- package/mevebu/libs/js/tools/robots-txt.js +3 -4
- package/mevebu/libs/js/tools/roman-numeral-converter.js +6 -8
- package/mevebu/libs/js/tools/sales-commission.js +8 -9
- package/mevebu/libs/js/tools/scientific-calculator.js +10 -14
- package/mevebu/libs/js/tools/scientific-notation.js +8 -9
- package/mevebu/libs/js/tools/script-planner.js +8 -10
- package/mevebu/libs/js/tools/selling-price.js +4 -5
- package/mevebu/libs/js/tools/semver-helper.js +3 -5
- package/mevebu/libs/js/tools/sentence-length.js +9 -10
- package/mevebu/libs/js/tools/sequence-analyzer.js +7 -8
- package/mevebu/libs/js/tools/sitemap-xml.js +4 -7
- package/mevebu/libs/js/tools/slug-converter.js +3 -5
- package/mevebu/libs/js/tools/slug-preview.js +6 -7
- package/mevebu/libs/js/tools/sort-lines.js +3 -5
- package/mevebu/libs/js/tools/speed-converter.js +7 -9
- package/mevebu/libs/js/tools/spin-wheel.js +19 -17
- package/mevebu/libs/js/tools/sql-formatter.js +5 -9
- package/mevebu/libs/js/tools/sql-minifier.js +5 -8
- package/mevebu/libs/js/tools/standard-deviation.js +6 -9
- package/mevebu/libs/js/tools/stopwatch.js +4 -6
- package/mevebu/libs/js/tools/study-schedule.js +8 -9
- package/mevebu/libs/js/tools/subtitle-formatter.js +10 -11
- package/mevebu/libs/js/tools/table-to-markdown.js +8 -12
- package/mevebu/libs/js/tools/tax-calculator.js +4 -5
- package/mevebu/libs/js/tools/temperature-converter.js +8 -11
- package/mevebu/libs/js/tools/text-diff.js +11 -7
- package/mevebu/libs/js/tools/text-direction.js +3 -5
- package/mevebu/libs/js/tools/text-reverser.js +3 -5
- package/mevebu/libs/js/tools/time-duration.js +8 -9
- package/mevebu/libs/js/tools/timestamp-converter.js +4 -6
- package/mevebu/libs/js/tools/timezone-converter.js +9 -13
- package/mevebu/libs/js/tools/title-length.js +6 -7
- package/mevebu/libs/js/tools/unicode-inspector.js +4 -5
- package/mevebu/libs/js/tools/url-encoder.js +4 -2
- package/mevebu/libs/js/tools/url-inspector.js +9 -11
- package/mevebu/libs/js/tools/url-parser.js +12 -13
- package/mevebu/libs/js/tools/utf8-bytes.js +6 -9
- package/mevebu/libs/js/tools/uuid-generator.js +107 -15
- package/mevebu/libs/js/tools/version-compare.js +3 -4
- package/mevebu/libs/js/tools/video-duration.js +5 -7
- package/mevebu/libs/js/tools/volume-converter.js +7 -9
- package/mevebu/libs/js/tools/weekly-planner.js +8 -10
- package/mevebu/libs/js/tools/weight-converter.js +7 -9
- package/mevebu/libs/js/tools/weighted-average.js +4 -5
- package/mevebu/libs/js/tools/word-counter.js +10 -13
- package/mevebu/libs/js/tools/word-limit.js +4 -5
- package/mevebu/libs/js/tools/yaml-formatter.js +6 -7
- package/package.json +1 -1
|
@@ -25,15 +25,14 @@ const CASES = [
|
|
|
25
25
|
["snake_case", (words) => words.map((word) => word.toLowerCase()).join("_")],
|
|
26
26
|
["kebab-case", (words) => words.map((word) => word.toLowerCase()).join("-")],
|
|
27
27
|
["CONSTANT_CASE", (words) => words.map((word) => word.toUpperCase()).join("_")],
|
|
28
|
-
["Train-Case", (words) => words.map(cap).join("-")]
|
|
29
|
-
];
|
|
28
|
+
["Train-Case", (words) => words.map(cap).join("-")]];
|
|
30
29
|
|
|
31
30
|
export function mount(slot) {
|
|
32
31
|
const PIN = "case-converter";
|
|
33
32
|
const saved = readPin(PIN, { input: "Hello world from mevebu", active: "camelCase" });
|
|
34
33
|
|
|
35
34
|
const source = textArea({ value: saved.input ?? "", rows: 4, "aria-label": "Text to convert", placeholder: "Type or paste text" });
|
|
36
|
-
const list = el("div", { class: "
|
|
35
|
+
const list = el("div", { class: "grid gap-1" });
|
|
37
36
|
let active = CASES.some(([name]) => name === saved.active) ? saved.active : "camelCase";
|
|
38
37
|
|
|
39
38
|
function convert() {
|
|
@@ -41,9 +40,10 @@ export function mount(slot) {
|
|
|
41
40
|
for (const row of list.children) {
|
|
42
41
|
const convertCase = CASES.find(([name]) => name === row.dataset.case)[1];
|
|
43
42
|
const value = words.length ? convertCase(words) : "";
|
|
44
|
-
row.querySelector("
|
|
43
|
+
row.querySelector("[data-value]").textContent = value;
|
|
45
44
|
const on = row.dataset.case === active;
|
|
46
|
-
row.
|
|
45
|
+
if (on) row.dataset.on = "";
|
|
46
|
+
else delete row.dataset.on;
|
|
47
47
|
row.setAttribute("aria-pressed", String(on));
|
|
48
48
|
}
|
|
49
49
|
writePin(PIN, { input: source.value, active });
|
|
@@ -53,23 +53,22 @@ export function mount(slot) {
|
|
|
53
53
|
const button = el(
|
|
54
54
|
"button",
|
|
55
55
|
{
|
|
56
|
-
class: "
|
|
56
|
+
class: "group flex w-full cursor-pointer items-baseline justify-between gap-3 rounded-r1 border border-transparent bg-transparent px-3 py-[9px] text-start transition-colors duration-150 hover:bg-deck-2 data-[on]:border-jade data-[on]:bg-jade-soft", type: "button", dataset: { case: name }, "aria-pressed": String(name === active),
|
|
57
57
|
onclick: async () => {
|
|
58
58
|
active = name;
|
|
59
59
|
convert();
|
|
60
|
-
const value = button.querySelector("
|
|
60
|
+
const value = button.querySelector("[data-value]").textContent.trim();
|
|
61
61
|
if (value) toast((await copyText(value)) ? "Copied" : "Copy failed");
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
el("span", { class: "
|
|
65
|
-
el("span", { class: "mvb-case__value" })
|
|
62
|
+
}},
|
|
63
|
+
el("span", { class: "flex-none text-2xs font-semibold text-ink-2 group-data-[on]:text-ink" }, name),
|
|
64
|
+
el("span", { class: "wrap-anywhere text-end font-mono text-sm", dataset: { value: "" } })
|
|
66
65
|
);
|
|
67
66
|
list.append(button);
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
source.addEventListener("input", convert);
|
|
71
70
|
|
|
72
|
-
const card = toolcard({ body: stack(panel("Text", source), panel("
|
|
71
|
+
const card = toolcard({ body: stack(panel("Text", source), panel("Cases", list)) });
|
|
73
72
|
slot.append(card.node);
|
|
74
73
|
convert();
|
|
75
74
|
|
|
@@ -24,8 +24,7 @@ const CASES = [
|
|
|
24
24
|
["snake_case", (words) => words.map((word) => word.toLowerCase()).join("_")],
|
|
25
25
|
["kebab-case", (words) => words.map((word) => word.toLowerCase()).join("-")],
|
|
26
26
|
["CONSTANT_CASE", (words) => words.map((word) => word.toUpperCase()).join("_")],
|
|
27
|
-
["Train-Case", (words) => words.map(cap).join("-")]
|
|
28
|
-
];
|
|
27
|
+
["Train-Case", (words) => words.map(cap).join("-")]];
|
|
29
28
|
|
|
30
29
|
function detectCase(text) {
|
|
31
30
|
const t = text.trim();
|
|
@@ -53,13 +52,13 @@ function detectCase(text) {
|
|
|
53
52
|
|
|
54
53
|
export function mount(slot) {
|
|
55
54
|
const source = textArea({ rows: 5, value: "helloWorld", "aria-label": "Text", placeholder: "Type or paste text" });
|
|
56
|
-
const detected = el("span", { class: "
|
|
55
|
+
const detected = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "No letters");
|
|
57
56
|
const kv = el(
|
|
58
57
|
"div",
|
|
59
|
-
{ class: "
|
|
60
|
-
el("div", { class: "
|
|
58
|
+
{ class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
|
|
59
|
+
el("div", { class: "grid gap-3 bg-deck px-3 py-[9px] [grid-template-columns:minmax(90px,34%)_minmax(0,1fr)]" }, el("span", { class: "wrap-anywhere text-2xs font-semibold text-ink-2" }, "Detected case"), detected)
|
|
61
60
|
);
|
|
62
|
-
const list = el("div", { class: "
|
|
61
|
+
const list = el("div", { class: "grid gap-1" });
|
|
63
62
|
let active = "No letters";
|
|
64
63
|
|
|
65
64
|
function detectedValue() {
|
|
@@ -73,9 +72,10 @@ export function mount(slot) {
|
|
|
73
72
|
detected.textContent = active;
|
|
74
73
|
for (const row of list.children) {
|
|
75
74
|
const convert = CASES.find(([name]) => name === row.dataset.case)[1];
|
|
76
|
-
row.querySelector("
|
|
75
|
+
row.querySelector("[data-value]").textContent = words.length ? convert(words) : "";
|
|
77
76
|
const on = row.dataset.case === active;
|
|
78
|
-
row.
|
|
77
|
+
if (on) row.dataset.on = "";
|
|
78
|
+
else delete row.dataset.on;
|
|
79
79
|
row.setAttribute("aria-pressed", String(on));
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -84,14 +84,13 @@ export function mount(slot) {
|
|
|
84
84
|
const button = el(
|
|
85
85
|
"button",
|
|
86
86
|
{
|
|
87
|
-
class: "
|
|
87
|
+
class: "group flex w-full cursor-pointer items-baseline justify-between gap-3 rounded-r1 border border-transparent bg-transparent px-3 py-[9px] text-start transition-colors duration-150 hover:bg-deck-2 data-[on]:border-jade data-[on]:bg-jade-soft", type: "button", dataset: { case: name }, "aria-pressed": "false",
|
|
88
88
|
onclick: async () => {
|
|
89
|
-
const value = button.querySelector("
|
|
89
|
+
const value = button.querySelector("[data-value]").textContent.trim();
|
|
90
90
|
if (value) toast((await copyText(value)) ? "Copied" : "Copy failed");
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
el("span", { class: "
|
|
94
|
-
el("span", { class: "mvb-case__value" })
|
|
91
|
+
}},
|
|
92
|
+
el("span", { class: "flex-none text-2xs font-semibold text-ink-2 group-data-[on]:text-ink" }, name),
|
|
93
|
+
el("span", { class: "wrap-anywhere text-end font-mono text-sm", dataset: { value: "" } })
|
|
95
94
|
);
|
|
96
95
|
list.append(button);
|
|
97
96
|
}
|
|
@@ -103,8 +102,7 @@ export function mount(slot) {
|
|
|
103
102
|
stack(panel("Text", source)),
|
|
104
103
|
stack(kv, panel("Cases", list))
|
|
105
104
|
),
|
|
106
|
-
foot: actions(copyButton(detectedValue, "Copy detected", "fill"))
|
|
107
|
-
});
|
|
105
|
+
foot: actions(copyButton(detectedValue, "Copy detected", "fill"))});
|
|
108
106
|
slot.append(card.node);
|
|
109
107
|
run();
|
|
110
108
|
|
|
@@ -12,14 +12,14 @@ export function mount(slot) {
|
|
|
12
12
|
const saved = readPin(PIN, {});
|
|
13
13
|
const counts = new Map();
|
|
14
14
|
|
|
15
|
-
const listWrap = el("div", { class: "
|
|
16
|
-
const stats = el("div", { class: "
|
|
15
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
16
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
17
17
|
const note = notice("note");
|
|
18
18
|
note.write("Values are generic. Match them to your currency.");
|
|
19
19
|
|
|
20
|
-
const tile = (key, value) => el("div", { class: "
|
|
21
|
-
el("span", { class: "
|
|
22
|
-
el("span", { class: "
|
|
20
|
+
const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
|
|
21
|
+
el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
|
|
22
|
+
el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
23
23
|
|
|
24
24
|
const readCount = (value) => {
|
|
25
25
|
const input = counts.get(value);
|
|
@@ -40,7 +40,7 @@ export function mount(slot) {
|
|
|
40
40
|
el("th", {}, fmt(value)),
|
|
41
41
|
el("td", {}, fmt(readCount(value))),
|
|
42
42
|
el("td", {}, fmt(value * readCount(value)))));
|
|
43
|
-
listWrap.replaceChildren(el("table", { class: "
|
|
43
|
+
listWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
44
44
|
el("thead", {}, el("tr", {}, el("th", {}, "Value"), el("th", {}, "Count"), el("th", {}, "Subtotal"))),
|
|
45
45
|
el("tbody", {}, ...rows)));
|
|
46
46
|
const pieces = DENOMINATIONS.reduce((sum, value) => sum + readCount(value), 0);
|
|
@@ -63,8 +63,7 @@ export function mount(slot) {
|
|
|
63
63
|
() => DENOMINATIONS.map((value) => `${fmt(value)} x ${readCount(value)} = ${fmt(value * readCount(value))}`).join("\n"),
|
|
64
64
|
"Copy count",
|
|
65
65
|
"fill"
|
|
66
|
-
))
|
|
67
|
-
});
|
|
66
|
+
))});
|
|
68
67
|
slot.append(card.node);
|
|
69
68
|
render();
|
|
70
69
|
|
|
@@ -20,15 +20,15 @@ export function mount(slot) {
|
|
|
20
20
|
[{ value: "in", label: "In" }, { value: "out", label: "Out" }],
|
|
21
21
|
{ value: "in", label: "Direction", onChange: (next) => { type = next; } }
|
|
22
22
|
);
|
|
23
|
-
const listWrap = el("div", { class: "
|
|
24
|
-
const stats = el("div", { class: "
|
|
23
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
24
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
25
25
|
|
|
26
26
|
const addButton = button("Add", { icon: "plus", kind: "fill" });
|
|
27
27
|
|
|
28
28
|
const save = () => writePin(PIN, { opening: Number(opening.value) || 0, entries });
|
|
29
|
-
const tile = (key, value) => el("div", { class: "
|
|
30
|
-
el("span", { class: "
|
|
31
|
-
el("span", { class: "
|
|
29
|
+
const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
|
|
30
|
+
el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
|
|
31
|
+
el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
32
32
|
|
|
33
33
|
function totals() {
|
|
34
34
|
let balance = Number(opening.value) || 0;
|
|
@@ -69,7 +69,7 @@ export function mount(slot) {
|
|
|
69
69
|
el("td", {}, fmt(balance)),
|
|
70
70
|
el("td", {}, removeButton(entry)));
|
|
71
71
|
});
|
|
72
|
-
listWrap.replaceChildren(el("table", { class: "
|
|
72
|
+
listWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
73
73
|
el("thead", {}, el("tr", {}, el("th", {}, "Label"), el("th", {}, "In"), el("th", {}, "Out"), el("th", {}, "Balance"), el("th", {}, ""))),
|
|
74
74
|
el("tbody", {}, ...rows)));
|
|
75
75
|
}
|
|
@@ -113,8 +113,7 @@ export function mount(slot) {
|
|
|
113
113
|
copyButton(() => totals().lines.join("\n"), "Copy summary"),
|
|
114
114
|
downloadButton(() => csv(), "cash-flow.csv", "Save CSV", "text/csv"),
|
|
115
115
|
addButton
|
|
116
|
-
)
|
|
117
|
-
});
|
|
116
|
+
)});
|
|
118
117
|
slot.append(card.node);
|
|
119
118
|
render();
|
|
120
119
|
|
|
@@ -5,8 +5,8 @@ const DAY = 86400000;
|
|
|
5
5
|
const pad = (value) => String(value).padStart(2, "0");
|
|
6
6
|
|
|
7
7
|
function tile(label) {
|
|
8
|
-
const value = el("span", { class: "
|
|
9
|
-
const node = el("div", { class: "
|
|
8
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
|
|
9
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
10
10
|
node.set = (text) => { value.textContent = text; };
|
|
11
11
|
return node;
|
|
12
12
|
}
|
|
@@ -16,14 +16,13 @@ export function mount(slot) {
|
|
|
16
16
|
const dateField = textInput({
|
|
17
17
|
type: "date",
|
|
18
18
|
value: `${soon.getFullYear()}-${pad(soon.getMonth() + 1)}-${pad(soon.getDate())}`,
|
|
19
|
-
"aria-label": "Expiry date"
|
|
20
|
-
});
|
|
19
|
+
"aria-label": "Expiry date"});
|
|
21
20
|
const warnField = numberInput({ min: "1", max: "365", value: "30", "aria-label": "Warn threshold in days" });
|
|
22
21
|
|
|
23
22
|
const daysTile = tile("Days left");
|
|
24
23
|
const weeksTile = tile("Weeks left");
|
|
25
24
|
const statusTile = tile("Status");
|
|
26
|
-
const stats = el("div", { class: "
|
|
25
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, daysTile, weeksTile, statusTile);
|
|
27
26
|
const status = notice("note");
|
|
28
27
|
const error = notice("error");
|
|
29
28
|
let summary = "";
|
|
@@ -53,8 +52,7 @@ export function mount(slot) {
|
|
|
53
52
|
|
|
54
53
|
if (days < 0) status.write(`Expired ${Math.abs(days)} days ago.`);
|
|
55
54
|
else if (days === 0) status.write("Expires today.");
|
|
56
|
-
else if (days <= threshold) status.write(`
|
|
57
|
-
else status.write(`Expires in ${days} days.`);
|
|
55
|
+
else if (days <= threshold) status.write(`Inside the ${threshold} day warning window.`);
|
|
58
56
|
|
|
59
57
|
summary = `Expires ${expiry.toLocaleDateString()}, ${days} days left, ${state}.`;
|
|
60
58
|
}
|
|
@@ -67,13 +65,11 @@ export function mount(slot) {
|
|
|
67
65
|
stack(
|
|
68
66
|
panel("Expiry date", dateField),
|
|
69
67
|
panel("Warn within days", warnField),
|
|
70
|
-
el("p", { class: "mvb-note" }, "The date stays in this tab."),
|
|
71
68
|
error
|
|
72
69
|
),
|
|
73
70
|
stack(stats, status)
|
|
74
71
|
),
|
|
75
|
-
foot: actions(copyButton(() => summary, "Copy summary", "fill"))
|
|
76
|
-
});
|
|
72
|
+
foot: actions(copyButton(() => summary, "Copy summary", "fill"))});
|
|
77
73
|
slot.append(card.node);
|
|
78
74
|
run();
|
|
79
75
|
|
|
@@ -5,12 +5,11 @@ const GROUPS = [
|
|
|
5
5
|
{ key: "added", label: "Added" },
|
|
6
6
|
{ key: "changed", label: "Changed" },
|
|
7
7
|
{ key: "fixed", label: "Fixed" },
|
|
8
|
-
{ key: "removed", label: "Removed" }
|
|
9
|
-
];
|
|
8
|
+
{ key: "removed", label: "Removed" }];
|
|
10
9
|
|
|
11
10
|
export function mount(slot) {
|
|
12
|
-
const version = textInput({ value: "1.0.0",
|
|
13
|
-
const date = el("input", { class: "
|
|
11
|
+
const version = textInput({ value: "1.0.0", mono: true, "aria-label": "Version" });
|
|
12
|
+
const date = el("input", { class: "min-w-0 rounded-r1 border border-orbit bg-deck text-ink transition-[border-color,box-shadow] duration-150 placeholder:text-ink-3 hover:border-orbit-hard focus:border-jade focus:outline-none focus:ring-3 focus:ring-jade-soft w-full min-h-[var(--ctl)] px-3 py-2.5 text-base", type: "date", value: new Date().toISOString().slice(0, 10), "aria-label": "Date" });
|
|
14
13
|
const fields = GROUPS.map((group) => ({ ...group, area: textArea({ rows: 3, "aria-label": group.label }) }));
|
|
15
14
|
const out = output({ rows: 16 });
|
|
16
15
|
let text = "";
|
|
@@ -37,8 +36,7 @@ export function mount(slot) {
|
|
|
37
36
|
stack(panel("Version", version), panel("Date", date), ...fields.map((group) => panel(group.label, group.area))),
|
|
38
37
|
stack(panel("Changelog", out))
|
|
39
38
|
),
|
|
40
|
-
foot: actions(copyButton(() => text, "Copy Markdown", "fill"))
|
|
41
|
-
});
|
|
39
|
+
foot: actions(copyButton(() => text, "Copy Markdown", "fill"))});
|
|
42
40
|
slot.append(card.node);
|
|
43
41
|
run();
|
|
44
42
|
|
|
@@ -6,8 +6,8 @@ const chars = (text) => [...text].length;
|
|
|
6
6
|
const withoutSpaces = (text) => [...text.replace(/\s/g, "")].length;
|
|
7
7
|
|
|
8
8
|
function tile(label) {
|
|
9
|
-
const value = el("span", { class: "
|
|
10
|
-
const node = el("div", { class: "
|
|
9
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
10
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
11
11
|
node.set = (text) => { value.textContent = text; };
|
|
12
12
|
return node;
|
|
13
13
|
}
|
|
@@ -19,11 +19,11 @@ export function mount(slot) {
|
|
|
19
19
|
const totalTile = tile("Characters");
|
|
20
20
|
const noSpaceTile = tile("Without spaces");
|
|
21
21
|
const wordsTile = tile("Words");
|
|
22
|
-
const stats = el("div", { class: "
|
|
22
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, totalTile, noSpaceTile, wordsTile);
|
|
23
23
|
|
|
24
|
-
const meter = el("div", { class: "
|
|
24
|
+
const meter = el("div", { class: "h-2 overflow-hidden rounded-full bg-deck-2", role: "img", "aria-label": "Character limit" }, el("div", { class: "h-full w-0 rounded-full bg-orbit-hard transition-[width,background-color] duration-150 data-[level=weak]:bg-danger data-[level=ok]:bg-star data-[level=strong]:bg-jade" }));
|
|
25
25
|
const limitNote = notice("note");
|
|
26
|
-
const fill = meter.
|
|
26
|
+
const fill = meter.firstElementChild;
|
|
27
27
|
|
|
28
28
|
let counts = { total: 0, noSpace: 0, words: 0 };
|
|
29
29
|
|
|
@@ -31,8 +31,7 @@ export function mount(slot) {
|
|
|
31
31
|
return [
|
|
32
32
|
`${counts.total} characters`,
|
|
33
33
|
`${counts.noSpace} without spaces`,
|
|
34
|
-
`${counts.words} words
|
|
35
|
-
].join("\n");
|
|
34
|
+
`${counts.words} words`].join("\n");
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function run() {
|
|
@@ -40,8 +39,7 @@ export function mount(slot) {
|
|
|
40
39
|
counts = {
|
|
41
40
|
total: chars(text),
|
|
42
41
|
noSpace: withoutSpaces(text),
|
|
43
|
-
words: text.trim() ? text.trim().split(/\s+/).length : 0
|
|
44
|
-
};
|
|
42
|
+
words: text.trim() ? text.trim().split(/\s+/).length : 0};
|
|
45
43
|
totalTile.set(String(counts.total));
|
|
46
44
|
noSpaceTile.set(String(counts.noSpace));
|
|
47
45
|
wordsTile.set(String(counts.words));
|
|
@@ -50,11 +48,11 @@ export function mount(slot) {
|
|
|
50
48
|
if (limit > 0) {
|
|
51
49
|
const ratio = counts.total / limit;
|
|
52
50
|
fill.style.width = `${Math.min(100, ratio * 100)}%`;
|
|
53
|
-
fill.
|
|
51
|
+
fill.dataset.level = ratio > 1 ? "weak" : ratio > 0.8 ? "ok" : "strong";
|
|
54
52
|
limitNote.write(ratio > 1 ? `${counts.total - limit} over the limit.` : `${limit - counts.total} characters left.`);
|
|
55
53
|
} else {
|
|
56
54
|
fill.style.width = "0%";
|
|
57
|
-
fill.
|
|
55
|
+
delete fill.dataset.level;
|
|
58
56
|
limitNote.write("");
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -69,8 +67,7 @@ export function mount(slot) {
|
|
|
69
67
|
stack(panel("Text", source), panel("Character limit", limitField)),
|
|
70
68
|
stack(stats, meter, limitNote)
|
|
71
69
|
),
|
|
72
|
-
foot: actions(clearButton, copyButton(summary, "Copy summary", "fill"))
|
|
73
|
-
});
|
|
70
|
+
foot: actions(clearButton, copyButton(summary, "Copy summary", "fill"))});
|
|
74
71
|
slot.append(card.node);
|
|
75
72
|
run();
|
|
76
73
|
|
|
@@ -9,16 +9,16 @@ export function mount(slot) {
|
|
|
9
9
|
let list = Array.isArray(saved) ? saved : [];
|
|
10
10
|
|
|
11
11
|
const entry = textInput({ placeholder: "New item", "aria-label": "New item" });
|
|
12
|
-
const listWrap = el("div", { class: "
|
|
13
|
-
const stats = el("div", { class: "
|
|
12
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
13
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
14
14
|
|
|
15
15
|
const addButton = button("Add", { icon: "plus", kind: "fill" });
|
|
16
16
|
const clearDone = button("Clear finished", { icon: "trash" });
|
|
17
17
|
const copyList = copyButton(() => listText(), "Copy list");
|
|
18
18
|
|
|
19
|
-
const tile = (key, value) => el("div", { class: "
|
|
20
|
-
el("span", { class: "
|
|
21
|
-
el("span", { class: "
|
|
19
|
+
const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
|
|
20
|
+
el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
|
|
21
|
+
el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
22
22
|
|
|
23
23
|
const save = () => writePin(PIN, list);
|
|
24
24
|
|
|
@@ -75,8 +75,7 @@ export function mount(slot) {
|
|
|
75
75
|
stack(panel("New item", entry)),
|
|
76
76
|
stack(stats, panel("Items", listWrap))
|
|
77
77
|
),
|
|
78
|
-
foot: actions(copyList, clearDone, addButton)
|
|
79
|
-
});
|
|
78
|
+
foot: actions(copyList, clearDone, addButton)});
|
|
80
79
|
slot.append(card.node);
|
|
81
80
|
renderList();
|
|
82
81
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { el, bench, stack, panel, textArea, notice, copyButton, actions, toolcard } from "../core.js";
|
|
3
3
|
|
|
4
4
|
function tile(label) {
|
|
5
|
-
const value = el("span", { class: "
|
|
6
|
-
const node = el("div", { class: "
|
|
5
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
6
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
7
7
|
node.set = (text) => { value.textContent = text; };
|
|
8
8
|
return node;
|
|
9
9
|
}
|
|
@@ -47,8 +47,8 @@ export function mount(slot) {
|
|
|
47
47
|
const refsTile = tile("References");
|
|
48
48
|
const missingTile = tile("Missing");
|
|
49
49
|
const unusedTile = tile("Unused");
|
|
50
|
-
const stats = el("div", { class: "
|
|
51
|
-
const tableWrap = el("div", { class: "
|
|
50
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, citedTile, refsTile, missingTile, unusedTile);
|
|
51
|
+
const tableWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
52
52
|
const note = notice("note");
|
|
53
53
|
note.write("A number match does not prove the reference is correct.");
|
|
54
54
|
let report = "";
|
|
@@ -66,14 +66,13 @@ export function mount(slot) {
|
|
|
66
66
|
|
|
67
67
|
const rows = [
|
|
68
68
|
...missing.map((n) => [n, "Cited, no reference"]),
|
|
69
|
-
...unused.map((n) => [n, "Reference, not cited"])
|
|
70
|
-
];
|
|
69
|
+
...unused.map((n) => [n, "Reference, not cited"])];
|
|
71
70
|
if (!body.value.trim() && !list.value.trim()) {
|
|
72
|
-
tableWrap.replaceChildren(el("p", { class: "
|
|
71
|
+
tableWrap.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Paste the text and the reference list."));
|
|
73
72
|
} else if (!rows.length) {
|
|
74
|
-
tableWrap.replaceChildren(el("p", { class: "
|
|
73
|
+
tableWrap.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Every number matches."));
|
|
75
74
|
} else {
|
|
76
|
-
tableWrap.replaceChildren(el("table", { class: "
|
|
75
|
+
tableWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
77
76
|
el("thead", {}, el("tr", {}, el("th", {}, "Number"), el("th", {}, "Problem"))),
|
|
78
77
|
el("tbody", {}, ...rows.map((row) => el("tr", {},
|
|
79
78
|
el("th", {}, String(row[0])),
|
|
@@ -84,8 +83,7 @@ export function mount(slot) {
|
|
|
84
83
|
|
|
85
84
|
report = [
|
|
86
85
|
...missing.map((n) => `${n}: cited, no reference`),
|
|
87
|
-
...unused.map((n) => `${n}: reference, not cited`)
|
|
88
|
-
].join("\n");
|
|
86
|
+
...unused.map((n) => `${n}: reference, not cited`)].join("\n");
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
body.addEventListener("input", run);
|
|
@@ -96,8 +94,7 @@ export function mount(slot) {
|
|
|
96
94
|
stack(panel("Text with citations", body), panel("Reference list", list), note),
|
|
97
95
|
stack(stats, panel("Problems", tableWrap))
|
|
98
96
|
),
|
|
99
|
-
foot: actions(copyButton(() => report, "Copy problems", "fill"))
|
|
100
|
-
});
|
|
97
|
+
foot: actions(copyButton(() => report, "Copy problems", "fill"))});
|
|
101
98
|
slot.append(card.node);
|
|
102
99
|
run();
|
|
103
100
|
|
|
@@ -7,8 +7,8 @@ const TOSS_MS = 400;
|
|
|
7
7
|
|
|
8
8
|
/** A tally tile. The value is the node the tool writes to. */
|
|
9
9
|
function tile(key) {
|
|
10
|
-
const value = el("span", { class: "
|
|
11
|
-
const row = el("div", { class: "
|
|
10
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
11
|
+
const row = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
12
12
|
return { row, value };
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -19,12 +19,12 @@ export function mount(slot) {
|
|
|
19
19
|
let tails = Number(saved.tails) || 0;
|
|
20
20
|
let timer = 0;
|
|
21
21
|
|
|
22
|
-
const coin = el("div", { class: "
|
|
23
|
-
const verdict = el("p", { class: "
|
|
22
|
+
const coin = el("div", { class: "grid size-32 select-none place-items-center rounded-full bg-jade text-display font-bold text-on-jade data-[toss]:animate-toss", role: "img", "aria-label": "Coin, not flipped yet" }, "?");
|
|
23
|
+
const verdict = el("p", { class: "text-lg font-bold tracking-[-0.01em] min-h-7", "aria-live": "polite" });
|
|
24
24
|
const headsTile = tile("Heads");
|
|
25
25
|
const tailsTile = tile("Tails");
|
|
26
26
|
const totalTile = tile("Total");
|
|
27
|
-
const stats = el("div", { class: "
|
|
27
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, headsTile.row, tailsTile.row, totalTile.row);
|
|
28
28
|
|
|
29
29
|
function render(decision) {
|
|
30
30
|
verdict.textContent = decision;
|
|
@@ -40,9 +40,9 @@ export function mount(slot) {
|
|
|
40
40
|
|
|
41
41
|
flipButton.addEventListener("click", () => {
|
|
42
42
|
clearTimeout(timer);
|
|
43
|
-
coin.
|
|
43
|
+
delete coin.dataset.toss;
|
|
44
44
|
void coin.offsetWidth;
|
|
45
|
-
coin.
|
|
45
|
+
coin.dataset.toss = "";
|
|
46
46
|
timer = setTimeout(() => {
|
|
47
47
|
const isHeads = randomInt(2) === 0;
|
|
48
48
|
if (isHeads) heads++;
|
|
@@ -65,16 +65,14 @@ export function mount(slot) {
|
|
|
65
65
|
stack(panel("Coin", coin)),
|
|
66
66
|
stack(panel("Result", verdict), stats)
|
|
67
67
|
),
|
|
68
|
-
foot: actions(resetButton, flipButton)
|
|
69
|
-
});
|
|
68
|
+
foot: actions(resetButton, flipButton)});
|
|
70
69
|
slot.append(card.node);
|
|
71
70
|
render("");
|
|
72
71
|
|
|
73
72
|
return {
|
|
74
73
|
unmount() {
|
|
75
74
|
clearTimeout(timer);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
75
|
+
}};
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
export default { mount };
|
|
@@ -70,12 +70,12 @@ function luminance({ r, g, b }) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function mount(slot) {
|
|
73
|
-
const anyField = textInput({ value: "#F5B942",
|
|
73
|
+
const anyField = textInput({ value: "#F5B942", mono: true, "aria-label": "Any color" });
|
|
74
74
|
const picker = colorInput({ value: "#f5b942", "aria-label": "Pick a color" });
|
|
75
|
-
const swatch = el("div", { class: "
|
|
75
|
+
const swatch = el("div", { class: "h-14 rounded-r1 border border-orbit-hard bg-deck-2" });
|
|
76
76
|
|
|
77
77
|
const readonly = (label) => {
|
|
78
|
-
const field = textInput({ readonly: true,
|
|
78
|
+
const field = textInput({ readonly: true, mono: true, "aria-label": `${label}, press to copy` });
|
|
79
79
|
const wrap = panel(label, field);
|
|
80
80
|
wrap.style.cursor = "pointer";
|
|
81
81
|
wrap.addEventListener("click", async () => {
|
|
@@ -91,14 +91,16 @@ export function mount(slot) {
|
|
|
91
91
|
const cssField = readonly("CSS variable");
|
|
92
92
|
|
|
93
93
|
const error = notice("error");
|
|
94
|
-
const reading = el("p", { class: "
|
|
94
|
+
const reading = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
// `from` names the control the value came from. The text field is never
|
|
97
|
+
// rewritten while it is being typed into: "#abc" is valid shorthand one
|
|
98
|
+
// keystroke before it is valid six digit hex, so echoing the expanded form
|
|
99
|
+
// back would wipe out the rest of what was being typed.
|
|
100
|
+
function show(color, from) {
|
|
97
101
|
const hex = hexOf(color);
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
picker.value = hex;
|
|
101
|
-
}
|
|
102
|
+
if (from !== "text") anyField.value = hex.toUpperCase();
|
|
103
|
+
picker.value = hex;
|
|
102
104
|
hexField.querySelector("input").value = hex.toUpperCase();
|
|
103
105
|
const { h, s, l } = hslOf(color);
|
|
104
106
|
rgbField.querySelector("input").value = color.a < 1
|
|
@@ -113,32 +115,36 @@ export function mount(slot) {
|
|
|
113
115
|
reading.textContent = `Contrast ${onWhite}:1 on white, ${onBlack}:1 on black.${Number(onWhite) >= 4.5 ? "" : " Low on white."}${Number(onBlack) >= 4.5 ? "" : " Low on black."}`;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
function read(text,
|
|
118
|
+
function read(text, from) {
|
|
119
|
+
// An empty field is not a mistake, it is someone clearing it to start over.
|
|
120
|
+
if (!text.trim()) {
|
|
121
|
+
error.write("");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
117
124
|
const color = parse(text);
|
|
118
125
|
if (!color) {
|
|
119
126
|
error.write("That color was not recognized.");
|
|
120
127
|
return;
|
|
121
128
|
}
|
|
122
129
|
error.write("");
|
|
123
|
-
show(color,
|
|
130
|
+
show(color, from);
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
anyField.addEventListener("input", () => read(anyField.value,
|
|
127
|
-
picker.addEventListener("input", () => read(picker.value,
|
|
133
|
+
anyField.addEventListener("input", () => read(anyField.value, "text"));
|
|
134
|
+
picker.addEventListener("input", () => read(picker.value, "picker"));
|
|
128
135
|
|
|
129
136
|
const card = toolcard({
|
|
130
137
|
body: bench(
|
|
131
138
|
stack(
|
|
132
|
-
panel("Color", el("div", { class: "
|
|
139
|
+
panel("Color", el("div", { class: "flex min-w-0 flex-wrap items-center gap-2" }, picker, anyField)),
|
|
133
140
|
error,
|
|
134
141
|
panel(null, swatch),
|
|
135
142
|
reading
|
|
136
143
|
),
|
|
137
144
|
stack(hexField, rgbField, hslField, cssField)
|
|
138
|
-
)
|
|
139
|
-
});
|
|
145
|
+
)});
|
|
140
146
|
slot.append(card.node);
|
|
141
|
-
read("#F5B942",
|
|
147
|
+
read("#F5B942", "init");
|
|
142
148
|
|
|
143
149
|
return {};
|
|
144
150
|
}
|
|
@@ -59,7 +59,7 @@ function palette(hex, scheme) {
|
|
|
59
59
|
|
|
60
60
|
export function mount(slot) {
|
|
61
61
|
const picker = colorInput({ value: "#f5b942", "aria-label": "Base color" });
|
|
62
|
-
const swatches = el("div", { class: "
|
|
62
|
+
const swatches = el("div", { class: "grid gap-2 [grid-template-columns:repeat(auto-fit,minmax(96px,1fr))]" });
|
|
63
63
|
let colors = [];
|
|
64
64
|
let scheme = "analogous";
|
|
65
65
|
|
|
@@ -67,8 +67,7 @@ export function mount(slot) {
|
|
|
67
67
|
[
|
|
68
68
|
{ value: "analogous", label: "Analogous" },
|
|
69
69
|
{ value: "complementary", label: "Complementary" },
|
|
70
|
-
{ value: "monochrome", label: "Monochrome" },
|
|
71
|
-
],
|
|
70
|
+
{ value: "monochrome", label: "Monochrome" }],
|
|
72
71
|
{ value: scheme, label: "Scheme", onChange: (value) => { scheme = value; paint(); } }
|
|
73
72
|
);
|
|
74
73
|
|
|
@@ -82,9 +81,9 @@ export function mount(slot) {
|
|
|
82
81
|
...colors.map((hex) =>
|
|
83
82
|
el(
|
|
84
83
|
"button",
|
|
85
|
-
{ class: "
|
|
86
|
-
el("span", { class: "
|
|
87
|
-
el("span", { class: "
|
|
84
|
+
{ class: "grid cursor-pointer gap-1.5 rounded-r1 border border-orbit bg-deck p-2 text-start transition-colors duration-150 hover:border-jade", type: "button", onclick: () => copyOne(hex) },
|
|
85
|
+
el("span", { class: "h-[52px] rounded-lg border border-orbit-hard", style: `background:${hex}` }),
|
|
86
|
+
el("span", { class: "font-mono text-2xs text-ink-2" }, hex)
|
|
88
87
|
)
|
|
89
88
|
)
|
|
90
89
|
);
|
|
@@ -99,8 +98,7 @@ export function mount(slot) {
|
|
|
99
98
|
),
|
|
100
99
|
foot: actions(
|
|
101
100
|
copyButton(() => colors.map((hex, i) => `--color-${i + 1}: ${hex};`).join("\n"), "Copy CSS variables", "fill")
|
|
102
|
-
)
|
|
103
|
-
});
|
|
101
|
+
)});
|
|
104
102
|
slot.append(card.node);
|
|
105
103
|
paint();
|
|
106
104
|
|