Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/defaults/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Package defaults embeds the built-in static assets (index.html, 404.html,
// style.css) that ship with the binary. These are used as a fallback when the
// configured files.root directory does not contain those files, ensuring that
// the server always has sensible default pages regardless of deployment layout.
//
// Embedded asset paths use the "public/" prefix, e.g.:
//
// data, err := fs.ReadFile(defaults.FS, "public/index.html")
package defaults

import "embed"

// FS holds the embedded public/ directory tree.
// Consumers should read files via fs.ReadFile(FS, "public/<name>") where
// <name> is one of: index.html, 404.html, style.css.
//
//go:embed public/index.html public/404.html public/style.css
var FS embed.FS
54 changes: 54 additions & 0 deletions internal/defaults/public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 — Not Found</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>

<div class="line">
<span class="prompt">$</span>
<span>GET <span class="path-val">…</span></span>
</div>

<div class="line spacer">
<span class="err">✗</span>
<span><strong>404</strong> — resource not found</span>
</div>

<div class="block block-error">
<div class="block-title">// error details</div>
<div class="kv">
<span class="key">status</span>
<span class="val err">404 Not Found</span>
</div>
<div class="kv">
<span class="key">hint</span>
<span class="val dim">the file does not exist or was moved</span>
</div>
<div class="kv">
<span class="key">tip</span>
<span class="val dim">double-check the URL for typos</span>
</div>
</div>

<div class="actions">
<a href="/" class="btn">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
</svg>
~/
</a>
</div>

<hr class="section-divider" />

<div class="footer-line">
Made with ❤️ by <a href="https://21no.de">21no.de</a>
&mdash; <a href="https://github.com/BackendStack21/static-web">static-web</a>
</div>

</body>
</html>
80 changes: 80 additions & 0 deletions internal/defaults/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>static-web</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>

<div class="line">
<span class="prompt">$</span>
<span>static-web start<span class="cursor"></span></span>
</div>

<div class="line spacer">
<span class="ok">✓</span>
<span>server is <strong>running</strong><span class="tag">live</span></span>
</div>

<div class="block">
<div class="block-title">// server info</div>
<div class="kv">
<span class="key">project</span>
<span class="val"><a href="https://github.com/BackendStack21/static-web">21no.de/static-web</a></span>
</div>
<div class="kv">
<span class="key">language</span>
<span class="val info">Go</span>
</div>
<div class="kv">
<span class="key">listen</span>
<span class="val">:8080</span>
</div>
<div class="kv">
<span class="key">serving</span>
<span class="val dim">./public/</span>
</div>
</div>

<div class="block">
<div class="block-title">// replace this page</div>
<div class="kv">
<span class="key">step 1</span>
<span class="val dim">drop your files into <span class="val">./public/</span></span>
</div>
<div class="kv">
<span class="key">step 2</span>
<span class="val dim">your <span class="val">index.html</span> replaces this page</span>
</div>
<div class="kv">
<span class="key">config</span>
<span class="val dim">edit <span class="val">config.toml</span> to customize</span>
</div>
</div>

<div class="block">
<div class="block-title">// capabilities</div>
<div class="kv"><span class="key ok">✓ cache</span> <span class="val dim">in-memory LRU, configurable TTL</span></div>
<div class="kv"><span class="key ok">✓ compress</span> <span class="val dim">gzip on-the-fly + pre-compressed sidecar files</span></div>
<div class="kv"><span class="key ok">✓ tls</span> <span class="val dim">TLS 1.2 / 1.3, HTTP/2 via ALPN</span></div>
<div class="kv"><span class="key ok">✓ headers</span> <span class="val dim">ETag, Cache-Control, CSP, CORS, HSTS</span></div>
<div class="kv"><span class="key ok">✓ security</span> <span class="val dim">dotfile blocking, security headers</span></div>
<div class="kv"><span class="key ok">✓ graceful</span> <span class="val dim">SIGHUP reload · SIGTERM drain &amp; shutdown</span></div>
</div>

<hr class="section-divider" />

<div class="line">
<span class="dim">#</span>
<span class="dim">docs &amp; source →</span>
<a href="https://github.com/BackendStack21/static-web">github.com/BackendStack21/static-web</a>
</div>

<div class="footer-line">
Made with ❤️ by <a href="https://21no.de">21no.de</a>
</div>

</body>
</html>
185 changes: 185 additions & 0 deletions internal/defaults/public/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #e6edf3;
--muted: #7d8590;
--green: #3fb950;
--cyan: #79c0ff;
--yellow: #e3b341;
--red: #f85149;
--mono: ui-monospace, "SF Mono", Menlo, "Cascadia Code", Consolas, monospace;
}

html, body { height: 100%; }

body {
font-family: var(--mono);
background: var(--bg);
color: var(--text);
font-size: 14px;
line-height: 1.6;
padding: 40px 24px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
min-height: 100vh;
max-width: 720px;
margin: 0 auto;
}

/* layout primitives */

.line {
display: flex;
align-items: baseline;
gap: 10px;
min-height: 1.6em;
}

.line + .line { margin-top: 2px; }
.spacer { margin-top: 20px; }

/* colour tokens */

.prompt { color: var(--muted); user-select: none; }
.ok { color: var(--green); }
.info { color: var(--cyan); }
.warn { color: var(--yellow); }
.err { color: var(--red); }
.dim { color: var(--muted); }

/* info block */

.block {
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--border);
border-radius: 6px;
padding: 16px 20px;
margin-top: 24px;
width: 100%;
}

.block.block-error {
border-left-color: var(--red);
}

.block-title {
color: var(--muted);
font-size: 11px;
letter-spacing: .08em;
text-transform: uppercase;
margin-bottom: 12px;
}

.kv {
display: flex;
gap: 12px;
line-height: 1.9;
}

.key {
color: var(--muted);
flex-shrink: 0;
min-width: 120px;
}

.val { color: var(--text); }

.path-val {
color: var(--red);
word-break: break-all;
}

/* tag / badge */

.tag {
display: inline-block;
background: rgba(63,185,80,.12);
color: var(--green);
border: 1px solid rgba(63,185,80,.25);
border-radius: 4px;
padding: 1px 7px;
font-size: 12px;
vertical-align: middle;
margin-left: 6px;
}

/* cursor */

.cursor {
display: inline-block;
width: 9px;
height: 1.1em;
background: var(--green);
vertical-align: text-bottom;
animation: blink 1.1s step-start infinite;
margin-left: 2px;
}

@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}

/* buttons */

.actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 24px;
}

.btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid var(--border);
border-radius: 6px;
padding: 7px 14px;
font-family: var(--mono);
font-size: 13px;
color: var(--text);
text-decoration: none;
background: var(--surface);
cursor: pointer;
transition: border-color .12s, color .12s;
}

.btn:hover {
border-color: var(--cyan);
color: var(--cyan);
}

/* divider / footer */

.section-divider {
border: none;
border-top: 1px solid var(--border);
margin: 28px 0;
width: 100%;
}

.footer-line {
color: var(--muted);
font-size: 12px;
}

a {
color: var(--cyan);
text-decoration: none;
}

a:hover { text-decoration: underline; }

/* responsive */

@media (max-width: 540px) {
body { font-size: 13px; padding: 24px 16px; }
.key { min-width: 90px; }
}
Loading