Skip to content

Commit d35de5a

Browse files
committed
feat(landing): deep SEO optimization
- Hreflang tags for uk/en/de + x-default - FAQPage JSON-LD structured data (Google rich snippets) - Sticky navigation bar with active section highlighting - Nav i18n keys for UA/EN/DE - Caddyfile: gzip compression, static asset caching (7d), no-cache HTML - Custom 404 error page with SecureShare branding - Heading hierarchy verified: H1>H2>H3>H4 correct
1 parent 86fc558 commit d35de5a

4 files changed

Lines changed: 257 additions & 0 deletions

File tree

‎server/Caddyfile‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,27 @@ secureshare-relay.duckdns.org {
7070
}
7171
}
7272

73+
# Custom 404 error page
74+
handle_errors {
75+
@404 expression {err.status_code} == 404
76+
handle @404 {
77+
root * /www
78+
rewrite * /404.html
79+
file_server
80+
}
81+
}
82+
7383
# Landing page — static site (everything else)
7484
handle {
7585
header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; img-src 'self'; connect-src 'self'"
86+
# Cache static assets (images, JS, icons)
87+
@static path *.png *.jpg *.jpeg *.gif *.webp *.ico *.js *.css *.svg *.woff2
88+
header @static Cache-Control "public, max-age=604800, immutable"
89+
# HTML — no cache (always fresh)
90+
@html path *.html /
91+
header @html Cache-Control "no-cache"
92+
# Compression
93+
encode gzip
7694
root * /www
7795
file_server
7896
}

‎server/www/404.html‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>404 — Page Not Found | SecureShare</title>
7+
<meta name="robots" content="noindex">
8+
<style>
9+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10+
body {
11+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12+
min-height: 100vh;
13+
display: flex;
14+
align-items: center;
15+
justify-content: center;
16+
background: linear-gradient(135deg, #0B1929 0%, #132F4C 50%, #1a3a5c 100%);
17+
color: #fff;
18+
text-align: center;
19+
padding: 24px;
20+
}
21+
.wrap { max-width: 480px; }
22+
.code {
23+
font-size: clamp(5rem, 15vw, 8rem);
24+
font-weight: 800;
25+
letter-spacing: -.04em;
26+
background: linear-gradient(135deg, #64B5F6, #43A047);
27+
-webkit-background-clip: text;
28+
-webkit-text-fill-color: transparent;
29+
background-clip: text;
30+
line-height: 1;
31+
margin-bottom: 16px;
32+
}
33+
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 12px; }
34+
p { color: rgba(255,255,255,.6); font-size: 1rem; line-height: 1.6; margin-bottom: 32px; }
35+
.btn {
36+
display: inline-flex;
37+
align-items: center;
38+
gap: 8px;
39+
padding: 14px 32px;
40+
border-radius: 50px;
41+
font-size: 1rem;
42+
font-weight: 600;
43+
background: linear-gradient(135deg, #43A047, #66BB6A);
44+
color: #fff;
45+
text-decoration: none;
46+
transition: all .2s;
47+
box-shadow: 0 4px 20px rgba(67,160,71,.4);
48+
}
49+
.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 28px rgba(67,160,71,.5); }
50+
.btn svg { width: 18px; height: 18px; }
51+
</style>
52+
</head>
53+
<body>
54+
<div class="wrap">
55+
<div class="code">404</div>
56+
<h1>Page not found</h1>
57+
<p>The page you're looking for doesn't exist or has been moved. Let's get you back on track.</p>
58+
<a href="/" class="btn">
59+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
60+
Go to Homepage
61+
</a>
62+
</div>
63+
</body>
64+
</html>

‎server/www/i18n.js‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@
116116
support_dev_desc: "New platforms and features",
117117
support_btn: "Buy me a coffee",
118118
support_note: "You can also star us on <a href=\"https://github.com/artmarchenko/SecureShare\" target=\"_blank\" rel=\"noopener\">GitHub</a> \u2014 it\u2019s free and very motivating!",
119+
nav_features: "Features",
120+
nav_how: "How it works",
121+
nav_preview: "Demo",
122+
nav_security: "Security",
123+
nav_faq: "FAQ",
124+
nav_changelog: "Versions",
125+
nav_support: "Support",
119126
footer_faq: "FAQ",
120127
footer_docs: "Documentation",
121128
footer_devs: "For developers",
@@ -239,6 +246,13 @@
239246
support_dev_desc: "Neue Plattformen und Features",
240247
support_btn: "Einen Kaffee spendieren",
241248
support_note: "Sie k\u00f6nnen uns auch einen Stern auf <a href=\"https://github.com/artmarchenko/SecureShare\" target=\"_blank\" rel=\"noopener\">GitHub</a> geben \u2014 das ist kostenlos und sehr motivierend!",
249+
nav_features: "Funktionen",
250+
nav_how: "Wie es funktioniert",
251+
nav_preview: "Demo",
252+
nav_security: "Sicherheit",
253+
nav_faq: "FAQ",
254+
nav_changelog: "Versionen",
255+
nav_support: "Unterst\u00fctzung",
242256
footer_faq: "FAQ",
243257
footer_docs: "Dokumentation",
244258
footer_devs: "F\u00fcr Entwickler",
@@ -302,6 +316,33 @@
302316
try { saved = localStorage.getItem('ss_lang') || 'uk'; } catch(e) {}
303317
if (saved !== 'uk') applyLang(saved);
304318

319+
/* ── Sticky Nav: scroll shadow + active link ──── */
320+
var siteNav = document.getElementById('siteNav');
321+
var navLinks = siteNav ? siteNav.querySelectorAll('a[href^="#"]') : [];
322+
var navSections = [];
323+
for (var n = 0; n < navLinks.length; n++) {
324+
var target = document.querySelector(navLinks[n].getAttribute('href'));
325+
if (target) navSections.push({ link: navLinks[n], el: target });
326+
}
327+
328+
function updateNav() {
329+
if (siteNav) {
330+
if (window.scrollY > 10) siteNav.classList.add('scrolled');
331+
else siteNav.classList.remove('scrolled');
332+
}
333+
var scrollPos = window.scrollY + 120;
334+
var activeIdx = 0;
335+
for (var s = 0; s < navSections.length; s++) {
336+
if (navSections[s].el.offsetTop <= scrollPos) activeIdx = s;
337+
}
338+
for (var s = 0; s < navSections.length; s++) {
339+
if (s === activeIdx) navSections[s].link.classList.add('active');
340+
else navSections[s].link.classList.remove('active');
341+
}
342+
}
343+
window.addEventListener('scroll', updateNav, { passive: true });
344+
updateNav();
345+
305346
/* ── Theme Toggle (dark / light) ─────────────── */
306347
var themeBtn = document.getElementById('themeToggle');
307348
var iconSun = themeBtn ? themeBtn.querySelector('.icon-sun') : null;

‎server/www/index.html‎

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<meta name="robots" content="index, follow">
1010
<meta name="theme-color" content="#0B1929">
1111
<link rel="canonical" href="https://secureshare-relay.duckdns.org/">
12+
<!-- Hreflang (multilingual single-page) -->
13+
<link rel="alternate" hreflang="uk" href="https://secureshare-relay.duckdns.org/">
14+
<link rel="alternate" hreflang="en" href="https://secureshare-relay.duckdns.org/">
15+
<link rel="alternate" hreflang="de" href="https://secureshare-relay.duckdns.org/">
16+
<link rel="alternate" hreflang="x-default" href="https://secureshare-relay.duckdns.org/">
1217
<!-- Open Graph -->
1318
<meta property="og:title" content="SecureShare — Безпечна передача файлів">
1419
<meta property="og:description" content="E2E шифрування. Без реєстрації. Без хмари. До 5 ГБ.">
@@ -63,6 +68,62 @@
6368
]
6469
}
6570
</script>
71+
<script type="application/ld+json">
72+
{
73+
"@context": "https://schema.org",
74+
"@type": "FAQPage",
75+
"mainEntity": [
76+
{
77+
"@type": "Question",
78+
"name": "Are my files really encrypted?",
79+
"acceptedAnswer": {
80+
"@type": "Answer",
81+
"text": "Yes. Every file is encrypted on the sender's device using AES-256-GCM with a unique key generated via X25519. The relay server only sees encrypted data and has no way to decrypt it."
82+
}
83+
},
84+
{
85+
"@type": "Question",
86+
"name": "What is the maximum file size?",
87+
"acceptedAnswer": {
88+
"@type": "Answer",
89+
"text": "Up to 5 GB. Files are transferred in 256 KB chunks with interrupted transfer resumption — even if the connection drops, you don't need to start over."
90+
}
91+
},
92+
{
93+
"@type": "Question",
94+
"name": "Is registration required?",
95+
"acceptedAnswer": {
96+
"@type": "Answer",
97+
"text": "No. Just download the app, choose a file, and share the session code with the recipient. No account, email, or phone needed."
98+
}
99+
},
100+
{
101+
"@type": "Question",
102+
"name": "What is the verification code?",
103+
"acceptedAnswer": {
104+
"@type": "Answer",
105+
"text": "After connecting, both participants see the same 8-character code. If the codes match — the connection is secure. This protects against man-in-the-middle (MITM) attacks."
106+
}
107+
},
108+
{
109+
"@type": "Question",
110+
"name": "Which operating systems are supported?",
111+
"acceptedAnswer": {
112+
"@type": "Answer",
113+
"text": "Windows 10/11 and Linux (x64). The app requires no installation — just download and run."
114+
}
115+
},
116+
{
117+
"@type": "Question",
118+
"name": "Are files stored on the server?",
119+
"acceptedAnswer": {
120+
"@type": "Answer",
121+
"text": "No. The relay server only forwards encrypted packets between participants in real time. After the transfer is complete — zero traces. Session codes are hashed and not logged."
122+
}
123+
}
124+
]
125+
}
126+
</script>
66127
<style>
67128
/* ── Reset & Base ─────────────────────────────── */
68129
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -190,6 +251,66 @@
190251
.btn-secondary:hover { background: rgba(255,255,255,.15); }
191252
.hero-buttons { display: flex; justify-content: center; gap: 16px; flex-wrap: wrap; }
192253

254+
/* ── Sticky Nav ───────────────────────────────── */
255+
.site-nav {
256+
position: sticky;
257+
top: 0;
258+
z-index: 900;
259+
background: rgba(255,255,255,.92);
260+
backdrop-filter: blur(12px);
261+
-webkit-backdrop-filter: blur(12px);
262+
border-bottom: 1px solid rgba(0,0,0,.06);
263+
padding: 0;
264+
transition: box-shadow .2s;
265+
}
266+
.site-nav.scrolled {
267+
box-shadow: 0 2px 16px rgba(0,0,0,.08);
268+
}
269+
[data-theme="dark"] .site-nav {
270+
background: rgba(15,18,24,.92);
271+
border-bottom-color: rgba(255,255,255,.06);
272+
}
273+
[data-theme="dark"] .site-nav.scrolled {
274+
box-shadow: 0 2px 16px rgba(0,0,0,.4);
275+
}
276+
.nav-inner {
277+
display: flex;
278+
justify-content: center;
279+
gap: 8px;
280+
overflow-x: auto;
281+
scrollbar-width: none;
282+
-ms-overflow-style: none;
283+
padding: 0 8px;
284+
}
285+
.nav-inner::-webkit-scrollbar { display: none; }
286+
.nav-inner a {
287+
padding: 14px 16px;
288+
font-size: .85rem;
289+
font-weight: 500;
290+
color: var(--gray);
291+
white-space: nowrap;
292+
text-decoration: none;
293+
transition: color .2s;
294+
border-bottom: 2px solid transparent;
295+
}
296+
.nav-inner a:hover {
297+
color: var(--blue);
298+
text-decoration: none;
299+
}
300+
.nav-inner a.active {
301+
color: var(--blue);
302+
border-bottom-color: var(--blue);
303+
font-weight: 600;
304+
}
305+
[data-theme="dark"] .nav-inner a { color: #94A3B8; }
306+
[data-theme="dark"] .nav-inner a:hover,
307+
[data-theme="dark"] .nav-inner a.active { color: #64B5F6; border-bottom-color: #64B5F6; }
308+
309+
@media (max-width: 768px) {
310+
.nav-inner { gap: 0; }
311+
.nav-inner a { padding: 12px 10px; font-size: .78rem; }
312+
}
313+
193314
/* ── Features ──────────────────────────────────── */
194315
.features {
195316
padding: 100px 0;
@@ -1057,6 +1178,19 @@ <h1>Secure<span>Share</span></h1>
10571178
</div>
10581179
</section>
10591180

1181+
<!-- Sticky Navigation (SEO + UX) -->
1182+
<nav class="site-nav" id="siteNav" aria-label="Main navigation">
1183+
<div class="container nav-inner">
1184+
<a href="#features" data-i18n="nav_features">Можливості</a>
1185+
<a href="#how" data-i18n="nav_how">Як працює</a>
1186+
<a href="#preview" data-i18n="nav_preview">Демо</a>
1187+
<a href="#security" data-i18n="nav_security">Безпека</a>
1188+
<a href="#faq" data-i18n="nav_faq">FAQ</a>
1189+
<a href="#changelog" data-i18n="nav_changelog">Версії</a>
1190+
<a href="#support" data-i18n="nav_support">Підтримка</a>
1191+
</div>
1192+
</nav>
1193+
10601194
<!-- Features -->
10611195
<section class="features" id="features">
10621196
<div class="container">

0 commit comments

Comments
 (0)