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
@@ -4,8 +4,7 @@ import { el, bench, stack, panel, textInput, selectInput, notice, copyButton, ac
4
4
  const FORMATS = [
5
5
  { value: "iso", label: "ISO (yyyy-mm-dd)" },
6
6
  { value: "dmy", label: "Day first (dd/mm/yyyy)" },
7
- { value: "mdy", label: "Month first (mm/dd/yyyy)" },
8
- ];
7
+ { value: "mdy", label: "Month first (mm/dd/yyyy)" }];
9
8
 
10
9
  const pad = (n) => String(n).padStart(2, "0");
11
10
 
@@ -41,20 +40,20 @@ function intlFormat(date, options, fallback) {
41
40
  }
42
41
  }
43
42
 
44
- const kvRow = (key, value) => el("div", { class: "mvb-kv__row" },
45
- el("span", { class: "mvb-kv__k" }, key),
46
- el("span", { class: "mvb-kv__v" }, value));
43
+ const kvRow = (key, value) => el("div", { class: "grid gap-3 bg-deck px-3 py-[9px] [grid-template-columns:minmax(90px,34%)_minmax(0,1fr)]" },
44
+ el("span", { class: "wrap-anywhere text-2xs font-semibold text-ink-2" }, key),
45
+ el("span", { class: "wrap-anywhere font-mono text-2xs" }, value));
47
46
 
48
47
  export function mount(slot) {
49
48
  const format = selectInput(FORMATS, { value: "iso", "aria-label": "Input format" });
50
49
  const entry = textInput({ placeholder: "2026-10-15", "aria-label": "Date" });
51
50
 
52
- const iso = el("span", { class: "mvb-kv__v" }, "-");
53
- const long = el("span", { class: "mvb-kv__v" }, "-");
54
- const medium = el("span", { class: "mvb-kv__v" }, "-");
55
- const weekday = el("span", { class: "mvb-kv__v" }, "-");
56
- const unix = el("span", { class: "mvb-kv__v" }, "-");
57
- const out = el("div", { class: "mvb-kv" },
51
+ const iso = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
52
+ const long = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
53
+ const medium = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
54
+ const weekday = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
55
+ const unix = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
56
+ const out = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
58
57
  kvRow("ISO", iso),
59
58
  kvRow("Long", long),
60
59
  kvRow("Medium", medium),
@@ -96,8 +95,7 @@ export function mount(slot) {
96
95
  stack(panel("Input format", format), panel("Date", entry), error),
97
96
  stack(out, note)
98
97
  ),
99
- foot: actions(copyButton(() => isoText, "Copy ISO date", "fill")),
100
- });
98
+ foot: actions(copyButton(() => isoText, "Copy ISO date", "fill"))});
101
99
  slot.append(card.node);
102
100
  run();
103
101
 
@@ -5,8 +5,7 @@ const OPTIONS = [
5
5
  { value: ",", label: "Comma" },
6
6
  { value: ";", label: "Semicolon" },
7
7
  { value: "\t", label: "Tab" },
8
- { value: "|", label: "Pipe" },
9
- ];
8
+ { value: "|", label: "Pipe" }];
10
9
 
11
10
  function parseDelimited(text, delimiter) {
12
11
  const rows = [];
@@ -68,7 +67,7 @@ function stringifyCSV(rows, delimiter) {
68
67
  }
69
68
 
70
69
  export function mount(slot) {
71
- const source = textArea({ rows: 12, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Data", placeholder: "Paste delimited data here" });
70
+ const source = textArea({ rows: 12, "aria-label": "Data", placeholder: "Paste delimited data here" });
72
71
  const from = selectInput([{ value: "auto", label: "Auto" }, ...OPTIONS], { value: "auto", "aria-label": "Current delimiter" });
73
72
  const to = selectInput(OPTIONS, { value: ";", "aria-label": "New delimiter" });
74
73
  const result = output({ rows: 12 });
@@ -98,8 +97,7 @@ export function mount(slot) {
98
97
  stack(panel("Data", source), panel("From", from), panel("To", to), error),
99
98
  stack(panel("Result", result))
100
99
  ),
101
- foot: actions(copyButton(() => current, "Copy result", "fill")),
102
- });
100
+ foot: actions(copyButton(() => current, "Copy result", "fill"))});
103
101
  slot.append(card.node);
104
102
  run();
105
103
 
@@ -6,8 +6,8 @@ function fmt(n) {
6
6
  }
7
7
 
8
8
  function tile(label) {
9
- const value = el("span", { class: "mvb-stat__v" }, "-");
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" }, "-");
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
  }
@@ -20,7 +20,7 @@ export function mount(slot) {
20
20
  const finalTile = tile("Final price");
21
21
  const savedTile = tile("Saved");
22
22
  const effectiveTile = tile("Effective discount");
23
- const stats = el("div", { class: "mvb-stats" }, finalTile, savedTile, effectiveTile);
23
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, finalTile, savedTile, effectiveTile);
24
24
  const error = notice("error");
25
25
  let text = "";
26
26
 
@@ -72,8 +72,7 @@ export function mount(slot) {
72
72
  ),
73
73
  stack(stats)
74
74
  ),
75
- foot: actions(copyButton(() => text, "Copy result", "fill")),
76
- });
75
+ foot: actions(copyButton(() => text, "Copy result", "fill"))});
77
76
  slot.append(card.node);
78
77
  run();
79
78
 
@@ -2,8 +2,8 @@
2
2
  import { el, bench, stack, panel, textInput, notice, copyButton, actions, toolcard } from "../core.js";
3
3
 
4
4
  function kvRow(key) {
5
- const value = el("span", { class: "mvb-kv__v" }, "-");
6
- const row = el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, key), value);
5
+ const value = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
6
+ const row = 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), value);
7
7
  return { row, value };
8
8
  }
9
9
 
@@ -18,7 +18,7 @@ export function mount(slot) {
18
18
  const input = textInput({ placeholder: "10.1000/xyz or https://doi.org/10.1000/xyz", "aria-label": "DOI" });
19
19
  const doiRow = kvRow("Cleaned DOI");
20
20
  const linkRow = kvRow("Resolver URL");
21
- const kv = el("div", { class: "mvb-kv" }, doiRow.row, linkRow.row);
21
+ const kv = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" }, doiRow.row, linkRow.row);
22
22
  const error = notice("error");
23
23
  const note = notice("note");
24
24
  note.write("A matching pattern does not prove the DOI is registered.");
@@ -56,8 +56,7 @@ export function mount(slot) {
56
56
  foot: actions(
57
57
  copyButton(() => doiRow.value.textContent, "Copy DOI"),
58
58
  copyButton(() => link, "Copy link", "fill")
59
- ),
60
- });
59
+ )});
61
60
  slot.append(card.node);
62
61
  run();
63
62
 
@@ -23,11 +23,9 @@ function parse(text) {
23
23
  export function mount(slot) {
24
24
  const source = textArea({
25
25
  rows: 12,
26
- class: "mvb-in mvb-in--area mvb-in--mono",
27
26
  "aria-label": "Environment variables",
28
27
  placeholder: "KEY=value",
29
- value: "API_URL=https://example.com\n# a note\nPORT=3000\nAPI_URL=https://other.com",
30
- });
28
+ value: "API_URL=https://example.com\n# a note\nPORT=3000\nAPI_URL=https://other.com"});
31
29
  const result = output({ rows: 12 });
32
30
  const warn = notice("warn");
33
31
  let sort = true;
@@ -58,11 +56,10 @@ export function mount(slot) {
58
56
 
59
57
  const card = toolcard({
60
58
  body: bench(
61
- stack(panel("Options", el("div", { class: "mvb-stack" }, sortSwitch, commentSwitch)), panel("Input", source), warn),
59
+ stack(panel("Options", el("div", { class: "grid content-start gap-4" }, sortSwitch, commentSwitch)), panel("Input", source), warn),
62
60
  stack(panel("Tidied", result))
63
61
  ),
64
- foot: actions(copyButton(() => result.textContent, "Copy list", "fill")),
65
- });
62
+ foot: actions(copyButton(() => result.textContent, "Copy list", "fill"))});
66
63
  slot.append(card.node);
67
64
  run();
68
65
 
@@ -8,15 +8,13 @@ const GREEK = {
8
8
  theta: "θ", iota: "ι", kappa: "κ", lambda: "λ", mu: "μ", nu: "ν", xi: "ξ",
9
9
  pi: "π", rho: "ρ", sigma: "σ", tau: "τ", phi: "φ", chi: "χ", psi: "ψ", omega: "ω",
10
10
  Gamma: "Γ", Delta: "Δ", Theta: "Θ", Lambda: "Λ", Xi: "Ξ", Pi: "Π", Sigma: "Σ",
11
- Phi: "Φ", Psi: "Ψ", Omega: "Ω",
12
- };
11
+ Phi: "Φ", Psi: "Ψ", Omega: "Ω"};
13
12
  const SYMBOLS = {
14
13
  times: "×", cdot: "·", div: "÷", pm: "±", mp: "∓", le: "≤", leq: "≤",
15
14
  ge: "≥", geq: "≥", ne: "≠", neq: "≠", approx: "≈", equiv: "≡", infty: "∞",
16
15
  sum: "Σ", prod: "Π", int: "∫", partial: "∂", nabla: "∇", rightarrow: "→",
17
16
  to: "→", leftarrow: "←", Rightarrow: "⇒", Leftarrow: "⇐", leftrightarrow: "↔",
18
- in: "∈", notin: "∉", forall: "∀", exists: "∃", angle: "∠", degree: "°",
19
- };
17
+ in: "∈", notin: "∉", forall: "∀", exists: "∃", angle: "∠", degree: "°"};
20
18
 
21
19
  const superscript = (value) => [...value].map((char) => SUP[char] ?? char).join("");
22
20
  const subscript = (value) => [...value].map((char) => SUB[char] ?? char).join("");
@@ -43,9 +41,8 @@ export function mount(slot) {
43
41
  rows: 12,
44
42
  value: "x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}",
45
43
  "aria-label": "LaTeX",
46
- placeholder: "\\frac{a}{b}",
47
- });
48
- const result = el("pre", { class: "mvb-out" });
44
+ placeholder: "\\frac{a}{b}"});
45
+ const result = el("pre", { class: "m-0 max-h-[340px] w-full overflow-auto rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3 font-mono text-sm leading-normal whitespace-pre-wrap [overflow-wrap:anywhere]" });
49
46
  const note = notice("note");
50
47
  note.write("Plain text preview, not rendered math.");
51
48
 
@@ -60,8 +57,7 @@ export function mount(slot) {
60
57
  stack(panel("LaTeX", source)),
61
58
  stack(panel("Preview", result), note)
62
59
  ),
63
- foot: actions(copyButton(() => source.value, "Copy LaTeX", "fill")),
64
- });
60
+ foot: actions(copyButton(() => source.value, "Copy LaTeX", "fill"))});
65
61
  slot.append(card.node);
66
62
  run();
67
63
 
@@ -18,7 +18,7 @@ export function mount(slot) {
18
18
  { value: mode, label: "Mode", onChange: (next) => { mode = next; save(); run(); } }
19
19
  );
20
20
  const sharesField = panel("Shares", shares);
21
- const result = el("div", { class: "mvb-scroll" });
21
+ const result = el("div", { class: "max-h-80 overflow-auto" });
22
22
  const error = notice("error");
23
23
  const remainder = notice("warn");
24
24
  let text = "";
@@ -34,7 +34,7 @@ export function mount(slot) {
34
34
  const names = people.value.split("\n").map((line) => line.trim()).filter(Boolean);
35
35
  const amount = Number(total.value);
36
36
  if (!names.length || total.value.trim() === "" || !Number.isFinite(amount) || amount < 0) {
37
- result.replaceChildren(el("p", { class: "mvb-note" }, "Add a total and one name per line."));
37
+ result.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Add a total and one name per line."));
38
38
  return;
39
39
  }
40
40
 
@@ -42,12 +42,12 @@ export function mount(slot) {
42
42
  if (mode === "custom") {
43
43
  weights = shares.value.split("\n").map((line) => line.trim()).filter(Boolean).map(Number);
44
44
  if (weights.length !== names.length || weights.some((w) => !Number.isFinite(w) || w < 0)) {
45
- result.replaceChildren(el("p", { class: "mvb-note" }, "Add one share for each name."));
45
+ result.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Add one share for each name."));
46
46
  error.write("Add one share for each name.");
47
47
  return;
48
48
  }
49
49
  if (weights.reduce((a, b) => a + b, 0) <= 0) {
50
- result.replaceChildren(el("p", { class: "mvb-note" }, "Add one share for each name."));
50
+ result.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Add one share for each name."));
51
51
  error.write("The shares add up to zero.");
52
52
  return;
53
53
  }
@@ -60,7 +60,7 @@ export function mount(slot) {
60
60
  const paid = rounded.reduce((a, b) => a + b, 0);
61
61
  const leftover = Math.round((amount - paid) * 100) / 100;
62
62
 
63
- result.replaceChildren(el("table", { class: "mvb-ttable" },
63
+ result.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" },
64
64
  el("thead", {}, el("tr", {}, el("th", {}, "Person"), el("th", {}, "Amount"))),
65
65
  el("tbody", {}, ...names.map((name, i) => el("tr", {},
66
66
  el("th", {}, name),
@@ -79,8 +79,7 @@ export function mount(slot) {
79
79
  stack(panel("Total", total), panel("Mode", modeChips), panel("People", people), sharesField, error),
80
80
  stack(panel("Each person", result), remainder)
81
81
  ),
82
- foot: actions(copyButton(() => text, "Copy split", "fill")),
83
- });
82
+ foot: actions(copyButton(() => text, "Copy split", "fill"))});
84
83
  slot.append(card.node);
85
84
  run();
86
85
 
@@ -7,8 +7,7 @@ const LAST = ["Ortega", "Wijaya", "Santos", "Kimura", "Novak", "Haddad", "Silva"
7
7
  const PLACES = [
8
8
  ["Jakarta", "ID", "+62"], ["Bandung", "ID", "+62"], ["Madrid", "ES", "+34"], ["Barcelona", "ES", "+34"],
9
9
  ["Austin", "US", "+1"], ["Seattle", "US", "+1"], ["Berlin", "DE", "+49"], ["Munich", "DE", "+49"],
10
- ["Osaka", "JP", "+81"], ["Tokyo", "JP", "+81"], ["Recife", "BR", "+55"], ["Lagos", "NG", "+234"],
11
- ];
10
+ ["Osaka", "JP", "+81"], ["Tokyo", "JP", "+81"], ["Recife", "BR", "+55"], ["Lagos", "NG", "+234"]];
12
11
  const pick = (list) => list[randomInt(list.length)];
13
12
 
14
13
  function person(id) {
@@ -23,8 +22,7 @@ function person(id) {
23
22
  phone: `${code} ${700 + randomInt(300)} ${1000000 + randomInt(8999999)}`,
24
23
  city,
25
24
  country,
26
- card: `4${String(randomInt(1000)).padStart(3, "0")} … ${1000 + randomInt(9000)}`,
27
- };
25
+ card: `4${String(randomInt(1000)).padStart(3, "0")} … ${1000 + randomInt(9000)}`};
28
26
  }
29
27
 
30
28
  export function mount(slot) {
@@ -34,18 +32,25 @@ export function mount(slot) {
34
32
  [
35
33
  { value: "json", label: "JSON" },
36
34
  { value: "csv", label: "CSV" },
37
- { value: "table", label: "Table" },
38
- ],
35
+ { value: "table", label: "Table" }],
39
36
  { value: "json", label: "Format", onChange: (value) => { format = value; render(); } }
40
37
  );
41
38
  const text = output({ rows: 14 });
42
- const table = el("div", { class: "mvb-scroll" });
39
+ const table = el("div", { class: "max-h-80 overflow-auto" });
43
40
  let rows = [];
44
41
 
45
42
  function totalCount() {
46
43
  return Math.max(1, Math.min(200, parseInt(countField.value || "1", 10) || 1));
47
44
  }
48
45
 
46
+ /** The same rows as the table, tab separated. */
47
+ function tsv() {
48
+ const columns = Object.keys(rows[0] ?? {});
49
+ return [
50
+ columns.join("\t"),
51
+ ...rows.map((row) => columns.map((key) => String(row[key])).join("\t"))].join("\n");
52
+ }
53
+
49
54
  function render() {
50
55
  const total = totalCount();
51
56
  rows = Array.from({ length: total }, (_, i) => person(i + 1));
@@ -66,7 +71,7 @@ export function mount(slot) {
66
71
  }
67
72
  const head = el("tr");
68
73
  for (const key of columns) head.append(el("th", {}, key));
69
- table.replaceChildren(el("table", { class: "mvb-ttable" }, el("thead", {}, head), body));
74
+ table.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" }, el("thead", {}, head), body));
70
75
  }
71
76
  }
72
77
 
@@ -83,8 +88,11 @@ export function mount(slot) {
83
88
  countField.value = String(totalCount());
84
89
  });
85
90
  copyButton.addEventListener("click", async () => {
86
- const text = format === "table" ? JSON.stringify(rows) : text.textContent;
87
- toast((await copyText(text)) ? "Copied" : "Copy failed");
91
+ // In table mode the visible table is what gets copied, as tab separated text
92
+ // so it lands in a spreadsheet as a table. The local name here used to shadow
93
+ // the readout above, which threw before the copy ever ran.
94
+ const payload = format === "table" ? tsv() : text.textContent;
95
+ toast((await copyText(payload)) ? "Copied" : "Copy failed");
88
96
  });
89
97
  saveButton.addEventListener("click", () => {
90
98
  if (format === "csv") download("test-data.csv", text.textContent, "text/csv");
@@ -94,10 +102,9 @@ export function mount(slot) {
94
102
  const card = toolcard({
95
103
  body: bench(
96
104
  stack(panel("Format", chips), panel("Records", countField)),
97
- el("div", { class: "mvb-scroll" }, text, table)
105
+ el("div", { class: "grid content-start gap-4" }, text, table)
98
106
  ),
99
- foot: actions(copyButton, saveButton, againButton),
100
- });
107
+ foot: actions(copyButton, saveButton, againButton)});
101
108
  slot.append(card.node);
102
109
  render();
103
110
 
@@ -9,9 +9,8 @@ export function mount(slot) {
9
9
  const replaceField = textInput({ placeholder: "Replace with", "aria-label": "Replace with" });
10
10
  const options = toggleRow([
11
11
  { value: "regex", label: "Regex", checked: false },
12
- { value: "ignore", label: "Ignore case", checked: false },
13
- ]);
14
- const result = textArea({ rows: 9, readonly: true, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Result" });
12
+ { value: "ignore", label: "Ignore case", checked: false }]);
13
+ const result = textArea({ rows: 9, readonly: true, "aria-label": "Result" });
15
14
  const error = notice("error");
16
15
  const countNote = notice("note");
17
16
 
@@ -77,8 +76,7 @@ export function mount(slot) {
77
76
  stack(panel("Text", source), panel("Find", findField), panel("Replace with", replaceField), options, error, countNote),
78
77
  stack(panel("Result", result))
79
78
  ),
80
- foot: actions(copyButton(() => result.value, "Copy result"), replaceOneButton, replaceAllButton),
81
- });
79
+ foot: actions(copyButton(() => result.value, "Copy result"), replaceOneButton, replaceAllButton)});
82
80
  slot.append(card.node);
83
81
  run();
84
82
 
@@ -12,10 +12,10 @@ export function mount(slot) {
12
12
 
13
13
  const question = textInput({ placeholder: "Question", "aria-label": "Question" });
14
14
  const answer = textArea({ rows: 3, placeholder: "Answer", "aria-label": "Answer" });
15
- const listWrap = el("div", { class: "mvb-scroll" });
16
- const stats = el("div", { class: "mvb-stats" });
17
- const face = el("pre", { class: "mvb-out" });
18
- const faceLabel = el("span", { class: "mvb-field__label" }, "Question");
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
+ const face = el("pre", { class: "m-0 max-h-[340px] w-full overflow-auto rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3 font-mono text-sm leading-normal whitespace-pre-wrap [overflow-wrap:anywhere]" });
18
+ const faceLabel = el("span", { class: "text-sm font-semibold text-ink-2" }, "Question");
19
19
 
20
20
  const addButton = button("Add card", { icon: "plus", kind: "fill" });
21
21
  const flipButton = button("Flip", { icon: "arrow-left-right" });
@@ -23,9 +23,9 @@ export function mount(slot) {
23
23
  const nextButton = button("Next");
24
24
 
25
25
  const save = () => writePin(PIN, cards);
26
- const tile = (key, value) => el("div", { class: "mvb-stat" },
27
- el("span", { class: "mvb-stat__v" }, String(value)),
28
- el("span", { class: "mvb-stat__k" }, key));
26
+ const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
27
+ el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
28
+ el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
29
29
 
30
30
  const textExport = () =>
31
31
  cards.map((card, i) => `${i + 1}. Q: ${card.question}\n A: ${card.answer}`).join("\n\n");
@@ -52,7 +52,7 @@ export function mount(slot) {
52
52
  const card = cards[index];
53
53
  faceLabel.textContent = flipped ? "Answer" : "Question";
54
54
  face.textContent = flipped ? card.answer : card.question;
55
- listWrap.replaceChildren(el("table", { class: "mvb-ttable" },
55
+ 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" },
56
56
  el("tbody", {}, ...cards.map((entry, i) => el("tr", {},
57
57
  el("th", {}, `${i + 1}. ${entry.question}`),
58
58
  el("td", {}, removeButton(entry)))))));
@@ -106,8 +106,7 @@ export function mount(slot) {
106
106
  copyButton(() => textExport(), "Copy text"),
107
107
  downloadButton(() => jsonExport(), "flashcards.json", "Save JSON", "application/json"),
108
108
  addButton
109
- ),
110
- });
109
+ )});
111
110
  slot.append(card.node);
112
111
  render();
113
112
 
@@ -12,16 +12,15 @@ const FONTS = [
12
12
  { value: 'Palatino, "Palatino Linotype", serif', label: "Palatino" },
13
13
  { value: "Garamond, Georgia, serif", label: "Garamond" },
14
14
  { value: '"Courier New", Courier, monospace', label: "Courier New" },
15
- { value: "ui-monospace, SFMono-Regular, Menlo, monospace", label: "Mono" },
16
- ];
15
+ { value: "ui-monospace, SFMono-Regular, Menlo, monospace", label: "Mono" }];
17
16
 
18
17
  function slider(label, { min, max, step, value, unit }) {
19
18
  const input = rangeInput({ min, max, step, value, "aria-label": label });
20
- const val = el("span", { class: "mvb-slider__val" }, `${value}${unit}`);
19
+ const val = el("span", { class: "text-end font-mono text-2xs text-ink-2" }, `${value}${unit}`);
21
20
  const node = el(
22
21
  "div",
23
- { class: "mvb-slider" },
24
- el("span", { class: "mvb-slider__label" }, label),
22
+ { class: "grid items-center gap-2.5 [grid-template-columns:88px_minmax(0,1fr)_62px] max-[45rem]:[grid-template-columns:minmax(0,1fr)_62px]" },
23
+ el("span", { class: "text-2xs font-semibold text-ink-2 max-[45rem]:col-span-2" }, label),
25
24
  input,
26
25
  val
27
26
  );
@@ -46,7 +45,7 @@ export function mount(slot) {
46
45
 
47
46
  const heading = el("h2", { style: "margin:0 0 8px" }, "Heading sample");
48
47
  const para = el("p", { style: "margin:0" }, "Body text sample. The quick brown fox jumps over the lazy dog.");
49
- const sample = el("div", { class: "mvb-preview__sample" }, heading, para);
48
+ const sample = el("div", { class: "w-full rounded-lg border border-orbit bg-deck px-[18px] py-4" }, heading, para);
50
49
  const code = output();
51
50
 
52
51
  function css() {
@@ -77,8 +76,7 @@ export function mount(slot) {
77
76
  stack(panel("Heading font", headingFont), panel("Body font", bodyFont), headingSize, bodySize, lineHeight, panel("Heading weight", weightChips)),
78
77
  stack(sample, code)
79
78
  ),
80
- foot: actions(copyButton(css, "Copy CSS", "fill")),
81
- });
79
+ foot: actions(copyButton(css, "Copy CSS", "fill"))});
82
80
  slot.append(card.node);
83
81
  update();
84
82
 
@@ -17,19 +17,19 @@ function fmt(n) {
17
17
  }
18
18
 
19
19
  const kvRow = (key, value) =>
20
- el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, key), el("span", { class: "mvb-kv__v" }, value));
20
+ 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));
21
21
 
22
22
  export function mount(slot) {
23
23
  const n1 = numberInput({ value: "1", "aria-label": "First numerator" });
24
24
  const d1 = numberInput({ value: "2", "aria-label": "First denominator" });
25
25
  const n2 = numberInput({ value: "1", "aria-label": "Second numerator" });
26
26
  const d2 = numberInput({ value: "3", "aria-label": "Second denominator" });
27
- const resultRow = el("span", { class: "mvb-kv__v" }, "-");
28
- const decimalRow = el("span", { class: "mvb-kv__v" }, "-");
29
- const mixedRow = el("span", { class: "mvb-kv__v" }, "-");
27
+ const resultRow = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
28
+ const decimalRow = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
29
+ const mixedRow = el("span", { class: "wrap-anywhere font-mono text-2xs" }, "-");
30
30
  const kv = el(
31
31
  "div",
32
- { class: "mvb-kv" },
32
+ { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
33
33
  kvRow("Fraction", resultRow),
34
34
  kvRow("Decimal", decimalRow),
35
35
  kvRow("Mixed number", mixedRow)
@@ -95,16 +95,14 @@ export function mount(slot) {
95
95
  { value: "+", label: "+" },
96
96
  { value: "-", label: "-" },
97
97
  { value: "x", label: "x" },
98
- { value: "/", label: "/" },
99
- ],
98
+ { value: "/", label: "/" }],
100
99
  {
101
100
  value: op,
102
101
  label: "Operation",
103
102
  onChange: (value) => {
104
103
  op = value;
105
104
  run();
106
- },
107
- }
105
+ }}
108
106
  );
109
107
 
110
108
  for (const input of [n1, d1, n2, d2]) input.addEventListener("input", run);
@@ -112,15 +110,14 @@ export function mount(slot) {
112
110
  const card = toolcard({
113
111
  body: bench(
114
112
  stack(
115
- panel("First fraction", el("div", { class: "mvb-row" }, n1, d1)),
113
+ panel("First fraction", el("div", { class: "flex min-w-0 flex-wrap items-center gap-2" }, n1, d1)),
116
114
  panel("Operation", chips),
117
- panel("Second fraction", el("div", { class: "mvb-row" }, n2, d2)),
115
+ panel("Second fraction", el("div", { class: "flex min-w-0 flex-wrap items-center gap-2" }, n2, d2)),
118
116
  error
119
117
  ),
120
118
  stack(panel("Result", kv))
121
119
  ),
122
- foot: actions(copyButton(() => text, "Copy fraction", "fill")),
123
- });
120
+ foot: actions(copyButton(() => text, "Copy fraction", "fill"))});
124
121
  slot.append(card.node);
125
122
  run();
126
123
 
@@ -7,56 +7,46 @@ const OPERATIONS = {
7
7
  clone: {
8
8
  label: "Clone a repository",
9
9
  fields: [["url", "Repository URL", "https://github.com/user/repo.git"], ["directory", "Directory (optional)", ""]],
10
- build: (v) => `git clone ${quote(v.url)}${v.directory ? ` ${quote(v.directory)}` : ""}`,
11
- },
10
+ build: (v) => `git clone ${quote(v.url)}${v.directory ? ` ${quote(v.directory)}` : ""}`},
12
11
  switch: {
13
12
  label: "Switch branch",
14
13
  fields: [["branch", "Branch", "main"]],
15
- build: (v) => `git switch ${quote(v.branch)}`,
16
- },
14
+ build: (v) => `git switch ${quote(v.branch)}`},
17
15
  pull: {
18
16
  label: "Pull",
19
17
  fields: [["remote", "Remote", "origin"], ["branch", "Branch (optional)", ""]],
20
- build: (v) => `git pull ${quote(v.remote)}${v.branch ? ` ${quote(v.branch)}` : ""}`,
21
- },
18
+ build: (v) => `git pull ${quote(v.remote)}${v.branch ? ` ${quote(v.branch)}` : ""}`},
22
19
  commit: {
23
20
  label: "Commit",
24
21
  fields: [["message", "Message", "Fix the thing"], ["files", "Paths (optional)", ""]],
25
- build: (v) => `git commit -m ${quote(v.message)}${v.files ? ` ${v.files}` : ""}`,
26
- },
22
+ build: (v) => `git commit -m ${quote(v.message)}${v.files ? ` ${v.files}` : ""}`},
27
23
  push: {
28
24
  label: "Push",
29
25
  fields: [["remote", "Remote", "origin"], ["branch", "Branch", "main"]],
30
- build: (v) => `git push ${quote(v.remote)} ${quote(v.branch)}`,
31
- },
26
+ build: (v) => `git push ${quote(v.remote)} ${quote(v.branch)}`},
32
27
  "push-force": {
33
28
  label: "Force push",
34
29
  fields: [["remote", "Remote", "origin"], ["branch", "Branch", "main"]],
35
30
  build: (v) => `git push --force-with-lease ${quote(v.remote)} ${quote(v.branch)}`,
36
- destructive: "Force push can overwrite commits on the remote.",
37
- },
31
+ destructive: "Force push can overwrite commits on the remote."},
38
32
  merge: {
39
33
  label: "Merge",
40
34
  fields: [["branch", "Branch", "feature"]],
41
- build: (v) => `git merge ${quote(v.branch)}`,
42
- },
35
+ build: (v) => `git merge ${quote(v.branch)}`},
43
36
  rebase: {
44
37
  label: "Rebase",
45
38
  fields: [["branch", "Branch", "main"]],
46
- build: (v) => `git rebase ${quote(v.branch)}`,
47
- },
39
+ build: (v) => `git rebase ${quote(v.branch)}`},
48
40
  reset: {
49
41
  label: "Reset hard",
50
42
  fields: [["ref", "Commit or ref", "HEAD~1"]],
51
43
  build: (v) => `git reset --hard ${quote(v.ref)}`,
52
- destructive: "Reset hard discards uncommitted work.",
53
- },
54
- };
44
+ destructive: "Reset hard discards uncommitted work."}};
55
45
 
56
46
  export function mount(slot) {
57
47
  const options = Object.entries(OPERATIONS).map(([value, config]) => ({ value, label: config.label }));
58
48
  const operationSelect = selectInput(options, { value: "clone", "aria-label": "Operation" });
59
- const fieldsHost = el("div", { class: "mvb-stack" });
49
+ const fieldsHost = el("div", { class: "grid content-start gap-4" });
60
50
  const result = output({ rows: 4 });
61
51
  const warn = notice("warn");
62
52
  const values = {};
@@ -89,8 +79,7 @@ export function mount(slot) {
89
79
  stack(panel("Operation", operationSelect), fieldsHost, warn),
90
80
  stack(panel("Command", result))
91
81
  ),
92
- foot: actions(copyButton(() => result.textContent, "Copy command", "fill")),
93
- });
82
+ foot: actions(copyButton(() => result.textContent, "Copy command", "fill"))});
94
83
  slot.append(card.node);
95
84
  paintFields();
96
85