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.
Files changed (167) hide show
  1. package/mevebu/app/mevebu.js +260 -208
  2. package/mevebu/app/tailwind.css +1639 -2077
  3. package/mevebu/app/tailwind.src.css +155 -1120
  4. package/mevebu/index.html +2 -2
  5. package/mevebu/libs/js/core.js +236 -96
  6. package/mevebu/libs/js/registry.js +11 -11
  7. package/mevebu/libs/js/tools/abstract-checklist.js +5 -7
  8. package/mevebu/libs/js/tools/add-subtract-days.js +9 -11
  9. package/mevebu/libs/js/tools/age-calculator.js +7 -8
  10. package/mevebu/libs/js/tools/api-request-builder.js +3 -4
  11. package/mevebu/libs/js/tools/area-converter.js +7 -9
  12. package/mevebu/libs/js/tools/aspect-ratio.js +8 -9
  13. package/mevebu/libs/js/tools/average-calculator.js +4 -5
  14. package/mevebu/libs/js/tools/base64.js +31 -11
  15. package/mevebu/libs/js/tools/binary-calculator.js +6 -7
  16. package/mevebu/libs/js/tools/border-radius.js +7 -9
  17. package/mevebu/libs/js/tools/box-shadow.js +6 -7
  18. package/mevebu/libs/js/tools/break-even.js +4 -5
  19. package/mevebu/libs/js/tools/business-days.js +7 -8
  20. package/mevebu/libs/js/tools/canonical-tag.js +5 -6
  21. package/mevebu/libs/js/tools/caption-counter.js +5 -7
  22. package/mevebu/libs/js/tools/case-converter.js +11 -12
  23. package/mevebu/libs/js/tools/case-inspector.js +14 -16
  24. package/mevebu/libs/js/tools/cash-denomination.js +7 -8
  25. package/mevebu/libs/js/tools/cash-flow.js +7 -8
  26. package/mevebu/libs/js/tools/cert-expiry.js +6 -10
  27. package/mevebu/libs/js/tools/changelog-generator.js +4 -6
  28. package/mevebu/libs/js/tools/character-counter.js +10 -13
  29. package/mevebu/libs/js/tools/checklist.js +6 -7
  30. package/mevebu/libs/js/tools/citation-number-check.js +10 -13
  31. package/mevebu/libs/js/tools/coin-flip.js +9 -11
  32. package/mevebu/libs/js/tools/color-converter.js +23 -17
  33. package/mevebu/libs/js/tools/color-palette.js +6 -8
  34. package/mevebu/libs/js/tools/color-picker.js +4 -5
  35. package/mevebu/libs/js/tools/content-calendar.js +3 -4
  36. package/mevebu/libs/js/tools/contrast-checker.js +5 -6
  37. package/mevebu/libs/js/tools/cron-builder.js +4 -6
  38. package/mevebu/libs/js/tools/css-clamp.js +5 -6
  39. package/mevebu/libs/js/tools/css-formatter.js +4 -7
  40. package/mevebu/libs/js/tools/css-grid.js +6 -7
  41. package/mevebu/libs/js/tools/css-minifier.js +5 -8
  42. package/mevebu/libs/js/tools/csv-to-json.js +2 -3
  43. package/mevebu/libs/js/tools/csv-viewer.js +9 -11
  44. package/mevebu/libs/js/tools/currency-format.js +5 -7
  45. package/mevebu/libs/js/tools/daily-planner.js +9 -10
  46. package/mevebu/libs/js/tools/data-masker.js +7 -9
  47. package/mevebu/libs/js/tools/data-storage.js +7 -9
  48. package/mevebu/libs/js/tools/data-validator.js +9 -11
  49. package/mevebu/libs/js/tools/date-difference.js +6 -7
  50. package/mevebu/libs/js/tools/date-format.js +11 -13
  51. package/mevebu/libs/js/tools/delimiter-converter.js +3 -5
  52. package/mevebu/libs/js/tools/discount-calculator.js +4 -5
  53. package/mevebu/libs/js/tools/doi-resolver.js +4 -5
  54. package/mevebu/libs/js/tools/env-formatter.js +3 -6
  55. package/mevebu/libs/js/tools/equation-formatter.js +5 -9
  56. package/mevebu/libs/js/tools/expense-split.js +6 -7
  57. package/mevebu/libs/js/tools/fake-data.js +20 -13
  58. package/mevebu/libs/js/tools/find-replace.js +3 -5
  59. package/mevebu/libs/js/tools/flashcard-creator.js +9 -10
  60. package/mevebu/libs/js/tools/font-pairing.js +6 -8
  61. package/mevebu/libs/js/tools/fraction-calculator.js +10 -13
  62. package/mevebu/libs/js/tools/git-command-builder.js +11 -22
  63. package/mevebu/libs/js/tools/gpa-calculator.js +8 -10
  64. package/mevebu/libs/js/tools/grade-calculator.js +7 -8
  65. package/mevebu/libs/js/tools/gradient-generator.js +10 -12
  66. package/mevebu/libs/js/tools/habit-tracker.js +8 -9
  67. package/mevebu/libs/js/tools/hash-generator.js +3 -5
  68. package/mevebu/libs/js/tools/hashtag-formatter.js +6 -8
  69. package/mevebu/libs/js/tools/hmac-generator.js +3 -5
  70. package/mevebu/libs/js/tools/hpp-calculator.js +4 -5
  71. package/mevebu/libs/js/tools/html-entity.js +2 -3
  72. package/mevebu/libs/js/tools/html-formatter.js +3 -6
  73. package/mevebu/libs/js/tools/http-status.js +5 -7
  74. package/mevebu/libs/js/tools/inventory-quantity.js +7 -8
  75. package/mevebu/libs/js/tools/json-formatter.js +3 -6
  76. package/mevebu/libs/js/tools/json-minifier.js +5 -8
  77. package/mevebu/libs/js/tools/json-to-csv.js +3 -5
  78. package/mevebu/libs/js/tools/json-to-typescript.js +2 -5
  79. package/mevebu/libs/js/tools/json-validator.js +2 -3
  80. package/mevebu/libs/js/tools/jwt-decoder.js +6 -7
  81. package/mevebu/libs/js/tools/jwt-expiry.js +5 -6
  82. package/mevebu/libs/js/tools/jwt-generator.js +4 -6
  83. package/mevebu/libs/js/tools/length-converter.js +7 -9
  84. package/mevebu/libs/js/tools/license-reference.js +10 -13
  85. package/mevebu/libs/js/tools/line-counter.js +5 -7
  86. package/mevebu/libs/js/tools/list-difference.js +5 -7
  87. package/mevebu/libs/js/tools/list-merger.js +4 -5
  88. package/mevebu/libs/js/tools/locale-number.js +5 -8
  89. package/mevebu/libs/js/tools/lorem-ipsum.js +4 -2
  90. package/mevebu/libs/js/tools/markdown-preview.js +5 -8
  91. package/mevebu/libs/js/tools/markdown-table.js +5 -7
  92. package/mevebu/libs/js/tools/markup-calculator.js +4 -5
  93. package/mevebu/libs/js/tools/meeting-agenda.js +8 -9
  94. package/mevebu/libs/js/tools/meta-preview.js +12 -13
  95. package/mevebu/libs/js/tools/meta-tags.js +6 -7
  96. package/mevebu/libs/js/tools/mime-types.js +5 -7
  97. package/mevebu/libs/js/tools/name-picker.js +2 -3
  98. package/mevebu/libs/js/tools/number-base-converter.js +9 -12
  99. package/mevebu/libs/js/tools/password-generator.js +9 -12
  100. package/mevebu/libs/js/tools/password-strength.js +21 -25
  101. package/mevebu/libs/js/tools/percent-to-fraction.js +6 -7
  102. package/mevebu/libs/js/tools/percentage-calculator.js +6 -9
  103. package/mevebu/libs/js/tools/percentage-change.js +4 -5
  104. package/mevebu/libs/js/tools/permutation-combination.js +9 -12
  105. package/mevebu/libs/js/tools/prefix-suffix.js +2 -3
  106. package/mevebu/libs/js/tools/priority-matrix.js +6 -10
  107. package/mevebu/libs/js/tools/profit-margin.js +4 -5
  108. package/mevebu/libs/js/tools/qr-generator.js +3 -4
  109. package/mevebu/libs/js/tools/qr-scanner.js +11 -12
  110. package/mevebu/libs/js/tools/quadratic-solver.js +7 -8
  111. package/mevebu/libs/js/tools/query-builder.js +2 -3
  112. package/mevebu/libs/js/tools/quiz-formatter.js +4 -6
  113. package/mevebu/libs/js/tools/ratio-calculator.js +7 -8
  114. package/mevebu/libs/js/tools/readability.js +12 -15
  115. package/mevebu/libs/js/tools/reading-time.js +5 -7
  116. package/mevebu/libs/js/tools/reference-completeness.js +6 -8
  117. package/mevebu/libs/js/tools/regex-tester.js +7 -10
  118. package/mevebu/libs/js/tools/remove-duplicate-lines.js +2 -3
  119. package/mevebu/libs/js/tools/remove-extra-spaces.js +3 -5
  120. package/mevebu/libs/js/tools/robots-txt.js +3 -4
  121. package/mevebu/libs/js/tools/roman-numeral-converter.js +6 -8
  122. package/mevebu/libs/js/tools/sales-commission.js +8 -9
  123. package/mevebu/libs/js/tools/scientific-calculator.js +10 -14
  124. package/mevebu/libs/js/tools/scientific-notation.js +8 -9
  125. package/mevebu/libs/js/tools/script-planner.js +8 -10
  126. package/mevebu/libs/js/tools/selling-price.js +4 -5
  127. package/mevebu/libs/js/tools/semver-helper.js +3 -5
  128. package/mevebu/libs/js/tools/sentence-length.js +9 -10
  129. package/mevebu/libs/js/tools/sequence-analyzer.js +7 -8
  130. package/mevebu/libs/js/tools/sitemap-xml.js +4 -7
  131. package/mevebu/libs/js/tools/slug-converter.js +3 -5
  132. package/mevebu/libs/js/tools/slug-preview.js +6 -7
  133. package/mevebu/libs/js/tools/sort-lines.js +3 -5
  134. package/mevebu/libs/js/tools/speed-converter.js +7 -9
  135. package/mevebu/libs/js/tools/spin-wheel.js +19 -17
  136. package/mevebu/libs/js/tools/sql-formatter.js +5 -9
  137. package/mevebu/libs/js/tools/sql-minifier.js +5 -8
  138. package/mevebu/libs/js/tools/standard-deviation.js +6 -9
  139. package/mevebu/libs/js/tools/stopwatch.js +4 -6
  140. package/mevebu/libs/js/tools/study-schedule.js +8 -9
  141. package/mevebu/libs/js/tools/subtitle-formatter.js +10 -11
  142. package/mevebu/libs/js/tools/table-to-markdown.js +8 -12
  143. package/mevebu/libs/js/tools/tax-calculator.js +4 -5
  144. package/mevebu/libs/js/tools/temperature-converter.js +8 -11
  145. package/mevebu/libs/js/tools/text-diff.js +11 -7
  146. package/mevebu/libs/js/tools/text-direction.js +3 -5
  147. package/mevebu/libs/js/tools/text-reverser.js +3 -5
  148. package/mevebu/libs/js/tools/time-duration.js +8 -9
  149. package/mevebu/libs/js/tools/timestamp-converter.js +4 -6
  150. package/mevebu/libs/js/tools/timezone-converter.js +9 -13
  151. package/mevebu/libs/js/tools/title-length.js +6 -7
  152. package/mevebu/libs/js/tools/unicode-inspector.js +4 -5
  153. package/mevebu/libs/js/tools/url-encoder.js +4 -2
  154. package/mevebu/libs/js/tools/url-inspector.js +9 -11
  155. package/mevebu/libs/js/tools/url-parser.js +12 -13
  156. package/mevebu/libs/js/tools/utf8-bytes.js +6 -9
  157. package/mevebu/libs/js/tools/uuid-generator.js +107 -15
  158. package/mevebu/libs/js/tools/version-compare.js +3 -4
  159. package/mevebu/libs/js/tools/video-duration.js +5 -7
  160. package/mevebu/libs/js/tools/volume-converter.js +7 -9
  161. package/mevebu/libs/js/tools/weekly-planner.js +8 -10
  162. package/mevebu/libs/js/tools/weight-converter.js +7 -9
  163. package/mevebu/libs/js/tools/weighted-average.js +4 -5
  164. package/mevebu/libs/js/tools/word-counter.js +10 -13
  165. package/mevebu/libs/js/tools/word-limit.js +4 -5
  166. package/mevebu/libs/js/tools/yaml-formatter.js +6 -7
  167. 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: "mvb-cases" });
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(".mvb-case__value").textContent = value;
43
+ row.querySelector("[data-value]").textContent = value;
45
44
  const on = row.dataset.case === active;
46
- row.classList.toggle("mvb-on", on);
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: "mvb-case", type: "button", dataset: { case: name }, "aria-pressed": String(name === active),
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(".mvb-case__value").textContent.trim();
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: "mvb-case__name" }, name),
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("Select a case to copy it", list)) });
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: "mvb-kv__v" }, "No letters");
55
+ const detected = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "No letters");
57
56
  const kv = el(
58
57
  "div",
59
- { class: "mvb-kv" },
60
- el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, "Detected case"), detected)
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: "mvb-cases" });
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(".mvb-case__value").textContent = words.length ? convert(words) : "";
75
+ row.querySelector("[data-value]").textContent = words.length ? convert(words) : "";
77
76
  const on = row.dataset.case === active;
78
- row.classList.toggle("mvb-on", on);
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: "mvb-case", type: "button", dataset: { case: name }, "aria-pressed": "false",
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(".mvb-case__value").textContent.trim();
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: "mvb-case__name" }, name),
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: "mvb-scroll" });
16
- const stats = el("div", { class: "mvb-stats" });
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: "mvb-stat" },
21
- el("span", { class: "mvb-stat__v" }, String(value)),
22
- el("span", { class: "mvb-stat__k" }, key));
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: "mvb-ttable" },
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: "mvb-scroll" });
24
- const stats = el("div", { class: "mvb-stats" });
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: "mvb-stat" },
30
- el("span", { class: "mvb-stat__v" }, String(value)),
31
- el("span", { class: "mvb-stat__k" }, key));
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: "mvb-ttable" },
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: "mvb-stat__v" }, "-");
9
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
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: "mvb-stats" }, daysTile, weeksTile, statusTile);
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(`Expires in ${days} days, within the ${threshold} day warning window.`);
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", class: "mvb-in mvb-in--mono", "aria-label": "Version" });
13
- const date = el("input", { class: "mvb-in", type: "date", value: new Date().toISOString().slice(0, 10), "aria-label": "Date" });
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: "mvb-stat__v" }, "0");
10
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
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: "mvb-stats" }, totalTile, noSpaceTile, wordsTile);
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: "mvb-meter", role: "img", "aria-label": "Character limit" }, el("div", { class: "mvb-meter__fill" }));
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.querySelector(".mvb-meter__fill");
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.className = `mvb-meter__fill ${ratio > 1 ? "mvb-weak" : ratio > 0.8 ? "mvb-ok" : "mvb-strong"}`;
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.className = "mvb-meter__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: "mvb-scroll" });
13
- const stats = el("div", { class: "mvb-stats" });
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: "mvb-stat" },
20
- el("span", { class: "mvb-stat__v" }, String(value)),
21
- el("span", { class: "mvb-stat__k" }, key));
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: "mvb-stat__v" }, "0");
6
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
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: "mvb-stats" }, citedTile, refsTile, missingTile, unusedTile);
51
- const tableWrap = el("div", { class: "mvb-scroll" });
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: "mvb-note" }, "Paste the text and the reference list."));
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: "mvb-note" }, "Every number matches."));
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: "mvb-ttable" },
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: "mvb-stat__v" }, "0");
11
- const row = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, key));
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: "mvb-coin", role: "img", "aria-label": "Coin, not flipped yet" }, "?");
23
- const verdict = el("p", { class: "mvb-flip__result min-h-7", "aria-live": "polite" });
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: "mvb-stats" }, headsTile.row, tailsTile.row, totalTile.row);
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.classList.remove("mvb-toss");
43
+ delete coin.dataset.toss;
44
44
  void coin.offsetWidth;
45
- coin.classList.add("mvb-toss");
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", class: "mvb-in mvb-in--mono", "aria-label": "Any color" });
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: "mvb-swatch" });
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, class: "mvb-in mvb-in--mono", "aria-label": `${label}, press to copy` });
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: "mvb-note" });
94
+ const reading = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
95
95
 
96
- function show(color, keepPicker) {
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 (!keepPicker) {
99
- anyField.value = hex.toUpperCase();
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, keepPicker) {
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, keepPicker);
130
+ show(color, from);
124
131
  }
125
132
 
126
- anyField.addEventListener("input", () => read(anyField.value, false));
127
- picker.addEventListener("input", () => read(picker.value, true));
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: "mvb-row" }, picker, anyField)),
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", false);
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: "mvb-swatches" });
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: "mvb-swatchcard", type: "button", onclick: () => copyOne(hex) },
86
- el("span", { class: "mvb-swatchcard__chip", style: `background:${hex}` }),
87
- el("span", { class: "mvb-swatchcard__code" }, hex)
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