mevebu 1.3.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 +1635 -2079
- package/mevebu/app/tailwind.src.css +155 -1127
- package/mevebu/index.html +2 -2
- package/mevebu/libs/js/core.js +235 -95
- 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 +2 -3
- 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 +10 -11
- 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 +5 -7
- 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 +6 -7
- 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 +6 -8
- 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 +8 -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/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-inspector.js +8 -9
- 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 +5 -9
- 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
|
@@ -16,8 +16,8 @@ function uniqueLines(text, ignoreCase) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function tile(label) {
|
|
19
|
-
const value = el("span", { class: "
|
|
20
|
-
const node = el("div", { class: "
|
|
19
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
20
|
+
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));
|
|
21
21
|
node.set = (text) => { value.textContent = text; };
|
|
22
22
|
return node;
|
|
23
23
|
}
|
|
@@ -33,7 +33,7 @@ export function mount(slot) {
|
|
|
33
33
|
const onlyATile = tile("Only in A");
|
|
34
34
|
const onlyBTile = tile("Only in B");
|
|
35
35
|
const bothTile = tile("In both");
|
|
36
|
-
const stats = el("div", { class: "
|
|
36
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, onlyATile, onlyBTile, bothTile);
|
|
37
37
|
let report = "";
|
|
38
38
|
|
|
39
39
|
function run() {
|
|
@@ -62,8 +62,7 @@ export function mount(slot) {
|
|
|
62
62
|
onlyB.join("\n"),
|
|
63
63
|
"",
|
|
64
64
|
`In both (${both.length}):`,
|
|
65
|
-
both.join("\n")
|
|
66
|
-
].join("\n");
|
|
65
|
+
both.join("\n")].join("\n");
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
first.addEventListener("input", run);
|
|
@@ -75,8 +74,7 @@ export function mount(slot) {
|
|
|
75
74
|
stack(panel("List A", first), panel("List B", second), panel(null, caseBox)),
|
|
76
75
|
stack(stats, panel("Only in A", onlyAOut), panel("Only in B", onlyBOut), panel("In both", bothOut))
|
|
77
76
|
),
|
|
78
|
-
foot: actions(copyButton(() => report, "Copy report", "fill"))
|
|
79
|
-
});
|
|
77
|
+
foot: actions(copyButton(() => report, "Copy report", "fill"))});
|
|
80
78
|
slot.append(card.node);
|
|
81
79
|
run();
|
|
82
80
|
|
|
@@ -4,8 +4,8 @@ import { el, bench, stack, panel, textArea, checkbox, output, copyButton, button
|
|
|
4
4
|
const linesOf = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
5
5
|
|
|
6
6
|
function tile(label) {
|
|
7
|
-
const value = el("span", { class: "
|
|
8
|
-
const node = el("div", { class: "
|
|
7
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
8
|
+
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));
|
|
9
9
|
node.set = (text) => { value.textContent = text; };
|
|
10
10
|
return node;
|
|
11
11
|
}
|
|
@@ -20,7 +20,7 @@ export function mount(slot) {
|
|
|
20
20
|
const listATile = tile("List A");
|
|
21
21
|
const listBTile = tile("List B");
|
|
22
22
|
const mergedTile = tile("Merged");
|
|
23
|
-
const stats = el("div", { class: "
|
|
23
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, listATile, listBTile, mergedTile);
|
|
24
24
|
let current = [];
|
|
25
25
|
|
|
26
26
|
function run() {
|
|
@@ -56,8 +56,7 @@ export function mount(slot) {
|
|
|
56
56
|
stack(panel("List A", first), panel("List B", second), panel(null, dedupeBox), panel(null, caseBox)),
|
|
57
57
|
stack(stats, panel("Merged", result))
|
|
58
58
|
),
|
|
59
|
-
foot: actions(clearButton, copyButton(() => current.join("\n"), "Copy merged", "fill"))
|
|
60
|
-
});
|
|
59
|
+
foot: actions(clearButton, copyButton(() => current.join("\n"), "Copy merged", "fill"))});
|
|
61
60
|
slot.append(card.node);
|
|
62
61
|
run();
|
|
63
62
|
|
|
@@ -10,8 +10,7 @@ const LOCALES = [
|
|
|
10
10
|
["id-ID", "Indonesian"],
|
|
11
11
|
["hi-IN", "Hindi"],
|
|
12
12
|
["ar-EG", "Arabic"],
|
|
13
|
-
["ja-JP", "Japanese"]
|
|
14
|
-
];
|
|
13
|
+
["ja-JP", "Japanese"]];
|
|
15
14
|
|
|
16
15
|
function format(locale, value, options) {
|
|
17
16
|
try {
|
|
@@ -26,8 +25,8 @@ export function mount(slot) {
|
|
|
26
25
|
const decimals = numberInput({ min: "0", max: "20", value: "2", "aria-label": "Fraction digits" });
|
|
27
26
|
const grouping = switchControl("Group thousands", { checked: true });
|
|
28
27
|
|
|
29
|
-
const table = el("table", { class: "
|
|
30
|
-
const tableWrap = el("div", { class: "
|
|
28
|
+
const table = 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" });
|
|
29
|
+
const tableWrap = el("div", { class: "max-h-80 overflow-auto" }, table);
|
|
31
30
|
const note = notice("note");
|
|
32
31
|
note.write("Formatting only. The number is not converted.");
|
|
33
32
|
|
|
@@ -40,8 +39,7 @@ export function mount(slot) {
|
|
|
40
39
|
const options = {
|
|
41
40
|
minimumFractionDigits: digits,
|
|
42
41
|
maximumFractionDigits: digits,
|
|
43
|
-
useGrouping: grouping.input.checked
|
|
44
|
-
};
|
|
42
|
+
useGrouping: grouping.input.checked};
|
|
45
43
|
const rows = LOCALES.map(([code, name]) => {
|
|
46
44
|
const shown = raw === "" || !Number.isFinite(parsed) ? "-" : format(code, parsed, options);
|
|
47
45
|
return el("tr", {}, el("th", {}, name), el("td", {}, shown));
|
|
@@ -68,8 +66,7 @@ export function mount(slot) {
|
|
|
68
66
|
),
|
|
69
67
|
stack(note, panel("By locale", tableWrap))
|
|
70
68
|
),
|
|
71
|
-
foot: actions(copyButton(() => tableText, "Copy list", "fill"))
|
|
72
|
-
});
|
|
69
|
+
foot: actions(copyButton(() => tableText, "Copy list", "fill"))});
|
|
73
70
|
slot.append(card.node);
|
|
74
71
|
run();
|
|
75
72
|
|
|
@@ -40,7 +40,7 @@ function blocks(source) {
|
|
|
40
40
|
const isBlockStart = (index) => {
|
|
41
41
|
const line = lines[index];
|
|
42
42
|
return !line.trim() || /^```/.test(line) || /^#{1,6}\s/.test(line) || /^\s*>/.test(line) ||
|
|
43
|
-
/^\s*([-*+]|\d+[.)])\s+/.test(line) || /^\s*([-*_])(\s*\1){2
|
|
43
|
+
/^\s*([-*+]|\d+[.)])\s+/.test(line) || /^\s*([-*_])(\s*\1){2}\s*$/.test(line) || isTableStart(index);
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
while (i < lines.length) {
|
|
@@ -64,7 +64,7 @@ function blocks(source) {
|
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (/^\s*([-*_])(\s*\1){2
|
|
67
|
+
if (/^\s*([-*_])(\s*\1){2}\s*$/.test(line)) {
|
|
68
68
|
nodes.push(el("hr"));
|
|
69
69
|
i++;
|
|
70
70
|
continue;
|
|
@@ -115,11 +115,9 @@ function blocks(source) {
|
|
|
115
115
|
export function mount(slot) {
|
|
116
116
|
const source = textArea({
|
|
117
117
|
rows: 16,
|
|
118
|
-
class: "mvb-in mvb-in--area mvb-in--mono",
|
|
119
118
|
"aria-label": "Markdown",
|
|
120
|
-
value: "# Title\n\nSome **bold**, *italic* and `code`.\n\n- one\n- two\n\n> A quote.\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst x = 1;\n```\n"
|
|
121
|
-
});
|
|
122
|
-
const preview = el("div", { class: "mvb-pane" });
|
|
119
|
+
value: "# Title\n\nSome **bold**, *italic* and `code`.\n\n- one\n- two\n\n> A quote.\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst x = 1;\n```\n"});
|
|
120
|
+
const preview = el("div", { class: "min-h-[120px] max-h-[380px] overflow-auto wrap-anywhere rounded-r1 border border-orbit bg-deck-2 px-4 py-3.5 leading-normal [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_h1]:my-4 [&_h2]:my-4 [&_h3]:my-4 [&_h1]:tracking-[-0.01em] [&_h2]:tracking-[-0.01em] [&_h3]:tracking-[-0.01em] [&_p]:mb-2.5 [&_ul]:mb-2.5 [&_ol]:mb-2.5 [&_pre]:mb-2.5 [&_ul]:ps-[22px] [&_ol]:ps-[22px] [&_code]:rounded-[5px] [&_code]:bg-deck [&_code]:px-[5px] [&_code]:py-px [&_code]:font-mono [&_code]:text-2xs [&_pre]:overflow-auto [&_pre]:rounded-r1 [&_pre]:bg-deck [&_pre]:px-3 [&_pre]:py-2.5 [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_a]:text-jade [&_table]:border-collapse [&_th]:border [&_td]:border [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-[9px] [&_td]:px-[9px] [&_th]:py-1.5 [&_td]:py-1.5" });
|
|
123
121
|
|
|
124
122
|
function run() {
|
|
125
123
|
preview.replaceChildren(...blocks(source.value));
|
|
@@ -132,8 +130,7 @@ export function mount(slot) {
|
|
|
132
130
|
stack(panel("Markdown", source)),
|
|
133
131
|
stack(panel("Preview", preview))
|
|
134
132
|
),
|
|
135
|
-
foot: actions(copyButton(() => source.value, "Copy markdown", "fill"))
|
|
136
|
-
});
|
|
133
|
+
foot: actions(copyButton(() => source.value, "Copy markdown", "fill"))});
|
|
137
134
|
slot.append(card.node);
|
|
138
135
|
run();
|
|
139
136
|
|
|
@@ -6,8 +6,8 @@ const cell = (text) => String(text ?? "").replace(/\|/g, "\\|").trim();
|
|
|
6
6
|
export function mount(slot) {
|
|
7
7
|
const columns = ["left", "left", "left"];
|
|
8
8
|
const rows = [["Name", "Role", "City"], ["Ada", "Engineer", "London"]];
|
|
9
|
-
const editor = el("div", { class: "
|
|
10
|
-
const alignment = el("div", { class: "
|
|
9
|
+
const editor = el("div", { class: "max-h-80 overflow-auto" });
|
|
10
|
+
const alignment = el("div", { class: "grid content-start gap-4" });
|
|
11
11
|
const result = output({ rows: 8 });
|
|
12
12
|
|
|
13
13
|
const toMarkdown = () => {
|
|
@@ -15,8 +15,7 @@ export function mount(slot) {
|
|
|
15
15
|
const marks = columns.map((align) => (align === "center" ? ":---:" : align === "right" ? "---:" : ":---"));
|
|
16
16
|
const lines = [
|
|
17
17
|
`| ${columns.map((_, index) => cell(head[index])).join(" | ")} |`,
|
|
18
|
-
`| ${marks.join(" | ")}
|
|
19
|
-
];
|
|
18
|
+
`| ${marks.join(" | ")} |`];
|
|
20
19
|
for (let r = 1; r < rows.length; r++) {
|
|
21
20
|
lines.push(`| ${columns.map((_, index) => cell(rows[r][index])).join(" | ")} |`);
|
|
22
21
|
}
|
|
@@ -26,7 +25,7 @@ export function mount(slot) {
|
|
|
26
25
|
const update = () => { result.write(toMarkdown()); };
|
|
27
26
|
|
|
28
27
|
function paintEditor() {
|
|
29
|
-
const table = el("table", { class: "
|
|
28
|
+
const table = 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" });
|
|
30
29
|
const headRow = el("tr");
|
|
31
30
|
for (let c = 0; c < columns.length; c++) {
|
|
32
31
|
const input = textInput({ value: rows[0][c] ?? "", "aria-label": `Column ${c + 1} header` });
|
|
@@ -100,8 +99,7 @@ export function mount(slot) {
|
|
|
100
99
|
stack(panel("Columns", row(addColumn, removeColumn)), panel("Rows", row(addRow, removeRow)), panel("Alignment", alignment)),
|
|
101
100
|
stack(panel("Cells", editor), panel("Markdown", result))
|
|
102
101
|
),
|
|
103
|
-
foot: actions(copyButton(() => result.textContent, "Copy table", "fill"))
|
|
104
|
-
});
|
|
102
|
+
foot: actions(copyButton(() => result.textContent, "Copy table", "fill"))});
|
|
105
103
|
slot.append(card.node);
|
|
106
104
|
paint();
|
|
107
105
|
|
|
@@ -6,8 +6,8 @@ function fmt(n) {
|
|
|
6
6
|
}
|
|
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" }, "-");
|
|
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
|
}
|
|
@@ -18,7 +18,7 @@ export function mount(slot) {
|
|
|
18
18
|
const price = tile("Selling price");
|
|
19
19
|
const profit = tile("Profit");
|
|
20
20
|
const margin = tile("Margin");
|
|
21
|
-
const stats = el("div", { class: "
|
|
21
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, price, profit, margin);
|
|
22
22
|
const error = notice("error");
|
|
23
23
|
let text = "";
|
|
24
24
|
|
|
@@ -58,8 +58,7 @@ export function mount(slot) {
|
|
|
58
58
|
stack(panel("Cost", cost), panel("Markup %", markup), error),
|
|
59
59
|
stack(stats)
|
|
60
60
|
),
|
|
61
|
-
foot: actions(copyButton(() => text, "Copy result", "fill"))
|
|
62
|
-
});
|
|
61
|
+
foot: actions(copyButton(() => text, "Copy result", "fill"))});
|
|
63
62
|
slot.append(card.node);
|
|
64
63
|
run();
|
|
65
64
|
|
|
@@ -22,21 +22,21 @@ export function mount(slot) {
|
|
|
22
22
|
const saved = readPin(PIN, { start: "09:00", items: [] });
|
|
23
23
|
let list = Array.isArray(saved.items) ? saved.items : [];
|
|
24
24
|
|
|
25
|
-
const startInput = el("input", { class: "
|
|
25
|
+
const startInput = 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: "time", value: saved.start || "09:00", "aria-label": "Meeting start" });
|
|
26
26
|
const topic = textInput({ placeholder: "Topic", "aria-label": "Topic" });
|
|
27
27
|
const owner = textInput({ placeholder: "Owner", "aria-label": "Owner" });
|
|
28
28
|
const minutes = numberInput({ min: 1, max: 600, value: 10, "aria-label": "Minutes" });
|
|
29
29
|
|
|
30
|
-
const listWrap = el("div", { class: "
|
|
31
|
-
const stats = el("div", { class: "
|
|
30
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
31
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
32
32
|
const error = notice("error");
|
|
33
33
|
|
|
34
34
|
const addButton = button("Add", { icon: "plus", kind: "fill" });
|
|
35
35
|
const copyAgenda = copyButton(() => agendaText(), "Copy agenda");
|
|
36
36
|
|
|
37
|
-
const tile = (key, value) => el("div", { class: "
|
|
38
|
-
el("span", { class: "
|
|
39
|
-
el("span", { class: "
|
|
37
|
+
const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
|
|
38
|
+
el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
|
|
39
|
+
el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
40
40
|
|
|
41
41
|
const base = () => {
|
|
42
42
|
const [h, m] = String(startInput.value || "00:00").split(":").map(Number);
|
|
@@ -83,7 +83,7 @@ export function mount(slot) {
|
|
|
83
83
|
el("td", {}, `${item.minutes} min`),
|
|
84
84
|
el("td", {}, removeButton(item))
|
|
85
85
|
));
|
|
86
|
-
listWrap.replaceChildren(el("table", { class: "
|
|
86
|
+
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" },
|
|
87
87
|
el("thead", {}, el("tr", {},
|
|
88
88
|
el("th", {}, "Start"), el("th", {}, "Topic"), el("th", {}, "Owner"),
|
|
89
89
|
el("th", {}, "Time"), el("th", {}, ""))),
|
|
@@ -126,8 +126,7 @@ export function mount(slot) {
|
|
|
126
126
|
),
|
|
127
127
|
stack(stats, panel("Agenda", listWrap))
|
|
128
128
|
),
|
|
129
|
-
foot: actions(copyAgenda, addButton)
|
|
130
|
-
});
|
|
129
|
+
foot: actions(copyAgenda, addButton)});
|
|
131
130
|
slot.append(card.node);
|
|
132
131
|
render();
|
|
133
132
|
|
|
@@ -7,17 +7,17 @@ export function mount(slot) {
|
|
|
7
7
|
const url = textInput({ value: "https://example.com", "aria-label": "URL" });
|
|
8
8
|
const description = textArea({ rows: 3, value: "Small privacy first tools that run entirely in the browser.", "aria-label": "Description" });
|
|
9
9
|
|
|
10
|
-
const urlLine = el("span", { class: "
|
|
11
|
-
const titleLine = el("span", { class: "
|
|
12
|
-
const descLine = el("p", { class: "
|
|
13
|
-
const sample = el("div", { class: "
|
|
14
|
-
el("div", { class: "
|
|
15
|
-
|
|
16
|
-
const titleCount = el("span", { class: "
|
|
17
|
-
const descCount = el("span", { class: "
|
|
18
|
-
const stats = el("div", { class: "
|
|
19
|
-
el("div", { class: "
|
|
20
|
-
el("div", { class: "
|
|
10
|
+
const urlLine = el("span", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "");
|
|
11
|
+
const titleLine = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "");
|
|
12
|
+
const descLine = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "");
|
|
13
|
+
const sample = el("div", { class: "w-full rounded-lg border border-orbit bg-deck px-[18px] py-4" },
|
|
14
|
+
el("div", { class: "grid content-start gap-4" }, urlLine, titleLine, descLine));
|
|
15
|
+
|
|
16
|
+
const titleCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
17
|
+
const descCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
18
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" },
|
|
19
|
+
el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, titleCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Title chars")),
|
|
20
|
+
el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, descCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Description chars"))
|
|
21
21
|
);
|
|
22
22
|
const note = notice("note");
|
|
23
23
|
|
|
@@ -40,8 +40,7 @@ export function mount(slot) {
|
|
|
40
40
|
foot: actions(
|
|
41
41
|
copyButton(() => title.value.trim(), "Copy title"),
|
|
42
42
|
copyButton(() => description.value.trim(), "Copy description", "fill")
|
|
43
|
-
)
|
|
44
|
-
});
|
|
43
|
+
)});
|
|
45
44
|
slot.append(card.node);
|
|
46
45
|
run();
|
|
47
46
|
|
|
@@ -16,11 +16,11 @@ export function mount(slot) {
|
|
|
16
16
|
);
|
|
17
17
|
const result = output({ rows: 8 });
|
|
18
18
|
|
|
19
|
-
const titleCount = el("span", { class: "
|
|
20
|
-
const descCount = el("span", { class: "
|
|
21
|
-
const stats = el("div", { class: "
|
|
22
|
-
el("div", { class: "
|
|
23
|
-
el("div", { class: "
|
|
19
|
+
const titleCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
20
|
+
const descCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
21
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" },
|
|
22
|
+
el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, titleCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Title chars")),
|
|
23
|
+
el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, descCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Description chars"))
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
function build() {
|
|
@@ -51,8 +51,7 @@ export function mount(slot) {
|
|
|
51
51
|
),
|
|
52
52
|
stack(stats, panel("Meta tags", result))
|
|
53
53
|
),
|
|
54
|
-
foot: actions(copyButton(() => result.textContent, "Copy tags", "fill"))
|
|
55
|
-
});
|
|
54
|
+
foot: actions(copyButton(() => result.textContent, "Copy tags", "fill"))});
|
|
56
55
|
slot.append(card.node);
|
|
57
56
|
build();
|
|
58
57
|
|
|
@@ -9,24 +9,23 @@ const TYPES = [
|
|
|
9
9
|
["mp3", "audio/mpeg", "Audio"], ["wav", "audio/wav", "Audio"], ["ogg", "audio/ogg", "Audio"], ["oga", "audio/ogg", "Audio"], ["m4a", "audio/mp4", "Audio"], ["flac", "audio/flac", "Audio"], ["aac", "audio/aac", "Audio"],
|
|
10
10
|
["mp4", "video/mp4", "Video"], ["webm", "video/webm", "Video"], ["ogv", "video/ogg", "Video"], ["mov", "video/quicktime", "Video"], ["avi", "video/x-msvideo", "Video"], ["mkv", "video/x-matroska", "Video"],
|
|
11
11
|
["woff", "font/woff", "Font"], ["woff2", "font/woff2", "Font"], ["ttf", "font/ttf", "Font"], ["otf", "font/otf", "Font"], ["eot", "application/vnd.ms-fontobject", "Font"],
|
|
12
|
-
["wasm", "application/wasm", "Binary"], ["bin", "application/octet-stream", "Binary"]
|
|
13
|
-
];
|
|
12
|
+
["wasm", "application/wasm", "Binary"], ["bin", "application/octet-stream", "Binary"]];
|
|
14
13
|
|
|
15
14
|
export function mount(slot) {
|
|
16
15
|
const filter = textInput({ type: "search", placeholder: "Extension or type", "aria-label": "Filter" });
|
|
17
|
-
const scroll = el("div", { class: "
|
|
16
|
+
const scroll = el("div", { class: "max-h-80 overflow-auto" });
|
|
18
17
|
let visible = TYPES;
|
|
19
18
|
|
|
20
19
|
function run() {
|
|
21
20
|
const query = filter.value.trim().toLowerCase();
|
|
22
21
|
visible = TYPES.filter(([ext, mime]) => !query || ext.includes(query) || mime.toLowerCase().includes(query));
|
|
23
22
|
if (!visible.length) {
|
|
24
|
-
scroll.replaceChildren(el("p", { class: "
|
|
23
|
+
scroll.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "No type matches."));
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
scroll.replaceChildren(el(
|
|
28
27
|
"table",
|
|
29
|
-
{ class: "
|
|
28
|
+
{ 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" },
|
|
30
29
|
el("thead", {}, el("tr", {}, el("th", {}, "Extension"), el("th", {}, "MIME type"), el("th", {}, "Kind"))),
|
|
31
30
|
el("tbody", {}, ...visible.map(([ext, mime, kind]) => el("tr", {},
|
|
32
31
|
el("td", {}, `.${ext}`),
|
|
@@ -43,8 +42,7 @@ export function mount(slot) {
|
|
|
43
42
|
stack(panel("Filter", filter)),
|
|
44
43
|
stack(panel("MIME types", scroll))
|
|
45
44
|
),
|
|
46
|
-
foot: actions(copyButton(() => visible.map(([ext, mime]) => `.${ext} ${mime}`).join("\n"), "Copy list", "fill"))
|
|
47
|
-
});
|
|
45
|
+
foot: actions(copyButton(() => visible.map(([ext, mime]) => `.${ext} ${mime}`).join("\n"), "Copy list", "fill"))});
|
|
48
46
|
slot.append(card.node);
|
|
49
47
|
run();
|
|
50
48
|
|
|
@@ -8,7 +8,7 @@ export function mount(slot) {
|
|
|
8
8
|
const listField = textArea({ value: saved.entries ?? "", rows: 8, "aria-label": "Entries, one per line", placeholder: "One entry per line" });
|
|
9
9
|
const countField = numberInput({ value: saved.winners ?? "1", min: "1", max: "500", "aria-label": "How many to draw" });
|
|
10
10
|
const uniqueBox = checkbox("No repeats", { checked: saved.unique !== false });
|
|
11
|
-
const notify = el("p", { class: "
|
|
11
|
+
const notify = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
|
|
12
12
|
|
|
13
13
|
const drawButton = button("Draw", { icon: "play", kind: "fill" });
|
|
14
14
|
const copyButton = button("Copy", { icon: "copy" });
|
|
@@ -58,8 +58,7 @@ export function mount(slot) {
|
|
|
58
58
|
stack(panel("Entries", listField), panel("Winners", countField), panel(null, uniqueBox)),
|
|
59
59
|
stack(panel("Drawn", result), notify)
|
|
60
60
|
),
|
|
61
|
-
foot: actions(copyButton, drawButton)
|
|
62
|
-
});
|
|
61
|
+
foot: actions(copyButton, drawButton)});
|
|
63
62
|
slot.append(card.node);
|
|
64
63
|
draw();
|
|
65
64
|
|
|
@@ -5,8 +5,7 @@ const BASES = [
|
|
|
5
5
|
{ value: "2", label: "Binary" },
|
|
6
6
|
{ value: "8", label: "Octal" },
|
|
7
7
|
{ value: "10", label: "Decimal" },
|
|
8
|
-
{ value: "16", label: "Hex" }
|
|
9
|
-
];
|
|
8
|
+
{ value: "16", label: "Hex" }];
|
|
10
9
|
|
|
11
10
|
const PATTERNS = { 2: /^[01]+$/, 8: /^[0-7]+$/, 10: /^[0-9]+$/, 16: /^[0-9a-f]+$/ };
|
|
12
11
|
const PREFIX = { 2: "0b", 8: "0o", 10: "", 16: "0x" };
|
|
@@ -23,17 +22,17 @@ function parseInBase(text, base) {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
const kvRow = (key, value) =>
|
|
26
|
-
el("div", { class: "
|
|
25
|
+
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" }, key), el("span", { class: "wrap-anywhere font-mono text-2xs" }, value));
|
|
27
26
|
|
|
28
27
|
export function mount(slot) {
|
|
29
28
|
const source = textInput({ value: "255", "aria-label": "Number", placeholder: "255" });
|
|
30
|
-
const binary = el("span", { class: "
|
|
31
|
-
const octal = el("span", { class: "
|
|
32
|
-
const decimal = el("span", { class: "
|
|
33
|
-
const hex = el("span", { class: "
|
|
29
|
+
const binary = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
30
|
+
const octal = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
31
|
+
const decimal = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
32
|
+
const hex = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
34
33
|
const kv = el(
|
|
35
34
|
"div",
|
|
36
|
-
{ class: "
|
|
35
|
+
{ class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
|
|
37
36
|
kvRow("Binary", binary),
|
|
38
37
|
kvRow("Octal", octal),
|
|
39
38
|
kvRow("Decimal", decimal),
|
|
@@ -76,8 +75,7 @@ export function mount(slot) {
|
|
|
76
75
|
onChange: (value) => {
|
|
77
76
|
base = value;
|
|
78
77
|
run();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
78
|
+
}});
|
|
81
79
|
|
|
82
80
|
source.addEventListener("input", run);
|
|
83
81
|
|
|
@@ -86,8 +84,7 @@ export function mount(slot) {
|
|
|
86
84
|
stack(panel("Number", source), panel("Input base", chips), error),
|
|
87
85
|
stack(panel("In every base", kv))
|
|
88
86
|
),
|
|
89
|
-
foot: actions(copyButton(() => text, "Copy all", "fill"))
|
|
90
|
-
});
|
|
87
|
+
foot: actions(copyButton(() => text, "Copy all", "fill"))});
|
|
91
88
|
slot.append(card.node);
|
|
92
89
|
run();
|
|
93
90
|
|
|
@@ -5,12 +5,11 @@ const SETS = {
|
|
|
5
5
|
lower: "abcdefghijklmnopqrstuvwxyz",
|
|
6
6
|
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
7
7
|
digits: "0123456789",
|
|
8
|
-
symbols: "!@#$%^&*()-_=+[]{};:,.<>?"
|
|
9
|
-
};
|
|
8
|
+
symbols: "!@#$%^&*()-_=+[]{};:,.<>?"};
|
|
10
9
|
|
|
11
10
|
export function mount(slot) {
|
|
12
11
|
const lengthField = rangeInput({ min: "6", max: "64", value: "16", "aria-label": "Length" });
|
|
13
|
-
const lengthLabel = el("span", { class: "
|
|
12
|
+
const lengthLabel = el("span", { class: "text-sm font-semibold text-ink-2" });
|
|
14
13
|
|
|
15
14
|
function showLength() {
|
|
16
15
|
lengthLabel.textContent = `Length: ${lengthField.value}`;
|
|
@@ -20,11 +19,10 @@ export function mount(slot) {
|
|
|
20
19
|
{ value: "lower", label: "a to z" },
|
|
21
20
|
{ value: "upper", label: "A to Z" },
|
|
22
21
|
{ value: "digits", label: "0 to 9" },
|
|
23
|
-
{ value: "symbols", label: "Symbols" }
|
|
24
|
-
]);
|
|
22
|
+
{ value: "symbols", label: "Symbols" }]);
|
|
25
23
|
|
|
26
|
-
const secret = textInput({
|
|
27
|
-
const meter = el("div", { class: "
|
|
24
|
+
const secret = textInput({ mono: true, readonly: true, "aria-label": "Password", placeholder: "Press Generate" });
|
|
25
|
+
const meter = el("div", { class: "h-2 overflow-hidden rounded-full bg-deck-2", role: "img", "aria-label": "Strength" }, 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" }));
|
|
28
26
|
const strength = notice("note");
|
|
29
27
|
const error = notice("error");
|
|
30
28
|
|
|
@@ -37,11 +35,11 @@ export function mount(slot) {
|
|
|
37
35
|
|
|
38
36
|
function draw() {
|
|
39
37
|
const source = pool();
|
|
40
|
-
const fill = meter.
|
|
38
|
+
const fill = meter.firstElementChild;
|
|
41
39
|
if (!source) {
|
|
42
40
|
secret.value = "";
|
|
43
41
|
fill.style.width = "0%";
|
|
44
|
-
fill.
|
|
42
|
+
delete fill.dataset.level;
|
|
45
43
|
meter.setAttribute("aria-label", "Strength");
|
|
46
44
|
strength.write("");
|
|
47
45
|
error.write("Pick at least one group above.");
|
|
@@ -65,7 +63,7 @@ export function mount(slot) {
|
|
|
65
63
|
const bits = Math.round(length * Math.log2(source.length));
|
|
66
64
|
const word = bits < 50 ? "weak" : bits < 90 ? "good" : "strong";
|
|
67
65
|
fill.style.width = `${Math.min(100, (bits / 128) * 100)}%`;
|
|
68
|
-
fill.
|
|
66
|
+
fill.dataset.level = bits < 50 ? "weak" : bits < 90 ? "ok" : "strong";
|
|
69
67
|
// The bar is colour only, so the reading it stands for also goes on the bar itself.
|
|
70
68
|
meter.setAttribute("aria-label", `Strength: ${bits} bits, ${word}`);
|
|
71
69
|
strength.write(`${bits} bits, ${word}.`);
|
|
@@ -84,8 +82,7 @@ export function mount(slot) {
|
|
|
84
82
|
stack(panel(lengthLabel, lengthField), groups, error),
|
|
85
83
|
stack(panel("Password", secret), meter, strength)
|
|
86
84
|
),
|
|
87
|
-
foot: actions(copyButton, generateButton)
|
|
88
|
-
});
|
|
85
|
+
foot: actions(copyButton, generateButton)});
|
|
89
86
|
slot.append(card.node);
|
|
90
87
|
draw();
|
|
91
88
|
|
|
@@ -3,24 +3,21 @@ import { el, icon, passwordInput, button, bench, stack, panel, notice, copyButto
|
|
|
3
3
|
|
|
4
4
|
const COMMON = new Set([
|
|
5
5
|
"password", "123456", "123456789", "qwerty", "letmein", "admin", "welcome",
|
|
6
|
-
"iloveyou", "monkey", "dragon", "abc123", "111111", "000000", "passw0rd"
|
|
7
|
-
]);
|
|
6
|
+
"iloveyou", "monkey", "dragon", "abc123", "111111", "000000", "passw0rd"]);
|
|
8
7
|
|
|
9
8
|
const GROUPS = [
|
|
10
9
|
{ test: (value) => /[a-z]/.test(value) },
|
|
11
10
|
{ test: (value) => /[A-Z]/.test(value) },
|
|
12
11
|
{ test: (value) => /[0-9]/.test(value) },
|
|
13
|
-
{ test: (value) => /[^A-Za-z0-9]/.test(value) }
|
|
14
|
-
];
|
|
12
|
+
{ test: (value) => /[^A-Za-z0-9]/.test(value) }];
|
|
15
13
|
|
|
16
14
|
const LEVELS = [
|
|
17
|
-
{ name: "Very weak",
|
|
18
|
-
{ name: "Weak",
|
|
19
|
-
{ name: "Fair",
|
|
20
|
-
{ name: "Good",
|
|
21
|
-
{ name: "Strong",
|
|
22
|
-
{ name: "Very strong",
|
|
23
|
-
];
|
|
15
|
+
{ name: "Very weak", key: "weak" },
|
|
16
|
+
{ name: "Weak", key: "weak" },
|
|
17
|
+
{ name: "Fair", key: "ok" },
|
|
18
|
+
{ name: "Good", key: "ok" },
|
|
19
|
+
{ name: "Strong", key: "strong" },
|
|
20
|
+
{ name: "Very strong", key: "strong" }];
|
|
24
21
|
|
|
25
22
|
function analyze(value) {
|
|
26
23
|
const length = value.length;
|
|
@@ -35,7 +32,7 @@ function analyze(value) {
|
|
|
35
32
|
const hints = [];
|
|
36
33
|
if (length < 12) hints.push("Use at least 12 characters.");
|
|
37
34
|
if (groups < 3) hints.push("Mix upper case, lower case, digits and symbols.");
|
|
38
|
-
if (/(.)\1{2
|
|
35
|
+
if (/(.)\1{2}/.test(value)) {
|
|
39
36
|
score -= 1;
|
|
40
37
|
hints.push("Repeated characters are easy to guess.");
|
|
41
38
|
}
|
|
@@ -47,17 +44,17 @@ function analyze(value) {
|
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
const kvRow = (key, value) =>
|
|
50
|
-
el("div", { class: "
|
|
47
|
+
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" }, key), el("span", { class: "wrap-anywhere font-mono text-2xs" }, value));
|
|
51
48
|
|
|
52
49
|
export function mount(slot) {
|
|
53
50
|
const field = passwordInput({ autocomplete: "off", "aria-label": "Password", placeholder: "Type a password" });
|
|
54
51
|
const showButton = button("Show", { icon: "eye" });
|
|
55
|
-
const meter = el("div", { class: "
|
|
56
|
-
const label = el("p", { class: "
|
|
57
|
-
const lengthValue = el("span", { class: "
|
|
58
|
-
const groupsValue = el("span", { class: "
|
|
59
|
-
const kv = el("div", { class: "
|
|
60
|
-
const hints = el("div", { class: "
|
|
52
|
+
const meter = el("div", { class: "h-2 overflow-hidden rounded-full bg-deck-2", role: "img", "aria-label": "Strength" }, 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" }));
|
|
53
|
+
const label = el("p", { class: "border-s-[3px] border-transparent ps-2 text-md font-bold text-ink" });
|
|
54
|
+
const lengthValue = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
55
|
+
const groupsValue = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
|
|
56
|
+
const kv = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" }, kvRow("Length", lengthValue), kvRow("Character groups", groupsValue));
|
|
57
|
+
const hints = el("div", { class: "grid content-start gap-4" });
|
|
61
58
|
const status = notice("note");
|
|
62
59
|
let report = "";
|
|
63
60
|
|
|
@@ -69,11 +66,11 @@ export function mount(slot) {
|
|
|
69
66
|
|
|
70
67
|
function run() {
|
|
71
68
|
const value = field.value;
|
|
72
|
-
const fill = meter.
|
|
69
|
+
const fill = meter.firstElementChild;
|
|
73
70
|
hints.replaceChildren();
|
|
74
71
|
if (!value) {
|
|
75
72
|
fill.style.width = "0%";
|
|
76
|
-
fill.
|
|
73
|
+
delete fill.dataset.level;
|
|
77
74
|
label.textContent = "";
|
|
78
75
|
meter.setAttribute("aria-label", "Strength");
|
|
79
76
|
lengthValue.textContent = "-";
|
|
@@ -85,7 +82,7 @@ export function mount(slot) {
|
|
|
85
82
|
const { score, groups, length, hints: why } = analyze(value);
|
|
86
83
|
const level = LEVELS[score];
|
|
87
84
|
fill.style.width = `${(score / 5) * 100}%`;
|
|
88
|
-
fill.
|
|
85
|
+
fill.dataset.level = level.key;
|
|
89
86
|
label.textContent = level.name;
|
|
90
87
|
// The bar is colour only, so the reading it stands for also goes on the bar itself.
|
|
91
88
|
meter.setAttribute("aria-label", `Strength: ${level.name}`);
|
|
@@ -104,11 +101,10 @@ export function mount(slot) {
|
|
|
104
101
|
|
|
105
102
|
const card = toolcard({
|
|
106
103
|
body: bench(
|
|
107
|
-
stack(panel("Password", el("div", { class: "
|
|
104
|
+
stack(panel("Password", el("div", { class: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2" }, field, showButton))),
|
|
108
105
|
stack(meter, label, kv, hints, status)
|
|
109
106
|
),
|
|
110
|
-
foot: actions(copyButton(() => report, "Copy report", "fill"))
|
|
111
|
-
});
|
|
107
|
+
foot: actions(copyButton(() => report, "Copy report", "fill"))});
|
|
112
108
|
slot.append(card.node);
|
|
113
109
|
run();
|
|
114
110
|
|