feat: add 15 tools and filter check

This commit is contained in:
chao5
2025-12-23 14:12:36 +08:00
parent 3986b2373b
commit 599d20c65d
34 changed files with 7165 additions and 37 deletions
+8 -1
View File
@@ -25,6 +25,14 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Validate filters
run: node scripts/check-filter.js
- name: Update sitemap
run: python3 scripts/update_sitemap.py
@@ -46,4 +54,3 @@ jobs:
git add sitemap.xml
git commit -m "chore: auto-update sitemap.xml"
git push
+592 -36
View File
@@ -7,7 +7,12 @@
"name": "JSON 转 YAML",
"slug": "json-to-yaml",
"category": "converter",
"tags": ["json", "yaml", "converter", "developer"],
"tags": [
"json",
"yaml",
"converter",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -27,7 +32,12 @@
"name": "JSON 格式化工具",
"slug": "json-formatter",
"category": "converter",
"tags": ["json", "formatter", "validator", "beautifier"],
"tags": [
"json",
"formatter",
"validator",
"beautifier"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -47,7 +57,12 @@
"name": "Base64 编码解码",
"slug": "base64-encode",
"category": "converter",
"tags": ["base64", "encoder", "decoder", "converter"],
"tags": [
"base64",
"encoder",
"decoder",
"converter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -67,7 +82,12 @@
"name": "颜色选择器",
"slug": "color-picker",
"category": "developer",
"tags": ["color", "picker", "design", "converter"],
"tags": [
"color",
"picker",
"design",
"converter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -87,7 +107,11 @@
"name": "正则表达式测试器",
"slug": "regex-tester",
"category": "developer",
"tags": ["regex", "testing", "developer"],
"tags": [
"regex",
"testing",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -107,7 +131,12 @@
"name": "URL 编码解码",
"slug": "url-encode",
"category": "converter",
"tags": ["url", "encoder", "decoder", "converter"],
"tags": [
"url",
"encoder",
"decoder",
"converter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -127,7 +156,11 @@
"name": "二维码生成器",
"slug": "qr-generator",
"category": "utility",
"tags": ["qrcode", "generator", "utility"],
"tags": [
"qrcode",
"generator",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -147,7 +180,12 @@
"name": "安全密码生成器",
"slug": "password-generator",
"category": "utility",
"tags": ["password", "security", "generator", "utility"],
"tags": [
"password",
"security",
"generator",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -167,7 +205,12 @@
"name": "UUID 生成器",
"slug": "uuid-generator",
"category": "developer",
"tags": ["uuid", "generator", "developer", "identifier"],
"tags": [
"uuid",
"generator",
"developer",
"identifier"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -187,7 +230,12 @@
"name": "字数统计工具",
"slug": "word-counter",
"category": "text",
"tags": ["text", "counter", "word", "characters"],
"tags": [
"text",
"counter",
"word",
"characters"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -207,7 +255,12 @@
"name": "Unix 时间戳转换",
"slug": "timestamp-converter",
"category": "converter",
"tags": ["timestamp", "unix", "datetime", "converter"],
"tags": [
"timestamp",
"unix",
"datetime",
"converter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -227,7 +280,13 @@
"name": "进制转换工具",
"slug": "base-converter",
"category": "converter",
"tags": ["base", "converter", "binary", "hexadecimal", "octal"],
"tags": [
"base",
"converter",
"binary",
"hexadecimal",
"octal"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -247,7 +306,12 @@
"name": "文本对比工具",
"slug": "text-diff",
"category": "text",
"tags": ["text", "diff", "compare", "difference"],
"tags": [
"text",
"diff",
"compare",
"difference"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -267,7 +331,13 @@
"name": "JWT 解码器",
"slug": "jwt-decoder",
"category": "developer",
"tags": ["jwt", "decoder", "json", "token", "authentication"],
"tags": [
"jwt",
"decoder",
"json",
"token",
"authentication"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -287,7 +357,12 @@
"name": "图片转 Base64",
"slug": "image-to-base64",
"category": "image",
"tags": ["image", "base64", "converter", "encoding"],
"tags": [
"image",
"base64",
"converter",
"encoding"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -307,7 +382,12 @@
"name": "XML 转 JSON",
"slug": "xml-to-json",
"category": "converter",
"tags": ["xml", "json", "converter", "format"],
"tags": [
"xml",
"json",
"converter",
"format"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -327,7 +407,12 @@
"name": "Markdown 转 HTML",
"slug": "markdown-to-html",
"category": "text",
"tags": ["markdown", "html", "converter", "editor"],
"tags": [
"markdown",
"html",
"converter",
"editor"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -347,7 +432,13 @@
"name": "哈希生成器",
"slug": "hash-generator",
"category": "developer",
"tags": ["hash", "md5", "sha", "crypto", "security"],
"tags": [
"hash",
"md5",
"sha",
"crypto",
"security"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -367,7 +458,12 @@
"name": "HTML 实体编码/解码",
"slug": "html-entity",
"category": "converter",
"tags": ["html", "entity", "encode", "decode"],
"tags": [
"html",
"entity",
"encode",
"decode"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -387,7 +483,12 @@
"name": "大小写转换工具",
"slug": "case-converter",
"category": "text",
"tags": ["text", "case", "converter", "format"],
"tags": [
"text",
"case",
"converter",
"format"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -407,7 +508,12 @@
"name": "CSS 压缩/格式化工具",
"slug": "css-minifier",
"category": "developer",
"tags": ["css", "minifier", "beautifier", "formatter"],
"tags": [
"css",
"minifier",
"beautifier",
"formatter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -427,7 +533,12 @@
"name": "Cron 表达式生成器",
"slug": "cron-generator",
"category": "developer",
"tags": ["cron", "scheduler", "expression", "generator"],
"tags": [
"cron",
"scheduler",
"expression",
"generator"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -447,7 +558,12 @@
"name": "百分比计算器",
"slug": "percentage-calculator",
"category": "utility",
"tags": ["calculator", "percentage", "math", "utility"],
"tags": [
"calculator",
"percentage",
"math",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -467,7 +583,12 @@
"name": "Lorem Ipsum 生成器",
"slug": "lorem-ipsum",
"category": "text",
"tags": ["lorem", "ipsum", "placeholder", "generator"],
"tags": [
"lorem",
"ipsum",
"placeholder",
"generator"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -487,7 +608,12 @@
"name": "YAML 验证器",
"slug": "yaml-validator",
"category": "converter",
"tags": ["yaml", "validator", "json", "converter"],
"tags": [
"yaml",
"validator",
"json",
"converter"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -507,7 +633,12 @@
"name": "URL Slug 生成器",
"slug": "slug-generator",
"category": "text",
"tags": ["slug", "url", "generator", "seo"],
"tags": [
"slug",
"url",
"generator",
"seo"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -527,7 +658,12 @@
"name": "文本去重工具",
"slug": "text-deduplicator",
"category": "text",
"tags": ["text", "dedupe", "cleanup", "list"],
"tags": [
"text",
"dedupe",
"cleanup",
"list"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -547,7 +683,12 @@
"name": "文本排序工具",
"slug": "text-sorter",
"category": "text",
"tags": ["text", "sorter", "list", "utility"],
"tags": [
"text",
"sorter",
"list",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -567,7 +708,12 @@
"name": "行号添加器",
"slug": "line-numberer",
"category": "text",
"tags": ["text", "line", "number", "editor"],
"tags": [
"text",
"line",
"number",
"editor"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -587,7 +733,12 @@
"name": "文本清理器",
"slug": "text-cleaner",
"category": "text",
"tags": ["text", "cleaner", "format", "utility"],
"tags": [
"text",
"cleaner",
"format",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -607,7 +758,12 @@
"name": "CSV 转 JSON",
"slug": "csv-to-json",
"category": "converter",
"tags": ["csv", "json", "converter", "table"],
"tags": [
"csv",
"json",
"converter",
"table"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -627,7 +783,12 @@
"name": "JSON 转 CSV",
"slug": "json-to-csv",
"category": "converter",
"tags": ["json", "csv", "converter", "export"],
"tags": [
"json",
"csv",
"converter",
"export"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -647,7 +808,12 @@
"name": "URL 解析器",
"slug": "url-parser",
"category": "developer",
"tags": ["url", "parser", "developer", "web"],
"tags": [
"url",
"parser",
"developer",
"web"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -667,7 +833,12 @@
"name": "颜色对比度检测",
"slug": "contrast-checker",
"category": "developer",
"tags": ["color", "contrast", "accessibility", "design"],
"tags": [
"color",
"contrast",
"accessibility",
"design"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -687,7 +858,12 @@
"name": "密码强度检测",
"slug": "password-strength",
"category": "utility",
"tags": ["password", "security", "checker", "utility"],
"tags": [
"password",
"security",
"checker",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -707,7 +883,12 @@
"name": "日期差值计算器",
"slug": "date-diff",
"category": "utility",
"tags": ["date", "time", "calculator", "utility"],
"tags": [
"date",
"time",
"calculator",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
@@ -721,6 +902,381 @@
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "js-formatter",
"name": "JavaScript 格式化工具",
"slug": "js-formatter",
"category": "developer",
"tags": [
"javascript",
"formatter",
"beautifier",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "美化 JavaScript 代码并提升可读性",
"longDescription": "JavaScript 格式化工具可以帮助你把凌乱的 JS 代码重新排版,支持常见缩进设置,方便阅读与分享。",
"icon": "🧩",
"color": "#f59e0b",
"entry": "tools/js-formatter/app.html",
"detail": "tools/js-formatter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "html-formatter",
"name": "HTML 格式化工具",
"slug": "html-formatter",
"category": "developer",
"tags": [
"html",
"formatter",
"beautifier",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "整理 HTML 结构并自动缩进",
"longDescription": "HTML 格式化工具支持对 HTML 片段进行自动缩进与换行,让结构更加清晰易读。",
"icon": "🧱",
"color": "#ef4444",
"entry": "tools/html-formatter/app.html",
"detail": "tools/html-formatter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "sql-formatter",
"name": "SQL 格式化工具",
"slug": "sql-formatter",
"category": "developer",
"tags": [
"sql",
"formatter",
"database",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "格式化 SQL 语句,提升可读性",
"longDescription": "SQL 格式化工具可以将复杂的 SQL 查询语句自动排版,突出关键字与层级结构。",
"icon": "🗄️",
"color": "#10b981",
"entry": "tools/sql-formatter/app.html",
"detail": "tools/sql-formatter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "http-status",
"name": "HTTP 状态码查询",
"slug": "http-status",
"category": "developer",
"tags": [
"http",
"status",
"api",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "快速查询常见 HTTP 状态码含义",
"longDescription": "HTTP 状态码查询工具整理了常用的状态码及说明,支持搜索过滤,适合开发调试与排查问题。",
"icon": "🛰️",
"color": "#0ea5e9",
"entry": "tools/http-status/app.html",
"detail": "tools/http-status/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "json-escape",
"name": "JSON 字符串转义",
"slug": "json-escape",
"category": "converter",
"tags": [
"json",
"escape",
"converter",
"string"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "在 JSON 字符串转义与反转义之间转换",
"longDescription": "JSON 字符串转义工具可以把普通文本转换为 JSON 可用的转义格式,或将转义字符还原为可读文本。",
"icon": "🧵",
"color": "#6366f1",
"entry": "tools/json-escape/app.html",
"detail": "tools/json-escape/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "url-query-builder",
"name": "URL 参数生成器",
"slug": "url-query-builder",
"category": "developer",
"tags": [
"url",
"query",
"builder",
"developer"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "批量生成 URL 查询参数并输出完整链接",
"longDescription": "URL 参数生成器支持通过键值对批量生成查询参数,自动编码,快速得到完整链接。",
"icon": "🧷",
"color": "#14b8a6",
"entry": "tools/url-query-builder/app.html",
"detail": "tools/url-query-builder/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "text-replacer",
"name": "文本查找替换",
"slug": "text-replacer",
"category": "text",
"tags": [
"text",
"replace",
"search",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "批量查找并替换文本内容",
"longDescription": "文本查找替换工具支持快速替换关键词,可选择大小写与正则模式,适合清理文本或批量替换内容。",
"icon": "🧲",
"color": "#8b5cf6",
"entry": "tools/text-replacer/app.html",
"detail": "tools/text-replacer/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "line-ending-converter",
"name": "行尾符转换器",
"slug": "line-ending-converter",
"category": "text",
"tags": [
"text",
"line",
"lf",
"crlf"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "在 LF 与 CRLF 行尾符之间转换",
"longDescription": "行尾符转换器可以快速将文本在 LF 和 CRLF 之间切换,适合跨系统协作与代码整理。",
"icon": "↩️",
"color": "#3b82f6",
"entry": "tools/line-ending-converter/app.html",
"detail": "tools/line-ending-converter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "text-reverser",
"name": "文本反转工具",
"slug": "text-reverser",
"category": "text",
"tags": [
"text",
"reverse",
"converter",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "支持整段或按行反转文本",
"longDescription": "文本反转工具可以将文本按字符或按行反转,适合特殊排版、加密提示或趣味处理。",
"icon": "🔁",
"color": "#f97316",
"entry": "tools/text-reverser/app.html",
"detail": "tools/text-reverser/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "image-compressor",
"name": "图片压缩工具",
"slug": "image-compressor",
"category": "image",
"tags": [
"image",
"compress",
"optimizer",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "本地压缩图片,减少文件体积",
"longDescription": "图片压缩工具使用浏览器 Canvas 在本地压缩图片,支持调整质量与输出格式。",
"icon": "🗜️",
"color": "#22c55e",
"entry": "tools/image-compressor/app.html",
"detail": "tools/image-compressor/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "image-resizer",
"name": "图片尺寸调整",
"slug": "image-resizer",
"category": "image",
"tags": [
"image",
"resize",
"editor",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "调整图片宽高,支持锁定比例",
"longDescription": "图片尺寸调整工具可在浏览器中修改图片分辨率,支持保持原始比例并导出新图片。",
"icon": "📐",
"color": "#06b6d4",
"entry": "tools/image-resizer/app.html",
"detail": "tools/image-resizer/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "image-to-webp",
"name": "图片转 WebP",
"slug": "image-to-webp",
"category": "image",
"tags": [
"image",
"webp",
"converter",
"compression"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "将图片转换为 WebP 格式",
"longDescription": "图片转 WebP 工具帮助你把 JPG/PNG 等格式转换为 WebP,减少体积并提升加载速度。",
"icon": "🧪",
"color": "#a855f7",
"entry": "tools/image-to-webp/app.html",
"detail": "tools/image-to-webp/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "unit-converter",
"name": "单位换算器",
"slug": "unit-converter",
"category": "utility",
"tags": [
"unit",
"converter",
"length",
"weight"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "长度、重量、温度等单位快速换算",
"longDescription": "单位换算器支持常用长度、重量与温度单位的快速转换,适合日常与工作场景。",
"icon": "📏",
"color": "#ec4899",
"entry": "tools/unit-converter/app.html",
"detail": "tools/unit-converter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "bmi-calculator",
"name": "BMI 计算器",
"slug": "bmi-calculator",
"category": "utility",
"tags": [
"bmi",
"health",
"calculator",
"utility"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "快速计算身体质量指数 BMI",
"longDescription": "BMI 计算器可根据身高体重计算 BMI 并给出健康范围提示,适合日常健康管理。",
"icon": "⚖️",
"color": "#84cc16",
"entry": "tools/bmi-calculator/app.html",
"detail": "tools/bmi-calculator/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "gradient-generator",
"name": "渐变色生成器",
"slug": "gradient-generator",
"category": "developer",
"tags": [
"gradient",
"color",
"css",
"design"
],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "生成 CSS 线性渐变并实时预览",
"longDescription": "渐变色生成器提供色彩与方向控制,实时预览并输出 CSS 渐变代码。",
"icon": "🌈",
"color": "#f43f5e",
"entry": "tools/gradient-generator/app.html",
"detail": "tools/gradient-generator/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": false,
"repo": "https://github.com/justhtmls/html-tools"
}
],
"categories": [
+67
View File
@@ -0,0 +1,67 @@
const fs = require('fs');
const path = require('path');
const data = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'index.json'), 'utf8'));
function filterTools({ category = 'all', search = '' }) {
const searchQuery = search.toLowerCase();
return data.tools.filter((tool) => {
const matchesCategory = category === 'all' || tool.category === category;
const matchesSearch = !searchQuery ||
tool.name.toLowerCase().includes(searchQuery) ||
tool.description.toLowerCase().includes(searchQuery) ||
tool.tags.some((tag) => tag.toLowerCase().includes(searchQuery));
return matchesCategory && matchesSearch;
});
}
function countMatches(label, options, expectAtLeastIds = []) {
const result = filterTools(options);
const ids = new Set(result.map((tool) => tool.id));
const missing = expectAtLeastIds.filter((id) => !ids.has(id));
console.log(`\n[${label}] count=${result.length}`);
if (missing.length) {
console.log(` missing: ${missing.join(', ')}`);
} else if (expectAtLeastIds.length) {
console.log(' includes expected ids: OK');
}
return { result, missing };
}
const checks = [];
checks.push(countMatches('Search: JSON', { search: 'JSON' }, [
'json-formatter',
'json-to-yaml',
'json-to-csv',
'json-escape',
]));
checks.push(countMatches('Search: 图片', { search: '图片' }, [
'image-to-base64',
'image-compressor',
'image-resizer',
'image-to-webp',
]));
checks.push(countMatches('Search: format', { search: 'format' }, [
'js-formatter',
'html-formatter',
'sql-formatter',
]));
checks.push(countMatches('Category: image', { category: 'image' }));
checks.push(countMatches('Category: developer', { category: 'developer' }, [
'http-status',
'url-query-builder',
'gradient-generator',
]));
const total = data.tools.length;
console.log(`\n[Total tools] ${total}`);
const failures = checks.flatMap((check) => check.missing || []);
if (failures.length) {
console.log('\nResult: FAIL');
process.exit(1);
}
console.log('\nResult: PASS');
+180
View File
@@ -450,4 +450,184 @@
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/js-formatter/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/js-formatter/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/html-formatter/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/html-formatter/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/sql-formatter/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/sql-formatter/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/http-status/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/http-status/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/json-escape/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/json-escape/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/url-query-builder/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/url-query-builder/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/text-replacer/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/text-replacer/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/line-ending-converter/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/line-ending-converter/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/text-reverser/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/text-reverser/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-compressor/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-compressor/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-resizer/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-resizer/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-to-webp/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/image-to-webp/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/unit-converter/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/unit-converter/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/bmi-calculator/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/bmi-calculator/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/gradient-generator/</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.htmls.dev/tools/gradient-generator/app.html</loc>
<lastmod>2025-12-23</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
</urlset>
+233
View File
@@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BMI 计算器</title>
<link rel="canonical" href="https://www.htmls.dev/tools/bmi-calculator/app.html">
<style>
:root {
--accent: #84cc16;
--accent-soft: #84cc1633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #84cc16 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>BMI 计算器</h1>
<p>快速计算身体质量指数 BMI</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>身高 (cm)</label>
<input type="number" id="height" placeholder="例如 170">
</div>
<div>
<label>体重 (kg)</label>
<input type="number" id="weight" placeholder="例如 60">
</div>
</div>
<div class="actions">
<button class="btn" id="calculate">计算 BMI</button>
</div>
<div class="output-box" style="margin-top: 16px;">
<p class="muted">BMI 结果</p>
<div class="result" id="bmi">--</div>
<p class="muted" id="status">--</p>
</div>
</div>
</div>
<script>
const heightInput = document.getElementById('height');
const weightInput = document.getElementById('weight');
const calculateBtn = document.getElementById('calculate');
const bmiEl = document.getElementById('bmi');
const statusEl = document.getElementById('status');
function getStatus(bmi) {
if (bmi < 18.5) return '偏瘦';
if (bmi < 25) return '正常';
if (bmi < 30) return '偏重';
return '肥胖';
}
function calculate() {
const height = parseFloat(heightInput.value);
const weight = parseFloat(weightInput.value);
if (!height || !weight) {
bmiEl.textContent = '--';
statusEl.textContent = '--';
return;
}
const bmi = weight / ((height / 100) ** 2);
bmiEl.textContent = bmi.toFixed(2);
statusEl.textContent = getStatus(bmi);
}
calculateBtn.addEventListener('click', calculate);
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BMI 计算器 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/bmi-calculator/">
<style>
:root {
--accent: #84cc16;
--accent-soft: #84cc1633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #84cc16 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>BMI 计算器</h1>
<p>快速计算身体质量指数 BMI</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>BMI 计算器可根据身高体重计算 BMI 并给出健康范围提示,适合日常健康管理。</p>
<h2>功能特点</h2>
<ul class="features">
<li>输入简单</li>
<li>即时计算</li>
<li>健康区间提示</li>
<li>本地离线</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>健康管理</li>
<li>健身记录</li>
<li>体重控制</li>
<li>营养咨询</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/bmi-calculator" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+238
View File
@@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渐变色生成器</title>
<link rel="canonical" href="https://www.htmls.dev/tools/gradient-generator/app.html">
<style>
:root {
--accent: #f43f5e;
--accent-soft: #f43f5e33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f43f5e 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>渐变色生成器</h1>
<p>生成 CSS 线性渐变并实时预览</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>起始颜色</label>
<input type="color" id="colorStart" value="#f43f5e">
</div>
<div>
<label>结束颜色</label>
<input type="color" id="colorEnd" value="#3b82f6">
</div>
</div>
<div style="margin-top: 12px;">
<label>方向</label>
<select id="direction">
<option value="to right">从左到右</option>
<option value="to bottom">从上到下</option>
<option value="45deg">45°</option>
<option value="135deg">135°</option>
</select>
</div>
<div class="preview" id="preview" style="margin-top: 16px; min-height: 160px;"></div>
<div class="output-box" style="margin-top: 16px;">
<label>CSS 代码</label>
<textarea id="cssOutput" class="code" readonly></textarea>
<div class="actions">
<button class="btn" id="copy">复制 CSS</button>
</div>
</div>
</div>
</div>
<script>
const colorStart = document.getElementById('colorStart');
const colorEnd = document.getElementById('colorEnd');
const direction = document.getElementById('direction');
const preview = document.getElementById('preview');
const cssOutput = document.getElementById('cssOutput');
const copyBtn = document.getElementById('copy');
function updateGradient() {
const value = `linear-gradient(${direction.value}, ${colorStart.value}, ${colorEnd.value})`;
preview.style.background = value;
cssOutput.value = `background: ${value};`;
}
[colorStart, colorEnd, direction].forEach(el => {
el.addEventListener('input', updateGradient);
});
copyBtn.addEventListener('click', () => {
cssOutput.select();
document.execCommand('copy');
});
updateGradient();
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渐变色生成器 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/gradient-generator/">
<style>
:root {
--accent: #f43f5e;
--accent-soft: #f43f5e33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f43f5e 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>渐变色生成器</h1>
<p>生成 CSS 线性渐变并实时预览</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>渐变色生成器提供色彩与方向控制,实时预览并输出 CSS 渐变代码。</p>
<h2>功能特点</h2>
<ul class="features">
<li>多方向选择</li>
<li>实时预览</li>
<li>输出 CSS 代码</li>
<li>一键复制</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>UI 设计</li>
<li>按钮背景</li>
<li>海报配色</li>
<li>前端样式</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/gradient-generator" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+247
View File
@@ -0,0 +1,247 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML 格式化工具</title>
<link rel="canonical" href="https://www.htmls.dev/tools/html-formatter/app.html">
<style>
:root {
--accent: #ef4444;
--accent-soft: #ef444433;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ef4444 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>HTML 格式化工具</h1>
<p>整理 HTML 结构并自动缩进</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>输入 HTML</label>
<textarea id="input" placeholder="粘贴 HTML 代码"></textarea>
</div>
<div>
<label>输出结果</label>
<textarea id="output" readonly placeholder="格式化后的 HTML"></textarea>
</div>
</div>
<div class="row" style="margin-top: 16px;">
<div style="min-width: 180px;">
<label>缩进设置</label>
<select id="indent">
<option value="2" selected>2 空格</option>
<option value="4">4 空格</option>
<option value="tab">Tab</option>
</select>
</div>
<div class="actions">
<button class="btn" id="format">格式化</button>
<button class="btn btn-secondary" id="clear">清空</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/js-beautify@1.15.1/js/lib/beautify-html.js"></script>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const indentSelect = document.getElementById('indent');
const formatBtn = document.getElementById('format');
const clearBtn = document.getElementById('clear');
const copyBtn = document.getElementById('copy');
function formatHtml() {
const value = input.value;
if (!value.trim()) {
output.value = '';
return;
}
const indent = indentSelect.value;
const useTabs = indent === 'tab';
const options = {
indent_size: useTabs ? 1 : Number(indent),
indent_with_tabs: useTabs,
};
output.value = html_beautify(value, options);
}
formatBtn.addEventListener('click', formatHtml);
clearBtn.addEventListener('click', () => {
input.value = '';
output.value = '';
});
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML 格式化工具 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/html-formatter/">
<style>
:root {
--accent: #ef4444;
--accent-soft: #ef444433;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ef4444 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>HTML 格式化工具</h1>
<p>整理 HTML 结构并自动缩进</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>HTML 格式化工具支持对 HTML 片段进行自动缩进与换行,让结构更加清晰易读。</p>
<h2>功能特点</h2>
<ul class="features">
<li>HTML 自动排版</li>
<li>保持标签层级</li>
<li>快速复制结果</li>
<li>本地离线处理</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>整理页面模板</li>
<li>优化 HTML 可读性</li>
<li>调试组件结构</li>
<li>教学示例排版</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/html-formatter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+245
View File
@@ -0,0 +1,245 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTTP 状态码查询</title>
<link rel="canonical" href="https://www.htmls.dev/tools/http-status/app.html">
<style>
:root {
--accent: #0ea5e9;
--accent-soft: #0ea5e933;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0ea5e9 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>HTTP 状态码查询</h1>
<p>快速查询常见 HTTP 状态码含义</p>
</div>
<div class="card">
<div class="row" style="margin-bottom: 12px;">
<div style="flex: 1;">
<label>搜索状态码或关键词</label>
<input type="text" id="search" placeholder="例如 404 / unauthorized / server">
</div>
</div>
<div id="list" class="output-box"></div>
</div>
</div>
<script>
const searchInput = document.getElementById('search');
const list = document.getElementById('list');
const statuses = [
{ code: 200, title: 'OK', desc: '请求成功' },
{ code: 201, title: 'Created', desc: '资源创建成功' },
{ code: 202, title: 'Accepted', desc: '请求已接收,处理中' },
{ code: 204, title: 'No Content', desc: '请求成功但无返回内容' },
{ code: 301, title: 'Moved Permanently', desc: '永久重定向' },
{ code: 302, title: 'Found', desc: '临时重定向' },
{ code: 304, title: 'Not Modified', desc: '资源未修改,使用缓存' },
{ code: 400, title: 'Bad Request', desc: '请求参数错误' },
{ code: 401, title: 'Unauthorized', desc: '未授权或身份验证失败' },
{ code: 403, title: 'Forbidden', desc: '服务器拒绝访问' },
{ code: 404, title: 'Not Found', desc: '资源不存在' },
{ code: 409, title: 'Conflict', desc: '请求冲突' },
{ code: 413, title: 'Payload Too Large', desc: '请求体过大' },
{ code: 415, title: 'Unsupported Media Type', desc: '不支持的媒体类型' },
{ code: 429, title: 'Too Many Requests', desc: '请求过多被限流' },
{ code: 500, title: 'Internal Server Error', desc: '服务器内部错误' },
{ code: 502, title: 'Bad Gateway', desc: '网关错误' },
{ code: 503, title: 'Service Unavailable', desc: '服务不可用' },
{ code: 504, title: 'Gateway Timeout', desc: '网关超时' },
];
function render(filter = '') {
const keyword = filter.trim().toLowerCase();
const filtered = statuses.filter(item => {
return (
item.code.toString().includes(keyword) ||
item.title.toLowerCase().includes(keyword) ||
item.desc.toLowerCase().includes(keyword)
);
});
if (!filtered.length) {
list.innerHTML = '<p class="muted">没有匹配的状态码</p>';
return;
}
list.innerHTML = filtered.map(item => `
<div style="padding: 10px 0; border-bottom: 1px solid #e2e8f0;">
<div class="row" style="justify-content: space-between;">
<strong>${item.code} ${item.title}</strong>
<span class="chip">HTTP</span>
</div>
<p class="muted" style="margin-top: 6px;">${item.desc}</p>
</div>
`).join('');
}
render();
searchInput.addEventListener('input', (event) => render(event.target.value));
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTTP 状态码查询 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/http-status/">
<style>
:root {
--accent: #0ea5e9;
--accent-soft: #0ea5e933;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0ea5e9 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>HTTP 状态码查询</h1>
<p>快速查询常见 HTTP 状态码含义</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>HTTP 状态码查询工具整理了常用的状态码及说明,支持搜索过滤,适合开发调试与排查问题。</p>
<h2>功能特点</h2>
<ul class="features">
<li>覆盖常见状态码</li>
<li>关键词搜索</li>
<li>分类说明</li>
<li>本地快速查阅</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>接口调试</li>
<li>错误排查</li>
<li>学习 HTTP 协议</li>
<li>写作或文档引用</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/http-status" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+283
View File
@@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片压缩工具</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-compressor/app.html">
<style>
:root {
--accent: #22c55e;
--accent-soft: #22c55e33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #22c55e 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片压缩工具</h1>
<p>本地压缩图片,减少文件体积</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>选择图片</label>
<input type="file" id="file" accept="image/*">
<div class="row" style="margin-top: 12px;">
<div style="flex: 1;">
<label>压缩质量:<span id="qualityLabel">0.8</span></label>
<input type="range" id="quality" min="0.1" max="1" step="0.05" value="0.8">
</div>
<div style="min-width: 160px;">
<label>输出格式</label>
<select id="format">
<option value="image/jpeg">JPEG</option>
<option value="image/webp">WebP</option>
<option value="image/png">PNG</option>
</select>
</div>
</div>
<div class="actions">
<button class="btn" id="compress">开始压缩</button>
<a class="btn btn-secondary" id="download" download>下载图片</a>
</div>
<p class="muted" id="sizeInfo">原图大小:-- / 压缩后:--</p>
</div>
<div>
<label>预览</label>
<div class="preview" id="preview">暂无预览</div>
</div>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('file');
const qualityInput = document.getElementById('quality');
const qualityLabel = document.getElementById('qualityLabel');
const formatSelect = document.getElementById('format');
const compressBtn = document.getElementById('compress');
const preview = document.getElementById('preview');
const sizeInfo = document.getElementById('sizeInfo');
const downloadLink = document.getElementById('download');
let image = new Image();
let originalSize = 0;
function formatBytes(bytes) {
if (!bytes) return '--';
const units = ['B', 'KB', 'MB'];
let value = bytes;
let index = 0;
while (value >= 1024 && index < units.length - 1) {
value /= 1024;
index++;
}
return `${value.toFixed(2)} ${units[index]}`;
}
qualityInput.addEventListener('input', () => {
qualityLabel.textContent = qualityInput.value;
});
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
originalSize = file.size;
const reader = new FileReader();
reader.onload = (event) => {
image = new Image();
image.onload = () => {
preview.innerHTML = '';
preview.appendChild(image);
sizeInfo.textContent = `原图大小:${formatBytes(originalSize)} / 压缩后:--`;
};
image.src = event.target.result;
};
reader.readAsDataURL(file);
});
compressBtn.addEventListener('click', () => {
if (!image.src) return;
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const format = formatSelect.value;
const quality = parseFloat(qualityInput.value);
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
const previewImg = new Image();
previewImg.onload = () => URL.revokeObjectURL(url);
previewImg.src = url;
preview.innerHTML = '';
preview.appendChild(previewImg);
downloadLink.href = url;
downloadLink.download = `compressed.${format.split('/')[1]}`;
sizeInfo.textContent = `原图大小:${formatBytes(originalSize)} / 压缩后:${formatBytes(blob.size)}`;
}, format, format === 'image/png' ? undefined : quality);
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片压缩工具 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-compressor/">
<style>
:root {
--accent: #22c55e;
--accent-soft: #22c55e33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #22c55e 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片压缩工具</h1>
<p>本地压缩图片,减少文件体积</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>图片压缩工具使用浏览器 Canvas 在本地压缩图片,支持调整质量与输出格式。</p>
<h2>功能特点</h2>
<ul class="features">
<li>质量可调</li>
<li>多格式输出</li>
<li>实时预览</li>
<li>本地处理隐私安全</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>网站图片优化</li>
<li>上传前压缩</li>
<li>节省存储空间</li>
<li>分享图片</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/image-compressor" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片尺寸调整</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-resizer/app.html">
<style>
:root {
--accent: #06b6d4;
--accent-soft: #06b6d433;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #06b6d4 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片尺寸调整</h1>
<p>调整图片宽高,支持锁定比例</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>选择图片</label>
<input type="file" id="file" accept="image/*">
<div class="grid" style="margin-top: 12px;">
<div>
<label>宽度 (px)</label>
<input type="number" id="width" min="1">
</div>
<div>
<label>高度 (px)</label>
<input type="number" id="height" min="1">
</div>
</div>
<div class="row" style="margin-top: 8px;">
<label><input type="checkbox" id="lock" checked> 锁定比例</label>
<div style="min-width: 160px;">
<label>输出格式</label>
<select id="format">
<option value="image/png">PNG</option>
<option value="image/jpeg">JPEG</option>
<option value="image/webp">WebP</option>
</select>
</div>
</div>
<div class="actions">
<button class="btn" id="resize">生成新图片</button>
<a class="btn btn-secondary" id="download" download>下载图片</a>
</div>
</div>
<div>
<label>预览</label>
<div class="preview" id="preview">暂无预览</div>
</div>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('file');
const widthInput = document.getElementById('width');
const heightInput = document.getElementById('height');
const lockInput = document.getElementById('lock');
const formatSelect = document.getElementById('format');
const resizeBtn = document.getElementById('resize');
const preview = document.getElementById('preview');
const downloadLink = document.getElementById('download');
let image = new Image();
let ratio = 1;
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
image = new Image();
image.onload = () => {
ratio = image.naturalWidth / image.naturalHeight;
widthInput.value = image.naturalWidth;
heightInput.value = image.naturalHeight;
preview.innerHTML = '';
preview.appendChild(image);
};
image.src = event.target.result;
};
reader.readAsDataURL(file);
});
widthInput.addEventListener('input', () => {
if (!lockInput.checked || !widthInput.value) return;
heightInput.value = Math.round(Number(widthInput.value) / ratio);
});
heightInput.addEventListener('input', () => {
if (!lockInput.checked || !heightInput.value) return;
widthInput.value = Math.round(Number(heightInput.value) * ratio);
});
resizeBtn.addEventListener('click', () => {
if (!image.src) return;
const width = Number(widthInput.value);
const height = Number(heightInput.value);
if (!width || !height) return;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
const format = formatSelect.value;
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
const previewImg = new Image();
previewImg.onload = () => URL.revokeObjectURL(url);
previewImg.src = url;
preview.innerHTML = '';
preview.appendChild(previewImg);
downloadLink.href = url;
downloadLink.download = `resized.${format.split('/')[1]}`;
}, format, 0.92);
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片尺寸调整 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-resizer/">
<style>
:root {
--accent: #06b6d4;
--accent-soft: #06b6d433;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #06b6d4 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片尺寸调整</h1>
<p>调整图片宽高,支持锁定比例</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>图片尺寸调整工具可在浏览器中修改图片分辨率,支持保持原始比例并导出新图片。</p>
<h2>功能特点</h2>
<ul class="features">
<li>自定义宽高</li>
<li>锁定比例</li>
<li>多格式导出</li>
<li>实时预览</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>压缩分辨率</li>
<li>适配社媒尺寸</li>
<li>制作缩略图</li>
<li>产品图片处理</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/image-resizer" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+253
View File
@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片转 WebP</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-to-webp/app.html">
<style>
:root {
--accent: #a855f7;
--accent-soft: #a855f733;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #a855f7 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片转 WebP</h1>
<p>将图片转换为 WebP 格式</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>选择图片</label>
<input type="file" id="file" accept="image/*">
<div style="margin-top: 12px;">
<label>输出质量:<span id="qualityLabel">0.85</span></label>
<input type="range" id="quality" min="0.1" max="1" step="0.05" value="0.85">
</div>
<div class="actions">
<button class="btn" id="convert">转换为 WebP</button>
<a class="btn btn-secondary" id="download" download>下载 WebP</a>
</div>
</div>
<div>
<label>预览</label>
<div class="preview" id="preview">暂无预览</div>
</div>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('file');
const qualityInput = document.getElementById('quality');
const qualityLabel = document.getElementById('qualityLabel');
const convertBtn = document.getElementById('convert');
const preview = document.getElementById('preview');
const downloadLink = document.getElementById('download');
let image = new Image();
qualityInput.addEventListener('input', () => {
qualityLabel.textContent = qualityInput.value;
});
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
image = new Image();
image.onload = () => {
preview.innerHTML = '';
preview.appendChild(image);
};
image.src = event.target.result;
};
reader.readAsDataURL(file);
});
convertBtn.addEventListener('click', () => {
if (!image.src) return;
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const quality = parseFloat(qualityInput.value);
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
const previewImg = new Image();
previewImg.onload = () => URL.revokeObjectURL(url);
previewImg.src = url;
preview.innerHTML = '';
preview.appendChild(previewImg);
downloadLink.href = url;
downloadLink.download = 'converted.webp';
}, 'image/webp', quality);
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片转 WebP - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/image-to-webp/">
<style>
:root {
--accent: #a855f7;
--accent-soft: #a855f733;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #a855f7 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片转 WebP</h1>
<p>将图片转换为 WebP 格式</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>图片转 WebP 工具帮助你把 JPG/PNG 等格式转换为 WebP,减少体积并提升加载速度。</p>
<h2>功能特点</h2>
<ul class="features">
<li>WebP 转换</li>
<li>质量可调</li>
<li>本地处理</li>
<li>快速下载</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>网站图片优化</li>
<li>移动端节省流量</li>
<li>批量转换</li>
<li>CDN 优化</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/image-to-webp" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+248
View File
@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 格式化工具</title>
<link rel="canonical" href="https://www.htmls.dev/tools/js-formatter/app.html">
<style>
:root {
--accent: #f59e0b;
--accent-soft: #f59e0b33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f59e0b 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JavaScript 格式化工具</h1>
<p>美化 JavaScript 代码并提升可读性</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>输入 JavaScript</label>
<textarea id="input" placeholder="粘贴需要格式化的 JS 代码"></textarea>
</div>
<div>
<label>输出结果</label>
<textarea id="output" readonly placeholder="格式化后的代码"></textarea>
</div>
</div>
<div class="row" style="margin-top: 16px;">
<div style="min-width: 180px;">
<label>缩进设置</label>
<select id="indent">
<option value="2">2 空格</option>
<option value="4" selected>4 空格</option>
<option value="tab">Tab</option>
</select>
</div>
<div class="actions">
<button class="btn" id="format">格式化</button>
<button class="btn btn-secondary" id="clear">清空</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
</div>
<p class="muted" style="margin-top: 10px;">提示:格式化不会执行代码,所有处理都在浏览器本地完成。</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/js-beautify@1.15.1/js/lib/beautify.js"></script>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const indentSelect = document.getElementById('indent');
const formatBtn = document.getElementById('format');
const clearBtn = document.getElementById('clear');
const copyBtn = document.getElementById('copy');
function formatCode() {
const value = input.value;
if (!value.trim()) {
output.value = '';
return;
}
const indent = indentSelect.value;
const useTabs = indent === 'tab';
const options = {
indent_size: useTabs ? 1 : Number(indent),
indent_with_tabs: useTabs,
};
output.value = js_beautify(value, options);
}
formatBtn.addEventListener('click', formatCode);
clearBtn.addEventListener('click', () => {
input.value = '';
output.value = '';
});
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 格式化工具 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/js-formatter/">
<style>
:root {
--accent: #f59e0b;
--accent-soft: #f59e0b33;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f59e0b 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JavaScript 格式化工具</h1>
<p>美化 JavaScript 代码并提升可读性</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>JavaScript 格式化工具可以帮助你把凌乱的 JS 代码重新排版,支持常见缩进设置,方便阅读与分享。</p>
<h2>功能特点</h2>
<ul class="features">
<li>多种缩进方案</li>
<li>一键格式化</li>
<li>快速复制结果</li>
<li>纯前端本地处理</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>阅读压缩 JS 代码</li>
<li>整理前端脚本</li>
<li>分享代码片段</li>
<li>团队协作前统一格式</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/js-formatter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+230
View File
@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON 字符串转义</title>
<link rel="canonical" href="https://www.htmls.dev/tools/json-escape/app.html">
<style>
:root {
--accent: #6366f1;
--accent-soft: #6366f133;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #6366f1 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JSON 字符串转义</h1>
<p>在 JSON 字符串转义与反转义之间转换</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>输入文本</label>
<textarea id="input" placeholder="输入原始文本或转义后的字符串"></textarea>
</div>
<div>
<label>输出结果</label>
<textarea id="output" readonly placeholder="转换后的结果"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="escape">转义</button>
<button class="btn btn-secondary" id="unescape">反转义</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
<p class="muted" style="margin-top: 8px;">适用于生成 JSON 字符串、处理转义符等场景。</p>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const escapeBtn = document.getElementById('escape');
const unescapeBtn = document.getElementById('unescape');
const copyBtn = document.getElementById('copy');
function escapeText() {
output.value = JSON.stringify(input.value).slice(1, -1);
}
function unescapeText() {
try {
const value = JSON.parse('"' + input.value.replace(/"/g, '\"') + '"');
output.value = value;
} catch (error) {
output.value = '无法解析:请检查转义字符格式';
}
}
escapeBtn.addEventListener('click', escapeText);
unescapeBtn.addEventListener('click', unescapeText);
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON 字符串转义 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/json-escape/">
<style>
:root {
--accent: #6366f1;
--accent-soft: #6366f133;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #6366f1 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JSON 字符串转义</h1>
<p>在 JSON 字符串转义与反转义之间转换</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>JSON 字符串转义工具可以把普通文本转换为 JSON 可用的转义格式,或将转义字符还原为可读文本。</p>
<h2>功能特点</h2>
<ul class="features">
<li>一键转义/反转义</li>
<li>支持换行与引号</li>
<li>快速复制</li>
<li>本地处理</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>调试接口返回文本</li>
<li>处理 JSON 配置</li>
<li>复制带引号的字符串</li>
<li>生成测试数据</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/json-escape" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+246
View File
@@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行尾符转换器</title>
<link rel="canonical" href="https://www.htmls.dev/tools/line-ending-converter/app.html">
<style>
:root {
--accent: #3b82f6;
--accent-soft: #3b82f633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #3b82f6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>行尾符转换器</h1>
<p>在 LF 与 CRLF 行尾符之间转换</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>输入文本</label>
<textarea id="input" placeholder="粘贴需要转换的文本"></textarea>
</div>
<div>
<label>输出结果</label>
<textarea id="output" readonly placeholder="转换后的文本"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="toLf">转为 LF</button>
<button class="btn btn-secondary" id="toCrlf">转为 CRLF</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
<p class="muted">当前行数:<span id="lines">0</span></p>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const toLfBtn = document.getElementById('toLf');
const toCrlfBtn = document.getElementById('toCrlf');
const copyBtn = document.getElementById('copy');
const linesEl = document.getElementById('lines');
function normalize(value) {
return value.replace(/
/g, '
').replace(/
/g, '
');
}
function updateLines(value) {
const normalized = normalize(value);
linesEl.textContent = normalized ? normalized.split('
').length : '0';
}
function convertToLf() {
const normalized = normalize(input.value);
output.value = normalized;
updateLines(normalized);
}
function convertToCrlf() {
const normalized = normalize(input.value);
output.value = normalized.replace(/
/g, '
');
updateLines(normalized);
}
input.addEventListener('input', () => updateLines(input.value));
toLfBtn.addEventListener('click', convertToLf);
toCrlfBtn.addEventListener('click', convertToCrlf);
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行尾符转换器 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/line-ending-converter/">
<style>
:root {
--accent: #3b82f6;
--accent-soft: #3b82f633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #3b82f6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>行尾符转换器</h1>
<p>在 LF 与 CRLF 行尾符之间转换</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>行尾符转换器可以快速将文本在 LF 和 CRLF 之间切换,适合跨系统协作与代码整理。</p>
<h2>功能特点</h2>
<ul class="features">
<li>一键转换行尾符</li>
<li>统计行数</li>
<li>支持大段文本</li>
<li>结果可复制</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>Windows 与 macOS 协作</li>
<li>Git 行尾符整理</li>
<li>代码格式修复</li>
<li>批量文本处理</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/line-ending-converter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+241
View File
@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL 格式化工具</title>
<link rel="canonical" href="https://www.htmls.dev/tools/sql-formatter/app.html">
<style>
:root {
--accent: #10b981;
--accent-soft: #10b98133;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #10b981 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SQL 格式化工具</h1>
<p>格式化 SQL 语句,提升可读性</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>输入 SQL</label>
<textarea id="input" placeholder="粘贴 SQL 语句"></textarea>
</div>
<div>
<label>输出结果</label>
<textarea id="output" readonly placeholder="格式化后的 SQL"></textarea>
</div>
</div>
<div class="row" style="margin-top: 16px;">
<div style="min-width: 180px;">
<label>缩进空格</label>
<select id="indent">
<option value="2" selected>2 空格</option>
<option value="4">4 空格</option>
</select>
</div>
<div class="actions">
<button class="btn" id="format">格式化</button>
<button class="btn btn-secondary" id="clear">清空</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sql-formatter@15.4.3/dist/sql-formatter.min.js"></script>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const indentSelect = document.getElementById('indent');
const formatBtn = document.getElementById('format');
const clearBtn = document.getElementById('clear');
const copyBtn = document.getElementById('copy');
function formatSql() {
const value = input.value;
if (!value.trim()) {
output.value = '';
return;
}
const indent = Number(indentSelect.value);
output.value = sqlFormatter.format(value, { indent });
}
formatBtn.addEventListener('click', formatSql);
clearBtn.addEventListener('click', () => {
input.value = '';
output.value = '';
});
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL 格式化工具 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/sql-formatter/">
<style>
:root {
--accent: #10b981;
--accent-soft: #10b98133;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #10b981 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SQL 格式化工具</h1>
<p>格式化 SQL 语句,提升可读性</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>SQL 格式化工具可以将复杂的 SQL 查询语句自动排版,突出关键字与层级结构。</p>
<h2>功能特点</h2>
<ul class="features">
<li>SQL 自动排版</li>
<li>支持常见数据库语法</li>
<li>快速复制结果</li>
<li>纯前端处理</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>阅读复杂 SQL</li>
<li>整理数据库脚本</li>
<li>代码审查</li>
<li>教学示例</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/sql-formatter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+252
View File
@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本查找替换</title>
<link rel="canonical" href="https://www.htmls.dev/tools/text-replacer/app.html">
<style>
:root {
--accent: #8b5cf6;
--accent-soft: #8b5cf633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #8b5cf6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本查找替换</h1>
<p>批量查找并替换文本内容</p>
</div>
<div class="card">
<div>
<label>原始文本</label>
<textarea id="input" placeholder="粘贴需要处理的文本"></textarea>
</div>
<div class="grid" style="margin-top: 16px;">
<div>
<label>查找内容</label>
<input type="text" id="findText" placeholder="输入要查找的文本">
</div>
<div>
<label>替换为</label>
<input type="text" id="replaceText" placeholder="替换内容">
</div>
</div>
<div class="row" style="margin-top: 12px;">
<label><input type="checkbox" id="ignoreCase"> 忽略大小写</label>
<label><input type="checkbox" id="useRegex"> 使用正则</label>
</div>
<div class="actions">
<button class="btn" id="replace">执行替换</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
<div class="output-box" style="margin-top: 16px;">
<label>输出结果</label>
<textarea id="output" readonly placeholder="替换后的文本"></textarea>
<p class="muted">替换次数:<span id="count">0</span></p>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const findText = document.getElementById('findText');
const replaceText = document.getElementById('replaceText');
const ignoreCase = document.getElementById('ignoreCase');
const useRegex = document.getElementById('useRegex');
const replaceBtn = document.getElementById('replace');
const copyBtn = document.getElementById('copy');
const output = document.getElementById('output');
const countEl = document.getElementById('count');
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function replaceAll() {
const source = input.value;
const findValue = findText.value;
if (!findValue) {
output.value = source;
countEl.textContent = '0';
return;
}
const flags = ignoreCase.checked ? 'gi' : 'g';
const pattern = useRegex.checked ? findValue : escapeRegExp(findValue);
const regex = new RegExp(pattern, flags);
const matches = source.match(regex);
const result = source.replace(regex, replaceText.value);
output.value = result;
countEl.textContent = matches ? matches.length : '0';
}
replaceBtn.addEventListener('click', replaceAll);
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本查找替换 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/text-replacer/">
<style>
:root {
--accent: #8b5cf6;
--accent-soft: #8b5cf633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #8b5cf6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本查找替换</h1>
<p>批量查找并替换文本内容</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>文本查找替换工具支持快速替换关键词,可选择大小写与正则模式,适合清理文本或批量替换内容。</p>
<h2>功能特点</h2>
<ul class="features">
<li>支持正则替换</li>
<li>大小写控制</li>
<li>统计替换次数</li>
<li>快速复制</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>清理文档内容</li>
<li>批量替换变量</li>
<li>日志处理</li>
<li>文本纠错</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/text-replacer" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+241
View File
@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本反转工具</title>
<link rel="canonical" href="https://www.htmls.dev/tools/text-reverser/app.html">
<style>
:root {
--accent: #f97316;
--accent-soft: #f9731633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f97316 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本反转工具</h1>
<p>支持整段或按行反转文本</p>
</div>
<div class="card">
<div>
<label>输入文本</label>
<textarea id="input" placeholder="粘贴需要反转的文本"></textarea>
</div>
<div class="row" style="margin-top: 12px;">
<div style="min-width: 200px;">
<label>反转模式</label>
<select id="mode">
<option value="chars">整段字符反转</option>
<option value="lines">按行顺序反转</option>
<option value="each-line">每行内部反转</option>
</select>
</div>
<div class="actions">
<button class="btn" id="convert">开始反转</button>
<button class="btn btn-secondary" id="copy">复制结果</button>
</div>
</div>
<div class="output-box" style="margin-top: 16px;">
<label>输出结果</label>
<textarea id="output" readonly placeholder="反转后的文本"></textarea>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const mode = document.getElementById('mode');
const output = document.getElementById('output');
const convertBtn = document.getElementById('convert');
const copyBtn = document.getElementById('copy');
function reverseText() {
const value = input.value;
if (!value) {
output.value = '';
return;
}
if (mode.value === 'chars') {
output.value = value.split('').reverse().join('');
} else if (mode.value === 'lines') {
output.value = value.split(/
?
/).reverse().join('
');
} else {
output.value = value.split(/
?
/).map(line => line.split('').reverse().join('')).join('
');
}
}
convertBtn.addEventListener('click', reverseText);
copyBtn.addEventListener('click', () => {
if (!output.value) return;
output.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本反转工具 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/text-reverser/">
<style>
:root {
--accent: #f97316;
--accent-soft: #f9731633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f97316 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本反转工具</h1>
<p>支持整段或按行反转文本</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>文本反转工具可以将文本按字符或按行反转,适合特殊排版、加密提示或趣味处理。</p>
<h2>功能特点</h2>
<ul class="features">
<li>多种反转模式</li>
<li>即时处理</li>
<li>复制结果</li>
<li>支持多行文本</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>趣味文本</li>
<li>镜像排版</li>
<li>加密提示</li>
<li>文本处理实验</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/text-reverser" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+314
View File
@@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单位换算器</title>
<link rel="canonical" href="https://www.htmls.dev/tools/unit-converter/app.html">
<style>
:root {
--accent: #ec4899;
--accent-soft: #ec489933;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ec4899 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>单位换算器</h1>
<p>长度、重量、温度等单位快速换算</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>单位类型</label>
<select id="category">
<option value="length">长度</option>
<option value="weight">重量</option>
<option value="temperature">温度</option>
</select>
</div>
<div>
<label>输入数值</label>
<input type="number" id="value" placeholder="输入数值">
</div>
</div>
<div class="grid" style="margin-top: 12px;">
<div>
<label></label>
<select id="fromUnit"></select>
</div>
<div>
<label></label>
<select id="toUnit"></select>
</div>
</div>
<div class="output-box" style="margin-top: 16px;">
<p class="muted">换算结果</p>
<div class="result" id="result">--</div>
</div>
</div>
</div>
<script>
const categorySelect = document.getElementById('category');
const valueInput = document.getElementById('value');
const fromUnitSelect = document.getElementById('fromUnit');
const toUnitSelect = document.getElementById('toUnit');
const resultEl = document.getElementById('result');
const units = {
length: {
name: '长度',
base: 'm',
items: {
m: 1,
km: 1000,
cm: 0.01,
mm: 0.001,
inch: 0.0254,
ft: 0.3048,
},
},
weight: {
name: '重量',
base: 'kg',
items: {
kg: 1,
g: 0.001,
lb: 0.45359237,
oz: 0.0283495231,
},
},
temperature: {
name: '温度',
items: {
c: '摄氏度 (°C)',
f: '华氏度 (°F)',
k: '开尔文 (K)',
},
},
};
function populateUnits() {
const category = categorySelect.value;
const unitSet = units[category];
fromUnitSelect.innerHTML = '';
toUnitSelect.innerHTML = '';
if (category === 'temperature') {
Object.entries(unitSet.items).forEach(([key, label]) => {
const option1 = new Option(label, key);
const option2 = new Option(label, key);
fromUnitSelect.add(option1);
toUnitSelect.add(option2);
});
} else {
Object.keys(unitSet.items).forEach((key) => {
const option1 = new Option(key, key);
const option2 = new Option(key, key);
fromUnitSelect.add(option1);
toUnitSelect.add(option2);
});
}
toUnitSelect.selectedIndex = 1;
calculate();
}
function convertTemperature(value, from, to) {
let celsius = value;
if (from === 'f') celsius = (value - 32) * 5 / 9;
if (from === 'k') celsius = value - 273.15;
let result = celsius;
if (to === 'f') result = celsius * 9 / 5 + 32;
if (to === 'k') result = celsius + 273.15;
return result;
}
function calculate() {
const value = parseFloat(valueInput.value);
if (Number.isNaN(value)) {
resultEl.textContent = '--';
return;
}
const category = categorySelect.value;
if (category === 'temperature') {
const result = convertTemperature(value, fromUnitSelect.value, toUnitSelect.value);
resultEl.textContent = `${result.toFixed(2)}`;
return;
}
const factorFrom = units[category].items[fromUnitSelect.value];
const factorTo = units[category].items[toUnitSelect.value];
const baseValue = value * factorFrom;
const result = baseValue / factorTo;
resultEl.textContent = result.toFixed(4);
}
categorySelect.addEventListener('change', populateUnits);
valueInput.addEventListener('input', calculate);
fromUnitSelect.addEventListener('change', calculate);
toUnitSelect.addEventListener('change', calculate);
populateUnits();
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单位换算器 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/unit-converter/">
<style>
:root {
--accent: #ec4899;
--accent-soft: #ec489933;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ec4899 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>单位换算器</h1>
<p>长度、重量、温度等单位快速换算</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>单位换算器支持常用长度、重量与温度单位的快速转换,适合日常与工作场景。</p>
<h2>功能特点</h2>
<ul class="features">
<li>多类别单位</li>
<li>实时换算</li>
<li>简单易用</li>
<li>无需网络</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>工程计算</li>
<li>出行换算</li>
<li>教学演示</li>
<li>日常查询</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/unit-converter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+257
View File
@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL 参数生成器</title>
<link rel="canonical" href="https://www.htmls.dev/tools/url-query-builder/app.html">
<style>
:root {
--accent: #14b8a6;
--accent-soft: #14b8a633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #14b8a6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 26px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
label {
font-size: 14px;
color: var(--text-muted);
}
input[type="text"],
input[type="number"],
input[type="url"],
input[type="file"],
select,
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: white;
}
textarea {
min-height: 140px;
resize: vertical;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 14px;
}
.btn {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.muted {
color: var(--text-muted);
font-size: 13px;
}
.output-box {
background: #f8fafc;
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.preview {
width: 100%;
min-height: 180px;
border-radius: 12px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.preview img {
max-width: 100%;
max-height: 300px;
display: block;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--text);
font-size: 12px;
}
.result {
font-size: 18px;
font-weight: 700;
color: var(--accent);
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>URL 参数生成器</h1>
<p>批量生成 URL 查询参数并输出完整链接</p>
</div>
<div class="card">
<div class="grid">
<div>
<label>基础 URL</label>
<input type="url" id="baseUrl" placeholder="https://example.com/page">
</div>
<div>
<label>参数列表(每行 key=value</label>
<textarea id="params" placeholder="utm_source=wechat
utm_medium=social
user=chao"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="build">生成链接</button>
<button class="btn btn-secondary" id="copy">复制链接</button>
</div>
<div class="output-box" style="margin-top: 16px;">
<label>完整 URL</label>
<input type="text" id="fullUrl" readonly class="code" placeholder="生成后的完整链接">
<label style="margin-top: 10px; display: block;">查询字符串</label>
<textarea id="queryString" readonly class="code" placeholder="?key=value&..."></textarea>
</div>
</div>
</div>
<script>
const baseUrlInput = document.getElementById('baseUrl');
const paramsInput = document.getElementById('params');
const buildBtn = document.getElementById('build');
const copyBtn = document.getElementById('copy');
const fullUrlOutput = document.getElementById('fullUrl');
const queryOutput = document.getElementById('queryString');
function buildUrl() {
let base = baseUrlInput.value.trim();
let hash = '';
let queryPart = '';
if (base.includes('#')) {
const parts = base.split('#');
base = parts[0];
hash = parts.slice(1).join('#');
}
if (base.includes('?')) {
const parts = base.split('?');
base = parts[0];
queryPart = parts.slice(1).join('?');
}
const params = new URLSearchParams(queryPart);
paramsInput.value.split('
').forEach(line => {
const trimmed = line.trim();
if (!trimmed) return;
const index = trimmed.indexOf('=');
if (index === -1) {
params.append(trimmed, '');
return;
}
const key = trimmed.slice(0, index).trim();
const value = trimmed.slice(index + 1).trim();
params.set(key, value);
});
const queryString = params.toString();
const url = base ? `${base}${queryString ? '?' + queryString : ''}${hash ? '#' + hash : ''}` : queryString;
fullUrlOutput.value = url;
queryOutput.value = queryString ? '?' + queryString : '';
}
buildBtn.addEventListener('click', buildUrl);
copyBtn.addEventListener('click', () => {
if (!fullUrlOutput.value) return;
fullUrlOutput.select();
document.execCommand('copy');
});
</script>
<script src="/assets/clicks.js" defer></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL 参数生成器 - JustHTMLs</title>
<link rel="canonical" href="https://www.htmls.dev/tools/url-query-builder/">
<style>
:root {
--accent: #14b8a6;
--accent-soft: #14b8a633;
--bg: #0f172a;
--text: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #14b8a6 0%, #0f172a 100%);
min-height: 100vh;
padding: 20px;
color: var(--text);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.3rem;
margin-bottom: 8px;
}
.card {
background: rgba(255, 255, 255, 0.96);
border-radius: 18px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #1f2937;
margin-bottom: 16px;
font-size: 1.4rem;
}
.card p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 14px;
}
.features {
list-style: none;
margin-top: 10px;
}
.features li {
padding: 8px 0;
color: #334155;
border-bottom: 1px solid #e2e8f0;
}
.features li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 12px 26px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
margin: 6px 10px 6px 0;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.2);
}
.btn-secondary {
background: #64748b;
}
.preview {
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.preview iframe {
width: 100%;
height: 420px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>URL 参数生成器</h1>
<p>批量生成 URL 查询参数并输出完整链接</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>URL 参数生成器支持通过键值对批量生成查询参数,自动编码,快速得到完整链接。</p>
<h2>功能特点</h2>
<ul class="features">
<li>按行输入参数</li>
<li>自动编码</li>
<li>支持追加已有参数</li>
<li>一键复制</li>
</ul>
<h2>使用场景</h2>
<ul class="features">
<li>生成 UTM 链接</li>
<li>调试 API 请求</li>
<li>批量构造 URL</li>
<li>分享带参数链接</li>
</ul>
<div style="margin-top: 24px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/url-query-builder" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
<script src="/assets/clicks.js" defer></script>
</body>
</html>