Add custom 404 page and Apache .htaccess
404.html reuses the site's hero/ledger patterns: a descending candle series that breaks its stop line, and a list linking to each front-page section. .htaccess wires ErrorDocument to it, plus compression, caching, and security headers for Apache hosts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
# ForexSignalz — Apache config for the static site.
|
||||||
|
# Only used on Apache hosts (cPanel, shared hosting, LAMP boxes).
|
||||||
|
# Netlify / GitHub Pages / Cloudflare Pages ignore this file — they serve
|
||||||
|
# 404.html automatically because of its filename.
|
||||||
|
|
||||||
|
# ── Custom error page ─────────────────────────────────────────
|
||||||
|
# Path is absolute from the document root. If you deploy the site into a
|
||||||
|
# subdirectory (e.g. /signals/), change this to /signals/404.html.
|
||||||
|
ErrorDocument 404 /404.html
|
||||||
|
|
||||||
|
DirectoryIndex index.html
|
||||||
|
|
||||||
|
# Don't list directory contents when there's no index file.
|
||||||
|
Options -Indexes
|
||||||
|
|
||||||
|
AddDefaultCharset UTF-8
|
||||||
|
|
||||||
|
# ── Redirect www → apex and force HTTPS ───────────────────────
|
||||||
|
# Uncomment once the domain has a valid certificate. Skip both rules if your
|
||||||
|
# host already terminates TLS and redirects in front of Apache — otherwise you
|
||||||
|
# can end up in a redirect loop.
|
||||||
|
# <IfModule mod_rewrite.c>
|
||||||
|
# RewriteEngine On
|
||||||
|
# RewriteCond %{HTTPS} off
|
||||||
|
# RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||||
|
# RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||||
|
# RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
|
||||||
|
# </IfModule>
|
||||||
|
|
||||||
|
# ── Compression ───────────────────────────────────────────────
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/css text/plain
|
||||||
|
AddOutputFilterByType DEFLATE application/javascript text/javascript
|
||||||
|
AddOutputFilterByType DEFLATE image/svg+xml application/json
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# ── Caching ───────────────────────────────────────────────────
|
||||||
|
# HTML stays fresh so edits go live immediately; assets are cached hard.
|
||||||
|
# style.css is cached for a week — bump it to style.css?v=2 in the HTML if you
|
||||||
|
# need a change to land for returning visitors sooner than that.
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresDefault "access plus 1 week"
|
||||||
|
ExpiresByType text/html "access plus 0 seconds"
|
||||||
|
ExpiresByType text/css "access plus 1 week"
|
||||||
|
ExpiresByType image/svg+xml "access plus 1 month"
|
||||||
|
ExpiresByType image/x-icon "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
<FilesMatch "\.html$">
|
||||||
|
Header set Cache-Control "no-cache, must-revalidate"
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# ── Security headers ────────────────────────────────────────
|
||||||
|
Header set X-Content-Type-Options "nosniff"
|
||||||
|
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
Header set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# ── Hide dotfiles (.htaccess, .gitignore, .git/) from the web ─
|
||||||
|
<IfModule mod_authz_core.c>
|
||||||
|
<FilesMatch "^\.">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
RedirectMatch 404 /\.git
|
||||||
@@ -696,6 +696,80 @@ html.js .never-list.in li:nth-child(5) { transition-delay: 0.45s; }
|
|||||||
html.js .anat-fig .anat-line { transform: none; transition: none; }
|
html.js .anat-fig .anat-line { transform: none; transition: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 404 ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
a.topbar-brand { text-decoration: none; }
|
||||||
|
|
||||||
|
.hero-404 { padding-bottom: clamp(9rem, 20vw, 13rem); }
|
||||||
|
|
||||||
|
/* the broken stop-loss: red is the risk colour, so the 404 wears it */
|
||||||
|
.hero-404 h1 .risk-line {
|
||||||
|
display: block;
|
||||||
|
background: linear-gradient(100deg, #A83E2E 0%, var(--risk-soft) 45%, var(--risk) 80%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-404 .stop-line {
|
||||||
|
transform-box: fill-box;
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
transform: scaleX(0);
|
||||||
|
animation: sweep-stop 1.1s cubic-bezier(0.2, 0.7, 0.3, 1) 0.7s forwards;
|
||||||
|
}
|
||||||
|
@keyframes sweep-stop { to { transform: scaleX(1); } }
|
||||||
|
|
||||||
|
.hero-fineness.err {
|
||||||
|
color: var(--risk-soft);
|
||||||
|
border-color: rgba(212, 96, 76, 0.25);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.hero-fineness.err .mono { color: var(--dim); }
|
||||||
|
|
||||||
|
.hero-404 .cta-fine a { color: var(--gold); }
|
||||||
|
|
||||||
|
/* Where-to-go list: same ledger rhythm, but each row is a link */
|
||||||
|
.lost-list {
|
||||||
|
list-style: none;
|
||||||
|
border-top: 1px solid var(--rule);
|
||||||
|
max-width: 42em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lost-list a {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 230px 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 1.1rem 0.5rem 1.1rem 0;
|
||||||
|
border-bottom: 1px solid var(--rule);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.15s, padding-left 0.15s;
|
||||||
|
}
|
||||||
|
.lost-list a:hover {
|
||||||
|
background: rgba(217, 174, 82, 0.05);
|
||||||
|
padding-left: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lost-key {
|
||||||
|
font-family: "Archivo", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: 85%;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
color: var(--gold-bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lost-note {
|
||||||
|
font-family: "Source Serif 4", Georgia, serif;
|
||||||
|
color: var(--dim);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.hero-404 .stop-line { transform: none; animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Responsive ──────────────────────────────────────────── */
|
/* ── Responsive ──────────────────────────────────────────── */
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
@@ -710,4 +784,8 @@ html.js .never-list.in li:nth-child(5) { transition-delay: 0.45s; }
|
|||||||
.topbar-note { display: none; }
|
.topbar-note { display: none; }
|
||||||
.hero-fineness { flex-direction: column; gap: 0.3rem; }
|
.hero-fineness { flex-direction: column; gap: 0.3rem; }
|
||||||
.hero { padding-bottom: 9rem; }
|
.hero { padding-bottom: 9rem; }
|
||||||
|
/* the 404 chart is tallest at its left edge, which is on-screen at this
|
||||||
|
width — needs more clearance than the rising hero chart */
|
||||||
|
.hero-404 { padding-bottom: 13rem; }
|
||||||
|
.lost-list a { grid-template-columns: 1fr; gap: 0.2rem; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user