Telegram bot contact intergratie

This commit is contained in:
2025-07-08 00:24:14 +02:00
parent 0722200af0
commit 6ae28d7e50
7 changed files with 351 additions and 300 deletions

View File

@ -19,6 +19,38 @@ The application supports the following environment variables:
| `KVK` | `12345678` | KVK number for contact information |
| `EMAIL` | `info@hogelandlinux.nl` | Contact email address |
| `PHONE` | `+31 6 12345678` | Contact phone number |
| `TELEGRAM_BOT_TOKEN` | *(empty)* | Telegram bot token for contact form notifications |
| `TELEGRAM_CHAT_ID` | *(empty)* | Telegram chat ID where notifications will be sent |
## Telegram Integration Setup (Optional)
The contact form can send notifications to a Telegram chat. To set this up:
### 1. Create a Telegram Bot
1. Message [@BotFather](https://t.me/BotFather) on Telegram
2. Send `/newbot` command
3. Follow the instructions to create your bot
4. Copy the bot token (looks like `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)
### 2. Get Your Chat ID
1. Add your bot to the chat where you want notifications
2. Send a message to the bot in that chat
3. Visit `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`
4. Look for the `chat.id` field in the response
5. Copy the chat ID (can be positive or negative number)
### 3. Configure Environment Variables
Set the following environment variables in your deployment:
```bash
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=123456789
```
**Note:** If these variables are not set, the contact form will still work but won't send Telegram notifications.
## Coolify Deployment Steps
@ -52,8 +84,12 @@ COMPANY_NAME=Hogeland Linux
KVK=12345678
EMAIL=info@hogelandlinux.nl
PHONE=+31 6 12345678
TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here
```
**Note:** The Telegram variables are optional. If not set, the contact form will work but won't send notifications.
### 5. Configure Domain
- Set your desired domain/subdomain

View File

@ -59,7 +59,7 @@ Phone: "+31 6 12345678", // Uw telefoonnummer
## Pagina's
### Hoofdpagina (/)
- Hero sectie met animeerde terminal demo
- Hero sectie met duidelijke call-to-action
- Voordelen van Linux migratie
- Informatie over Windows 10 End of Life
- Linux distributies showcase met aanbevelingen
@ -81,12 +81,12 @@ Het design is:
- **Responsive**: Werkt op desktop, tablet en mobiel
- **Professioneel**: Vertrouwen opwekkend voor bedrijven
- **Eco-vriendelijk**: Groene kleurenpalet die duurzaamheid benadrukt
- **Tech-georiënteerd**: Linux terminal demo en visuele mockups
- **Tech-georiënteerd**: Visuele mockups en functionaliteit demonstraties
## Technische Features
### Visual Elements
- **Animeerde Terminal**: Realistische Arch Linux terminal met pacman en fastfetch
- **Clean Design**: Moderne en professionele interface zonder technische complexiteit
- **Distro Showcase**: 6 populaire Linux distributies met doelgroepen
- **Desktop Mockups**: Visuele representaties van Linux interfaces
- **Performance Vergelijkingen**: Grafische weergave van Linux vs Windows prestaties

View File

@ -11,4 +11,6 @@ services:
- KVK=12345678
- EMAIL=info@hogelandlinux.nl
- PHONE=+31 6 12345678
- TELEGRAM_BOT_TOKEN= # Set your Telegram bot token here
- TELEGRAM_CHAT_ID= # Set your Telegram chat ID here
restart: unless-stopped

239
main.go
View File

@ -1,28 +1,48 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
)
// Configuration holds all website configuration
type Config struct {
CompanyName string
KVK string
Email string
Phone string
Port string
CompanyName string
KVK string
Email string
Phone string
Port string
TelegramBotToken string
TelegramChatID string
}
// PageData holds data for template rendering
type PageData struct {
CompanyName string
Title string
KVK string
Email string
Phone string
Config
Title string
CurrentYear int
ErrorMessage string
SuccessMessage string
FormData ContactForm
}
// ContactForm holds form data
type ContactForm struct {
Name string
Email string
Phone string
Computer string
Service string
Message string
}
// Server holds the application state
@ -31,6 +51,13 @@ type Server struct {
templates *template.Template
}
// Rate limiting
var (
mu sync.Mutex
lastSubmissionTime = make(map[string]time.Time)
submissionCooldown = 5 * time.Minute
)
// getEnv returns environment variable or default value
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
@ -43,11 +70,13 @@ func getEnv(key, defaultValue string) string {
func NewServer() *Server {
// Configuration - can be overridden with environment variables
config := Config{
CompanyName: getEnv("COMPANY_NAME", "Hogeland Linux"),
KVK: getEnv("KVK", "12345678"), // Replace with actual KVK number
Email: getEnv("EMAIL", "info@hogelandlinux.nl"),
Phone: getEnv("PHONE", "+31 6 12345678"),
Port: ":" + getEnv("PORT", "8080"),
CompanyName: getEnv("COMPANY_NAME", "Hogeland Linux"),
KVK: getEnv("KVK", "12345678"), // Replace with actual KVK number
Email: getEnv("EMAIL", "info@hogelandlinux.nl"),
Phone: getEnv("PHONE", "+31 6 12345678"),
Port: ":" + getEnv("PORT", "8080"),
TelegramBotToken: getEnv("TELEGRAM_BOT_TOKEN", ""), // Set this in environment
TelegramChatID: getEnv("TELEGRAM_CHAT_ID", ""), // Set this in environment
}
// Parse templates with error handling
@ -65,11 +94,9 @@ func NewServer() *Server {
// createPageData creates PageData with the given title
func (s *Server) createPageData(title string) PageData {
return PageData{
CompanyName: s.config.CompanyName,
Config: s.config,
Title: title,
KVK: s.config.KVK,
Email: s.config.Email,
Phone: s.config.Phone,
CurrentYear: time.Now().Year(),
}
}
@ -90,9 +117,183 @@ func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) {
// contactHandler handles the contact page
func (s *Server) contactHandler(w http.ResponseWriter, r *http.Request) {
data := s.createPageData("Contact - " + s.config.CompanyName)
if r.Method == "POST" {
s.handleContactForm(w, r, &data)
return
}
s.renderTemplate(w, "contact.html", data)
}
// handleContactForm processes the contact form submission
func (s *Server) handleContactForm(w http.ResponseWriter, r *http.Request, data *PageData) {
// Get client IP for rate limiting
ip := getClientIP(r)
// Check rate limiting
mu.Lock()
if lastTime, exists := lastSubmissionTime[ip]; exists {
if time.Since(lastTime) < submissionCooldown {
mu.Unlock()
data.ErrorMessage = "U heeft recent al een bericht verstuurd. Probeer het over een paar minuten opnieuw."
s.renderTemplate(w, "contact.html", *data)
return
}
}
mu.Unlock()
// Parse form data
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
data.ErrorMessage = "Er is een fout opgetreden bij het verwerken van uw bericht."
s.renderTemplate(w, "contact.html", *data)
return
}
// Extract form data
form := ContactForm{
Name: strings.TrimSpace(r.FormValue("name")),
Email: strings.TrimSpace(r.FormValue("email")),
Phone: strings.TrimSpace(r.FormValue("phone")),
Computer: strings.TrimSpace(r.FormValue("computer")),
Service: strings.TrimSpace(r.FormValue("service")),
Message: strings.TrimSpace(r.FormValue("message")),
}
// Store form data for re-rendering on error
data.FormData = form
// Validate required fields
if form.Name == "" || form.Email == "" || form.Message == "" {
data.ErrorMessage = "Vul alle verplichte velden in (naam, email, bericht)."
s.renderTemplate(w, "contact.html", *data)
return
}
// Send to Telegram if configured
if s.config.TelegramBotToken != "" && s.config.TelegramChatID != "" {
err := s.sendToTelegram(form)
if err != nil {
log.Printf("Error sending message to Telegram: %v", err)
data.ErrorMessage = "Er is een fout opgetreden bij het versturen van uw bericht. Probeer het later opnieuw."
s.renderTemplate(w, "contact.html", *data)
return
}
}
// Update last submission time on success
mu.Lock()
lastSubmissionTime[ip] = time.Now()
mu.Unlock()
// On success, render success message
data.SuccessMessage = "Bedankt voor uw bericht! Wij nemen zo snel mogelijk contact met u op."
data.FormData = ContactForm{} // Clear form data
s.renderTemplate(w, "contact.html", *data)
}
// sendToTelegram sends the contact form data to Telegram
func (s *Server) sendToTelegram(form ContactForm) error {
// Format message
message := fmt.Sprintf(
"🔔 Nieuw contactformulier\n\n"+
"👤 *Naam:* %s\n"+
"📧 *Email:* %s\n"+
"📞 *Telefoon:* %s\n"+
"💻 *Computer:* %s\n"+
"🛠️ *Service:* %s\n\n"+
"💬 *Bericht:*\n%s",
escapeMarkdown(form.Name),
escapeMarkdown(form.Email),
escapeMarkdown(form.Phone),
escapeMarkdown(form.Computer),
escapeMarkdown(form.Service),
escapeMarkdown(form.Message),
)
// Prepare Telegram API request
telegramURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", s.config.TelegramBotToken)
payload := map[string]interface{}{
"chat_id": s.config.TelegramChatID,
"text": message,
"parse_mode": "Markdown",
}
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err)
}
// Send HTTP request
resp, err := http.Post(telegramURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to send HTTP request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("Telegram API error: %s, Status Code: %d", string(bodyBytes), resp.StatusCode)
}
return nil
}
// escapeMarkdown escapes special characters for Telegram Markdown
func escapeMarkdown(text string) string {
if text == "" {
return "N/A"
}
// Escape special Markdown characters
replacer := strings.NewReplacer(
"*", "\\*",
"_", "\\_",
"`", "\\`",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"~", "\\~",
">", "\\>",
"#", "\\#",
"+", "\\+",
"-", "\\-",
"=", "\\=",
"|", "\\|",
"{", "\\{",
"}", "\\}",
".", "\\.",
"!", "\\!",
)
return replacer.Replace(text)
}
// getClientIP extracts the client IP address from the request
func getClientIP(r *http.Request) string {
// Check X-Forwarded-For header first (for reverse proxies)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// X-Forwarded-For can contain multiple IPs, take the first one
if idx := strings.Index(xff, ","); idx != -1 {
return strings.TrimSpace(xff[:idx])
}
return strings.TrimSpace(xff)
}
// Check X-Real-IP header
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return strings.TrimSpace(xri)
}
// Fall back to RemoteAddr
if idx := strings.LastIndex(r.RemoteAddr, ":"); idx != -1 {
return r.RemoteAddr[:idx]
}
return r.RemoteAddr
}
// healthHandler provides a simple health check endpoint
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

View File

@ -146,10 +146,10 @@ p {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.hero-content h2 {
@ -169,100 +169,6 @@ p {
gap: 1rem;
}
.hero-image {
display: flex;
justify-content: center;
align-items: center;
}
/* Terminal Window Styling */
.terminal-window {
background: #1a1a1a;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
max-width: 520px;
margin: 0 auto 0 0;
overflow: hidden;
border: 1px solid #333;
}
.terminal-header {
background: #2d2d2d;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 1rem;
border-bottom: 1px solid #333;
}
.terminal-buttons {
display: flex;
gap: 8px;
}
.terminal-buttons span {
width: 12px;
height: 12px;
border-radius: 50%;
}
.btn-close {
background: #ff5f57;
}
.btn-minimize {
background: #ffbd2e;
}
.btn-maximize {
background: #28ca42;
}
.terminal-title {
color: #ccc;
font-size: 0.9rem;
font-weight: 500;
}
.terminal-body {
padding: 20px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace;
font-size: 14px;
line-height: 1.6;
min-height: 200px;
}
.terminal-line {
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.prompt {
color: #10b981;
font-weight: 600;
min-width: 180px;
}
.command {
color: #60a5fa;
}
.output {
color: #d1d5db;
}
.cursor {
color: #10b981;
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* Buttons */
.btn {
padding: 12px 24px;
@ -872,6 +778,33 @@ p {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* Alert Styles */
.alert {
padding: 1rem 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
border: 1px solid;
font-size: 1rem;
line-height: 1.5;
}
.alert-error {
background-color: #fef2f2;
color: #dc2626;
border-color: #fecaca;
}
.alert-success {
background-color: #f0fdf4;
color: #16a34a;
border-color: #bbf7d0;
}
.alert strong {
font-weight: 600;
margin-right: 0.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
@ -1015,12 +948,6 @@ footer {
padding: 3rem 0;
}
.hero-content {
grid-template-columns: 1fr;
gap: 2rem;
text-align: center;
}
.hero-content h2 {
font-size: 2.5rem;
line-height: 1.2;
@ -1036,19 +963,6 @@ footer {
gap: 1rem;
}
.terminal-window {
max-width: 100%;
margin: 0;
border-radius: 8px;
}
.terminal-body {
padding: 16px;
font-size: 13px;
min-height: 180px;
overflow-x: auto;
}
.benefits-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
@ -1154,22 +1068,6 @@ footer {
max-width: 300px;
}
.terminal-window {
border-radius: 6px;
overflow: hidden;
}
.terminal-body {
padding: 12px;
font-size: 12px;
min-height: 160px;
}
.prompt {
min-width: 120px;
font-size: 11px;
}
/* Card padding adjustments */
.benefit-card, .service-card, .distro-card, .cta-benefit {
padding: 1.25rem;
@ -1266,23 +1164,12 @@ footer {
padding: 2rem 0;
}
.hero-content {
grid-template-columns: 1fr 1fr;
gap: 2rem;
text-align: left;
}
.hero-content h2 {
font-size: 2rem;
}
.hero-buttons {
justify-content: flex-start;
}
.terminal-window {
max-height: 250px;
overflow-y: auto;
justify-content: center;
}
}
@ -1296,11 +1183,6 @@ footer {
font-size: 1rem;
}
.terminal-body {
font-size: 11px;
padding: 10px;
}
.benefit-card, .service-card, .distro-card, .cta-benefit {
padding: 1rem;
}
@ -1313,9 +1195,7 @@ footer {
/* High DPI screens */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.terminal-body {
font-size: 13px;
}
/* No terminal-specific styles needed */
}
/* Animations */

View File

@ -79,42 +79,55 @@
<div class="contact-form">
<h2>Stuur een bericht</h2>
<form action="#" method="POST">
{{if .ErrorMessage}}
<div class="alert alert-error">
<strong>❌ Fout:</strong> {{.ErrorMessage}}
</div>
{{end}}
{{if .SuccessMessage}}
<div class="alert alert-success">
<strong>✅ Gelukt:</strong> {{.SuccessMessage}}
</div>
{{end}}
<form action="/contact" method="POST">
<div class="form-group">
<label for="name">Naam *</label>
<input type="text" id="name" name="name" required>
<input type="text" id="name" name="name" value="{{.FormData.Name}}" required>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required>
<input type="email" id="email" name="email" value="{{.FormData.Email}}" required>
</div>
<div class="form-group">
<label for="phone">Telefoon</label>
<input type="tel" id="phone" name="phone">
<input type="tel" id="phone" name="phone" value="{{.FormData.Phone}}">
</div>
<div class="form-group">
<label for="computer">Huidige computer</label>
<input type="text" id="computer" name="computer" placeholder="Bijv. Dell Inspiron 2015, HP Pavilion 2018">
<input type="text" id="computer" name="computer" value="{{.FormData.Computer}}" placeholder="Bijv. Dell Inspiron 2015, HP Pavilion 2018">
</div>
<div class="form-group">
<label for="service">Gewenste service</label>
<select id="service" name="service">
<option value="">Selecteer een optie</option>
<option value="advice">Gratis advies</option>
<option value="installation">Linux installatie</option>
<option value="migration">Data migratie</option>
<option value="training">Training & ondersteuning</option>
<option value="other">Anders</option>
<option value="advice" {{if eq .FormData.Service "advice"}}selected{{end}}>Gratis advies</option>
<option value="installation" {{if eq .FormData.Service "installation"}}selected{{end}}>Linux installatie</option>
<option value="migration" {{if eq .FormData.Service "migration"}}selected{{end}}>Data migratie</option>
<option value="training" {{if eq .FormData.Service "training"}}selected{{end}}>Training & ondersteuning</option>
<option value="other" {{if eq .FormData.Service "other"}}selected{{end}}>Anders</option>
</select>
</div>
<div class="form-group">
<label for="message">Bericht *</label>
<textarea id="message" name="message" rows="5" required placeholder="Vertel ons over uw situatie en hoe wij u kunnen helpen..."></textarea>
<textarea id="message" name="message" rows="5" required placeholder="Vertel ons over uw situatie en hoe wij u kunnen helpen...">{{.FormData.Message}}</textarea>
</div>
<button type="submit" class="btn btn-primary">Verstuur bericht</button>
@ -172,7 +185,7 @@
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2024 {{.CompanyName}}. Alle rechten voorbehouden.</p>
<p>&copy; {{.CurrentYear}} {{.CompanyName}}. Alle rechten voorbehouden.</p>
</div>
</div>
</footer>

View File

@ -12,22 +12,24 @@
<nav class="navbar">
<div class="nav-container">
<div class="nav-logo">
<h1><a href="/">Hogeland Linux</a></h1>
<h1><a href="/">{{.CompanyName}}</a></h1>
</div>
<div class="nav-links">
<a href="/">Home</a>
<a href="/" class="active">Home</a>
<a href="#voordelen">Voordelen</a>
<a href="#distros">Linux Keuze</a>
<a href="#diensten">Diensten</a>
<a href="/contact">Contact</a>
<a href="#services">Diensten</a>
<a href="#distros">Distributies</a>
</div>
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()">
<span></span>
</button>
<div class="mobile-menu" id="mobile-menu">
<a href="/">Home</a>
<a href="/" class="active">Home</a>
<a href="#voordelen">Voordelen</a>
<a href="#distros">Linux Keuze</a>
<a href="#diensten">Diensten</a>
<a href="/contact">Contact</a>
<a href="#services">Diensten</a>
<a href="#distros">Distributies</a>
</div>
</div>
</nav>
@ -43,47 +45,6 @@
<a href="/contact" class="btn btn-secondary">Gratis advies</a>
</div>
</div>
<!-- <div class="hero-image">
<div class="terminal-window">
<div class="terminal-header">
<div class="terminal-buttons">
<span class="btn-close"></span>
<span class="btn-minimize"></span>
<span class="btn-maximize"></span>
</div>
<div class="terminal-title">Terminal</div>
</div>
<div class="terminal-body">
<div class="terminal-line">
<span class="prompt">ainrommer@computer:~$</span>
<span class="command">sudo pacman -Syu</span>
</div>
<div class="terminal-line">
<span class="output">:: Synchronizing package databases...</span>
</div>
<div class="terminal-line">
<span class="output">✓ Everything is up to date.</span>
</div>
<div class="terminal-line">
<span class="prompt">ainrommer@computer:~$</span>
<span class="command">fastfetch</span>
</div>
<div class="terminal-line">
<span class="output">🔥 Garuda Linux (Dragonized Gaming)</span>
</div>
<div class="terminal-line">
<span class="output">🐧 Linux 6.11.2-zen1-1-zen</span>
</div>
<div class="terminal-line">
<span class="output">💾 8.2 GiB / 16.0 GiB (51%)</span>
</div>
<div class="terminal-line">
<span class="prompt">ainrommer@computer:~$</span>
<span class="cursor">_</span>
</div>
</div>
</div>
</div> -->
</section>
<section id="voordelen" class="benefits">
@ -166,7 +127,9 @@
<div class="distros-grid">
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🐧</div>
<div class="distro-logo">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEiUlEQVR4nO1ZXWgcVRQeVNAna0Xw3tSaB/viQ6EP6ltMobXN3EmsRkpDraVCaSISsbV/YrE0P+CDtJuIPpX0xUIxaaRVUBRbX6q2D0piEOKDm02Zu5P9S5Z2/5LtHDl3kmUnO7tzZzO7bWEPHHZnmLnzffeev3uuojSkIQ1Zs8Bp5ZGw9uwrOqMndUbHdEYnuUoSXCW5ZU3gPZ3RUXwGn8V3lPstkZ3PUF2lA5yRWc4oeFKVhPBdHKPuwEPa8+u5SoY4IxnPwEuUZHCs4K7mp+oCnjPSqTM6t3bgdtVVauDYNQMOu5VHrVn3FzgvXZEAfstX8MHW5ie4SsZrD56u+Mc4ftMX8DgbGD3qBp4VSFyBVuWxNRPw22zCWhNEDr0qlGsb3EicWxt4Rjr9Bp/75xasSG7qprhX7nmdUVPX6JtVgU9sX79OZ5T7SSDSsxVWSwRXwiU6Ydj2PvsqGfYM8vWNMP9ZD6R//RbyPAhmJgVmNg15IwQLgSMQ/2RPKYGeygSWTWnIE3jMjtUkqehhDcA0wUnyxizM7dsifgsmNHnD3Q+EkoynjG2VB5LAtSa4c/FzMPZuFtepny5BOUlfGwNj3xZYGD4GicGDkuALpjQgBR6LLKxTZAe+880XFrjr4+La2PMi3JuPlCWRn7sNqR8vCk1eGPRCYEaqAMRKUXbQ+Kkum8kkzuy3TKl3hzCncOcLQqNH2uHu1REwlxZLCMVPvyNvpip5yd18GD0pNVj7c7CkB21gctN/AW8vbxbR3tcgHzfsKxKegfCuZrlV0OgJGQJjMoMl+g7YgJiLOYi+v83d0Xt3lKxEov9dOQKMjroTUOnfFTNoT6tw3IXhozYQd6+clzaF1Hcjdue+Pi5LYNKVAFdJzDWDTv4Gc3s3C4dckeiHTJoA+kSxLIWmZX0gKkMgJ5NBMRSmfvi6cB1+a5O8M1atJFsdge5WBwJHRSh8EAnEHE1o6mYBbHbihkhcNhM6rEkDidXShHRXJ8YyuAnmh4/ZnfjqiLwTf3+hdk6sy4bRM/ttIDA0Ypx3ey/6wU6A1WG074CPYVQ2kWkbYEn/zwYke+tn4KypIvh7qxMZD8onMpUe97eUwPJ4uZTARBZ5b6t1/1SXsHN0bNTYRx2W2TiVEp++7W8p4bmYuzQkgCTP94nryKEWMHPZEqCFGTdmRfhFTY4M+F/MeS6nGYXkVx+L2oh3bITF6T/Lgk//Mir2BJhD5gcPVtxOOhDoV2q9oUl+eaLCzIesDU1R6M1N/SFJwuOGpupuRAduKbshfe2ycHCxpcykRMVpbSm7SohFsLZyJxBQvEqs7eknfd/UO2T0SHeLm+kYVfdNl/ugpl8EVmf03NTv7m0Vlb5RFfgiEgE/V6GQ0btbXO1fV+lZ5aFuLe72qcn7b9umx+vZ3NVVehm/qdSgvX7OT5/gzjZ/1vf2erFgr9I6jPB91o2q+6BeBcOar0dMjATqdsTkcMjX76V2KprxGXz3vhzyORaAbeRl7NtYEYtMcEbihWNW/M/IhDhmVelxrCofiGPWhjREefjlf0AT6pAJW8ezAAAAAElFTkSuQmCC" alt="ubuntu--v1">
</div>
<h3>Ubuntu</h3>
<span class="distro-tag">Populair</span>
</div>
@ -181,7 +144,11 @@
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🍃</div>
<div class="distro-logo">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="48" height="48" viewBox="0 0 48 48">
<path fill="#c5e1a5" d="M21.5,44C13.492,44,7,37.508,7,29.5V18H2V4h29.031C39.298,4,46,10.702,46,18.969V44H21.5z"></path><path fill="#689f38" d="M30.031,8H6v6h2c1.657,0,3,1.343,3,3v0v11.5C11,34.851,16.149,40,22.5,40H38c2.209,0,4-1.791,4-4 V19.969C42,13.359,36.641,8,30.031,8z"></path><path fill="#fff" d="M33.5,15c-1.577,0-2.996,0.672-4,1.74c-1.004-1.069-2.423-1.74-4-1.74c-3.033,0-5.5,2.473-5.5,5.512 V28h3v-7.488c0-1.381,1.122-2.505,2.5-2.505S28,19.13,28,20.512V28h3v-7.488v0c0-1.381,1.122-2.505,2.5-2.505S36,19.13,36,20.512 V28.5c0,1.93-1.57,3.5-3.5,3.5h-12c-1.93,0-3.5-1.57-3.5-3.5V12h-3v16.5c0,3.584,2.916,6.5,6.5,6.5h12c3.584,0,6.5-2.916,6.5-6.5 v-7.988C39,17.472,36.533,15,33.5,15z"></path>
</svg>
</div>
<h3>Linux Mint</h3>
<span class="distro-tag">Windows-achtig</span>
</div>
@ -196,7 +163,9 @@
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🚀</div>
<div class="distro-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="inxzwh639poeU1X9W8tTQa" x1="7.037" x2="45.033" y1="7.037" y2="45.033" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#05acb3"/><stop offset="1" stop-color="#038387"/></linearGradient><path fill="url(#inxzwh639poeU1X9W8tTQa)" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/><linearGradient id="inxzwh639poeU1X9W8tTQb" x1="22.277" x2="31.658" y1="31.726" y2="57.724" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".472" stop-color="#dde0e2"/><stop offset="1" stop-color="#bbc1c4"/></linearGradient><path fill="url(#inxzwh639poeU1X9W8tTQb)" d="M15.5,38.5h17c1.105,0,2-0.895,2-2v0c0-1.105-0.895-2-2-2h-17c-1.105,0-2,0.895-2,2v0 C13.5,37.605,14.395,38.5,15.5,38.5z"/><linearGradient id="inxzwh639poeU1X9W8tTQc" x1="30.056" x2="40.896" y1="16.127" y2="46.17" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".472" stop-color="#dde0e2"/><stop offset="1" stop-color="#bbc1c4"/></linearGradient><path fill="url(#inxzwh639poeU1X9W8tTQc)" d="M34,16c-3-1-3.5,0.5-4,2.5c-0.618,2.473-1,7-1,8.5s1,2,2,0.5s4-6.5,4.5-8S35.956,16.652,34,16 z"/><linearGradient id="inxzwh639poeU1X9W8tTQd" x1="28.561" x2="31.626" y1="29.85" y2="38.346" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".472" stop-color="#dde0e2"/><stop offset="1" stop-color="#bbc1c4"/></linearGradient><path fill="url(#inxzwh639poeU1X9W8tTQd)" d="M27.996,30.447c-0.642,0.833-0.433,2.571,1.067,2.589c0.938,0.011,1.584-0.887,1.509-2.029 C30.518,30.184,29.104,29.01,27.996,30.447z"/><linearGradient id="inxzwh639poeU1X9W8tTQe" x1="17.026" x2="40.638" y1="8.349" y2="73.788" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f0f0f0"/><stop offset="1" stop-color="#fff"/></linearGradient><path fill="url(#inxzwh639poeU1X9W8tTQe)" d="M26,12c-3.056-5.239-8.399-4.379-13.366-0.748c-1.265,0.924-1.651,2.649-0.91,4.029 l8.91,16.606c0.49,0.913,1.596,1.3,2.549,0.892h0c1.006-0.431,1.479-1.591,1.059-2.602c-0.819-1.975-2.095-5.059-2.742-6.677 C23.5,22.5,29.846,18.592,26,12z M21.352,19.609c-0.515,0.303-1.907,0.452-3.239-2.812c-1.213-2.971-0.849-4.335-0.212-4.547 s1.971,0.485,3.244,3.001C22.418,17.767,21.868,19.306,21.352,19.609z"/></svg>
</div>
<h3>Pop!_OS</h3>
<span class="distro-tag">Gaming</span>
</div>
@ -211,7 +180,9 @@
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🎨</div>
<div class="distro-logo">
<img src="data:image/png;base64,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" alt="external-elementary-os-is-a-linux-distribution-based-on-ubuntu-logo-regular-tal-revivo">
</div>
<h3>Elementary OS</h3>
<span class="distro-tag">Mooi</span>
</div>
@ -226,7 +197,9 @@
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🔧</div>
<div class="distro-logo">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFU0lEQVR4nO1YWW8bVRi1gAfojLcsxWmbZnNBQoDgFyCBVAG1E8/YYydx9sQuhWeekBoJQdpCnxFCVUECBSVt9sTLzDhOkzheEloEVA2pKtpCkGhSpOy7P3Qn8XiJJ3TGdqmQj3SVaMYP59w53/m+e2WyLLLIIov/BcqaJ7QlLYEPipv97cXN/utFjb6HhQ2+zWP145tH68fmC+rGrmtqx9o11tH38+vGy2RPAt5o9T6jbQlaS1sCE6UtAShpCUBxsx+KmibgeOMEFDb44Gj9OBypG4eC2jHQ1IzC8zWjcNh6DfKqvb5cy0i1jOp8+j8hX2YPvq21hWbKbEEQSz6/egTyqkYgt9IL6krvdI7Fc/KxES+wTx06YQ9d0tpDkCr5HLQsw6A2D4PKPPzVMWriuYySL33vx8MnTgd/SDd5NRJAeUBJsZM4NZqfMfJae3A6c+Q9oDR5QGFkpnHKkZ9+26Sy81XDkEu5IYfsBxXRC2qiD1SkE9QUG0/exILCyAJOspOyeu+zaROQiufzKml47QwL3zD3YfbhOmxuh2F2fh2+dt+Dl200KI3OOPJyTgADGEl/mRbyWnvgZCrkjR/7YWl1G5JhaXUbys/6QEE64shH1iED+07qOS81KquG4fUzrCD5CBZXt+ClJhfISTqOPPcVCOZWSn1Caw/VSC1Y5HlkmwjuP1jjdltj7uf+/j63xr+75LwLePngPgG7IthKyQJKbUG/1LRBBYs8HwEirTK6UVSCknRDxVlfVNzcGmC6bgEBzLjk2SaVqERps70T5knmkb0xacOAxtTPv9vY2gFMdzWpAJykw0rKXSJaABrMUsl5laEnzuuK8u5o2pA0vPXhNf7dvb9WAdP1CAhgACfY06IF7E2VkpuUsiJBgL47GpWECz66/Av/7rJLuAb2vsJ34gU0+W+k0mETBcj1XdGoJNzwqs0Nd/5cgduzy/CKzc09ExbATIkWUNTkm3+kDmuhIcc4CCrUXQ09HHEV0c9ZJhb4qSuA67t2vV7eD7jBwdmGK16D4yDyqJAfiBZQ2Ojb+Lcm9UKjGy5euQ0//bYIK+vRvEfkEwXEFuznnTO7IkjmUde6eAEHkTc7oeHilGCT4gTokwtAWFrdAkzXlVkBB+08Ih+OJuR+AfpuzvNCX+Czjl9FfQFMioWEPI9sE7vzf8yvQe2FSThiGeB3Xl7eA3JdvIBD73YAdupqtAaSjA54Oos46XhgoTnPx5IvtjpAZXTtdtjYkThBACIvgjDE9wHmW9ECko4HxkH4+e4STwrt/C75+HkeRSUinD4BrPhGlnw86IuzD7JN4s7z83zaBNBhZYWzWLSA5ONB734BiHgCeeTvAnN01llcEZ06wBcwyYzJpCBZh1URA1zmR1DdFgQF6dx/GDE4oO58iP/djTsLgOt7pQkw0mZJApIewE0uuNAxwxObmV2GwsoBkBPOvVShOfLoGSrwCNrapwE3DIknT9A3JR9okt4emFgorXVwp6gI0MGk9nwINFQft9DOx5JfWNmC41UDgBO0BAHuN2VSIXj1QTrBei4IOwd1sj2g31R+EpC4+8wXkslzAoTubVDBVgyAtS3I7a4Q0LuqTwNiZx7YIx9I+VpFkHykYA1DnDXOfT/NFeny2jY346D/0TPONtJ2/hau8+alRD4iQJA832BQ0Q5xCYNikotKlDbomSTPM4G03copKXbxQPJpXhjyfDpv4xQmT8/jII8R9M2U0kYIcsrzotzI/J0Z8nQYdViMZCyy1tanZJmCusJVKCeZToxgFiSTJZgNjKTn0EiMDudoMJM022SRRRZZyJ5E/ANvJojABjwtUQAAAABJRU5ErkJggg==" alt="fedora">
</div>
<h3>Fedora</h3>
<span class="distro-tag">Geavanceerd</span>
</div>
@ -241,7 +214,11 @@
<div class="distro-card">
<div class="distro-header">
<div class="distro-logo">🔥</div>
<div class="distro-logo">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="48" height="48" viewBox="0 0 48 48">
<path fill="#1e88e5" d="M28.465,38.611c0.419-1.105,0.664-2.365,0.664-3.714c0-4.133-2.211-7.494-4.929-7.494 c-2.741,0-4.951,3.361-4.951,7.494c0,1.326,0.221,2.586,0.641,3.669c-9.041,0.951-15.407,4.731-17.993,6.432 c4.355-6.278,8.909-13.638,13.262-22.105c1.083-2.101,2.101-4.178,3.05-6.211c0.375,0.243,0.751,0.509,1.171,0.775 c1.945,1.215,3.759,1.879,5.084,2.233c-0.973-0.73-2.033-1.613-3.116-2.697c-0.817-0.817-1.547-1.637-2.167-2.433 C21.016,10.538,22.608,6.669,24,3c2.32,6.144,5.217,12.842,8.841,19.893c2.343,4.531,4.731,8.754,7.117,12.644 c-0.685-0.375-1.437-0.73-2.233-1.039c-1.371-0.53-2.652-0.862-3.759-1.06c1.503,0.751,3.25,1.747,5.084,3.073 c1.194,0.885,2.254,1.769,3.161,2.631c0.021,0.021,0.021,0.021,0.045,0.045c1.26,2.056,2.565,3.957,3.846,5.813 C43.561,43.319,37.306,39.605,28.465,38.611z"></path>
</svg>
</div>
<h3>Garuda Linux</h3>
<span class="distro-tag">Prestaties</span>
</div>
@ -398,7 +375,7 @@
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2024 {{.CompanyName}}. Alle rechten voorbehouden.</p>
<p>&copy; {{.CurrentYear}} {{.CompanyName}}. Alle rechten voorbehouden.</p>
</div>
</div>
</footer>
@ -426,65 +403,7 @@
});
});
// Terminal animation
const terminalBody = document.querySelector('.terminal-body');
const lines = [
{ type: 'prompt', content: 'ainrommer@computer ~ $' },
{ type: 'command', content: 'neofetch', delay: 1000 },
{ type: 'output', content: '\n .-/+oossssoo+/-.\n .:+ssssssssssssssss+:.\n -+ssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n -+ssssssssssssssssssssss+-\n-+ssssssssssssssssssssss+-\n`-+ssssssssssssssssssss+-`\n `-+ssssssssssssssssss+-`\n `-+ssssssssssssssss+-`\n `-+ssssssssssssss+-`\n `-+ssssssssssss+-`\n `-+ssssssssss+-`\n `-+ssssssss+-`\n `-+ssssss+-`\n `-+ssss+-`\n `-+ss+-`\n `-+s+-`\n `-+-`\n ``\nOS: Arch Linux x86_64\nKernel: 6.6.8-arch1-1\nUptime: 3 hours, 42 mins\nPackages: 1337 (pacman)\nShell: zsh 5.9\nResolution: 1920x1080\nDE: KDE Plasma 5.27.10\nWM: KWin\nWM Theme: Breeze\nTheme: Breeze Dark [Plasma], Breeze [GTK2/3]\nIcons: Breeze Dark [Plasma], breeze-dark [GTK2/3]\nTerminal: konsole\nCPU: AMD Ryzen 7 5800X (16) @ 3.800GHz\nGPU: NVIDIA GeForce RTX 3070\nMemory: 2847MiB / 32768MiB', delay: 2000 },
{ type: 'prompt', content: 'ainrommer@computer ~ $' },
{ type: 'command', content: 'sudo pacman -Syu', delay: 3000 },
{ type: 'output', content: ':: Synchronizing package databases...\n core 174.7 KiB 623 KiB/s 00:00 [######################] 100%\n extra 1744.4 KiB 1234 KiB/s 00:01 [######################] 100%\n multilib 193.9 KiB 567 KiB/s 00:00 [######################] 100%\n:: Starting full system upgrade...\n:: Replace gtk4 with extra/gtk4? [Y/n] y\nthere is nothing to do', delay: 4000 },
{ type: 'prompt', content: 'ainrommer@computer ~ $' },
{ type: 'cursor', content: '', delay: 5000 }
];
function typeWriter(text, element, speed = 50) {
return new Promise((resolve) => {
let i = 0;
const timer = setInterval(() => {
if (i < text.length) {
element.textContent += text.charAt(i);
i++;
} else {
clearInterval(timer);
resolve();
}
}, speed);
});
}
function animateTerminal() {
terminalBody.innerHTML = '';
let currentDelay = 0;
lines.forEach((line, index) => {
setTimeout(() => {
const lineElement = document.createElement('div');
lineElement.className = 'terminal-line';
if (line.type === 'prompt') {
lineElement.innerHTML = '<span class="prompt">' + line.content + '</span>';
} else if (line.type === 'command') {
lineElement.innerHTML = '<span class="prompt">ainrommer@computer ~ $ </span><span class="command">' + line.content + '</span>';
} else if (line.type === 'output') {
lineElement.innerHTML = '<span class="output">' + line.content + '</span>';
} else if (line.type === 'cursor') {
lineElement.innerHTML = '<span class="prompt">ainrommer@computer ~ $ </span><span class="cursor">█</span>';
}
terminalBody.appendChild(lineElement);
terminalBody.scrollTop = terminalBody.scrollHeight;
}, currentDelay);
currentDelay += line.delay || 0;
});
}
// Start animation when page loads
window.addEventListener('load', () => {
setTimeout(animateTerminal, 1000);
});
// Mobile menu functionality and other interactive features
</script>
</body>
</html>