Skip to content

Commit 3bf11bb

Browse files
committed
added base64 decoder
1 parent 5c05ec1 commit 3bf11bb

3 files changed

Lines changed: 194 additions & 0 deletions

File tree

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# syskit
2+
3+
A collection of system and developer utilities built as a fast, lightweight web app. No backend, no tracking — everything runs in the browser.
4+
5+
**Live:** [syskit.atsiom.com](https://syskit.atsiom.com)
6+
7+
---
8+
9+
## Tools
10+
11+
| Tool | Description |
12+
|---|---|
13+
| **chmod** | Calculate Unix file permission bits with octal and symbolic output |
14+
| **crontab** | Build and validate cron expressions with a human-readable schedule preview |
15+
| **CIDR** | Subnet calculator — usable hosts, network/broadcast address, address range |
16+
| **RAID** | Compute usable capacity, fault tolerance, and efficiency for common RAID levels |
17+
| **sed** | Generate sed substitution and deletion commands interactively |
18+
| **awk** | Build awk one-liners for field extraction and pattern filtering |
19+
| **IP info** | Look up geolocation and ASN info for any IP address |
20+
| **nslookup** | Query DNS records (A, AAAA, MX, TXT, NS, CNAME) via DNS-over-HTTPS |
21+
| **epoch** | Convert Unix timestamps to human-readable time and vice versa |
22+
| **DNS prop** | Check DNS propagation across multiple global resolvers |
23+
| **URL encode** | Percent-encode and decode URLs |
24+
| **Base64** | Encode and decode Base64 strings with full Unicode support |
25+
| **regex** | Test regular expressions with live match highlighting and group details |
26+
27+
---
28+
29+
## Development
30+
31+
**Prerequisites:** Node.js 18+
32+
33+
```bash
34+
# Install dependencies
35+
npm install
36+
37+
# Start dev server
38+
npm run dev
39+
40+
# Production build
41+
npm run build
42+
43+
# Preview production build locally
44+
npm run preview
45+
```
46+
47+
---
48+
49+
## Deployment
50+
51+
The app deploys automatically to GitHub Pages on every push to `main` via GitHub Actions.
52+
53+
To deploy manually:
54+
55+
```bash
56+
npm run gh-deploy
57+
```
58+
59+
This builds the app and pushes the `dist/` directory to the `gh-pages` branch.
60+
61+
---
62+
63+
## Tech Stack
64+
65+
- [React 18](https://react.dev)
66+
- [Vite 5](https://vitejs.dev)
67+
- DNS-over-HTTPS (DoH) for all DNS queries — no server needed
68+
69+
---
70+
71+
## License
72+
73+
MIT — see [LICENSE](LICENSE) for details.
74+
75+
---
76+
77+
*This app was built with [Claude Code](https://claude.ai/code). This README was also generated with Claude Code.*

src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import NsLookup from "./components/NsLookup.jsx";
1111
import EpochConverter from "./components/EpochConverter.jsx";
1212
import DnsPropagation from "./components/DnsPropagation.jsx";
1313
import UrlEncoder from "./components/UrlEncoder.jsx";
14+
import Base64Codec from "./components/Base64Codec.jsx";
1415
import RegexTester from "./components/RegexTester.jsx";
1516
import Disclaimer from "./components/Disclaimer.jsx";
1617

@@ -26,6 +27,7 @@ const TOOLS = [
2627
{ id: "epoch", label: "epoch", glyph: "ts", Component: EpochConverter, badge: "time" },
2728
{ id: "dnsprop", label: "DNS prop", glyph: "⇢", Component: DnsPropagation, badge: "checker" },
2829
{ id: "urlencode", label: "URL encode", glyph: "%20", Component: UrlEncoder, badge: "encoding" },
30+
{ id: "base64", label: "Base64", glyph: "b64", Component: Base64Codec, badge: "encoding" },
2931
{ id: "regex", label: "regex", glyph: ".*", Component: RegexTester, badge: "pattern" },
3032
{ id: "disclaimer", label: "Disclaimer", glyph: "§", Component: Disclaimer, badge: "legal" },
3133
];

src/components/Base64Codec.jsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { useState } from "react";
2+
import { Card, PageHeader, CopyButton, s } from "./shared/index.jsx";
3+
4+
const labelStyle = {
5+
display: "block", fontSize: "var(--xs)", fontFamily: "var(--font-mono)",
6+
textTransform: "uppercase", letterSpacing: "0.1em", color: "var(--text-faint)", marginBottom: 6,
7+
};
8+
9+
function ResultBox({ label, value, color, error }) {
10+
return (
11+
<div>
12+
<label style={labelStyle}>{label}</label>
13+
<div style={{
14+
background: "var(--surface-2)", border: `2px solid ${error ? "var(--red-dim)" : "var(--border-2)"}`,
15+
borderRadius: 10, padding: "12px 14px",
16+
display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 10, minHeight: 52,
17+
}}>
18+
<span style={{
19+
fontFamily: "var(--font-mono)", fontSize: "var(--sm)", fontWeight: 500,
20+
color: error ? "var(--red)" : value ? color : "var(--text-faint)",
21+
wordBreak: "break-all", flex: 1, lineHeight: 1.6,
22+
}}>
23+
{value || "—"}
24+
</span>
25+
{value && !error && <CopyButton text={value} />}
26+
</div>
27+
</div>
28+
);
29+
}
30+
31+
export default function Base64Codec() {
32+
const [encoded, setEncoded] = useState("");
33+
const [decoded, setDecoded] = useState("");
34+
35+
const decodedResult = (() => {
36+
if (!encoded) return "";
37+
try {
38+
return decodeURIComponent(escape(atob(encoded)));
39+
} catch {
40+
return "Error: invalid Base64 input";
41+
}
42+
})();
43+
44+
const encodedResult = (() => {
45+
if (!decoded) return "";
46+
try {
47+
return btoa(unescape(encodeURIComponent(decoded)));
48+
} catch {
49+
return "Error: unable to encode input";
50+
}
51+
})();
52+
53+
const decodeError = decodedResult.startsWith("Error:");
54+
const encodeError = encodedResult.startsWith("Error:");
55+
56+
return (
57+
<div className="page-enter">
58+
<PageHeader title="Base64 encode / decode" badge="encoding" description="Decode Base64 strings to plain text or encode plain text to Base64." />
59+
60+
<Card title="Decode">
61+
<label style={labelStyle}>Base64 string</label>
62+
<textarea
63+
value={encoded}
64+
onChange={(e) => setEncoded(e.target.value)}
65+
rows={3}
66+
placeholder="Paste a Base64-encoded string…"
67+
style={{ ...s.monoInput, resize: "vertical", fontSize: "var(--sm)", color: "var(--amber)" }}
68+
/>
69+
<div style={{ display: "flex", gap: 8, marginTop: 10, marginBottom: "1.2rem" }}>
70+
<button onClick={() => setEncoded("")}
71+
style={{ ...s.presetBtn, padding: "6px 14px", fontSize: "var(--xs)" }}>
72+
Clear
73+
</button>
74+
{decodedResult && !decodeError && (
75+
<button onClick={() => { setDecoded(decodedResult); setEncoded(""); }}
76+
style={{ ...s.presetBtn, padding: "6px 14px", fontSize: "var(--xs)" }}>
77+
Use as encode input
78+
</button>
79+
)}
80+
</div>
81+
82+
<div style={{ height: 1, background: "var(--border)", marginBottom: "1.2rem" }} />
83+
84+
<ResultBox label="Decoded output" value={decodedResult} color="var(--green)" error={decodeError} />
85+
</Card>
86+
87+
<Card title="Encode">
88+
<label style={labelStyle}>Plain text</label>
89+
<textarea
90+
value={decoded}
91+
onChange={(e) => setDecoded(e.target.value)}
92+
rows={3}
93+
placeholder="Paste plain text to encode as Base64…"
94+
style={{ ...s.monoInput, resize: "vertical", fontSize: "var(--sm)", color: "var(--teal)" }}
95+
/>
96+
<div style={{ display: "flex", gap: 8, marginTop: 10, marginBottom: "1.2rem" }}>
97+
<button onClick={() => setDecoded("")}
98+
style={{ ...s.presetBtn, padding: "6px 14px", fontSize: "var(--xs)" }}>
99+
Clear
100+
</button>
101+
{encodedResult && !encodeError && (
102+
<button onClick={() => { setEncoded(encodedResult); setDecoded(""); }}
103+
style={{ ...s.presetBtn, padding: "6px 14px", fontSize: "var(--xs)" }}>
104+
Use as decode input
105+
</button>
106+
)}
107+
</div>
108+
109+
<div style={{ height: 1, background: "var(--border)", marginBottom: "1.2rem" }} />
110+
111+
<ResultBox label="Encoded output" value={encodedResult} color="var(--amber)" error={encodeError} />
112+
</Card>
113+
</div>
114+
);
115+
}

0 commit comments

Comments
 (0)