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
@@ -21,11 +21,11 @@ function relative(seconds) {
21
21
  }
22
22
 
23
23
  const kvRow = (key, value) =>
24
- el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, key), el("span", { class: "mvb-kv__v" }, value));
24
+ 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));
25
25
 
26
26
  export function mount(slot) {
27
- const source = textArea({ rows: 4, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Token", placeholder: "Paste a JWT" });
28
- const claims = el("div", { class: "mvb-kv" });
27
+ const source = textArea({ rows: 4, "aria-label": "Token", placeholder: "Paste a JWT" });
28
+ const claims = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" });
29
29
  const status = notice("note");
30
30
  const error = notice("error");
31
31
  let summary = "";
@@ -82,12 +82,11 @@ export function mount(slot) {
82
82
  stack(
83
83
  panel("Token", source),
84
84
  error,
85
- el("p", { class: "mvb-note" }, "Only the payload is read in this tab. The signature is not checked, so a valid look is not proof the token is genuine.")
85
+ el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "Only the payload is read in this tab. The signature is not checked, so a valid look is not proof the token is genuine.")
86
86
  ),
87
87
  stack(panel("Time claims", claims), status)
88
88
  ),
89
- foot: actions(copyButton(() => summary, "Copy claims", "fill")),
90
- });
89
+ foot: actions(copyButton(() => summary, "Copy claims", "fill"))});
91
90
  slot.append(card.node);
92
91
  run();
93
92
 
@@ -21,12 +21,11 @@ export function mount(slot) {
21
21
  const headerField = textArea({ rows: 3, value: '{\n "alg": "HS256",\n "typ": "JWT"\n}' });
22
22
  const payloadField = textArea({
23
23
  rows: 7,
24
- value: `{\n "sub": "1234567890",\n "name": "Ada",\n "admin": true,\n "iat": ${now}\n}`,
25
- });
24
+ value: `{\n "sub": "1234567890",\n "name": "Ada",\n "admin": true,\n "iat": ${now}\n}`});
26
25
  const secretField = textInput({ type: "password", value: "your-256-bit-secret", autocomplete: "new-password" });
27
26
  const showButton = el(
28
27
  "button",
29
- { class: "mvb-icobtn", type: "button", "aria-label": "Show secret", "aria-pressed": "false" },
28
+ { class: "inline-flex size-[var(--ctl)] cursor-pointer items-center justify-center rounded-r1 border border-transparent bg-transparent text-ink-2 transition-colors duration-150 hover:bg-deck-2 hover:text-ink focus-visible:border-jade", type: "button", "aria-label": "Show secret", "aria-pressed": "false" },
30
29
  icon("eye")
31
30
  );
32
31
  const error = notice("error");
@@ -94,13 +93,12 @@ export function mount(slot) {
94
93
  stack(
95
94
  panel("Header", headerField),
96
95
  panel("Payload", payloadField),
97
- panel("Secret", el("div", { class: "mvb-secret" }, secretField, showButton)),
96
+ panel("Secret", el("div", { class: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2" }, secretField, showButton)),
98
97
  error
99
98
  ),
100
99
  stack(panel("Token", tokenOut), timePanel)
101
100
  ),
102
- foot: actions(copyBtn),
103
- });
101
+ foot: actions(copyBtn)});
104
102
  slot.append(card.node);
105
103
  sign();
106
104
 
@@ -9,8 +9,7 @@ const UNITS = [
9
9
  { value: "in", label: "Inch", factor: 0.0254 },
10
10
  { value: "ft", label: "Foot", factor: 0.3048 },
11
11
  { value: "yd", label: "Yard", factor: 0.9144 },
12
- { value: "mi", label: "Mile", factor: 1609.344 },
13
- ];
12
+ { value: "mi", label: "Mile", factor: 1609.344 }];
14
13
 
15
14
  const byId = Object.fromEntries(UNITS.map((unit) => [unit.value, unit]));
16
15
  const options = UNITS.map(({ value, label }) => ({ value, label }));
@@ -27,11 +26,11 @@ export function mount(slot) {
27
26
  const value = numberInput({ value: "1", step: "any", "aria-label": "Value" });
28
27
  const from = selectInput(options, { value: "m", "aria-label": "From unit" });
29
28
  const to = selectInput(options, { value: "ft", "aria-label": "To unit" });
30
- const result = el("span", { class: "mvb-stat__v" }, "-");
31
- const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "Result"), result));
32
- const factor = el("p", { class: "mvb-note" });
29
+ const result = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
30
+ 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));
31
+ const factor = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
33
32
  const error = notice("error");
34
- const scroll = el("div", { class: "mvb-scroll" });
33
+ const scroll = el("div", { class: "max-h-80 overflow-auto" });
35
34
  let text = "";
36
35
 
37
36
  function run() {
@@ -51,7 +50,7 @@ export function mount(slot) {
51
50
  const out = (n * a.factor) / b.factor;
52
51
  result.textContent = `${fmt(out)} ${b.value}`;
53
52
  factor.textContent = `1 ${a.value} = ${fmt(a.factor / b.factor)} ${b.value}`;
54
- scroll.replaceChildren(el("table", { class: "mvb-ttable" },
53
+ 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" },
55
54
  el("thead", {}, el("tr", {}, el("th", {}, "Unit"), el("th", {}, "Value"))),
56
55
  el("tbody", {}, ...UNITS.map((unit) => el("tr", {},
57
56
  el("td", {}, unit.label),
@@ -75,8 +74,7 @@ export function mount(slot) {
75
74
  stack(panel("Value", value), panel("From", from), panel("To", to), error),
76
75
  stack(panel("Result", stats), factor, panel("All units", scroll))
77
76
  ),
78
- foot: actions(swap, copyButton(() => text, "Copy result", "fill")),
79
- });
77
+ foot: actions(swap, copyButton(() => text, "Copy result", "fill"))});
80
78
  slot.append(card.node);
81
79
  run();
82
80
 
@@ -7,19 +7,18 @@ const LICENSES = [
7
7
  { value: "GPL-3.0", label: "GPL-3.0", can: "Use, modify and distribute.", must: "Release derivative works under the same license and share the source.", cannot: "Add restrictions beyond the license." },
8
8
  { value: "BSD-3-Clause", label: "BSD-3-Clause", can: "Use, modify and distribute, including commercially.", must: "Keep the copyright notice and disclaimer.", cannot: "Use contributor names to endorse derivatives." },
9
9
  { value: "MPL-2.0", label: "MPL-2.0", can: "Use, modify and distribute.", must: "Share changes to MPL-licensed files under MPL.", cannot: "Hold contributors liable." },
10
- { value: "Unlicense", label: "Unlicense", can: "Use, modify and distribute with no conditions.", must: "Nothing.", cannot: "Hold the author liable." },
11
- ];
10
+ { value: "Unlicense", label: "Unlicense", can: "Use, modify and distribute with no conditions.", must: "Nothing.", cannot: "Hold the author liable." }];
12
11
 
13
12
  const kvRow = (key, value) =>
14
- el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, key), el("span", { class: "mvb-kv__v" }, value));
13
+ 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));
15
14
 
16
15
  export function mount(slot) {
17
- const name = el("span", { class: "mvb-stat__v" }, LICENSES[0].label);
18
- const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "License"), name));
19
- const can = el("span", { class: "mvb-kv__v" });
20
- const must = el("span", { class: "mvb-kv__v" });
21
- const cannot = el("span", { class: "mvb-kv__v" });
22
- const kv = el("div", { class: "mvb-kv" }, kvRow("Can", can), kvRow("Must", must), kvRow("Cannot", cannot));
16
+ const name = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, LICENSES[0].label);
17
+ 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" }, "License"), name));
18
+ const can = el("span", { class: "wrap-anywhere font-mono text-2xs" });
19
+ const must = el("span", { class: "wrap-anywhere font-mono text-2xs" });
20
+ const cannot = el("span", { class: "wrap-anywhere font-mono text-2xs" });
21
+ const kv = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" }, kvRow("Can", can), kvRow("Must", must), kvRow("Cannot", cannot));
23
22
  const disclaimer = notice("note");
24
23
  disclaimer.write("Short summary, not legal advice.");
25
24
  let current = LICENSES[0];
@@ -31,8 +30,7 @@ export function mount(slot) {
31
30
  onChange: (value) => {
32
31
  current = LICENSES.find((entry) => entry.value === value);
33
32
  run();
34
- },
35
- });
33
+ }});
36
34
 
37
35
  function run() {
38
36
  name.textContent = current.label;
@@ -47,8 +45,7 @@ export function mount(slot) {
47
45
  stack(panel("License", chips), disclaimer),
48
46
  stack(panel("Summary", stats), panel("Terms", kv))
49
47
  ),
50
- foot: actions(copyButton(() => text, "Copy summary", "fill")),
51
- });
48
+ foot: actions(copyButton(() => text, "Copy summary", "fill"))});
52
49
  slot.append(card.node);
53
50
  run();
54
51
 
@@ -2,8 +2,8 @@
2
2
  import { el, bench, stack, panel, textArea, button, 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
  }
@@ -14,7 +14,7 @@ export function mount(slot) {
14
14
  const totalTile = tile("Total lines");
15
15
  const filledTile = tile("Filled");
16
16
  const blankTile = tile("Blank");
17
- const stats = el("div", { class: "mvb-stats" }, totalTile, filledTile, blankTile);
17
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, totalTile, filledTile, blankTile);
18
18
 
19
19
  let counts = { total: 0, filled: 0, blank: 0 };
20
20
 
@@ -22,8 +22,7 @@ export function mount(slot) {
22
22
  return [
23
23
  `${counts.total} total lines`,
24
24
  `${counts.filled} filled`,
25
- `${counts.blank} blank`,
26
- ].join("\n");
25
+ `${counts.blank} blank`].join("\n");
27
26
  }
28
27
 
29
28
  function run() {
@@ -44,8 +43,7 @@ export function mount(slot) {
44
43
  stack(panel("Text", source)),
45
44
  stack(stats)
46
45
  ),
47
- foot: actions(clearButton, copyButton(summary, "Copy summary", "fill")),
48
- });
46
+ foot: actions(clearButton, copyButton(summary, "Copy summary", "fill"))});
49
47
  slot.append(card.node);
50
48
  run();
51
49
 
@@ -16,8 +16,8 @@ function uniqueLines(text, ignoreCase) {
16
16
  }
17
17
 
18
18
  function tile(label) {
19
- const value = el("span", { class: "mvb-stat__v" }, "0");
20
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
19
+ const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
20
+ const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
21
21
  node.set = (text) => { value.textContent = text; };
22
22
  return node;
23
23
  }
@@ -33,7 +33,7 @@ export function mount(slot) {
33
33
  const onlyATile = tile("Only in A");
34
34
  const onlyBTile = tile("Only in B");
35
35
  const bothTile = tile("In both");
36
- const stats = el("div", { class: "mvb-stats" }, onlyATile, onlyBTile, bothTile);
36
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, onlyATile, onlyBTile, bothTile);
37
37
  let report = "";
38
38
 
39
39
  function run() {
@@ -62,8 +62,7 @@ export function mount(slot) {
62
62
  onlyB.join("\n"),
63
63
  "",
64
64
  `In both (${both.length}):`,
65
- both.join("\n"),
66
- ].join("\n");
65
+ both.join("\n")].join("\n");
67
66
  }
68
67
 
69
68
  first.addEventListener("input", run);
@@ -75,8 +74,7 @@ export function mount(slot) {
75
74
  stack(panel("List A", first), panel("List B", second), panel(null, caseBox)),
76
75
  stack(stats, panel("Only in A", onlyAOut), panel("Only in B", onlyBOut), panel("In both", bothOut))
77
76
  ),
78
- foot: actions(copyButton(() => report, "Copy report", "fill")),
79
- });
77
+ foot: actions(copyButton(() => report, "Copy report", "fill"))});
80
78
  slot.append(card.node);
81
79
  run();
82
80
 
@@ -4,8 +4,8 @@ import { el, bench, stack, panel, textArea, checkbox, output, copyButton, button
4
4
  const linesOf = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5
5
 
6
6
  function tile(label) {
7
- const value = el("span", { class: "mvb-stat__v" }, "0");
8
- const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
7
+ const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
8
+ const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
9
9
  node.set = (text) => { value.textContent = text; };
10
10
  return node;
11
11
  }
@@ -20,7 +20,7 @@ export function mount(slot) {
20
20
  const listATile = tile("List A");
21
21
  const listBTile = tile("List B");
22
22
  const mergedTile = tile("Merged");
23
- const stats = el("div", { class: "mvb-stats" }, listATile, listBTile, mergedTile);
23
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, listATile, listBTile, mergedTile);
24
24
  let current = [];
25
25
 
26
26
  function run() {
@@ -56,8 +56,7 @@ export function mount(slot) {
56
56
  stack(panel("List A", first), panel("List B", second), panel(null, dedupeBox), panel(null, caseBox)),
57
57
  stack(stats, panel("Merged", result))
58
58
  ),
59
- foot: actions(clearButton, copyButton(() => current.join("\n"), "Copy merged", "fill")),
60
- });
59
+ foot: actions(clearButton, copyButton(() => current.join("\n"), "Copy merged", "fill"))});
61
60
  slot.append(card.node);
62
61
  run();
63
62
 
@@ -10,8 +10,7 @@ const LOCALES = [
10
10
  ["id-ID", "Indonesian"],
11
11
  ["hi-IN", "Hindi"],
12
12
  ["ar-EG", "Arabic"],
13
- ["ja-JP", "Japanese"],
14
- ];
13
+ ["ja-JP", "Japanese"]];
15
14
 
16
15
  function format(locale, value, options) {
17
16
  try {
@@ -26,8 +25,8 @@ export function mount(slot) {
26
25
  const decimals = numberInput({ min: "0", max: "20", value: "2", "aria-label": "Fraction digits" });
27
26
  const grouping = switchControl("Group thousands", { checked: true });
28
27
 
29
- const table = el("table", { class: "mvb-ttable" });
30
- const tableWrap = el("div", { class: "mvb-scroll" }, table);
28
+ const table = el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" });
29
+ const tableWrap = el("div", { class: "max-h-80 overflow-auto" }, table);
31
30
  const note = notice("note");
32
31
  note.write("Formatting only. The number is not converted.");
33
32
 
@@ -40,8 +39,7 @@ export function mount(slot) {
40
39
  const options = {
41
40
  minimumFractionDigits: digits,
42
41
  maximumFractionDigits: digits,
43
- useGrouping: grouping.input.checked,
44
- };
42
+ useGrouping: grouping.input.checked};
45
43
  const rows = LOCALES.map(([code, name]) => {
46
44
  const shown = raw === "" || !Number.isFinite(parsed) ? "-" : format(code, parsed, options);
47
45
  return el("tr", {}, el("th", {}, name), el("td", {}, shown));
@@ -68,8 +66,7 @@ export function mount(slot) {
68
66
  ),
69
67
  stack(note, panel("By locale", tableWrap))
70
68
  ),
71
- foot: actions(copyButton(() => tableText, "Copy list", "fill")),
72
- });
69
+ foot: actions(copyButton(() => tableText, "Copy list", "fill"))});
73
70
  slot.append(card.node);
74
71
  run();
75
72
 
@@ -1,6 +1,6 @@
1
1
  // Placeholder text by the word, the sentence or the paragraph. The classic
2
2
  // opening is a checkbox, not a lock-in, and regeneration never clears the copy.
3
- import { toast, copyText, chipRow, numberInput, checkbox, button, bench, stack, panel, textArea, readPin, writePin, actions, toolcard } from "../core.js";
3
+ import { toast, copyText, chipRow, numberInput, checkbox, output, button, bench, stack, panel, readPin, writePin, actions, toolcard } from "../core.js";
4
4
 
5
5
  const WORDS = "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud exercitation ullamco laboris nisi aliquip ex ea commodo consequat duis aute irure in reprehenderit voluptate velit esse cillum eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt culpa qui officia deserunt mollit anim id est laborum".split(" ");
6
6
 
@@ -42,7 +42,9 @@ export function mount(slot) {
42
42
  );
43
43
  const amount = numberInput({ value: saved.amount ?? "3", min: "1", max: "200", "aria-label": "How many" });
44
44
  const classic = checkbox("Start with the classic opening", { checked: saved.classic !== false });
45
- const result = textArea({ rows: 12, readonly: true, class: "mvb-in mvb-in--area", "aria-label": "Generated text" });
45
+ // Generated text is read, not typed, so it sits on the recessed surface.
46
+ const result = output({ tag: "textarea", rows: 12 });
47
+ result.setAttribute("aria-label", "Generated text");
46
48
 
47
49
  function totalCount() {
48
50
  return Math.max(1, Math.min(200, parseInt(amount.value || "1", 10) || 1));
@@ -40,7 +40,7 @@ function blocks(source) {
40
40
  const isBlockStart = (index) => {
41
41
  const line = lines[index];
42
42
  return !line.trim() || /^```/.test(line) || /^#{1,6}\s/.test(line) || /^\s*>/.test(line) ||
43
- /^\s*([-*+]|\d+[.)])\s+/.test(line) || /^\s*([-*_])(\s*\1){2,}\s*$/.test(line) || isTableStart(index);
43
+ /^\s*([-*+]|\d+[.)])\s+/.test(line) || /^\s*([-*_])(\s*\1){2}\s*$/.test(line) || isTableStart(index);
44
44
  };
45
45
 
46
46
  while (i < lines.length) {
@@ -64,7 +64,7 @@ function blocks(source) {
64
64
  continue;
65
65
  }
66
66
 
67
- if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
67
+ if (/^\s*([-*_])(\s*\1){2}\s*$/.test(line)) {
68
68
  nodes.push(el("hr"));
69
69
  i++;
70
70
  continue;
@@ -115,11 +115,9 @@ function blocks(source) {
115
115
  export function mount(slot) {
116
116
  const source = textArea({
117
117
  rows: 16,
118
- class: "mvb-in mvb-in--area mvb-in--mono",
119
118
  "aria-label": "Markdown",
120
- value: "# Title\n\nSome **bold**, *italic* and `code`.\n\n- one\n- two\n\n> A quote.\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst x = 1;\n```\n",
121
- });
122
- const preview = el("div", { class: "mvb-pane" });
119
+ value: "# Title\n\nSome **bold**, *italic* and `code`.\n\n- one\n- two\n\n> A quote.\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst x = 1;\n```\n"});
120
+ const preview = el("div", { class: "min-h-[120px] max-h-[380px] overflow-auto wrap-anywhere rounded-r1 border border-orbit bg-deck-2 px-4 py-3.5 leading-normal [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_h1]:my-4 [&_h2]:my-4 [&_h3]:my-4 [&_h1]:tracking-[-0.01em] [&_h2]:tracking-[-0.01em] [&_h3]:tracking-[-0.01em] [&_p]:mb-2.5 [&_ul]:mb-2.5 [&_ol]:mb-2.5 [&_pre]:mb-2.5 [&_ul]:ps-[22px] [&_ol]:ps-[22px] [&_code]:rounded-[5px] [&_code]:bg-deck [&_code]:px-[5px] [&_code]:py-px [&_code]:font-mono [&_code]:text-2xs [&_pre]:overflow-auto [&_pre]:rounded-r1 [&_pre]:bg-deck [&_pre]:px-3 [&_pre]:py-2.5 [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_a]:text-jade [&_table]:border-collapse [&_th]:border [&_td]:border [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-[9px] [&_td]:px-[9px] [&_th]:py-1.5 [&_td]:py-1.5" });
123
121
 
124
122
  function run() {
125
123
  preview.replaceChildren(...blocks(source.value));
@@ -132,8 +130,7 @@ export function mount(slot) {
132
130
  stack(panel("Markdown", source)),
133
131
  stack(panel("Preview", preview))
134
132
  ),
135
- foot: actions(copyButton(() => source.value, "Copy markdown", "fill")),
136
- });
133
+ foot: actions(copyButton(() => source.value, "Copy markdown", "fill"))});
137
134
  slot.append(card.node);
138
135
  run();
139
136
 
@@ -6,8 +6,8 @@ const cell = (text) => String(text ?? "").replace(/\|/g, "\\|").trim();
6
6
  export function mount(slot) {
7
7
  const columns = ["left", "left", "left"];
8
8
  const rows = [["Name", "Role", "City"], ["Ada", "Engineer", "London"]];
9
- const editor = el("div", { class: "mvb-scroll" });
10
- const alignment = el("div", { class: "mvb-stack" });
9
+ const editor = el("div", { class: "max-h-80 overflow-auto" });
10
+ const alignment = el("div", { class: "grid content-start gap-4" });
11
11
  const result = output({ rows: 8 });
12
12
 
13
13
  const toMarkdown = () => {
@@ -15,8 +15,7 @@ export function mount(slot) {
15
15
  const marks = columns.map((align) => (align === "center" ? ":---:" : align === "right" ? "---:" : ":---"));
16
16
  const lines = [
17
17
  `| ${columns.map((_, index) => cell(head[index])).join(" | ")} |`,
18
- `| ${marks.join(" | ")} |`,
19
- ];
18
+ `| ${marks.join(" | ")} |`];
20
19
  for (let r = 1; r < rows.length; r++) {
21
20
  lines.push(`| ${columns.map((_, index) => cell(rows[r][index])).join(" | ")} |`);
22
21
  }
@@ -26,7 +25,7 @@ export function mount(slot) {
26
25
  const update = () => { result.write(toMarkdown()); };
27
26
 
28
27
  function paintEditor() {
29
- const table = el("table", { class: "mvb-ttable" });
28
+ const table = el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" });
30
29
  const headRow = el("tr");
31
30
  for (let c = 0; c < columns.length; c++) {
32
31
  const input = textInput({ value: rows[0][c] ?? "", "aria-label": `Column ${c + 1} header` });
@@ -100,8 +99,7 @@ export function mount(slot) {
100
99
  stack(panel("Columns", row(addColumn, removeColumn)), panel("Rows", row(addRow, removeRow)), panel("Alignment", alignment)),
101
100
  stack(panel("Cells", editor), panel("Markdown", result))
102
101
  ),
103
- foot: actions(copyButton(() => result.textContent, "Copy table", "fill")),
104
- });
102
+ foot: actions(copyButton(() => result.textContent, "Copy table", "fill"))});
105
103
  slot.append(card.node);
106
104
  paint();
107
105
 
@@ -6,8 +6,8 @@ function fmt(n) {
6
6
  }
7
7
 
8
8
  function tile(label) {
9
- const value = el("span", { class: "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
  }
@@ -18,7 +18,7 @@ export function mount(slot) {
18
18
  const price = tile("Selling price");
19
19
  const profit = tile("Profit");
20
20
  const margin = tile("Margin");
21
- const stats = el("div", { class: "mvb-stats" }, price, profit, margin);
21
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, price, profit, margin);
22
22
  const error = notice("error");
23
23
  let text = "";
24
24
 
@@ -58,8 +58,7 @@ export function mount(slot) {
58
58
  stack(panel("Cost", cost), panel("Markup %", markup), error),
59
59
  stack(stats)
60
60
  ),
61
- foot: actions(copyButton(() => text, "Copy result", "fill")),
62
- });
61
+ foot: actions(copyButton(() => text, "Copy result", "fill"))});
63
62
  slot.append(card.node);
64
63
  run();
65
64
 
@@ -22,21 +22,21 @@ export function mount(slot) {
22
22
  const saved = readPin(PIN, { start: "09:00", items: [] });
23
23
  let list = Array.isArray(saved.items) ? saved.items : [];
24
24
 
25
- const startInput = el("input", { class: "mvb-in", type: "time", value: saved.start || "09:00", "aria-label": "Meeting start" });
25
+ const startInput = el("input", { class: "min-w-0 rounded-r1 border border-orbit bg-deck text-ink transition-[border-color,box-shadow] duration-150 placeholder:text-ink-3 hover:border-orbit-hard focus:border-jade focus:outline-none focus:ring-3 focus:ring-jade-soft w-full min-h-[var(--ctl)] px-3 py-2.5 text-base", type: "time", value: saved.start || "09:00", "aria-label": "Meeting start" });
26
26
  const topic = textInput({ placeholder: "Topic", "aria-label": "Topic" });
27
27
  const owner = textInput({ placeholder: "Owner", "aria-label": "Owner" });
28
28
  const minutes = numberInput({ min: 1, max: 600, value: 10, "aria-label": "Minutes" });
29
29
 
30
- const listWrap = el("div", { class: "mvb-scroll" });
31
- const stats = el("div", { class: "mvb-stats" });
30
+ const listWrap = el("div", { class: "max-h-80 overflow-auto" });
31
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
32
32
  const error = notice("error");
33
33
 
34
34
  const addButton = button("Add", { icon: "plus", kind: "fill" });
35
35
  const copyAgenda = copyButton(() => agendaText(), "Copy agenda");
36
36
 
37
- const tile = (key, value) => el("div", { class: "mvb-stat" },
38
- el("span", { class: "mvb-stat__v" }, String(value)),
39
- el("span", { class: "mvb-stat__k" }, key));
37
+ const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
38
+ el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
39
+ el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
40
40
 
41
41
  const base = () => {
42
42
  const [h, m] = String(startInput.value || "00:00").split(":").map(Number);
@@ -83,7 +83,7 @@ export function mount(slot) {
83
83
  el("td", {}, `${item.minutes} min`),
84
84
  el("td", {}, removeButton(item))
85
85
  ));
86
- listWrap.replaceChildren(el("table", { class: "mvb-ttable" },
86
+ listWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
87
87
  el("thead", {}, el("tr", {},
88
88
  el("th", {}, "Start"), el("th", {}, "Topic"), el("th", {}, "Owner"),
89
89
  el("th", {}, "Time"), el("th", {}, ""))),
@@ -126,8 +126,7 @@ export function mount(slot) {
126
126
  ),
127
127
  stack(stats, panel("Agenda", listWrap))
128
128
  ),
129
- foot: actions(copyAgenda, addButton),
130
- });
129
+ foot: actions(copyAgenda, addButton)});
131
130
  slot.append(card.node);
132
131
  render();
133
132
 
@@ -7,17 +7,17 @@ export function mount(slot) {
7
7
  const url = textInput({ value: "https://example.com", "aria-label": "URL" });
8
8
  const description = textArea({ rows: 3, value: "Small privacy first tools that run entirely in the browser.", "aria-label": "Description" });
9
9
 
10
- const urlLine = el("span", { class: "mvb-note" }, "");
11
- const titleLine = el("span", { class: "mvb-stat__v" }, "");
12
- const descLine = el("p", { class: "mvb-note" }, "");
13
- const sample = el("div", { class: "mvb-preview__sample" },
14
- el("div", { class: "mvb-stack" }, urlLine, titleLine, descLine));
15
-
16
- const titleCount = el("span", { class: "mvb-stat__v" }, "0");
17
- const descCount = el("span", { class: "mvb-stat__v" }, "0");
18
- const stats = el("div", { class: "mvb-stats" },
19
- el("div", { class: "mvb-stat" }, titleCount, el("span", { class: "mvb-stat__k" }, "Title chars")),
20
- el("div", { class: "mvb-stat" }, descCount, el("span", { class: "mvb-stat__k" }, "Description chars"))
10
+ const urlLine = el("span", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "");
11
+ const titleLine = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "");
12
+ const descLine = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "");
13
+ const sample = el("div", { class: "w-full rounded-lg border border-orbit bg-deck px-[18px] py-4" },
14
+ el("div", { class: "grid content-start gap-4" }, urlLine, titleLine, descLine));
15
+
16
+ const titleCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
17
+ const descCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
18
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" },
19
+ el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, titleCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Title chars")),
20
+ el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, descCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Description chars"))
21
21
  );
22
22
  const note = notice("note");
23
23
 
@@ -40,8 +40,7 @@ export function mount(slot) {
40
40
  foot: actions(
41
41
  copyButton(() => title.value.trim(), "Copy title"),
42
42
  copyButton(() => description.value.trim(), "Copy description", "fill")
43
- ),
44
- });
43
+ )});
45
44
  slot.append(card.node);
46
45
  run();
47
46
 
@@ -16,11 +16,11 @@ export function mount(slot) {
16
16
  );
17
17
  const result = output({ rows: 8 });
18
18
 
19
- const titleCount = el("span", { class: "mvb-stat__v" }, "0");
20
- const descCount = el("span", { class: "mvb-stat__v" }, "0");
21
- const stats = el("div", { class: "mvb-stats" },
22
- el("div", { class: "mvb-stat" }, titleCount, el("span", { class: "mvb-stat__k" }, "Title chars")),
23
- el("div", { class: "mvb-stat" }, descCount, el("span", { class: "mvb-stat__k" }, "Description chars"))
19
+ const titleCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
20
+ const descCount = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
21
+ const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" },
22
+ el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, titleCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Title chars")),
23
+ el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, descCount, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Description chars"))
24
24
  );
25
25
 
26
26
  function build() {
@@ -51,8 +51,7 @@ export function mount(slot) {
51
51
  ),
52
52
  stack(stats, panel("Meta tags", result))
53
53
  ),
54
- foot: actions(copyButton(() => result.textContent, "Copy tags", "fill")),
55
- });
54
+ foot: actions(copyButton(() => result.textContent, "Copy tags", "fill"))});
56
55
  slot.append(card.node);
57
56
  build();
58
57
 
@@ -9,24 +9,23 @@ const TYPES = [
9
9
  ["mp3", "audio/mpeg", "Audio"], ["wav", "audio/wav", "Audio"], ["ogg", "audio/ogg", "Audio"], ["oga", "audio/ogg", "Audio"], ["m4a", "audio/mp4", "Audio"], ["flac", "audio/flac", "Audio"], ["aac", "audio/aac", "Audio"],
10
10
  ["mp4", "video/mp4", "Video"], ["webm", "video/webm", "Video"], ["ogv", "video/ogg", "Video"], ["mov", "video/quicktime", "Video"], ["avi", "video/x-msvideo", "Video"], ["mkv", "video/x-matroska", "Video"],
11
11
  ["woff", "font/woff", "Font"], ["woff2", "font/woff2", "Font"], ["ttf", "font/ttf", "Font"], ["otf", "font/otf", "Font"], ["eot", "application/vnd.ms-fontobject", "Font"],
12
- ["wasm", "application/wasm", "Binary"], ["bin", "application/octet-stream", "Binary"],
13
- ];
12
+ ["wasm", "application/wasm", "Binary"], ["bin", "application/octet-stream", "Binary"]];
14
13
 
15
14
  export function mount(slot) {
16
15
  const filter = textInput({ type: "search", placeholder: "Extension or type", "aria-label": "Filter" });
17
- const scroll = el("div", { class: "mvb-scroll" });
16
+ const scroll = el("div", { class: "max-h-80 overflow-auto" });
18
17
  let visible = TYPES;
19
18
 
20
19
  function run() {
21
20
  const query = filter.value.trim().toLowerCase();
22
21
  visible = TYPES.filter(([ext, mime]) => !query || ext.includes(query) || mime.toLowerCase().includes(query));
23
22
  if (!visible.length) {
24
- scroll.replaceChildren(el("p", { class: "mvb-note" }, "No type matches."));
23
+ scroll.replaceChildren(el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" }, "No type matches."));
25
24
  return;
26
25
  }
27
26
  scroll.replaceChildren(el(
28
27
  "table",
29
- { class: "mvb-ttable" },
28
+ { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
30
29
  el("thead", {}, el("tr", {}, el("th", {}, "Extension"), el("th", {}, "MIME type"), el("th", {}, "Kind"))),
31
30
  el("tbody", {}, ...visible.map(([ext, mime, kind]) => el("tr", {},
32
31
  el("td", {}, `.${ext}`),
@@ -43,8 +42,7 @@ export function mount(slot) {
43
42
  stack(panel("Filter", filter)),
44
43
  stack(panel("MIME types", scroll))
45
44
  ),
46
- foot: actions(copyButton(() => visible.map(([ext, mime]) => `.${ext} ${mime}`).join("\n"), "Copy list", "fill")),
47
- });
45
+ foot: actions(copyButton(() => visible.map(([ext, mime]) => `.${ext} ${mime}`).join("\n"), "Copy list", "fill"))});
48
46
  slot.append(card.node);
49
47
  run();
50
48
 
@@ -8,7 +8,7 @@ export function mount(slot) {
8
8
  const listField = textArea({ value: saved.entries ?? "", rows: 8, "aria-label": "Entries, one per line", placeholder: "One entry per line" });
9
9
  const countField = numberInput({ value: saved.winners ?? "1", min: "1", max: "500", "aria-label": "How many to draw" });
10
10
  const uniqueBox = checkbox("No repeats", { checked: saved.unique !== false });
11
- const notify = el("p", { class: "mvb-note" });
11
+ const notify = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
12
12
 
13
13
  const drawButton = button("Draw", { icon: "play", kind: "fill" });
14
14
  const copyButton = button("Copy", { icon: "copy" });
@@ -58,8 +58,7 @@ export function mount(slot) {
58
58
  stack(panel("Entries", listField), panel("Winners", countField), panel(null, uniqueBox)),
59
59
  stack(panel("Drawn", result), notify)
60
60
  ),
61
- foot: actions(copyButton, drawButton),
62
- });
61
+ foot: actions(copyButton, drawButton)});
63
62
  slot.append(card.node);
64
63
  draw();
65
64