mevebu 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,34 @@
1
1
  # mevebu
2
2
 
3
- Small tools that run in your browser. No accounts, no uploads, no tracking.
3
+ **186 small tools, each on its own page, all running in your browser.**
4
4
 
5
- mevebu is a set of tiny, privacy-first utilities served from your own machine. Every tool runs
6
- entirely in the tab, so nothing you paste ever leaves your computer.
5
+ A box of browser tools for the jobs that do not deserve an app: convert a value,
6
+ hash a text, sign a token, format JSON, pick a color, run a timer, count words,
7
+ build a cron line, check a JWT, read a QR. Every tool opens on its own page and
8
+ most of them update as you type, so there is no submit button and no waiting.
7
9
 
8
- ## Install
10
+ Nothing leaves your tab. There is no account to make, nothing to upload, and
11
+ nothing saved to a server.
9
12
 
10
- ```sh
11
- npm install -g mevebu
13
+ ```
14
+ tools 186
15
+ categories 20
16
+ followers 1 (one tab, no account)
17
+ uploads 0
12
18
  ```
13
19
 
14
- ## Use
20
+ ## Install and run
15
21
 
16
22
  ```sh
23
+ npm install -g mevebu
17
24
  mevebu
18
25
  ```
19
26
 
20
- The tools open in your default browser. Press `Ctrl+C` in the terminal to stop.
27
+ `mevebu` starts a local server (it tries port 5555, then 5678, then 5432, then
28
+ any free port), opens your browser to the home page, and serves the static
29
+ folder bundled with the package. Press `Ctrl+C` in the terminal to stop it.
21
30
 
22
- ### Options
31
+ ### CLI
23
32
 
24
33
  ```
25
34
  -p, --port <number> Try this port first (5555, 5678, 5432 otherwise).
@@ -29,15 +38,16 @@ The tools open in your default browser. Press `Ctrl+C` in the terminal to stop.
29
38
  -h, --help Print this help and exit.
30
39
  ```
31
40
 
32
- The port is stable across restarts: mevebu tries **5555** first, then **5678**, then **5432**,
33
- and falls back to any free port only when those are taken.
41
+ The first three ports are stable across restarts, so the bookmark to your tools
42
+ page keeps working.
34
43
 
35
- ## Tools
44
+ ## What's inside
36
45
 
37
- 186 tools, each with its own page. Most update as you type, with no submit button.
46
+ A tool is a small page that does one job. The registry lists them all; here is
47
+ the shape of it.
38
48
 
39
- | Category | Tools |
40
- | :--- | :--- |
49
+ | Category | Count |
50
+ | :--- | ---: |
41
51
  | Random | 3 |
42
52
  | Passwords and keys | 10 |
43
53
  | Developer | 21 |
@@ -58,16 +68,108 @@ and falls back to any free port only when those are taken.
58
68
  | GitHub | 8 |
59
69
  | Units | 8 |
60
70
  | QR codes | 2 |
61
-
62
- Press `Ctrl+K` in the app to search the full list.
71
+ | **Total** | **186** |
72
+
73
+ The counts are kept honest by the registry, so the page and the README never
74
+ drift apart. Press `Ctrl+K` in the app to search the full list by name, by
75
+ keyword, or by what the tool does.
76
+
77
+ A short tour of the most reached-for tools, one from each side of the catalog:
78
+
79
+ - **Password generator**: make a password, copy it, and the page never talks
80
+ to anything but your clipboard.
81
+ - **JSON formatter**: paste, and the formatted version fills in as you type.
82
+ - **JWT generator and decoder**: sign a token with HS256, RS256 or ES256, or
83
+ paste a token and read its header and payload.
84
+ - **Color palette**: pick one color, get a matching set in analogous,
85
+ complementary or monochrome schemes.
86
+ - **Countdown timer**: a quiet, fullscreen timer that keeps running when the
87
+ tab is hidden.
88
+ - **QR code generator**: turn a URL, a Wi-Fi config or any text into a scannable
89
+ image right in the tab.
90
+ - **Slug converter**: keep a sentence readable while you turn it into a URL.
91
+
92
+ The full list is at the home page and under each category.
93
+
94
+ ## How it stays private
95
+
96
+ Every tool is a static HTML file, a tiny JavaScript module, and a Lucide icon.
97
+ The HTML and the JavaScript are served from your machine when you run `mevebu`,
98
+ or from the file you host when you copy the `mevebu/` folder somewhere. There is
99
+ no server-side code, no analytics, and no API key.
100
+
101
+ When you paste a password, a token, or a CSV into a tool:
102
+
103
+ - the text is processed in the browser tab, by the file that the page loaded
104
+ from, using the standard Web Crypto API or the standard browser APIs the
105
+ tool needs
106
+ - nothing is sent to a remote server, because there is no remote server in
107
+ the loop
108
+ - nothing is saved to disk by the tool, because the tool does not have a way
109
+ to write to your file system
110
+ - the only network request the page makes is to load itself, and to read the
111
+ GitHub star count for the footer chip (and that request fails silently when
112
+ you are offline)
113
+
114
+ You can verify this by opening the network tab in your browser's dev tools and
115
+ watching a tool do its work. There is no upload. The same tool, loaded the same
116
+ way, behaves the same in a hotel Wi-Fi as it does on a server room.
117
+
118
+ ## Hosting the static folder
119
+
120
+ If you would rather not run the CLI, the whole app is a plain static site. Copy
121
+ the `mevebu/` folder from this repo, or from the npm package, to any host that
122
+ serves files: a static site host, a CDN, an object store, a USB drive. Routes
123
+ are hash routes (`#/security/password-generator`), so no server rewrites are
124
+ needed.
125
+
126
+ The shipped folder is plain files, the CSS is one committed stylesheet that
127
+ Tailwind built from one source file, and there is no separate build step. The
128
+ hosting layer never runs code on your behalf.
63
129
 
64
130
  ## Development
65
131
 
66
132
  ```sh
67
- npm run dev # serve on http://localhost:5555
68
- npm run icons # regenerate vendored lucide icons
133
+ git clone https://github.com/mevebu/mevebu
134
+ cd mevebu
135
+ npm install
136
+ npm run css # build the stylesheet from tailwind.src.css
137
+ npm run icons # regenerate the vendored Lucide icon set
138
+ npm start # serve the static folder on http://localhost:5555
69
139
  ```
70
140
 
141
+ The app is a few hundred lines of vanilla JavaScript (ESM) in
142
+ `mevebu/app/mevebu.js` and a small shared vocabulary in `mevebu/libs/js/core.js`.
143
+ Every tool is a single file in `mevebu/libs/js/tools/`, exports a `mount(slot)`
144
+ function and (optionally) an `unmount()` to stop timers, cameras and observers.
145
+ The registry in `mevebu/libs/js/registry.js` is the single source of truth: add
146
+ an entry to `TOOLS` and a file, and the catalog, search, breadcrumb, and
147
+ category page pick it up.
148
+
149
+ The CSS is Tailwind v4, built from one source file, with the stylesheet output
150
+ committed so the shipped folder is still plain static files. There is no
151
+ component layer and no `mvb-` class: every utility is written at the call
152
+ site, and the design tokens live in `mevebu/app/tailwind.src.css`.
153
+
154
+ Layout and color decisions, the type scale, the orbit lines, the "energy 1,
155
+ rhythm 2, motion 1" dials, and the rest of the visual system, are recorded in
156
+ `AGENTS.md` and `DESIGN.md`, the two docs an agent reads first.
157
+
158
+ ## Report a problem
159
+
160
+ Every tool page has a small "Report a problem" link at the bottom, alongside
161
+ the tool's actions. It opens a GitHub issue with the tool name, the page URL
162
+ and the mevebu version already filled in, so all you have to do is describe
163
+ what happened and what you expected.
164
+
71
165
  ## License
72
166
 
73
- MIT
167
+ MIT. The whole list, the whole source, the whole thing is in the repository.
168
+ If a tool here saved you time, a coffee keeps the next one coming:
169
+ <https://buymeacoffee.com/fastro>.
170
+
171
+ ## Links
172
+
173
+ - Repository: <https://github.com/mevebu/mevebu>
174
+ - npm: <https://www.npmjs.com/package/mevebu>
175
+ - Donate: <https://buymeacoffee.com/fastro>
@@ -7,6 +7,7 @@ import {
7
7
  $, $$, el, icon, getFavorites, isFavorite, toggleFavorite, setTheme,
8
8
  modal, modalHead, button, copyButton, openPalette, shuffle, toolcard, blank,
9
9
  VERSION, PACKAGE_NAME, checkForUpdate, updateCommand, compareVersions, releaseUrl,
10
+ textInput, textArea, panel,
10
11
  } from "../libs/js/core.js";
11
12
 
12
13
  const DONATE_URL = "https://buymeacoffee.com/fastro";
@@ -128,7 +129,7 @@ function masthead() {
128
129
 
129
130
  return el(
130
131
  "header",
131
- { class: "sticky top-0 z-30 border-b border-orbit bg-deck" },
132
+ { id: "masthead", class: "z-30 border-b border-orbit bg-deck data-[pin]:sticky data-[pin]:top-0 data-[pin]:transition-shadow" },
132
133
  el(
133
134
  "div",
134
135
  { class: "mx-[var(--margin)] flex h-[var(--bar)] items-center gap-[clamp(14px,3vw,32px)]" },
@@ -754,6 +755,117 @@ function moreFromMev(tool) {
754
755
  );
755
756
  }
756
757
 
758
+ // What the browser tells us about itself, without asking the user to retype it.
759
+ // Every field can come back empty (a hardened browser, a private mode), so the
760
+ // form still works when it does.
761
+ function browserFacts() {
762
+ const ua = navigator.userAgent;
763
+ const browser = /Edg\//.test(ua) ? "Edge"
764
+ : /OPR\//.test(ua) ? "Opera"
765
+ : /Firefox\//.test(ua) ? "Firefox"
766
+ : /Chrome\//.test(ua) ? "Chrome"
767
+ : /Safari\//.test(ua) ? "Safari"
768
+ : "Unknown";
769
+ const version = (ua.match(new RegExp(browser + "\\/([\\d.]+)")) ?? [])[1] ?? "";
770
+ const platform = navigator.userAgentData?.platform
771
+ ?? (/Windows/.test(ua) ? "Windows" : /Mac OS X/.test(ua) ? "macOS"
772
+ : /Android/.test(ua) ? "Android" : /iPhone|iPad/.test(ua) ? "iOS"
773
+ : /Linux/.test(ua) ? "Linux" : "");
774
+ const screen = `${window.screen.width}x${window.screen.height} @${window.devicePixelRatio}x`;
775
+ return { browser: `${browser} ${version}`.trim(), platform, screen };
776
+ }
777
+
778
+ function openReport(tool) {
779
+ const facts = browserFacts();
780
+ const doing = textArea({ rows: 3, placeholder: "I was building a daily expression for a backup job." });
781
+ const happened = textArea({ rows: 3, placeholder: "The preview stayed empty when I typed 0 3." });
782
+ const expected = textArea({ rows: 3, placeholder: "I expected it to read \"03:00 every day\"." });
783
+ const browserField = textInput({ value: facts.browser });
784
+ const platformField = textInput({ value: [facts.platform, facts.screen].filter(Boolean).join(" · ") });
785
+ let opened = false;
786
+
787
+ const submit = button("Open GitHub issue", { icon: "external-link", kind: "fill" });
788
+ // The actions row renders outside the form, so the button names it explicitly.
789
+ submit.type = "submit";
790
+ submit.setAttribute("form", "report-form");
791
+ submit.disabled = true;
792
+ submit.setAttribute("aria-disabled", "true");
793
+ const validate = () => {
794
+ const ready = happened.value.trim().length > 0 && doing.value.trim().length > 0;
795
+ submit.disabled = !ready;
796
+ if (ready) submit.removeAttribute("aria-disabled");
797
+ else submit.setAttribute("aria-disabled", "true");
798
+ };
799
+ for (const area of [doing, happened]) area.addEventListener("input", validate);
800
+
801
+ const form = el(
802
+ "form",
803
+ {
804
+ id: "report-form",
805
+ class: "grid gap-3.5",
806
+ onsubmit: (event) => {
807
+ event.preventDefault();
808
+ if (opened || submit.disabled) return;
809
+ opened = true;
810
+ const body = [
811
+ "### What I was doing",
812
+ doing.value.trim() || "_not given_",
813
+ "",
814
+ "### What happened",
815
+ happened.value.trim() || "_not given_",
816
+ "",
817
+ "### What I expected",
818
+ expected.value.trim() || "_not given_",
819
+ "",
820
+ "### Where",
821
+ `- Tool: ${tool.name} (\`${tool.id}\`)`,
822
+ `- Page: #/${tool.category}/${tool.id}`,
823
+ `- mevebu: ${VERSION}`,
824
+ "",
825
+ "### Browser",
826
+ `- ${browserField.value || "unknown"}`,
827
+ `- ${platformField.value || "unknown"}`,
828
+ ].join("\n");
829
+ const title = `Bug in ${tool.name}`;
830
+ const href = `https://github.com/mevebu/mevebu/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
831
+ window.open(href, "_blank", "noopener");
832
+ },
833
+ },
834
+ panel("What were you doing?", doing),
835
+ panel("What happened?", happened),
836
+ panel("What did you expect?", expected, "Optional"),
837
+ el(
838
+ "div",
839
+ { class: "grid gap-3 rounded-r2 border border-orbit bg-deck-2 p-3.5" },
840
+ el("p", { class: "m-0 text-2xs text-ink-3" },
841
+ "Already filled in from this tab. Edit it if something is off."),
842
+ panel("Browser", browserField),
843
+ panel("Screen", platformField),
844
+ ),
845
+ submit
846
+ );
847
+
848
+ modal({
849
+ label: `Report a problem with ${tool.name}`,
850
+ panelClass: "m-auto w-[min(560px,100%)] p-[clamp(22px,3vw,28px)] max-h-[calc(100dvh-40px)] overflow-y-auto",
851
+ body: [
852
+ modalHead("flag", `Report a problem with ${tool.name}`),
853
+ el("p", { class: "m-0 text-sm text-ink-2" },
854
+ "Tell us what went wrong and the issue opens on GitHub with this page and browser already filled in."),
855
+ form,
856
+ ],
857
+ actions: [submit],
858
+ });
859
+ }
860
+
861
+ function reportButton(tool) {
862
+ return button("Report a problem", {
863
+ icon: "flag",
864
+ class: "mt-4",
865
+ onclick: () => openReport(tool),
866
+ });
867
+ }
868
+
757
869
  async function toolPage(tool, category) {
758
870
  const token = ++navToken;
759
871
  const slot = el("div", {});
@@ -764,6 +876,7 @@ async function toolPage(tool, category) {
764
876
  { class: PAGE_NARROW },
765
877
  benchHead(tool, category),
766
878
  slot,
879
+ el("div", { class: "flex flex-col items-start" }, reportButton(tool)),
767
880
  more
768
881
  );
769
882
 
@@ -900,6 +1013,20 @@ const scrollMemory = new Map();
900
1013
  let activePath = null;
901
1014
  let historyMove = false;
902
1015
 
1016
+
1017
+ // Sticky everywhere except the tools index and a single tool page, where
1018
+ // the page owns the whole frame and the navbar would steal vertical space.
1019
+ function pinMasthead(path) {
1020
+ const head = document.getElementById("masthead");
1021
+ if (!head) return;
1022
+ const pinned = !(
1023
+ path === "/tools" ||
1024
+ /^\/[^/]+\/[^/]+$/.test(path)
1025
+ );
1026
+ if (pinned) head.dataset.pin = "";
1027
+ else delete head.dataset.pin;
1028
+ }
1029
+
903
1030
  async function render(path) {
904
1031
  const restoring = historyMove;
905
1032
  historyMove = false;
@@ -909,6 +1036,7 @@ async function render(path) {
909
1036
  if (!node) return;
910
1037
  $("#view").replaceChildren(node);
911
1038
  markActiveNav(["/", "/tools", "/categories", "/about"].includes(path) ? path : "");
1039
+ pinMasthead(path);
912
1040
  activePath = path;
913
1041
  window.scrollTo({ top: restoring ? scrollMemory.get(path) ?? 0 : 0 });
914
1042
  if (!history.state?.mevebu) history.replaceState({ mevebu: true }, "", location.href);
@@ -208,18 +208,12 @@
208
208
  .static {
209
209
  position: static;
210
210
  }
211
- .sticky {
212
- position: sticky;
213
- }
214
211
  .inset-0 {
215
212
  inset: 0px;
216
213
  }
217
214
  .end-3 {
218
215
  inset-inline-end: calc(var(--spacing) * 3);
219
216
  }
220
- .top-0 {
221
- top: 0px;
222
- }
223
217
  .top-0\.5 {
224
218
  top: calc(var(--spacing) * 0.5);
225
219
  }
@@ -265,6 +259,9 @@
265
259
  .m-0 {
266
260
  margin: 0px;
267
261
  }
262
+ .m-auto {
263
+ margin: auto;
264
+ }
268
265
  .mx-\[var\(--margin\)\] {
269
266
  margin-inline: var(--margin);
270
267
  }
@@ -304,6 +301,9 @@
304
301
  .mt-3\.5 {
305
302
  margin-top: calc(var(--spacing) * 3.5);
306
303
  }
304
+ .mt-4 {
305
+ margin-top: calc(var(--spacing) * 4);
306
+ }
307
307
  .mt-5 {
308
308
  margin-top: calc(var(--spacing) * 5);
309
309
  }
@@ -469,6 +469,9 @@
469
469
  .max-h-\[380px\] {
470
470
  max-height: 380px;
471
471
  }
472
+ .max-h-\[calc\(100dvh-40px\)\] {
473
+ max-height: calc(100dvh - 40px);
474
+ }
472
475
  .max-h-\[min\(60vh\,360px\)\] {
473
476
  max-height: min(60vh, 360px);
474
477
  }
@@ -523,6 +526,9 @@
523
526
  .w-\[min\(480px\,100\%\)\] {
524
527
  width: min(480px, 100%);
525
528
  }
529
+ .w-\[min\(560px\,100\%\)\] {
530
+ width: min(560px, 100%);
531
+ }
526
532
  .w-\[min\(640px\,100\%\)\] {
527
533
  width: min(640px, 100%);
528
534
  }
@@ -923,6 +929,9 @@
923
929
  .p-2 {
924
930
  padding: calc(var(--spacing) * 2);
925
931
  }
932
+ .p-3\.5 {
933
+ padding: calc(var(--spacing) * 3.5);
934
+ }
926
935
  .p-5 {
927
936
  padding: calc(var(--spacing) * 5);
928
937
  }
@@ -1016,6 +1025,9 @@
1016
1025
  .py-\[13px\] {
1017
1026
  padding-block: 13px;
1018
1027
  }
1028
+ .py-\[clamp\(20px\,5vh\,40px\)\] {
1029
+ padding-block: clamp(20px, 5vh, 40px);
1030
+ }
1019
1031
  .py-\[clamp\(28px\,6vw\,56px\)\] {
1020
1032
  padding-block: clamp(28px, 6vw, 56px);
1021
1033
  }
@@ -1082,9 +1094,6 @@
1082
1094
  .pt-\[clamp\(32px\,5vw\,60px\)\] {
1083
1095
  padding-top: clamp(32px, 5vw, 60px);
1084
1096
  }
1085
- .pt-\[clamp\(60px\,9vh\,110px\)\] {
1086
- padding-top: clamp(60px, 9vh, 110px);
1087
- }
1088
1097
  .pb-2 {
1089
1098
  padding-bottom: calc(var(--spacing) * 2);
1090
1099
  }
@@ -1094,9 +1103,6 @@
1094
1103
  .pb-7 {
1095
1104
  padding-bottom: calc(var(--spacing) * 7);
1096
1105
  }
1097
- .pb-10 {
1098
- padding-bottom: calc(var(--spacing) * 10);
1099
- }
1100
1106
  .pb-\[var\(--margin\)\] {
1101
1107
  padding-bottom: var(--margin);
1102
1108
  }
@@ -1582,6 +1588,17 @@
1582
1588
  --tw-shadow: 0 0 0 1px var(--tw-shadow-color, var(--orbit));
1583
1589
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1584
1590
  }
1591
+ .data-\[pin\]\:sticky[data-pin] {
1592
+ position: sticky;
1593
+ }
1594
+ .data-\[pin\]\:top-0[data-pin] {
1595
+ top: 0px;
1596
+ }
1597
+ .data-\[pin\]\:transition-shadow[data-pin] {
1598
+ transition-property: box-shadow;
1599
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
1600
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
1601
+ }
1585
1602
  .data-\[state\=open\]\:opacity-100[data-state="open"] {
1586
1603
  opacity: 100%;
1587
1604
  }
@@ -197,14 +197,14 @@ export function modalHead(iconName, title) {
197
197
  export function modal({ label, body, actions = [], onOpen, onClose, panelClass } = {}) {
198
198
  lastFocus = document.activeElement;
199
199
  const veil = el("div", {
200
- class: "group fixed inset-0 z-80 grid items-start justify-items-center overflow-y-auto bg-[var(--scrim)] px-5 pt-[clamp(60px,9vh,110px)] pb-10 opacity-0 transition-opacity duration-200 data-[state=open]:opacity-100",
200
+ class: "group fixed inset-0 z-80 grid place-items-center overflow-y-auto bg-[var(--scrim)] px-5 py-[clamp(20px,5vh,40px)] opacity-0 transition-opacity duration-200 data-[state=open]:opacity-100",
201
201
  });
202
202
  const panel = el(
203
203
  "div",
204
204
  {
205
205
  class:
206
206
  "relative grid gap-4 rounded-r3 border border-orbit bg-deck shadow-[var(--shadow)] transition-transform duration-200 ease-[var(--ease)] translate-y-3.5 scale-[0.97] group-data-[state=open]:translate-y-0 group-data-[state=open]:scale-100 " +
207
- (panelClass ?? "w-[min(480px,100%)] p-[clamp(22px,3vw,28px)]"),
207
+ (panelClass ?? "m-auto w-[min(480px,100%)] p-[clamp(22px,3vw,28px)]"),
208
208
  role: "dialog",
209
209
  "aria-modal": "true",
210
210
  "aria-label": label,
@@ -750,7 +750,7 @@ export function blank(iconName, text) {
750
750
  // ---------- version ----------
751
751
 
752
752
  // Kept in one place. The CLI reads the same value from package.json.
753
- export const VERSION = "1.7.1";
753
+ export const VERSION = "1.7.3";
754
754
  export const PACKAGE_NAME = "mevebu";
755
755
  export const REPO_SLUG = "mevebu/mevebu";
756
756
 
@@ -1,5 +1,5 @@
1
1
  // GENERATED FILE, DO NOT EDIT.
2
- // Source: lucide v1.57.0 (ISC), icons/168 names from scripts/vendor-icons.mjs
2
+ // Source: lucide v1.59.0 (ISC), icons/173 names from scripts/vendor-icons.mjs
3
3
  // Regenerate with: npm run icons
4
4
 
5
5
  const D = {
@@ -13,9 +13,11 @@ const D = {
13
13
  "coffee": [ ["path", { d: "M10 2v2" }], ["path", { d: "M14 2v2" }], [ "path", { d: "M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1" } ], ["path", { d: "M6 2v2" }] ],
14
14
  "external-link": [ ["path", { d: "M15 3h6v6" }], ["path", { d: "M10 14 21 3" }], ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }] ],
15
15
  "corner-down-left": [ ["path", { d: "M20 4v7a4 4 0 0 1-4 4H4" }], ["path", { d: "m9 10-5 5 5 5" }] ],
16
+ "flag": [ [ "path", { d: "M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528" } ] ],
16
17
  "info": [ ["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "M12 16v-4" }], ["path", { d: "M12 8h.01" }] ],
17
18
  "circle-question-mark": [ ["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }], ["path", { d: "M12 17h.01" }] ],
18
19
  "circle-arrow-up": [ ["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "m16 12-4-4-4 4" }], ["path", { d: "M12 16V8" }] ],
20
+ "bug": [ ["path", { d: "M12 20v-9" }], ["path", { d: "M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z" }], ["path", { d: "M14.12 3.88 16 2" }], ["path", { d: "M21 21a4 4 0 0 0-3.81-4" }], ["path", { d: "M21 5a4 4 0 0 1-3.55 3.97" }], ["path", { d: "M22 13h-4" }], ["path", { d: "M3 21a4 4 0 0 1 3.81-4" }], ["path", { d: "M3 5a4 4 0 0 0 3.55 3.97" }], ["path", { d: "M6 13H2" }], ["path", { d: "m8 2 1.88 1.88" }], ["path", { d: "M9 7.13V6a3 3 0 1 1 6 0v1.13" }] ],
19
21
  "shuffle": [ ["path", { d: "m18 14 4 4-4 4" }], ["path", { d: "m18 2 4 4-4 4" }], ["path", { d: "M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22" }], ["path", { d: "M2 6h1.972a4 4 0 0 1 3.6 2.2" }], ["path", { d: "M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45" }] ],
20
22
  "shield-check": [ [ "path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" } ], ["path", { d: "m9 12 2 2 4-4" }] ],
21
23
  "terminal": [ ["path", { d: "M12 19h8" }], ["path", { d: "m4 17 6-6-6-6" }] ],
@@ -57,6 +59,9 @@ const D = {
57
59
  "loader": [ ["path", { d: "M12 2v4" }], ["path", { d: "m16.2 7.8 2.9-2.9" }], ["path", { d: "M18 12h4" }], ["path", { d: "m16.2 16.2 2.9 2.9" }], ["path", { d: "M12 18v4" }], ["path", { d: "m4.9 19.1 2.9-2.9" }], ["path", { d: "M2 12h4" }], ["path", { d: "m4.9 4.9 2.9 2.9" }] ],
58
60
  "play": [ [ "path", { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" } ] ],
59
61
  "lock": [ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }], ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }] ],
62
+ "wrench": [ [ "path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" } ] ],
63
+ "gauge": [ ["path", { d: "m12 14 4-4" }], ["path", { d: "M3.34 19a10 10 0 1 1 17.32 0" }] ],
64
+ "monitor": [ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }], ["line", { x1: "8", x2: "16", y1: "21", y2: "21" }], ["line", { x1: "12", x2: "12", y1: "17", y2: "21" }] ],
60
65
  "calendar-days": [ ["path", { d: "M8 2v3" }], ["path", { d: "M16 2v3" }], ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }], ["path", { d: "M3 9h18" }], ["path", { d: "M8 13h.01" }], ["path", { d: "M12 13h.01" }], ["path", { d: "M16 13h.01" }], ["path", { d: "M8 17h.01" }], ["path", { d: "M12 17h.01" }], ["path", { d: "M16 17h.01" }] ],
61
66
  "graduation-cap": [ [ "path", { d: "M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z" } ], ["path", { d: "M22 10v6" }], ["path", { d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5" }] ],
62
67
  "calculator": [ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }], ["line", { x1: "8", x2: "16", y1: "6", y2: "6" }], ["line", { x1: "16", x2: "16", y1: "14", y2: "18" }], ["path", { d: "M16 10h.01" }], ["path", { d: "M12 10h.01" }], ["path", { d: "M8 10h.01" }], ["path", { d: "M12 14h.01" }], ["path", { d: "M8 14h.01" }], ["path", { d: "M12 18h.01" }], ["path", { d: "M8 18h.01" }] ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mevebu",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Small tools that run in your browser. No accounts, no uploads, no tracking.",
5
5
  "license": "MIT",
6
6
  "type": "module",