mevebu 1.7.1 → 1.7.2

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>
@@ -128,7 +128,7 @@ function masthead() {
128
128
 
129
129
  return el(
130
130
  "header",
131
- { class: "sticky top-0 z-30 border-b border-orbit bg-deck" },
131
+ { id: "masthead", class: "z-30 border-b border-orbit bg-deck data-[pin]:sticky data-[pin]:top-0 data-[pin]:transition-shadow" },
132
132
  el(
133
133
  "div",
134
134
  { class: "mx-[var(--margin)] flex h-[var(--bar)] items-center gap-[clamp(14px,3vw,32px)]" },
@@ -754,6 +754,30 @@ function moreFromMev(tool) {
754
754
  );
755
755
  }
756
756
 
757
+ function reportLink(tool) {
758
+ const title = `Bug in ${tool.name}`;
759
+ const body = [
760
+ "### What I was doing",
761
+ "<!-- one line is enough -->",
762
+ "",
763
+ "### What happened",
764
+ "",
765
+ "### What I expected",
766
+ "",
767
+ `### Tool`,
768
+ `mevebu ${VERSION}`,
769
+ `Page: #/${tool.category}/${tool.id}`,
770
+ "",
771
+ "### Browser",
772
+ ].join("\n");
773
+ return el("a", {
774
+ class: "mt-3 flex items-center gap-1.5 self-start text-2xs font-semibold text-ink-3 no-underline hover:text-ink-2",
775
+ href: `https://github.com/mevebu/mevebu/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`,
776
+ target: "_blank",
777
+ rel: "noopener",
778
+ }, icon("bug"), el("span", {}, "Report a problem"));
779
+ }
780
+
757
781
  async function toolPage(tool, category) {
758
782
  const token = ++navToken;
759
783
  const slot = el("div", {});
@@ -764,6 +788,7 @@ async function toolPage(tool, category) {
764
788
  { class: PAGE_NARROW },
765
789
  benchHead(tool, category),
766
790
  slot,
791
+ el("div", { class: "flex flex-col items-stretch" }, reportLink(tool)),
767
792
  more
768
793
  );
769
794
 
@@ -900,6 +925,20 @@ const scrollMemory = new Map();
900
925
  let activePath = null;
901
926
  let historyMove = false;
902
927
 
928
+
929
+ // Sticky everywhere except the tools index and a single tool page, where
930
+ // the page owns the whole frame and the navbar would steal vertical space.
931
+ function pinMasthead(path) {
932
+ const head = document.getElementById("masthead");
933
+ if (!head) return;
934
+ const pinned = !(
935
+ path === "/tools" ||
936
+ /^\/[^/]+\/[^/]+$/.test(path)
937
+ );
938
+ if (pinned) head.dataset.pin = "";
939
+ else delete head.dataset.pin;
940
+ }
941
+
903
942
  async function render(path) {
904
943
  const restoring = historyMove;
905
944
  historyMove = false;
@@ -909,6 +948,7 @@ async function render(path) {
909
948
  if (!node) return;
910
949
  $("#view").replaceChildren(node);
911
950
  markActiveNav(["/", "/tools", "/categories", "/about"].includes(path) ? path : "");
951
+ pinMasthead(path);
912
952
  activePath = path;
913
953
  window.scrollTo({ top: restoring ? scrollMemory.get(path) ?? 0 : 0 });
914
954
  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
  }
@@ -661,6 +658,9 @@
661
658
  .items-start {
662
659
  align-items: flex-start;
663
660
  }
661
+ .items-stretch {
662
+ align-items: stretch;
663
+ }
664
664
  .justify-between {
665
665
  justify-content: space-between;
666
666
  }
@@ -736,6 +736,9 @@
736
736
  .gap-y-3 {
737
737
  row-gap: calc(var(--spacing) * 3);
738
738
  }
739
+ .self-start {
740
+ align-self: flex-start;
741
+ }
739
742
  .truncate {
740
743
  overflow: hidden;
741
744
  text-overflow: ellipsis;
@@ -1016,6 +1019,9 @@
1016
1019
  .py-\[13px\] {
1017
1020
  padding-block: 13px;
1018
1021
  }
1022
+ .py-\[clamp\(20px\,5vh\,40px\)\] {
1023
+ padding-block: clamp(20px, 5vh, 40px);
1024
+ }
1019
1025
  .py-\[clamp\(28px\,6vw\,56px\)\] {
1020
1026
  padding-block: clamp(28px, 6vw, 56px);
1021
1027
  }
@@ -1082,9 +1088,6 @@
1082
1088
  .pt-\[clamp\(32px\,5vw\,60px\)\] {
1083
1089
  padding-top: clamp(32px, 5vw, 60px);
1084
1090
  }
1085
- .pt-\[clamp\(60px\,9vh\,110px\)\] {
1086
- padding-top: clamp(60px, 9vh, 110px);
1087
- }
1088
1091
  .pb-2 {
1089
1092
  padding-bottom: calc(var(--spacing) * 2);
1090
1093
  }
@@ -1094,9 +1097,6 @@
1094
1097
  .pb-7 {
1095
1098
  padding-bottom: calc(var(--spacing) * 7);
1096
1099
  }
1097
- .pb-10 {
1098
- padding-bottom: calc(var(--spacing) * 10);
1099
- }
1100
1100
  .pb-\[var\(--margin\)\] {
1101
1101
  padding-bottom: var(--margin);
1102
1102
  }
@@ -1582,6 +1582,17 @@
1582
1582
  --tw-shadow: 0 0 0 1px var(--tw-shadow-color, var(--orbit));
1583
1583
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1584
1584
  }
1585
+ .data-\[pin\]\:sticky[data-pin] {
1586
+ position: sticky;
1587
+ }
1588
+ .data-\[pin\]\:top-0[data-pin] {
1589
+ top: 0px;
1590
+ }
1591
+ .data-\[pin\]\:transition-shadow[data-pin] {
1592
+ transition-property: box-shadow;
1593
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
1594
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
1595
+ }
1585
1596
  .data-\[state\=open\]\:opacity-100[data-state="open"] {
1586
1597
  opacity: 100%;
1587
1598
  }
@@ -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.2";
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/169 names from scripts/vendor-icons.mjs
3
3
  // Regenerate with: npm run icons
4
4
 
5
5
  const D = {
@@ -16,6 +16,7 @@ const D = {
16
16
  "info": [ ["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "M12 16v-4" }], ["path", { d: "M12 8h.01" }] ],
17
17
  "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
18
  "circle-arrow-up": [ ["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "m16 12-4-4-4 4" }], ["path", { d: "M12 16V8" }] ],
19
+ "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
20
  "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
21
  "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
22
  "terminal": [ ["path", { d: "M12 19h8" }], ["path", { d: "m4 17 6-6-6-6" }] ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mevebu",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Small tools that run in your browser. No accounts, no uploads, no tracking.",
5
5
  "license": "MIT",
6
6
  "type": "module",