Skip to content

Commit 6c918c2

Browse files
committed
seo improvements
1 parent ba37b5f commit 6c918c2

5 files changed

Lines changed: 390 additions & 26 deletions

File tree

index.html

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,63 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>syskit</title>
6+
7+
<!-- Primary meta -->
8+
<title>syskit — developer tools for the browser</title>
9+
<meta name="description" content="Free browser-based tools for developers and sysadmins. chmod calculator, cron builder, CIDR subnet calculator, RAID planner, regex tester, JWT decoder, Base64, URL encode, DNS lookup, and more. No sign-up, no tracking." />
10+
<meta name="keywords" content="chmod calculator, cron expression builder, CIDR calculator, RAID calculator, regex tester, JWT decoder, base64 encoder, URL encoder, DNS lookup, nslookup, epoch converter, developer tools" />
11+
<link rel="canonical" href="https://syskit.atsiom.com/" id="canonical" />
12+
13+
<!-- Open Graph -->
14+
<meta property="og:type" content="website" />
15+
<meta property="og:site_name" content="syskit" />
16+
<meta property="og:url" content="https://syskit.atsiom.com/" id="og-url" />
17+
<meta property="og:title" content="syskit — developer tools for the browser" id="og-title" />
18+
<meta property="og:description" content="Free browser-based tools for developers and sysadmins. chmod, cron, CIDR, RAID, regex, JWT, Base64, DNS lookup, and more." id="og-desc" />
19+
<meta property="og:image" content="https://syskit.atsiom.com/og-image.png" />
20+
21+
<!-- Twitter Card -->
22+
<meta name="twitter:card" content="summary_large_image" />
23+
<meta name="twitter:title" content="syskit — developer tools for the browser" id="tw-title" />
24+
<meta name="twitter:description" content="Free browser-based tools for developers and sysadmins. chmod, cron, CIDR, RAID, regex, JWT, Base64, DNS lookup, and more." id="tw-desc" />
25+
<meta name="twitter:image" content="https://syskit.atsiom.com/og-image.png" />
26+
27+
<!-- JSON-LD structured data -->
28+
<script type="application/ld+json">
29+
{
30+
"@context": "https://schema.org",
31+
"@type": "WebApplication",
32+
"name": "syskit",
33+
"url": "https://syskit.atsiom.com/",
34+
"description": "Free browser-based tools for developers and sysadmins. No sign-up, no tracking, works offline.",
35+
"applicationCategory": "DeveloperApplication",
36+
"operatingSystem": "Any",
37+
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
38+
"author": { "@type": "Organization", "name": "Atsiom LLC", "url": "https://www.atsiom.com" },
39+
"featureList": [
40+
"chmod calculator",
41+
"crontab expression builder",
42+
"CIDR subnet calculator",
43+
"RAID capacity planner",
44+
"sed command generator",
45+
"awk one-liner builder",
46+
"IP geolocation lookup",
47+
"DNS record lookup",
48+
"Unix epoch converter",
49+
"DNS propagation checker",
50+
"URL encoder and decoder",
51+
"Base64 encoder and decoder",
52+
"Regular expression tester",
53+
"JWT token decoder"
54+
]
55+
}
56+
</script>
57+
58+
<!-- Fonts -->
59+
<link rel="preconnect" href="https://fonts.googleapis.com" />
60+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
61+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" />
62+
763
<style>body { visibility: hidden; }</style>
864
<script>
965
(function () {

public/sitemap.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
33

4+
<url>
5+
<loc>https://syskit.atsiom.com/</loc>
6+
<changefreq>monthly</changefreq>
7+
<priority>1.0</priority>
8+
</url>
9+
410
<url>
511
<loc>https://syskit.atsiom.com/chmod</loc>
612
<changefreq>monthly</changefreq>

src/App.jsx

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect, useRef } from "react";
22
import atsiomLogo from "./assets/atsiom-logo.png";
3+
import Home from "./components/Home.jsx";
34
import ChmodCalculator from "./components/ChmodCalculator.jsx";
45
import CrontabCalculator from "./components/CrontabCalculator.jsx";
56
import CIDRCalculator from "./components/CIDRCalculator.jsx";
@@ -16,6 +17,51 @@ import RegexTester from "./components/RegexTester.jsx";
1617
import JwtDecoder from "./components/JwtDecoder.jsx";
1718
import Disclaimer from "./components/Disclaimer.jsx";
1819

20+
const SITE_URL = "https://syskit.atsiom.com";
21+
22+
const TOOL_META = {
23+
chmod: { title: "chmod calculator — syskit", desc: "Calculate Unix file permission bits and generate the chmod command. Set owner, group, and other permissions interactively." },
24+
cron: { title: "cron expression builder — syskit", desc: "Build cron expressions visually and preview the human-readable schedule. Supports standard 5-field crontab syntax." },
25+
cidr: { title: "CIDR subnet calculator — syskit", desc: "Subnet calculator — compute usable hosts, network address, broadcast, and full IP range from any CIDR notation." },
26+
raid: { title: "RAID calculator — syskit", desc: "Compute usable storage, fault tolerance, and efficiency for RAID 0, 1, 5, 6, and 10. Plan your storage configuration." },
27+
sed: { title: "sed command generator — syskit", desc: "Generate sed substitution and deletion commands interactively. Build one-liners without memorizing the syntax." },
28+
awk: { title: "awk one-liner builder — syskit", desc: "Build awk one-liners for field extraction and pattern filtering. Generate ready-to-run commands from a visual interface." },
29+
ipinfo: { title: "IP geolocation lookup — syskit", desc: "Geo-locate any IP address with ISP details and a live map. Look up city, country, ASN, and network info instantly." },
30+
nslookup: { title: "DNS lookup — syskit", desc: "Query A, AAAA, MX, TXT, NS, and CNAME records via DNS-over-HTTPS. No installation required — runs in your browser." },
31+
epoch: { title: "Unix epoch converter — syskit", desc: "Convert Unix timestamps to human-readable dates and back. Supports seconds, milliseconds, and custom time zones." },
32+
dnsprop: { title: "DNS propagation checker — syskit",desc: "Check DNS propagation across multiple global resolvers. See if your DNS changes have reached different regions." },
33+
urlencode: { title: "URL encoder / decoder — syskit", desc: "Percent-encode and decode URLs and query strings. Convert special characters for safe use in HTTP requests." },
34+
base64: { title: "Base64 encoder / decoder — syskit",desc: "Encode plain text to Base64 and decode Base64 strings. Supports Unicode input and output." },
35+
regex: { title: "regex tester — syskit", desc: "Test regular expressions with live match highlighting. Supports global, case-insensitive, multiline, and dotAll flags." },
36+
jwt: { title: "JWT decoder — syskit", desc: "Inspect JWT header and payload claims without verifying the signature. Decode tokens and view expiry, issuer, and custom claims." },
37+
disclaimer:{ title: "Disclaimer — syskit", desc: "Terms of use and disclaimer for syskit developer tools." },
38+
};
39+
40+
const HOME_META = {
41+
title: "syskit — developer tools for the browser",
42+
desc: "Free browser-based tools for developers and sysadmins. chmod calculator, cron builder, CIDR subnet calculator, RAID planner, regex tester, JWT decoder, Base64, URL encode, DNS lookup, and more. No sign-up, no tracking.",
43+
};
44+
45+
function updatePageMeta(toolId) {
46+
const meta = toolId ? TOOL_META[toolId] : null;
47+
const { title, desc } = meta ?? HOME_META;
48+
const path = toolId ? `/${toolId}` : "/";
49+
const url = SITE_URL + path;
50+
51+
document.title = title;
52+
53+
const setMeta = (sel, content) => { const el = document.querySelector(sel); if (el) el.setAttribute("content", content); };
54+
const setId = (id, val, attr = "content") => { const el = document.getElementById(id); if (el) el.setAttribute(attr, val); };
55+
56+
setMeta('meta[name="description"]', desc);
57+
setId("canonical", url, "href");
58+
setId("og-url", url);
59+
setId("og-title", title);
60+
setId("og-desc", desc);
61+
setId("tw-title", title);
62+
setId("tw-desc", desc);
63+
}
64+
1965
const TOOLS = [
2066
{ id: "chmod", label: "chmod", glyph: "rwx", Component: ChmodCalculator, badge: "permissions" },
2167
{ id: "cron", label: "crontab", glyph: "*/5", Component: CrontabCalculator, badge: "scheduler" },
@@ -36,10 +82,9 @@ const TOOLS = [
3682

3783
function getToolFromPath() {
3884
const segments = window.location.pathname.split("/").filter(Boolean);
85+
if (!segments.length) return null; // home
3986
const id = segments[segments.length - 1] ?? "";
40-
const fromPath = TOOLS.find((t) => t.id === id)?.id;
41-
if (fromPath) return fromPath;
42-
return localStorage.getItem("syskit-last-tool") ?? "chmod";
87+
return TOOLS.find((t) => t.id === id)?.id ?? null;
4388
}
4489

4590
const THEME_OPTIONS = [
@@ -127,6 +172,7 @@ function ThemeToggle({ theme, onChange }) {
127172
export default function App() {
128173
const [activeTool, setActiveTool] = useState(getToolFromPath);
129174
const [theme, setTheme] = useState(() => localStorage.getItem("syskit-theme") || "system");
175+
const [sidebarOpen, setSidebarOpen] = useState(false);
130176

131177
const resolveTheme = (t) =>
132178
t === "system"
@@ -151,12 +197,16 @@ export default function App() {
151197
}, [theme]);
152198

153199
useEffect(() => {
154-
localStorage.setItem("syskit-last-tool", activeTool);
155-
const segments = window.location.pathname.split("/").filter(Boolean);
156-
const current = segments[segments.length - 1] ?? "";
157-
if (current !== activeTool) {
158-
const base = segments.slice(0, -1).join("/");
159-
history.pushState({}, "", (base ? "/" + base : "") + "/" + activeTool);
200+
if (activeTool === null) {
201+
if (window.location.pathname !== "/") history.pushState({}, "", "/");
202+
} else {
203+
localStorage.setItem("syskit-last-tool", activeTool);
204+
const segments = window.location.pathname.split("/").filter(Boolean);
205+
const current = segments[segments.length - 1] ?? "";
206+
if (current !== activeTool) {
207+
const base = segments.slice(0, -1).join("/");
208+
history.pushState({}, "", (base ? "/" + base : "") + "/" + activeTool);
209+
}
160210
}
161211
}, [activeTool]);
162212

@@ -166,19 +216,28 @@ export default function App() {
166216
return () => window.removeEventListener("popstate", onPop);
167217
}, []);
168218

219+
useEffect(() => { updatePageMeta(activeTool); }, [activeTool]);
220+
169221

170222

171223
const scrollRef = useRef(null);
172224
useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = 0; }, [activeTool]);
173225

174226
const activeMeta = TOOLS.find((t) => t.id === activeTool);
175-
const ActiveComponent = activeMeta?.Component || ChmodCalculator;
227+
const ActiveComponent = activeMeta?.Component ?? null;
228+
229+
const selectTool = (id) => { setActiveTool(id); setSidebarOpen(false); };
176230

177231
return (
178232
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
179233

234+
{/* ── SIDEBAR OVERLAY (mobile) ── */}
235+
{sidebarOpen && (
236+
<div className="sidebar-overlay" onClick={() => setSidebarOpen(false)} />
237+
)}
238+
180239
{/* ── SIDEBAR ── */}
181-
<aside style={{
240+
<aside className={`app-sidebar${sidebarOpen ? " open" : ""}`} style={{
182241
width: "var(--sidebar-w)", flexShrink: 0,
183242
background: "var(--surface)", borderRight: "1px solid var(--border)",
184243
display: "flex", flexDirection: "column",
@@ -187,18 +246,31 @@ export default function App() {
187246

188247
{/* Brand */}
189248
<div style={{ height: "var(--header-h)", display: "flex", alignItems: "center", padding: "0 1.25rem", borderBottom: "1px solid var(--border)", flexShrink: 0 }}>
190-
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--lg)", fontWeight: 400, color: "var(--green)", letterSpacing: "0.04em" }}>syskit<span style={{ color: "var(--text-faint)" }}>:</span></span>
249+
<button onClick={() => selectTool(null)} style={{ background: "none", border: "none", padding: 0, cursor: "pointer" }}>
250+
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--lg)", fontWeight: 400, color: "var(--green)", letterSpacing: "0.04em" }}>
251+
syskit<span style={{ color: "var(--text-faint)" }}>:</span>
252+
</span>
253+
</button>
191254
</div>
192255

193256
{/* Nav items */}
194257
<div style={{ flex: 1, padding: "0.75rem 0.5rem", overflowY: "auto" }}>
258+
{/* Home link */}
259+
<button
260+
onClick={() => selectTool(null)}
261+
style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "9px 10px", border: `1px solid ${activeTool === null ? "var(--green-dim)" : "transparent"}`, background: activeTool === null ? "var(--green-bg)" : "transparent", borderRadius: 10, cursor: "pointer", textAlign: "left", transition: "all 0.13s", marginBottom: 6 }}
262+
>
263+
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--xs)", fontWeight: 400, color: activeTool === null ? "var(--green)" : "var(--text-faint)", background: activeTool === null ? "rgba(46,204,113,0.12)" : "var(--surface-2)", border: `1px solid ${activeTool === null ? "var(--green-dim)" : "var(--border)"}`, borderRadius: 8, padding: "2px 6px", flexShrink: 0, minWidth: 40, textAlign: "center", transition: "all 0.13s" }}>~/</span>
264+
<span style={{ fontSize: "var(--md)", fontWeight: 400, color: activeTool === null ? "var(--green)" : "var(--text-muted)", transition: "color 0.13s" }}>home</span>
265+
</button>
266+
195267
<div style={{ fontFamily: "var(--font-mono)", fontSize: "var(--xs)", fontWeight: 400, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--text-faint)", padding: "0.5rem 0.75rem 0.5rem", marginBottom: 4 }}>Tools</div>
196268
{TOOLS.map((tool) => {
197269
const isActive = activeTool === tool.id;
198270
return (
199271
<button
200272
key={tool.id}
201-
onClick={() => setActiveTool(tool.id)}
273+
onClick={() => selectTool(tool.id)}
202274
style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "9px 10px", border: `1px solid ${isActive ? "var(--green-dim)" : "transparent"}`, background: isActive ? "var(--green-bg)" : "transparent", borderRadius: 10, cursor: "pointer", textAlign: "left", transition: "all 0.13s", marginBottom: 2 }}
203275
>
204276
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--xs)", fontWeight: 400, color: isActive ? "var(--green)" : "var(--text-faint)", background: isActive ? "rgba(46,204,113,0.12)" : "var(--surface-2)", border: `1px solid ${isActive ? "var(--green-dim)" : "var(--border)"}`, borderRadius: 8, padding: "2px 6px", flexShrink: 0, minWidth: 40, textAlign: "center", transition: "all 0.13s" }}>
@@ -228,21 +300,47 @@ export default function App() {
228300
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0, height: "100vh", overflow: "hidden" }}>
229301

230302
{/* Header */}
231-
<header style={{ height: "var(--header-h)", flexShrink: 0, background: "var(--surface)", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", padding: "0 2.5rem", gap: 10 }}>
232-
<span style={{ fontSize: "var(--md)", fontWeight: 600, color: "var(--text)" }}>
233-
{activeMeta?.label}
234-
</span>
235-
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--xs)", color: "var(--text-faint)", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8, padding: "2px 9px" }}>
236-
{activeMeta?.badge}
237-
</span>
303+
<header className="app-header" style={{ height: "var(--header-h)", flexShrink: 0, background: "var(--surface)", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", padding: "0 2.5rem", gap: 10 }}>
304+
305+
{/* LEFT — hamburger + brand (mobile only, CSS shows these) */}
306+
<button
307+
className="hamburger-btn"
308+
onClick={() => setSidebarOpen((v) => !v)}
309+
style={{ display: "none", alignItems: "center", justifyContent: "center", width: 34, height: 34, background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8, flexShrink: 0 }}
310+
>
311+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
312+
<line x1="3" y1="6" x2="21" y2="6"/>
313+
<line x1="3" y1="12" x2="21" y2="12"/>
314+
<line x1="3" y1="18" x2="21" y2="18"/>
315+
</svg>
316+
</button>
317+
<button className="mobile-brand" onClick={() => selectTool(null)}
318+
style={{ display: "none", background: "none", border: "none", padding: 0, fontFamily: "var(--font-mono)", fontSize: "var(--lg)", fontWeight: 400, color: "var(--green)", letterSpacing: "0.04em", cursor: "pointer" }}>
319+
syskit<span style={{ color: "var(--text-faint)" }}>:</span>
320+
</button>
321+
322+
{/* CENTER — tool name + badge, desktop only, only when a tool is active */}
323+
{activeTool !== null && activeMeta && (
324+
<span className="header-tool-info">
325+
<span style={{ fontSize: "var(--md)", fontWeight: 600, color: "var(--text)" }}>{activeMeta.label}</span>
326+
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--xs)", color: "var(--text-faint)", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8, padding: "2px 9px" }}>
327+
{activeMeta.badge}
328+
</span>
329+
</span>
330+
)}
331+
332+
{/* RIGHT — spacer + theme toggle */}
238333
<div style={{ flex: 1 }} />
239334
<ThemeToggle theme={theme} onChange={setTheme} />
240335
</header>
241336

242337
{/* Scrollable content */}
243-
<div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: "2rem 2.5rem 0" }}>
338+
<div ref={scrollRef} className="app-content" style={{ flex: 1, overflowY: "auto", padding: "2rem 2.5rem 0" }}>
244339
<div style={{ width: "100%", maxWidth: 960, margin: "0 auto", paddingBottom: "5rem" }}>
245-
<ActiveComponent key={activeTool} />
340+
{activeTool === null
341+
? <Home key="home" tools={TOOLS} onSelect={selectTool} />
342+
: ActiveComponent ? <ActiveComponent key={activeTool} /> : null
343+
}
246344
</div>
247345
</div>
248346
</div>

0 commit comments

Comments
 (0)