- HTML 45.5%
- CSS 24.3%
- Python 21.2%
- PHP 9%
| deploy | ||
| en | ||
| icons | ||
| .gitignore | ||
| avatar.png | ||
| avatar.webp | ||
| CHANGELOG.md | ||
| favicon.ico | ||
| index.html | ||
| LICENSE.IT.md | ||
| LICENSE.md | ||
| og-image.png | ||
| README.it-IT.md | ||
| README.md | ||
| readme.txt | ||
| robots.txt | ||
| sitemap.xml | ||
| style.css | ||
Contact Page - Static Version
Italiano | English
Minimal Linktree/Linkstack-style landing page, 100% static (plain HTML/CSS, zero JavaScript).
Source repository of the page published at me.emanuelegori.uno, with a copy at emanuelegori.alwaysdata.net.
Contents
- Features
- Quick Start
- Installation
- Deployment
- Customization
- Privacy
- Contributing
- Licenses
- Support
- Credits
- Changelog
Features
- Plain HTML/CSS - Zero JavaScript, maximum simplicity
- Modern design - Responsive dark mode with glassmorphism
- Privacy-first - No tracking, analytics or cookies
- Light and fast - Instant loading
- Self-hosted - Full control over your data
- Accessible - Works with JS disabled
- Bilingual - Italian and English page, with automatic redirect based on the browser language
- Light and dark theme - Follows the system, with a CSS-only sun/moon button
- SEO optimized - Open Graph meta tags, Twitter Cards, Schema.org,
hreflang - Fediverse verification -
<link rel="me">tags for the Mastodon green badge
Quick Start
# 1. Clone the repository
git clone https://git.emanuelegori.uno/emanuelegori/contact-page-static.git
cd contact-page-static
# 2. Customize the content (both languages)
nano index.html en/index.html
# 3. Publish on any web server
# Example: nginx, Apache, GitHub Pages, Netlify
Done! Your page is ready.
Installation
Requirements
- A web server (nginx, Apache, Caddy, etc.)
- Or static hosting (GitHub Pages, Netlify, Vercel, Cloudflare Pages)
- No runtime dependencies
Project Structure
contact-page-static/
├── index.html # Main page (Italian)
├── en/
│ └── index.html # English version
├── style.css # CSS styles
├── favicon.ico # Site favicon
├── avatar.png # Your profile picture (fallback, 240×240px)
├── avatar.webp # Same picture in WebP, served via <picture>
├── og-image.png # Social preview image (1200×630px)
├── robots.txt # Crawler directives + sitemap reference
├── sitemap.xml # XML sitemap with language alternates
├── icons/ # Link icons (SVG, PNG for raster logos)
├── deploy/ # Deploy scripts + nginx.conf (not published)
├── README.md # This file
├── README.it-IT.md # Italian documentation
├── CHANGELOG.md # Change history (Italian)
├── LICENSE.md # Code license (GPL v2)
└── LICENSE.IT.md # Unofficial Italian translation of the GPL v2
.md files, readme.txt and the deploy/ folder are not published by the
deploy scripts.
English version
/ is the Italian page, /en/ the English one. With deploy/nginx.conf,
visitors opening / with a browser whose preferred language is not Italian are
redirected to /en/; the IT link points to /?lang=it to stay on the Italian
page. Without that configuration (static hosting) both pages still work, just
without the automatic redirect. When you change a text, update it in both pages.
Light and dark theme
The theme follows the system setting (prefers-color-scheme). The sun/moon
button next to IT · EN is a hidden checkbox: when checked, :has() inverts the
system theme. No JavaScript or cookies, so the choice is not remembered when
reloading the page or switching language. In browsers without :has() the
button has no effect; the automatic theme still works.
Colors are variables in :root; the light theme redefines them in two identical
blocks in style.css (light system without the button, dark system with the
button): if you change a light-theme color, update both. A white monochrome logo
with class="icon-mono" turns black in the light theme.
Deployment
Option 1: Traditional Web Server
# Copy the files to the document root
sudo cp -r * /var/www/html/contact/
# Or with rsync
rsync -av --exclude='.git' * user@server:/var/www/html/contact/
nginx configuration (minimal; for the language redirect see deploy/nginx.conf):
server {
listen 80;
server_name me.yourdomain.com;
root /var/www/html/contact;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Option 2: Docker (optional)
Create a docker-compose.yml:
services:
contact-page:
image: nginx:alpine
container_name: contact-page-static
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./:/usr/share/nginx/html:ro
- ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- proxy
networks:
proxy:
external: true
Deploy:
docker compose up -d
Option 3: Free Static Hosting
The project works with:
- GitHub Pages - Free, custom domain
- Netlify - Automatic deploy from Git
- Vercel - Automatic optimizations
- Cloudflare Pages - Free global CDN
GitHub Pages example:
- Push to a GitHub repository
- Settings → Pages → Source:
mainbranch - Site available at
username.github.io/contact-page-static
Automatic deploy (deploy/ folder)
| Script | Where | How |
|---|---|---|
webhook-listener.py |
VPS with Docker | receives the Forgejo webhook on every push to main (HMAC-SHA256 signature) |
update-site.php |
shared hosting (Alwaysdata) | scheduled task: compares the SHA of the latest commit and updates when it changes |
update-site.py |
any server with Python | same as update-site.php, via cron |
All three copy the publishable files and update, in both pages, the "Last
update" date, the version (read from CHANGELOG.md), the copyright year and the
CSS ?v= parameter, which makes browsers download the new stylesheet with each
version. Environment variables in deploy/.env.example.
Customization
1. Edit Content (index.html and en/index.html)
<!-- Name and bio -->
<h1 class="name">Your Name</h1>
<p class="tagline">Your tagline</p>
<p class="bio">Your bio...</p>
<!-- Add/remove links: one card per <li> -->
<li>
<a href="your-url" class="link-card" data-category="blog" target="_blank" rel="noopener me">
<div class="link-icon">
<img src="icons/icon-name.svg" alt="" width="64" height="64" decoding="async">
</div>
<div class="link-name">Link Name</div>
<div class="link-description">Description</div>
</a>
</li>
The icon has alt="" because it is decorative: the card name is already in the
text. In en/index.html paths start with ../ (../icons/icon-name.svg).
2. Change Colors (style.css)
:root {
--bg-primary: #0a0a0a; /* Main background */
--bg-card: rgba(42, 42, 42, 0.6); /* Card background */
--text-primary: #ffffff; /* Main text */
--text-secondary: #b0b0b0; /* Secondary text */
--accent-blog: #ff7b00; /* Hover border, per category */
}
3. Add an Avatar
Upload avatar.png (or .jpg) to the project root. The HTML is already set up.
For better performance, resize it to 240×240px and add an avatar.webp next to it:
the <picture> tag serves WebP to browsers that support it and PNG to the others.
4. Custom Favicon
- Create your
favicon.ico(16x16, 32x32, 48x48 px) - Save it in the project root
- The link is already in
index.html:<link rel="icon" href="favicon.ico" type="image/x-icon">
5. Social Image (og-image.png)
For good previews on Facebook/LinkedIn/Twitter:
Specs:
- Size: 1200 × 630 px
- Format: PNG or JPG
- Content: name, tagline, avatar, URL
- Background: consistent with the page design
Suggested tools:
- Canva - Social media templates
- GIMP - Open source editor
- Figma - Professional design
6. Add Icons
Icons in icons/:
| File | Use |
|---|---|
homelab-notes.png |
Homelab Notes blog logo (128×128 raster, light stroke on transparent) |
wordpress.svg |
WordPress.org profile |
forgejo.svg |
Forgejo / Gitea |
git.svg |
Generic Git |
mastodon.svg |
Mastodon / Fediverse |
peertube.svg |
PeerTube |
bluesky.svg |
Bluesky |
keyoxide.svg |
Keyoxide |
email.svg |
|
gravatar.svg |
Gravatar |
phanpy.svg |
Phanpy |
pixelfed.svg |
Pixelfed |
twitter.svg |
Twitter / X |
linkedin.svg |
Recommended sources:
- Simple Icons - Brand logos
- Feather Icons - Generic icons
- Heroicons - UI icons
- Dashboardicons - Dashboard icons
Save it as icons/icon-name.svg and reference it in the HTML. The page
background is dark: a logo with a black stroke needs to be lightened and made
transparent.
7. Mastodon Verification (green badge ✅)
To get the green badge on Mastodon:
-
Add
<link rel="me">to the<head>ofindex.html:<link rel="me" href="https://mastodon.instance/@username"> -
In your Mastodon profile, add the link to the page:
Metadata → Label: Website → Content: https://me.yourdomain.com -
Save → the green badge appears automatically
Privacy
This page is fully privacy-first:
| Feature | Status |
|---|---|
| Third-party JavaScript | ❌ None |
| Tracking/Analytics | ❌ None |
| Cookies | ❌ None |
| External requests | ❌ None |
| Self-hosted | ✅ 100% |
| Verifiable code | ✅ Open source |
| Works without JS | ✅ Yes |
GDPR compliance: no cookie banner or dedicated privacy policy needed for the page itself (only for any external links).
Contributing
Pull requests are welcome! For major changes:
- Open an issue first
- Describe the proposed change
- Fork and create a branch for your feature
- Submit the pull request
Guidelines:
- Keep the code clean and commented
- Test on different browsers (Firefox, Chrome, Safari)
- Check accessibility (screen readers, keyboard navigation)
- Update both languages, pages and READMEs
- Respect the existing licenses
Licenses
Source Code (HTML, CSS)
The code is released under the GNU General Public License v2 or later (GPL v2+).
See LICENSE.md for the full text; LICENSE.IT.md is an unofficial Italian translation.
Why GPL v2+:
- Compatible with WordPress and the GPL ecosystem
- Ensures derivative works stay open source
- Aligned with FOSS values
Documentation
The documentation (README, guides, wiki) is released under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Full text: creativecommons.org/licenses/by-nc-sa/4.0.
You can:
- Share and adapt the documents
- Use them for non-commercial purposes
- Give credit to the author
You cannot:
- Use the documentation commercially
- Apply additional restrictions
Support
Issues or Questions
- Open an issue
- Blog: emanuelegori.uno
- Mastodon: @emanuelegori@mastodon.uno
FAQ
Q: Can I use it for my site?
A: Yes! It is GPL v2+, free to use, modify and distribute.
Q: Do I have to keep the credits?
A: Yes, the GPL requires keeping the copyright notice.
Q: Does it work on mobile?
A: Yes, it is fully responsive.
Q: Can I remove the footer?
A: Technically yes, but the footer contains the credits required by the license.
Credits
- Developed by Emanuele Gori
- Icons from Simple Icons, Feather Icons, Dashboardicons
- Sun and moon icons from Lucide (ISC license)
- Inspired by the principles of privacy, simplicity and digital freedom
Changelog
See CHANGELOG.md for the change history (in Italian).
© 2025-2026 Emanuele Gori | Theme: ContactPage v2.3.2 | GPL v2+