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.
Files changed (164) hide show
  1. package/mevebu/app/mevebu.js +260 -208
  2. package/mevebu/app/tailwind.css +1635 -2079
  3. package/mevebu/app/tailwind.src.css +155 -1127
  4. package/mevebu/index.html +2 -2
  5. package/mevebu/libs/js/core.js +235 -95
  6. package/mevebu/libs/js/tools/abstract-checklist.js +5 -7
  7. package/mevebu/libs/js/tools/add-subtract-days.js +9 -11
  8. package/mevebu/libs/js/tools/age-calculator.js +7 -8
  9. package/mevebu/libs/js/tools/api-request-builder.js +3 -4
  10. package/mevebu/libs/js/tools/area-converter.js +7 -9
  11. package/mevebu/libs/js/tools/aspect-ratio.js +8 -9
  12. package/mevebu/libs/js/tools/average-calculator.js +4 -5
  13. package/mevebu/libs/js/tools/base64.js +2 -3
  14. package/mevebu/libs/js/tools/binary-calculator.js +6 -7
  15. package/mevebu/libs/js/tools/border-radius.js +7 -9
  16. package/mevebu/libs/js/tools/box-shadow.js +6 -7
  17. package/mevebu/libs/js/tools/break-even.js +4 -5
  18. package/mevebu/libs/js/tools/business-days.js +7 -8
  19. package/mevebu/libs/js/tools/canonical-tag.js +5 -6
  20. package/mevebu/libs/js/tools/caption-counter.js +5 -7
  21. package/mevebu/libs/js/tools/case-converter.js +10 -11
  22. package/mevebu/libs/js/tools/case-inspector.js +14 -16
  23. package/mevebu/libs/js/tools/cash-denomination.js +7 -8
  24. package/mevebu/libs/js/tools/cash-flow.js +7 -8
  25. package/mevebu/libs/js/tools/cert-expiry.js +5 -7
  26. package/mevebu/libs/js/tools/changelog-generator.js +4 -6
  27. package/mevebu/libs/js/tools/character-counter.js +10 -13
  28. package/mevebu/libs/js/tools/checklist.js +6 -7
  29. package/mevebu/libs/js/tools/citation-number-check.js +10 -13
  30. package/mevebu/libs/js/tools/coin-flip.js +9 -11
  31. package/mevebu/libs/js/tools/color-converter.js +6 -7
  32. package/mevebu/libs/js/tools/color-palette.js +6 -8
  33. package/mevebu/libs/js/tools/color-picker.js +4 -5
  34. package/mevebu/libs/js/tools/content-calendar.js +3 -4
  35. package/mevebu/libs/js/tools/contrast-checker.js +5 -6
  36. package/mevebu/libs/js/tools/cron-builder.js +4 -6
  37. package/mevebu/libs/js/tools/css-clamp.js +5 -6
  38. package/mevebu/libs/js/tools/css-formatter.js +4 -7
  39. package/mevebu/libs/js/tools/css-grid.js +6 -7
  40. package/mevebu/libs/js/tools/css-minifier.js +5 -8
  41. package/mevebu/libs/js/tools/csv-to-json.js +2 -3
  42. package/mevebu/libs/js/tools/csv-viewer.js +9 -11
  43. package/mevebu/libs/js/tools/currency-format.js +5 -7
  44. package/mevebu/libs/js/tools/daily-planner.js +9 -10
  45. package/mevebu/libs/js/tools/data-masker.js +6 -8
  46. package/mevebu/libs/js/tools/data-storage.js +7 -9
  47. package/mevebu/libs/js/tools/data-validator.js +9 -11
  48. package/mevebu/libs/js/tools/date-difference.js +6 -7
  49. package/mevebu/libs/js/tools/date-format.js +11 -13
  50. package/mevebu/libs/js/tools/delimiter-converter.js +3 -5
  51. package/mevebu/libs/js/tools/discount-calculator.js +4 -5
  52. package/mevebu/libs/js/tools/doi-resolver.js +4 -5
  53. package/mevebu/libs/js/tools/env-formatter.js +3 -6
  54. package/mevebu/libs/js/tools/equation-formatter.js +5 -9
  55. package/mevebu/libs/js/tools/expense-split.js +6 -7
  56. package/mevebu/libs/js/tools/fake-data.js +8 -13
  57. package/mevebu/libs/js/tools/find-replace.js +3 -5
  58. package/mevebu/libs/js/tools/flashcard-creator.js +9 -10
  59. package/mevebu/libs/js/tools/font-pairing.js +6 -8
  60. package/mevebu/libs/js/tools/fraction-calculator.js +10 -13
  61. package/mevebu/libs/js/tools/git-command-builder.js +11 -22
  62. package/mevebu/libs/js/tools/gpa-calculator.js +8 -10
  63. package/mevebu/libs/js/tools/grade-calculator.js +7 -8
  64. package/mevebu/libs/js/tools/gradient-generator.js +10 -12
  65. package/mevebu/libs/js/tools/habit-tracker.js +8 -9
  66. package/mevebu/libs/js/tools/hash-generator.js +3 -5
  67. package/mevebu/libs/js/tools/hashtag-formatter.js +6 -8
  68. package/mevebu/libs/js/tools/hmac-generator.js +3 -5
  69. package/mevebu/libs/js/tools/hpp-calculator.js +4 -5
  70. package/mevebu/libs/js/tools/html-entity.js +2 -3
  71. package/mevebu/libs/js/tools/html-formatter.js +3 -6
  72. package/mevebu/libs/js/tools/http-status.js +5 -7
  73. package/mevebu/libs/js/tools/inventory-quantity.js +7 -8
  74. package/mevebu/libs/js/tools/json-formatter.js +3 -6
  75. package/mevebu/libs/js/tools/json-minifier.js +5 -8
  76. package/mevebu/libs/js/tools/json-to-csv.js +3 -5
  77. package/mevebu/libs/js/tools/json-to-typescript.js +2 -5
  78. package/mevebu/libs/js/tools/json-validator.js +2 -3
  79. package/mevebu/libs/js/tools/jwt-decoder.js +6 -7
  80. package/mevebu/libs/js/tools/jwt-expiry.js +5 -6
  81. package/mevebu/libs/js/tools/jwt-generator.js +4 -6
  82. package/mevebu/libs/js/tools/length-converter.js +7 -9
  83. package/mevebu/libs/js/tools/license-reference.js +10 -13
  84. package/mevebu/libs/js/tools/line-counter.js +5 -7
  85. package/mevebu/libs/js/tools/list-difference.js +5 -7
  86. package/mevebu/libs/js/tools/list-merger.js +4 -5
  87. package/mevebu/libs/js/tools/locale-number.js +5 -8
  88. package/mevebu/libs/js/tools/markdown-preview.js +5 -8
  89. package/mevebu/libs/js/tools/markdown-table.js +5 -7
  90. package/mevebu/libs/js/tools/markup-calculator.js +4 -5
  91. package/mevebu/libs/js/tools/meeting-agenda.js +8 -9
  92. package/mevebu/libs/js/tools/meta-preview.js +12 -13
  93. package/mevebu/libs/js/tools/meta-tags.js +6 -7
  94. package/mevebu/libs/js/tools/mime-types.js +5 -7
  95. package/mevebu/libs/js/tools/name-picker.js +2 -3
  96. package/mevebu/libs/js/tools/number-base-converter.js +9 -12
  97. package/mevebu/libs/js/tools/password-generator.js +9 -12
  98. package/mevebu/libs/js/tools/password-strength.js +21 -25
  99. package/mevebu/libs/js/tools/percent-to-fraction.js +6 -7
  100. package/mevebu/libs/js/tools/percentage-calculator.js +6 -9
  101. package/mevebu/libs/js/tools/percentage-change.js +4 -5
  102. package/mevebu/libs/js/tools/permutation-combination.js +9 -12
  103. package/mevebu/libs/js/tools/prefix-suffix.js +2 -3
  104. package/mevebu/libs/js/tools/priority-matrix.js +6 -10
  105. package/mevebu/libs/js/tools/profit-margin.js +4 -5
  106. package/mevebu/libs/js/tools/qr-generator.js +3 -4
  107. package/mevebu/libs/js/tools/qr-scanner.js +11 -12
  108. package/mevebu/libs/js/tools/quadratic-solver.js +7 -8
  109. package/mevebu/libs/js/tools/query-builder.js +2 -3
  110. package/mevebu/libs/js/tools/quiz-formatter.js +4 -6
  111. package/mevebu/libs/js/tools/ratio-calculator.js +7 -8
  112. package/mevebu/libs/js/tools/readability.js +12 -15
  113. package/mevebu/libs/js/tools/reading-time.js +5 -7
  114. package/mevebu/libs/js/tools/reference-completeness.js +6 -8
  115. package/mevebu/libs/js/tools/regex-tester.js +7 -10
  116. package/mevebu/libs/js/tools/remove-duplicate-lines.js +2 -3
  117. package/mevebu/libs/js/tools/remove-extra-spaces.js +3 -5
  118. package/mevebu/libs/js/tools/robots-txt.js +3 -4
  119. package/mevebu/libs/js/tools/roman-numeral-converter.js +6 -8
  120. package/mevebu/libs/js/tools/sales-commission.js +8 -9
  121. package/mevebu/libs/js/tools/scientific-calculator.js +10 -14
  122. package/mevebu/libs/js/tools/scientific-notation.js +8 -9
  123. package/mevebu/libs/js/tools/script-planner.js +8 -10
  124. package/mevebu/libs/js/tools/selling-price.js +4 -5
  125. package/mevebu/libs/js/tools/semver-helper.js +3 -5
  126. package/mevebu/libs/js/tools/sentence-length.js +9 -10
  127. package/mevebu/libs/js/tools/sequence-analyzer.js +7 -8
  128. package/mevebu/libs/js/tools/sitemap-xml.js +4 -7
  129. package/mevebu/libs/js/tools/slug-converter.js +3 -5
  130. package/mevebu/libs/js/tools/slug-preview.js +6 -7
  131. package/mevebu/libs/js/tools/sort-lines.js +3 -5
  132. package/mevebu/libs/js/tools/speed-converter.js +7 -9
  133. package/mevebu/libs/js/tools/spin-wheel.js +19 -17
  134. package/mevebu/libs/js/tools/sql-formatter.js +5 -9
  135. package/mevebu/libs/js/tools/sql-minifier.js +5 -8
  136. package/mevebu/libs/js/tools/standard-deviation.js +6 -9
  137. package/mevebu/libs/js/tools/stopwatch.js +4 -6
  138. package/mevebu/libs/js/tools/study-schedule.js +8 -9
  139. package/mevebu/libs/js/tools/subtitle-formatter.js +10 -11
  140. package/mevebu/libs/js/tools/table-to-markdown.js +8 -12
  141. package/mevebu/libs/js/tools/tax-calculator.js +4 -5
  142. package/mevebu/libs/js/tools/temperature-converter.js +8 -11
  143. package/mevebu/libs/js/tools/text-diff.js +11 -7
  144. package/mevebu/libs/js/tools/text-direction.js +3 -5
  145. package/mevebu/libs/js/tools/text-reverser.js +3 -5
  146. package/mevebu/libs/js/tools/time-duration.js +8 -9
  147. package/mevebu/libs/js/tools/timestamp-converter.js +4 -6
  148. package/mevebu/libs/js/tools/timezone-converter.js +9 -13
  149. package/mevebu/libs/js/tools/title-length.js +6 -7
  150. package/mevebu/libs/js/tools/unicode-inspector.js +4 -5
  151. package/mevebu/libs/js/tools/url-inspector.js +8 -9
  152. package/mevebu/libs/js/tools/url-parser.js +12 -13
  153. package/mevebu/libs/js/tools/utf8-bytes.js +6 -9
  154. package/mevebu/libs/js/tools/uuid-generator.js +5 -9
  155. package/mevebu/libs/js/tools/version-compare.js +3 -4
  156. package/mevebu/libs/js/tools/video-duration.js +5 -7
  157. package/mevebu/libs/js/tools/volume-converter.js +7 -9
  158. package/mevebu/libs/js/tools/weekly-planner.js +8 -10
  159. package/mevebu/libs/js/tools/weight-converter.js +7 -9
  160. package/mevebu/libs/js/tools/weighted-average.js +4 -5
  161. package/mevebu/libs/js/tools/word-counter.js +10 -13
  162. package/mevebu/libs/js/tools/word-limit.js +4 -5
  163. package/mevebu/libs/js/tools/yaml-formatter.js +6 -7
  164. package/package.json +1 -1
@@ -6,8 +6,7 @@ const UNITS = [
6
6
  { value: "kmh", label: "Kilometer per hour", factor: 0.2777777777777778 },
7
7
  { value: "mph", label: "Mile per hour", factor: 0.44704 },
8
8
  { value: "fps", label: "Foot per second", factor: 0.3048 },
9
- { value: "kn", label: "Knot", factor: 0.5144444444444445 },
10
- ];
9
+ { value: "kn", label: "Knot", factor: 0.5144444444444445 }];
11
10
 
12
11
  const byId = Object.fromEntries(UNITS.map((unit) => [unit.value, unit]));
13
12
  const options = UNITS.map(({ value, label }) => ({ value, label }));
@@ -24,11 +23,11 @@ export function mount(slot) {
24
23
  const value = numberInput({ value: "1", step: "any", "aria-label": "Value" });
25
24
  const from = selectInput(options, { value: "kmh", "aria-label": "From unit" });
26
25
  const to = selectInput(options, { value: "mph", "aria-label": "To unit" });
27
- const result = el("span", { class: "mvb-stat__v" }, "-");
28
- const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "Result"), result));
29
- const factor = el("p", { class: "mvb-note" });
26
+ const result = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
27
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Result"), result));
28
+ const factor = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
30
29
  const error = notice("error");
31
- const scroll = el("div", { class: "mvb-scroll" });
30
+ const scroll = el("div", { class: "max-h-80 overflow-auto" });
32
31
  let text = "";
33
32
 
34
33
  function run() {
@@ -48,7 +47,7 @@ export function mount(slot) {
48
47
  const out = (n * a.factor) / b.factor;
49
48
  result.textContent = `${fmt(out)} ${b.value}`;
50
49
  factor.textContent = `1 ${a.value} = ${fmt(a.factor / b.factor)} ${b.value}`;
51
- scroll.replaceChildren(el("table", { class: "mvb-ttable" },
50
+ scroll.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" },
52
51
  el("thead", {}, el("tr", {}, el("th", {}, "Unit"), el("th", {}, "Value"))),
53
52
  el("tbody", {}, ...UNITS.map((unit) => el("tr", {},
54
53
  el("td", {}, unit.label),
@@ -72,8 +71,7 @@ export function mount(slot) {
72
71
  stack(panel("Value", value), panel("From", from), panel("To", to), error),
73
72
  stack(panel("Result", stats), factor, panel("All units", scroll))
74
73
  ),
75
- foot: actions(swap, copyButton(() => text, "Copy result", "fill")),
76
- });
74
+ foot: actions(swap, copyButton(() => text, "Copy result", "fill"))});
77
75
  slot.append(card.node);
78
76
  run();
79
77
 
@@ -2,7 +2,9 @@
2
2
  // spin, driven by the browser random source, so every option has equal odds.
3
3
  import { el, toast, copyButton, randomInt, bench, stage, stack, panel, textArea, button, readPin, writePin, toolcard, actions } from "../core.js";
4
4
 
5
- const SEGMENTS = ["#2456D6", "#151D2E", "#5F8BFF", "#4B5871", "#1A43AD", "#69748C", "#1D2A4D", "#C2CBDF"];
5
+ // The slice palette. A green ramp that alternates dark and light, so two
6
+ // neighbouring slices never read as one wedge and every label stays legible.
7
+ const SEGMENTS = ["#0f6b4a", "#e0f0e7", "#0a5138", "#bfcac1", "#35805f", "#1d3b2c", "#8fd0b3", "#0b2a1e"];
6
8
 
7
9
  /**
8
10
  * The slice label, in whichever ink reads better on that fill. Every fill in the
@@ -12,7 +14,7 @@ const SEGMENTS = ["#2456D6", "#151D2E", "#5F8BFF", "#4B5871", "#1A43AD", "#69748
12
14
  function textOn(hex) {
13
15
  const linear = (value) => (value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
14
16
  const [r, g, b] = [1, 3, 5].map((at) => linear(parseInt(hex.slice(at, at + 2), 16) / 255));
15
- return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.2056 ? "#151D2E" : "#FFFFFF";
17
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.2056 ? "#101a15" : "#FFFFFF";
16
18
  }
17
19
 
18
20
  function entriesFrom(text) {
@@ -24,15 +26,15 @@ export function mount(slot) {
24
26
  const saved = readPin(PIN, { entries: "Ana\nBudi\nCitra\nDimas\nElena\nFajar" });
25
27
 
26
28
  const listField = textArea({ value: saved.entries ?? "", "aria-label": "Options, one per line", placeholder: "One option per line" });
27
- const canvas = el("canvas", { class: "mvb-wheel", width: 360, height: 360, role: "img", "aria-label": "The wheel" });
29
+ const canvas = el("canvas", { class: "h-auto w-[min(320px,100%)]", width: 360, height: 360, role: "img", "aria-label": "The wheel" });
28
30
  const context = canvas.getContext("2d");
29
- const historyBox = el("div", { class: "mvb-wheel__history", role: "list" });
31
+ const historyBox = el("div", { class: "flex max-w-[340px] flex-wrap justify-center gap-1.5", role: "list" });
30
32
 
31
33
  const spinButton = button("Spin", { icon: "rotate-cw", kind: "fill" });
32
34
  const clearButton = button("Clear past picks", { icon: "trash", kind: "quiet" });
33
35
  const copyWinner = copyButton(() => winner, "Copy winner");
34
36
 
35
- const status = el("div", { class: "mvb-wheel__status", "aria-live": "polite" }, "Add options, then spin.");
37
+ const status = el("div", { class: "text-balance text-xl font-bold tracking-[-0.01em]", "aria-live": "polite" }, "Add options, then spin.");
36
38
 
37
39
  let rotation = 0;
38
40
  let busy = false;
@@ -45,15 +47,17 @@ export function mount(slot) {
45
47
  const center = size / 2;
46
48
  const radius = center - 12;
47
49
  const root = getComputedStyle(document.documentElement);
48
- const ground = root.getPropertyValue("--mvb-void").trim() || "#fff";
49
- const orbit = root.getPropertyValue("--mvb-orbit-hard").trim() || "#888";
50
- const faint = root.getPropertyValue("--mvb-ink-3").trim() || "#888";
51
- const comet = root.getPropertyValue("--mvb-comet").trim() || "#2456D6";
50
+ const ground = root.getPropertyValue("--void").trim() || "#fff";
51
+ const orbit = root.getPropertyValue("--orbit-hard").trim() || "#888";
52
+ const faint = root.getPropertyValue("--ink-3").trim() || "#888";
53
+ const jade = root.getPropertyValue("--jade").trim() || "#0f6b4a";
54
+ // Canvas text is outside the stylesheet, so it reads the same stack the page uses.
55
+ const stack = root.getPropertyValue("--sans").trim() || "system-ui, sans-serif";
52
56
 
53
57
  context.clearRect(0, 0, size, size);
54
58
  if (entries.length < 2) {
55
59
  context.fillStyle = faint;
56
- context.font = "15px system-ui";
60
+ context.font = `15px ${stack}`;
57
61
  context.textAlign = "center";
58
62
  context.fillText(entries.length ? "Add one more option to spin" : "Type options to spin", center, center);
59
63
  status.textContent = entries.length ? "One more option to go." : "Add options, then spin.";
@@ -73,7 +77,7 @@ export function mount(slot) {
73
77
  context.rotate(a0 + slice / 2);
74
78
  context.textAlign = "right";
75
79
  context.fillStyle = textOn(SEGMENTS[i % SEGMENTS.length]);
76
- context.font = "600 13px system-ui, sans-serif";
80
+ context.font = `600 13px ${stack}`;
77
81
  const label = entry.length > 13 ? `${entry.slice(0, 12)}…` : entry;
78
82
  context.fillText(label, radius - 12, 4);
79
83
  context.restore();
@@ -90,7 +94,7 @@ export function mount(slot) {
90
94
  context.lineTo(center + 8, 2);
91
95
  context.lineTo(center, 20);
92
96
  context.closePath();
93
- context.fillStyle = comet;
97
+ context.fillStyle = jade;
94
98
  context.fill();
95
99
  if (status.dataset.winner !== "1") status.textContent = "Add options, then spin.";
96
100
  }
@@ -123,7 +127,7 @@ export function mount(slot) {
123
127
  status.dataset.winner = "1";
124
128
  winner = entries[winnerIndex];
125
129
  status.textContent = winner;
126
- const chip = el("span", { class: "mvb-wheel__chip", role: "listitem" }, winner);
130
+ const chip = el("span", { class: "rounded-full border border-orbit bg-deck-2 px-2.5 py-1 text-2xs font-semibold", role: "listitem" }, winner);
127
131
  historyBox.prepend(chip);
128
132
  while (historyBox.children.length > 12) historyBox.lastChild.remove();
129
133
  }
@@ -150,8 +154,7 @@ export function mount(slot) {
150
154
  stack(panel("Options, one per line", listField)),
151
155
  stage(canvas, status, historyBox)
152
156
  ),
153
- foot: actions(copyWinner, clearButton, spinButton),
154
- });
157
+ foot: actions(copyWinner, clearButton, spinButton)});
155
158
  slot.append(card.node);
156
159
  draw();
157
160
 
@@ -159,8 +162,7 @@ export function mount(slot) {
159
162
  unmount() {
160
163
  cancelAnimationFrame(frame);
161
164
  busy = false;
162
- },
163
- };
165
+ }};
164
166
  }
165
167
 
166
168
  export default { mount };
@@ -4,8 +4,7 @@ import { bench, stack, panel, textArea, notice, switchControl, copyButton, actio
4
4
  const CLAUSES = [
5
5
  "SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "HAVING", "LIMIT", "OFFSET", "VALUES", "SET",
6
6
  "INSERT INTO", "UPDATE", "DELETE FROM", "INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN",
7
- "CROSS JOIN", "JOIN", "UNION ALL", "UNION", "CREATE TABLE", "ALTER TABLE", "DROP TABLE", "WITH", "RETURNING",
8
- ];
7
+ "CROSS JOIN", "JOIN", "UNION ALL", "UNION", "CREATE TABLE", "ALTER TABLE", "DROP TABLE", "WITH", "RETURNING"];
9
8
  const WORDS = [...CLAUSES, "AND", "OR", "ON", "AS", "IN", "IS", "NOT", "NULL", "LIKE", "BETWEEN", "DISTINCT", "ASC", "DESC", "CASE", "WHEN", "THEN", "ELSE", "END"];
10
9
 
11
10
  const toPattern = (word) => word.replace(/ /g, "\\s+");
@@ -33,18 +32,16 @@ function format(sql, upper) {
33
32
  out = out.replace(clauseRe, "\n$1");
34
33
  out = out.replace(/\s*\b(AND|OR)\b/gi, "\n $1");
35
34
  out = out.replace(/\(\s+/g, "(").replace(/\s+\)/g, ")");
36
- out = out.replace(/\n{3,}/g, "\n\n").trim();
35
+ out = out.replace(/\n{3}/g, "\n\n").trim();
37
36
  return restore(out, store);
38
37
  }
39
38
 
40
39
  export function mount(slot) {
41
40
  const source = textArea({
42
41
  rows: 12,
43
- class: "mvb-in mvb-in--area mvb-in--mono",
44
42
  "aria-label": "SQL",
45
- value: "select id, name from users where active = true and age > 18 order by name",
46
- });
47
- const result = textArea({ rows: 12, readonly: true, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Formatted SQL" });
43
+ value: "select id, name from users where active = true and age > 18 order by name"});
44
+ const result = textArea({ rows: 12, readonly: true, "aria-label": "Formatted SQL" });
48
45
  const error = notice("error");
49
46
  let upper = true;
50
47
 
@@ -67,8 +64,7 @@ export function mount(slot) {
67
64
  stack(panel("Options", casing), panel("SQL", source), error),
68
65
  stack(panel("Formatted", result))
69
66
  ),
70
- foot: actions(copyButton(() => result.value, "Copy query", "fill")),
71
- });
67
+ foot: actions(copyButton(() => result.value, "Copy query", "fill"))});
72
68
  slot.append(card.node);
73
69
  run();
74
70
 
@@ -4,7 +4,7 @@ import { el, bench, stack, panel, textArea, switchControl, copyButton, actions,
4
4
  const bytes = (text) => new TextEncoder().encode(text).length;
5
5
 
6
6
  const tile = (key, value) =>
7
- el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__v" }, value), el("span", { class: "mvb-stat__k" }, key));
7
+ el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, value), el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
8
8
 
9
9
  function minify(sql, stripComments) {
10
10
  let out = "";
@@ -59,12 +59,10 @@ function minify(sql, stripComments) {
59
59
  export function mount(slot) {
60
60
  const source = textArea({
61
61
  rows: 12,
62
- class: "mvb-in mvb-in--area mvb-in--mono",
63
62
  "aria-label": "SQL",
64
- value: "SELECT id,\n name\n FROM users\n WHERE active = true\n AND age > 18;",
65
- });
66
- const result = textArea({ rows: 12, readonly: true, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Minified SQL" });
67
- const stats = el("div", { class: "mvb-stats" });
63
+ value: "SELECT id,\n name\n FROM users\n WHERE active = true\n AND age > 18;"});
64
+ const result = textArea({ rows: 12, readonly: true, "aria-label": "Minified SQL" });
65
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
68
66
  let stripComments = true;
69
67
 
70
68
  const comments = switchControl("Remove comments", { checked: true });
@@ -91,8 +89,7 @@ export function mount(slot) {
91
89
  stack(panel("Options", comments), panel("SQL", source)),
92
90
  stack(panel("Minified", result), stats)
93
91
  ),
94
- foot: actions(copyButton(() => result.value, "Copy query", "fill")),
95
- });
92
+ foot: actions(copyButton(() => result.value, "Copy query", "fill"))});
96
93
  slot.append(card.node);
97
94
  run();
98
95
 
@@ -12,8 +12,8 @@ function parseNumbers(text) {
12
12
  }
13
13
 
14
14
  function tile(label) {
15
- const value = el("span", { class: "mvb-stat__v" }, "-");
16
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
15
+ const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
16
+ 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));
17
17
  node.set = (text) => { value.textContent = text; };
18
18
  return node;
19
19
  }
@@ -24,7 +24,7 @@ export function mount(slot) {
24
24
  const variance = tile("Variance");
25
25
  const deviation = tile("Std deviation");
26
26
  const count = tile("Count");
27
- const stats = el("div", { class: "mvb-stats" }, mean, variance, deviation, count);
27
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, mean, variance, deviation, count);
28
28
  const error = notice("error");
29
29
  let kind = "population";
30
30
  let text = "";
@@ -66,16 +66,14 @@ export function mount(slot) {
66
66
  const chips = chipRow(
67
67
  [
68
68
  { value: "population", label: "Population" },
69
- { value: "sample", label: "Sample" },
70
- ],
69
+ { value: "sample", label: "Sample" }],
71
70
  {
72
71
  value: kind,
73
72
  label: "Data set",
74
73
  onChange: (value) => {
75
74
  kind = value;
76
75
  run();
77
- },
78
- }
76
+ }}
79
77
  );
80
78
 
81
79
  source.addEventListener("input", run);
@@ -85,8 +83,7 @@ export function mount(slot) {
85
83
  stack(panel("Numbers", source), panel("Data set", chips), error),
86
84
  stack(stats)
87
85
  ),
88
- foot: actions(copyButton(() => text, "Copy summary", "fill")),
89
- });
86
+ foot: actions(copyButton(() => text, "Copy summary", "fill"))});
90
87
  slot.append(card.node);
91
88
  run();
92
89
 
@@ -15,7 +15,7 @@ function format(ms) {
15
15
 
16
16
  export function mount(slot) {
17
17
  const display = output({ rows: 2 });
18
- const listWrap = el("div", { class: "mvb-scroll" });
18
+ const listWrap = el("div", { class: "max-h-80 overflow-auto" });
19
19
 
20
20
  const startButton = button("Start", { icon: "play", kind: "fill" });
21
21
  const lapButton = button("Lap", { icon: "plus" });
@@ -59,7 +59,7 @@ export function mount(slot) {
59
59
  el("td", {}, format(total))
60
60
  );
61
61
  });
62
- listWrap.replaceChildren(el("table", { class: "mvb-ttable" },
62
+ 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" },
63
63
  el("thead", {}, el("tr", {}, el("th", {}, "#"), el("th", {}, "Lap"), el("th", {}, "Total"))),
64
64
  el("tbody", {}, ...rows)
65
65
  ));
@@ -115,8 +115,7 @@ export function mount(slot) {
115
115
  stack(panel("Laps", listWrap)),
116
116
  stack(display)
117
117
  ),
118
- foot: actions(copyLaps, resetButton, lapButton, startButton),
119
- });
118
+ foot: actions(copyLaps, resetButton, lapButton, startButton)});
120
119
  slot.append(card.node);
121
120
  paint();
122
121
  renderLaps();
@@ -126,8 +125,7 @@ export function mount(slot) {
126
125
  unmount() {
127
126
  cancelAnimationFrame(frame);
128
127
  running = false;
129
- },
130
- };
128
+ }};
131
129
  }
132
130
 
133
131
  export default { mount };
@@ -9,16 +9,16 @@ export function mount(slot) {
9
9
  let list = Array.isArray(saved) ? saved : [];
10
10
 
11
11
  const name = textInput({ placeholder: "Topic", "aria-label": "Topic" });
12
- const date = el("input", { class: "mvb-in", type: "date", "aria-label": "Session date" });
13
- const listWrap = el("div", { class: "mvb-scroll" });
14
- const stats = el("div", { class: "mvb-stats" });
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", "aria-label": "Session date" });
13
+ const listWrap = el("div", { class: "max-h-80 overflow-auto" });
14
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
15
15
 
16
16
  const addButton = button("Add", { icon: "plus", kind: "fill" });
17
17
 
18
18
  const save = () => writePin(PIN, list);
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 sorted = () => list.slice().sort((a, b) => (a.date || "9999").localeCompare(b.date || "9999"));
24
24
 
@@ -50,7 +50,7 @@ export function mount(slot) {
50
50
  if (!rows.length) {
51
51
  listWrap.replaceChildren(blank("calendar-days", "No topics yet."));
52
52
  } else {
53
- listWrap.replaceChildren(el("table", { class: "mvb-ttable" },
53
+ 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" },
54
54
  el("thead", {}, el("tr", {}, el("th", {}, "Topic"), el("th", {}, "Date"), el("th", {}, ""))),
55
55
  el("tbody", {}, ...rows.map(rowNode))));
56
56
  }
@@ -77,8 +77,7 @@ export function mount(slot) {
77
77
  stack(panel("Topic", name), panel("Session date", date)),
78
78
  stack(stats, panel("Plan", listWrap))
79
79
  ),
80
- foot: actions(copyButton(() => sorted().map((item) => `${item.date || "No date"} ${item.done ? "[x]" : "[ ]"} ${item.name}`).join("\n"), "Copy plan"), addButton),
81
- });
80
+ foot: actions(copyButton(() => sorted().map((item) => `${item.date || "No date"} ${item.done ? "[x]" : "[ ]"} ${item.name}`).join("\n"), "Copy plan"), addButton)});
82
81
  slot.append(card.node);
83
82
  render();
84
83
 
@@ -5,8 +5,8 @@ const isTiming = (line) => line.includes("-->");
5
5
  const isIndex = (line) => /^\d+$/.test(line.trim());
6
6
 
7
7
  function tile(label) {
8
- const value = el("span", { class: "mvb-stat__v" }, "0");
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" }, "0");
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
  }
@@ -18,9 +18,9 @@ export function mount(slot) {
18
18
  const linesTile = tile("Lines");
19
19
  const longTile = tile("Long lines");
20
20
  const longestTile = tile("Longest");
21
- const stats = el("div", { class: "mvb-stats" }, linesTile, longTile, longestTile);
21
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, linesTile, longTile, longestTile);
22
22
 
23
- const longWrap = el("div", { class: "mvb-scroll" });
23
+ const longWrap = el("div", { class: "max-h-80 overflow-auto" });
24
24
  const out = output({ rows: 10 });
25
25
  const note = notice("note");
26
26
 
@@ -41,16 +41,16 @@ export function mount(slot) {
41
41
  if (!long.length) {
42
42
  longWrap.replaceChildren(blank("film", "No line runs past the limit."));
43
43
  } else {
44
- longWrap.replaceChildren(el("div", { class: "mvb-kv" },
45
- ...long.map((line) => el("div", { class: "mvb-kv__row" },
46
- el("span", { class: "mvb-kv__k" }, `${line.length}`),
47
- el("span", { class: "mvb-kv__v" }, line)))));
44
+ longWrap.replaceChildren(el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
45
+ ...long.map((line) => el("div", { class: "grid gap-3 bg-deck px-3 py-[9px] [grid-template-columns:minmax(90px,34%)_minmax(0,1fr)]" },
46
+ el("span", { class: "wrap-anywhere text-2xs font-semibold text-ink-2" }, `${line.length}`),
47
+ el("span", { class: "wrap-anywhere font-mono text-2xs" }, line)))));
48
48
  }
49
49
 
50
50
  tidied = raw
51
51
  .map((line) => line.replace(/\s+$/, ""))
52
52
  .join("\n")
53
- .replace(/\n{3,}/g, "\n\n")
53
+ .replace(/\n{3}/g, "\n\n")
54
54
  .replace(/^\n+|\n+$/g, "");
55
55
  out.write(tidied);
56
56
  }
@@ -63,8 +63,7 @@ export function mount(slot) {
63
63
  stack(panel("Subtitles", source), panel("Maximum line length", max), note),
64
64
  stack(stats, panel("Long lines", longWrap), panel("Tidied", out))
65
65
  ),
66
- foot: actions(copyButton(() => tidied, "Copy text", "fill")),
67
- });
66
+ foot: actions(copyButton(() => tidied, "Copy text", "fill"))});
68
67
  slot.append(card.node);
69
68
  run();
70
69
 
@@ -55,32 +55,30 @@ function detectDelimiter(text) {
55
55
  const escapeCell = (cell) => String(cell ?? "").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim();
56
56
 
57
57
  function tile(label) {
58
- const value = el("span", { class: "mvb-stat__v" }, "0");
59
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
58
+ const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
59
+ 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));
60
60
  node.set = (text) => { value.textContent = text; };
61
61
  return node;
62
62
  }
63
63
 
64
64
  export function mount(slot) {
65
- const source = textArea({ rows: 12, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Table data", placeholder: "Paste rows copied from a spreadsheet" });
65
+ const source = textArea({ rows: 12, "aria-label": "Table data", placeholder: "Paste rows copied from a spreadsheet" });
66
66
  const delimiter = selectInput([
67
67
  { value: "auto", label: "Auto" },
68
68
  { value: ",", label: "Comma" },
69
69
  { value: ";", label: "Semicolon" },
70
70
  { value: "\t", label: "Tab" },
71
- { value: "|", label: "Pipe" },
72
- ], { value: "auto", "aria-label": "Delimiter" });
71
+ { value: "|", label: "Pipe" }], { value: "auto", "aria-label": "Delimiter" });
73
72
  const alignment = selectInput([
74
73
  { value: "none", label: "None" },
75
74
  { value: "left", label: "Left" },
76
75
  { value: "center", label: "Center" },
77
- { value: "right", label: "Right" },
78
- ], { value: "none", "aria-label": "Alignment" });
76
+ { value: "right", label: "Right" }], { value: "none", "aria-label": "Alignment" });
79
77
 
80
78
  const result = output({ rows: 12 });
81
79
  const rowsTile = tile("Rows");
82
80
  const colsTile = tile("Columns");
83
- const stats = el("div", { class: "mvb-stats" }, rowsTile, colsTile);
81
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, rowsTile, colsTile);
84
82
  const error = notice("error");
85
83
  let current = "";
86
84
 
@@ -112,8 +110,7 @@ export function mount(slot) {
112
110
  current = [
113
111
  line(header.map(escapeCell)),
114
112
  line(separator(header.length)),
115
- ...body.map((row) => line(header.map((_, i) => escapeCell(row[i])))),
116
- ].join("\n");
113
+ ...body.map((row) => line(header.map((_, i) => escapeCell(row[i]))))].join("\n");
117
114
  result.write(current);
118
115
  }
119
116
 
@@ -126,8 +123,7 @@ export function mount(slot) {
126
123
  stack(panel("Table data", source), panel("Delimiter", delimiter), panel("Alignment", alignment), error),
127
124
  stack(stats, panel("Markdown", result))
128
125
  ),
129
- foot: actions(copyButton(() => current, "Copy Markdown", "fill")),
130
- });
126
+ foot: actions(copyButton(() => current, "Copy Markdown", "fill"))});
131
127
  slot.append(card.node);
132
128
  run();
133
129
 
@@ -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
  }
@@ -23,7 +23,7 @@ export function mount(slot) {
23
23
  const taxTile = tile("Tax");
24
24
  const netTile = tile("Net");
25
25
  const grossTile = tile("Total");
26
- const stats = el("div", { class: "mvb-stats" }, taxTile, netTile, grossTile);
26
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, taxTile, netTile, grossTile);
27
27
  const error = notice("error");
28
28
  const note = notice("note");
29
29
  note.write("You set the rate. No official rule is applied.");
@@ -73,8 +73,7 @@ export function mount(slot) {
73
73
  stack(panel("Amount", amount), panel("Amount type", chips), panel("Tax rate %", rate), error),
74
74
  stack(stats, note)
75
75
  ),
76
- foot: actions(copyButton(() => text, "Copy result", "fill")),
77
- });
76
+ foot: actions(copyButton(() => text, "Copy result", "fill"))});
78
77
  slot.append(card.node);
79
78
  run();
80
79
 
@@ -4,8 +4,7 @@ import { el, bench, stack, panel, numberInput, selectInput, notice, copyButton,
4
4
  const SCALES = [
5
5
  { value: "c", label: "Celsius" },
6
6
  { value: "f", label: "Fahrenheit" },
7
- { value: "k", label: "Kelvin" },
8
- ];
7
+ { value: "k", label: "Kelvin" }];
9
8
 
10
9
  const byId = Object.fromEntries(SCALES.map((scale) => [scale.value, scale]));
11
10
  const options = SCALES.map(({ value, label }) => ({ value, label }));
@@ -19,8 +18,7 @@ const FORMULAS = {
19
18
  "f-c": "C = (F - 32) x 5/9",
20
19
  "f-k": "K = (F - 32) x 5/9 + 273.15",
21
20
  "k-c": "C = K - 273.15",
22
- "k-f": "F = (K - 273.15) x 9/5 + 32",
23
- };
21
+ "k-f": "F = (K - 273.15) x 9/5 + 32"};
24
22
 
25
23
  function fmt(value) {
26
24
  if (!Number.isFinite(value)) return "-";
@@ -34,11 +32,11 @@ export function mount(slot) {
34
32
  const value = numberInput({ value: "0", step: "any", "aria-label": "Value" });
35
33
  const from = selectInput(options, { value: "c", "aria-label": "From scale" });
36
34
  const to = selectInput(options, { value: "f", "aria-label": "To scale" });
37
- const result = el("span", { class: "mvb-stat__v" }, "-");
38
- const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "Result"), result));
39
- const formula = el("p", { class: "mvb-note" });
35
+ const result = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
36
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Result"), result));
37
+ const formula = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
40
38
  const error = notice("error");
41
- const scroll = el("div", { class: "mvb-scroll" });
39
+ const scroll = el("div", { class: "max-h-80 overflow-auto" });
42
40
  let text = "";
43
41
 
44
42
  function run() {
@@ -59,7 +57,7 @@ export function mount(slot) {
59
57
  const out = fromCelsius(celsius, b.value);
60
58
  result.textContent = `${fmt(out)} ${b.value === "k" ? "K" : `°${b.value.toUpperCase()}`}`;
61
59
  formula.textContent = a.value === b.value ? "" : FORMULAS[`${a.value}-${b.value}`];
62
- scroll.replaceChildren(el("table", { class: "mvb-ttable" },
60
+ scroll.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" },
63
61
  el("thead", {}, el("tr", {}, el("th", {}, "Scale"), el("th", {}, "Value"))),
64
62
  el("tbody", {}, ...SCALES.map((scale) => el("tr", {},
65
63
  el("td", {}, scale.label),
@@ -83,8 +81,7 @@ export function mount(slot) {
83
81
  stack(panel("Value", value), panel("From", from), panel("To", to), error),
84
82
  stack(panel("Result", stats), formula, panel("All scales", scroll))
85
83
  ),
86
- foot: actions(swap, copyButton(() => text, "Copy result", "fill")),
87
- });
84
+ foot: actions(swap, copyButton(() => text, "Copy result", "fill"))});
88
85
  slot.append(card.node);
89
86
  run();
90
87
 
@@ -8,9 +8,8 @@ export function mount(slot) {
8
8
  const right = textArea({ rows: 9, "aria-label": "Second text", placeholder: "Second text" });
9
9
  const options = toggleRow([
10
10
  { value: "ignore", label: "Ignore case", checked: false },
11
- { value: "spaces", label: "Ignore spaces", checked: false },
12
- ]);
13
- const diffBox = el("div", { class: "mvb-diff" });
11
+ { value: "spaces", label: "Ignore spaces", checked: false }]);
12
+ const diffBox = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" });
14
13
  const note = notice("note");
15
14
  let rows = [];
16
15
 
@@ -66,8 +65,14 @@ export function mount(slot) {
66
65
  const removed = rows.filter(([type]) => type === "del").length;
67
66
  diffBox.replaceChildren(...rows.map(([type, raw]) => el(
68
67
  "div",
69
- { class: `mvb-diff__row${type === "add" ? " mvb-diff__row--add" : type === "del" ? " mvb-diff__row--del" : ""}` },
70
- el("span", { class: "mvb-diff__sign" }, type === "add" ? "+" : type === "del" ? "-" : " "),
68
+ {
69
+ // One row skin. The add/del tint and the sign colour hang off the data
70
+ // attribute, so a row never carries two competing background utilities.
71
+ class:
72
+ "grid bg-deck font-mono text-2xs [grid-template-columns:34px_minmax(0,1fr)] [&>span]:wrap-anywhere [&>span]:px-2.5 [&>span]:py-[3px] [&>span]:whitespace-pre-wrap data-[kind=add]:bg-[var(--add)] data-[kind=add]:[&>span:first-child]:text-jade data-[kind=del]:bg-[var(--del)] data-[kind=del]:[&>span:first-child]:text-danger",
73
+ dataset: { kind: type },
74
+ },
75
+ el("span", { class: "border-e border-orbit text-center text-ink-3 select-none" }, type === "add" ? "+" : type === "del" ? "-" : " "),
71
76
  el("span", {}, raw)
72
77
  )));
73
78
  const counts = !added && !removed
@@ -93,8 +98,7 @@ export function mount(slot) {
93
98
  stack(panel("First text", left), panel("Second text", right), options, note),
94
99
  stack(panel("Difference", diffBox))
95
100
  ),
96
- foot: actions(swapButton, copyButton(diffText, "Copy diff", "fill")),
97
- });
101
+ foot: actions(swapButton, copyButton(diffText, "Copy diff", "fill"))});
98
102
  slot.append(card.node);
99
103
  run();
100
104
 
@@ -14,7 +14,7 @@ function detect(text) {
14
14
 
15
15
  export function mount(slot) {
16
16
  const source = textArea({ rows: 6, "aria-label": "Text", placeholder: "Type or paste text" });
17
- const sample = el("div", { class: "mvb-preview__sample" }, "Sample text");
17
+ const sample = el("div", { class: "w-full rounded-lg border border-orbit bg-deck px-[18px] py-4" }, "Sample text");
18
18
  const code = output();
19
19
 
20
20
  let mode = "auto";
@@ -22,8 +22,7 @@ export function mount(slot) {
22
22
  [
23
23
  { value: "auto", label: "Auto" },
24
24
  { value: "ltr", label: "Left to right" },
25
- { value: "rtl", label: "Right to left" },
26
- ],
25
+ { value: "rtl", label: "Right to left" }],
27
26
  { value: "auto", label: "Direction", onChange: (next) => { mode = next; run(); } }
28
27
  );
29
28
 
@@ -44,8 +43,7 @@ export function mount(slot) {
44
43
  stack(panel("Text", source), panel("Direction", dirChips)),
45
44
  stack(panel("Preview", sample), panel("Attribute", code))
46
45
  ),
47
- foot: actions(copyButton(() => snippet, "Copy attribute", "fill")),
48
- });
46
+ foot: actions(copyButton(() => snippet, "Copy attribute", "fill"))});
49
47
  slot.append(card.node);
50
48
  run();
51
49