mevebu 1.7.2 → 1.8.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.
package/README.md CHANGED
@@ -1,36 +1,61 @@
1
+ <div align="center">
2
+
1
3
  # mevebu
2
4
 
3
- **186 small tools, each on its own page, all running in your browser.**
5
+ **187 free browser tools, one page each. No sign up. No uploads. Everything runs in your tab.**
4
6
 
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
+ A private toolbox for the small jobs that do not deserve a whole app: format JSON, decode a JWT, make a password, build a cron line, pick a color, scan a QR code, and a lot more.
9
8
 
10
- Nothing leaves your tab. There is no account to make, nothing to upload, and
11
- nothing saved to a server.
9
+ [![npm version](https://img.shields.io/npm/v/mevebu?color=2ea44f&label=npm)](https://www.npmjs.com/package/mevebu)
10
+ [![npm downloads](https://img.shields.io/npm/dm/mevebu?color=2ea44f)](https://www.npmjs.com/package/mevebu)
11
+ [![License: MIT](https://img.shields.io/github/license/mevebu/mevebu?color=blue)](./LICENSE)
12
+ [![GitHub stars](https://img.shields.io/github/stars/mevebu/mevebu?style=flat&color=yellow)](https://github.com/mevebu/mevebu/stargazers)
13
+ ![Tools](https://img.shields.io/badge/tools-187-brightgreen)
14
+ ![Privacy](https://img.shields.io/badge/privacy-100%25%20in--browser-blue)
12
15
 
13
- ```
14
- tools 186
15
- categories 20
16
- followers 1 (one tab, no account)
17
- uploads 0
18
- ```
16
+ [Quick start](#quick-start) · [What's inside](#whats-inside) · [Privacy](#privacy-by-design) · [Host it yourself](#host-it-yourself) · [Development](#development) · [FAQ](#faq)
17
+
18
+ </div>
19
+
20
+ <!-- Tip: add a screenshot here, for example: ![mevebu home page](docs/screenshot.png) -->
21
+
22
+ ---
23
+
24
+ ## What is mevebu?
25
+
26
+ mevebu is a collection of **187 small online tools** that all run **inside your browser**. Each tool lives on its own page, does one job, and does it fast. Most of them update as you type, so there is no submit button and no waiting.
27
+
28
+ Think of it as a clean, private alternative to the usual "free online tools" websites. There are no accounts, no ads in your way, no uploads, and no tracking. Your text, passwords, tokens, and files stay on your device.
29
+
30
+ You can run it on your own computer with one command, or copy the folder to any static host and use it from anywhere.
31
+
32
+ ## Why you will like it
19
33
 
20
- ## Install and run
34
+ - **Private by design.** Nothing you paste ever leaves your browser tab.
35
+ - **No account, no sign up.** Open a tool and use it.
36
+ - **Fast.** Plain HTML and vanilla JavaScript. No heavy framework, no loading spinners.
37
+ - **One tool, one page.** Every tool has its own clean page you can bookmark.
38
+ - **Instant search.** Press `Ctrl+K` and find any tool by name, keyword, or what it does.
39
+ - **Works offline.** Once installed, it runs fully on your machine.
40
+ - **Easy to host.** It is a plain static site. Drop it on any file host.
41
+ - **Free and open source.** MIT licensed.
21
42
 
22
- ```sh
43
+ ## Quick start
44
+
45
+ You need [Node.js](https://nodejs.org) installed. Then run:
46
+
47
+ ```bash
23
48
  npm install -g mevebu
24
49
  mevebu
25
50
  ```
26
51
 
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.
52
+ That is it. mevebu starts a small local server, opens your browser on the home page, and serves the tools that ship with the package. Press `Ctrl+C` in the terminal to stop it.
30
53
 
31
- ### CLI
54
+ mevebu tries port `5555` first, then `5678`, then `5432`, and finally any free port. The first three are stable between restarts, so your bookmarks keep working.
32
55
 
33
- ```
56
+ ### CLI options
57
+
58
+ ```text
34
59
  -p, --port <number> Try this port first (5555, 5678, 5432 otherwise).
35
60
  -d, --dir <path> Folder to serve. Defaults to the bundled tools.
36
61
  --no-open Do not open the browser.
@@ -38,98 +63,86 @@ folder bundled with the package. Press `Ctrl+C` in the terminal to stop it.
38
63
  -h, --help Print this help and exit.
39
64
  ```
40
65
 
41
- The first three ports are stable across restarts, so the bookmark to your tools
42
- page keeps working.
43
-
44
66
  ## What's inside
45
67
 
46
- A tool is a small page that does one job. The registry lists them all; here is
47
- the shape of it.
48
-
49
- | Category | Count |
50
- | :--- | ---: |
51
- | Random | 3 |
52
- | Passwords and keys | 10 |
53
- | Developer | 21 |
54
- | Convert | 3 |
55
- | Text and data | 14 |
56
- | Daily use | 8 |
57
- | Math | 17 |
58
- | Date and time | 7 |
59
- | Data and lists | 8 |
60
- | Design | 10 |
61
- | Web and SEO | 12 |
62
- | Encoding | 8 |
63
- | Academic | 15 |
64
- | Study | 7 |
65
- | Business | 10 |
66
- | Locale | 7 |
67
- | Content | 8 |
68
- | GitHub | 8 |
69
- | Units | 8 |
70
- | QR codes | 2 |
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.
68
+ mevebu has **187 tools in 20 categories**. Here is the full breakdown:
69
+
70
+ | Category | Tools |
71
+ | ------------------ | ----: |
72
+ | Random | 3 |
73
+ | Passwords and keys | 10 |
74
+ | Developer | 21 |
75
+ | Convert | 4 |
76
+ | Text and data | 14 |
77
+ | Daily use | 8 |
78
+ | Math | 17 |
79
+ | Date and time | 7 |
80
+ | Data and lists | 8 |
81
+ | Design | 10 |
82
+ | Web and SEO | 12 |
83
+ | Encoding | 8 |
84
+ | Academic | 15 |
85
+ | Study | 7 |
86
+ | Business | 10 |
87
+ | Locale | 7 |
88
+ | Content | 8 |
89
+ | GitHub | 8 |
90
+ | Units | 8 |
91
+ | QR codes | 2 |
92
+ | **Total** | **187** |
93
+
94
+ The counts come straight from the tool registry, so this table and the app never drift apart.
95
+
96
+ ### Popular tools
97
+
98
+ | Tool | What it does |
99
+ | ---- | ------------ |
100
+ | **Password generator** | Make a strong password and copy it. The page only talks to your clipboard. |
101
+ | **JSON formatter** | Paste your JSON and get a clean, formatted version as you type. |
102
+ | **JWT generator and decoder** | Sign a token with HS256, RS256, or ES256, or paste one and read its header and payload. |
103
+ | **Color palette** | Pick one color and get matching analogous, complementary, or monochrome schemes. |
104
+ | **Countdown timer** | A quiet, fullscreen timer that keeps running when the tab is hidden. |
105
+ | **QR code generator** | Turn a URL, a Wi-Fi config, or any text into a scannable image. |
106
+ | **Slug converter** | Turn a sentence into a clean, readable URL slug. |
107
+
108
+ Browse everything from the home page or by category, or just press `Ctrl+K` and start typing.
109
+
110
+ ## Privacy by design
111
+
112
+ mevebu is built so that your data has nowhere to go. Every tool is a static HTML file, a tiny JavaScript module, and a Lucide icon. There is no server side code, no analytics, and no API key.
100
113
 
101
114
  When you paste a password, a token, or a CSV into a tool:
102
115
 
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.
116
+ - It is processed in your browser tab, using the standard Web Crypto API or other built-in browser APIs.
117
+ - Nothing is sent to a remote server, because there is no remote server in the loop.
118
+ - Nothing is saved to disk by the tool.
119
+ - The only network request the page makes is to load itself and to read the GitHub star count for the footer chip. That request fails quietly when you are offline.
120
+
121
+ Do not just take our word for it. Open the Network tab in your browser dev tools and watch a tool work. You will not see an upload.
122
+
123
+ | | mevebu | Typical online tool sites |
124
+ | --- | :---: | :---: |
125
+ | Account needed | No | Often |
126
+ | Your data uploaded | Never | Sometimes |
127
+ | Works offline | Yes | Rarely |
128
+ | Open source | Yes (MIT) | Rarely |
129
+ | Self-hostable | Yes | Rarely |
130
+
131
+ ## Host it yourself
132
+
133
+ Prefer not to use the CLI? mevebu is a plain static site, so you can host it anywhere files can be served: a static site host, a CDN, an object store, or even a USB drive.
134
+
135
+ 1. Copy the `mevebu/` folder from this repo (or from the npm package).
136
+ 2. Upload it to your host.
137
+ 3. Open `index.html`.
138
+
139
+ Routes are hash routes, like `#/security/password-generator`, so you do not need any server rewrites. The CSS is one committed stylesheet built by Tailwind, and there is no extra build step. Your host never has to run code.
129
140
 
130
141
  ## Development
131
142
 
132
- ```sh
143
+ Want to hack on mevebu or add your own tool? Here is how to get started:
144
+
145
+ ```bash
133
146
  git clone https://github.com/mevebu/mevebu
134
147
  cd mevebu
135
148
  npm install
@@ -138,38 +151,76 @@ npm run icons # regenerate the vendored Lucide icon set
138
151
  npm start # serve the static folder on http://localhost:5555
139
152
  ```
140
153
 
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.
154
+ ### Project layout
155
+
156
+ ```text
157
+ bin/ The mevebu command line entry
158
+ mevebu/ The static app that gets served
159
+ app/ App shell (mevebu.js) and the Tailwind source (tailwind.src.css)
160
+ libs/js/ Shared core.js, the registry, and one file per tool in tools/
161
+ scripts/ Helper scripts for the project
162
+ ```
163
+
164
+ ### How it works
165
+
166
+ - The app is a few hundred lines of vanilla JavaScript (ESM) in `mevebu/app/mevebu.js`, plus a small shared helper set in `mevebu/libs/js/core.js`.
167
+ - The registry in `mevebu/libs/js/registry.js` is the single source of truth for all tools.
168
+ - The styling is Tailwind v4, built from one source file. The built stylesheet is committed, so the shipped folder stays plain static files.
169
+ - The design system (type scale, layout, color, and motion) is documented in `AGENTS.md` and `DESIGN.md`.
170
+
171
+ ### Add a new tool
172
+
173
+ 1. Create one file in `mevebu/libs/js/tools/`.
174
+ 2. Export a `mount(slot)` function. Optionally export `unmount()` to clean up timers, cameras, and observers.
175
+ 3. Add one entry to `TOOLS` in `mevebu/libs/js/registry.js`.
176
+
177
+ That is all. The catalog, search, breadcrumbs, and category pages pick it up automatically.
178
+
179
+ ## FAQ
180
+
181
+ **What is mevebu?**
182
+ mevebu is a free, open source collection of 187 browser based tools for developers, students, creators, and everyday use. Every tool runs locally in your browser.
183
+
184
+ **Is mevebu really free?**
185
+ Yes. It is MIT licensed, with no paid tier, no account, and no usage limits.
148
186
 
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`.
187
+ **Does mevebu upload or store my data?**
188
+ No. Your input is processed in your browser tab and never sent to a server.
153
189
 
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.
190
+ **Can I use mevebu offline?**
191
+ Yes. After you install it with npm, it runs on your own machine without needing the internet.
192
+
193
+ **Can I host mevebu on my own site?**
194
+ Yes. Copy the `mevebu/` folder to any static host. No server side setup is needed.
195
+
196
+ **How do I find a tool quickly?**
197
+ Press `Ctrl+K` and search by tool name, keyword, or what the tool does.
198
+
199
+ **Can I add my own tool?**
200
+ Yes. Add one file and one registry entry. See [Add a new tool](#add-a-new-tool).
157
201
 
158
202
  ## Report a problem
159
203
 
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.
204
+ Every tool page has a small **Report a problem** link at the bottom. It opens a GitHub issue with the tool name, the page URL, and the mevebu version already filled in. You only need to describe what happened and what you expected.
164
205
 
165
- ## License
206
+ You can also [open an issue](https://github.com/mevebu/mevebu/issues) directly, or send a pull request.
207
+
208
+ ## Support and follow
209
+
210
+ If a tool here saved you time, you can help keep the next one coming:
166
211
 
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>.
212
+ [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-support-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/fastro)
213
+
214
+ Star the repo if you like it, and follow mevebu for updates:
215
+
216
+ [GitHub](https://github.com/mevebu) · [TikTok](https://www.tiktok.com/@mevebu01) · [Instagram](https://www.instagram.com/mevebu01) · [Facebook](https://www.facebook.com/mevebu) · [YouTube](https://www.youtube.com/@mevebu) · [Threads](https://www.threads.net/@mevebu01)
170
217
 
171
218
  ## Links
172
219
 
173
220
  - Repository: <https://github.com/mevebu/mevebu>
174
- - npm: <https://www.npmjs.com/package/mevebu>
175
- - Donate: <https://buymeacoffee.com/fastro>
221
+ - npm package: <https://www.npmjs.com/package/mevebu>
222
+ - Issues: <https://github.com/mevebu/mevebu/issues>
223
+
224
+ ## License
225
+
226
+ [MIT](./LICENSE). The whole list, the whole source, the whole thing is right here in the repository.
@@ -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";
@@ -754,28 +755,115 @@ function moreFromMev(tool) {
754
755
  );
755
756
  }
756
757
 
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"));
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
+ });
779
867
  }
780
868
 
781
869
  async function toolPage(tool, category) {
@@ -788,7 +876,7 @@ async function toolPage(tool, category) {
788
876
  { class: PAGE_NARROW },
789
877
  benchHead(tool, category),
790
878
  slot,
791
- el("div", { class: "flex flex-col items-stretch" }, reportLink(tool)),
879
+ el("div", { class: "flex flex-col items-start" }, reportButton(tool)),
792
880
  more
793
881
  );
794
882