Add Coolify deployment configuration
This commit is contained in:
43
.dockerignore
Normal file
43
.dockerignore
Normal file
@ -0,0 +1,43 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
|
||||
# Build artifacts
|
||||
main
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Docker files (not needed in container)
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
.dockerignore
|
143
DEPLOYMENT.md
Normal file
143
DEPLOYMENT.md
Normal file
@ -0,0 +1,143 @@
|
||||
# Coolify Deployment Guide
|
||||
|
||||
This guide explains how to deploy the Linux Service website to your self-hosted Coolify instance with automatic updates from your Gitea repository.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Coolify installed and running
|
||||
- Access to your Gitea instance (gitea.boner.be)
|
||||
- Domain name configured for your service
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The application supports the following environment variables:
|
||||
|
||||
| Variable | Default Value | Description |
|
||||
|----------|---------------|-------------|
|
||||
| `PORT` | `8080` | Port the application listens on |
|
||||
| `COMPANY_NAME` | `Hogeland Linux` | Company name displayed on the website |
|
||||
| `KVK` | `12345678` | KVK number for contact information |
|
||||
| `EMAIL` | `info@hogelandlinux.nl` | Contact email address |
|
||||
| `PHONE` | `+31 6 12345678` | Contact phone number |
|
||||
|
||||
## Coolify Deployment Steps
|
||||
|
||||
### 1. Create New Resource in Coolify
|
||||
|
||||
1. Open your Coolify dashboard
|
||||
2. Click "New Resource"
|
||||
3. Select "Public Repository" or "Private Repository" (if your Gitea repo is private)
|
||||
|
||||
### 2. Configure Repository
|
||||
|
||||
**Repository URL:** `https://gitea.boner.be/[your-username]/[repository-name]`
|
||||
|
||||
**Branch:** `master`
|
||||
|
||||
**Build Pack:** `Docker`
|
||||
|
||||
### 3. Configure Build Settings
|
||||
|
||||
- **Dockerfile Location:** `./Dockerfile`
|
||||
- **Build Context:** `.`
|
||||
- **Ports:** `8080`
|
||||
|
||||
### 4. Set Environment Variables
|
||||
|
||||
In the Coolify environment variables section, add:
|
||||
|
||||
```
|
||||
PORT=8080
|
||||
COMPANY_NAME=Hogeland Linux
|
||||
KVK=12345678
|
||||
EMAIL=info@hogelandlinux.nl
|
||||
PHONE=+31 6 12345678
|
||||
```
|
||||
|
||||
### 5. Configure Domain
|
||||
|
||||
- Set your desired domain/subdomain
|
||||
- Coolify will automatically handle SSL certificate generation
|
||||
|
||||
### 6. Enable Auto-Deploy
|
||||
|
||||
1. Go to the "Settings" tab of your application
|
||||
2. Enable "Auto Deploy"
|
||||
3. Set the branch to `master`
|
||||
4. Configure webhook URL in your Gitea repository
|
||||
|
||||
### 7. Gitea Webhook Configuration
|
||||
|
||||
To enable automatic deployments when you push to master:
|
||||
|
||||
1. Go to your repository on gitea.boner.be
|
||||
2. Navigate to Settings → Webhooks
|
||||
3. Click "Add Webhook" → "Gitea"
|
||||
4. Set the Payload URL to your Coolify webhook URL (found in your app settings)
|
||||
5. Set Content Type to `application/json`
|
||||
6. Select "Just the push event"
|
||||
7. Check "Active"
|
||||
8. Click "Add Webhook"
|
||||
|
||||
## Docker Commands for Local Testing
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t linuxservice .
|
||||
|
||||
# Run locally
|
||||
docker run -p 8080:8080 \
|
||||
-e COMPANY_NAME="Hogeland Linux" \
|
||||
-e EMAIL="info@hogelandlinux.nl" \
|
||||
linuxservice
|
||||
|
||||
# Or use docker-compose
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Build Fails**: Check that all files (templates/, static/) are committed to your repository
|
||||
2. **Port Issues**: Ensure PORT environment variable matches the exposed port
|
||||
3. **Template Errors**: Verify that templates directory is included in the Docker image
|
||||
|
||||
### Logs
|
||||
|
||||
Check application logs in Coolify dashboard under the "Logs" tab.
|
||||
|
||||
### Health Check
|
||||
|
||||
The application should respond to `GET /` with the homepage. You can check this endpoint to verify the deployment.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
linuxservice/
|
||||
├── Dockerfile # Container configuration
|
||||
├── docker-compose.yml # Local development
|
||||
├── .dockerignore # Docker build optimization
|
||||
├── main.go # Go application
|
||||
├── go.mod # Go module definition
|
||||
├── static/ # Static assets (CSS, images, etc.)
|
||||
│ └── style.css
|
||||
├── templates/ # HTML templates
|
||||
│ ├── index.html
|
||||
│ └── contact.html
|
||||
└── DEPLOYMENT.md # This file
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- The Docker image uses multi-stage builds for smaller size
|
||||
- Static files are served directly by the Go application
|
||||
- No external dependencies required
|
||||
- Minimal resource usage (suitable for small VPS instances)
|
||||
|
||||
## Security Notes
|
||||
|
||||
- The application runs as a non-root user in the container
|
||||
- Only port 8080 is exposed
|
||||
- No sensitive data is stored in the application
|
||||
- Environment variables should be used for configuration
|
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@ -0,0 +1,38 @@
|
||||
# Build stage
|
||||
FROM golang:1.24.4-alpine AS builder
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum* ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
|
||||
|
||||
# Final stage
|
||||
FROM alpine:latest
|
||||
|
||||
# Install ca-certificates for HTTPS requests
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# Copy the binary from builder stage
|
||||
COPY --from=builder /app/main .
|
||||
|
||||
# Copy static files and templates
|
||||
COPY --from=builder /app/static ./static
|
||||
COPY --from=builder /app/templates ./templates
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run
|
||||
CMD ["./main"]
|
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
linuxservice:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- PORT=8080
|
||||
- COMPANY_NAME=Hogeland Linux
|
||||
- KVK=12345678
|
||||
- EMAIL=info@hogelandlinux.nl
|
||||
- PHONE=+31 6 12345678
|
||||
restart: unless-stopped
|
29
main.go
29
main.go
@ -4,6 +4,7 @@ import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Configuration holds all website configuration
|
||||
@ -30,15 +31,23 @@ type Server struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
// getEnv returns environment variable or default value
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// NewServer creates a new server instance
|
||||
func NewServer() *Server {
|
||||
// Configuration - replace these with your actual values
|
||||
// Configuration - can be overridden with environment variables
|
||||
config := Config{
|
||||
CompanyName: "Hogeland Linux",
|
||||
KVK: "12345678", // Replace with actual KVK number
|
||||
Email: "info@hogelandlinux.nl",
|
||||
Phone: "+31 6 12345678",
|
||||
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"),
|
||||
}
|
||||
|
||||
// Parse templates with error handling
|
||||
@ -84,6 +93,13 @@ func (s *Server) contactHandler(w http.ResponseWriter, r *http.Request) {
|
||||
s.renderTemplate(w, "contact.html", data)
|
||||
}
|
||||
|
||||
// healthHandler provides a simple health check endpoint
|
||||
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"healthy","service":"linuxservice"}`))
|
||||
}
|
||||
|
||||
// setupRoutes configures all HTTP routes
|
||||
func (s *Server) setupRoutes() {
|
||||
// Static files
|
||||
@ -93,6 +109,7 @@ func (s *Server) setupRoutes() {
|
||||
// Page routes
|
||||
http.HandleFunc("/", s.homeHandler)
|
||||
http.HandleFunc("/contact", s.contactHandler)
|
||||
http.HandleFunc("/health", s.healthHandler)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
356
static/style.css
356
static/style.css
@ -19,7 +19,7 @@ body {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
@ -40,20 +40,22 @@ p {
|
||||
|
||||
/* Navigation */
|
||||
.navbar {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
background: #1f2937;
|
||||
padding: 1rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-logo h1 {
|
||||
@ -75,14 +77,60 @@ p {
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: #4b5563;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.nav-links a:hover,
|
||||
.nav-links a.active {
|
||||
color: #059669;
|
||||
color: #10b981;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
/* Mobile Navigation */
|
||||
.mobile-menu-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle:hover {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #1f2937;
|
||||
border-top: 1px solid #374151;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mobile-menu.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-menu a {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid #374151;
|
||||
}
|
||||
|
||||
.mobile-menu a:hover {
|
||||
background: #374151;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
@ -217,15 +265,19 @@ p {
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
font-size: 1rem;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@ -834,12 +886,14 @@ p {
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s ease;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
@ -926,11 +980,41 @@ footer {
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.benefits-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.distros-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
@ -939,30 +1023,63 @@ footer {
|
||||
|
||||
.hero-content h2 {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
justify-content: center;
|
||||
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;
|
||||
}
|
||||
|
||||
.benefit-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.eol-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.distros-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.distro-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.contact-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
@ -972,48 +1089,233 @@ footer {
|
||||
|
||||
.cta-benefits {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Section padding adjustments */
|
||||
.benefits, .windows-eol, .distros, .services, .linux-features, .cta {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
/* Typography adjustments */
|
||||
h1 { font-size: 2.2rem; }
|
||||
h2 { font-size: 1.8rem; }
|
||||
h3 { font-size: 1.3rem; }
|
||||
|
||||
/* Performance bars responsive */
|
||||
.performance-bars {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.performance-item {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.perf-label {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.hero-content h2 {
|
||||
font-size: 2rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
max-width: 100%;
|
||||
margin: 0 1rem;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.terminal-body {
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
min-height: 150px;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
min-width: 130px;
|
||||
min-width: 120px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Card padding adjustments */
|
||||
.benefit-card, .service-card, .distro-card, .cta-benefit {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
/* Icon size adjustments */
|
||||
.benefit-icon, .cta-benefit .benefit-icon {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
/* Contact form improvements */
|
||||
.contact-form {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Footer adjustments */
|
||||
footer {
|
||||
padding: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Section padding for very small screens */
|
||||
.benefits, .windows-eol, .distros, .services, .linux-features, .cta {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
/* Typography for very small screens */
|
||||
h1 { font-size: 1.8rem; }
|
||||
h2 { font-size: 1.5rem; }
|
||||
h3 { font-size: 1.2rem; }
|
||||
|
||||
p {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Stats grid for mobile */
|
||||
.eol-stats {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat h3 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
/* Distro features on mobile */
|
||||
.distro-features {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.feature {
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Desktop mockup adjustments */
|
||||
.feature-mockup {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.mockup-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape phone adjustments */
|
||||
@media (max-width: 768px) and (orientation: landscape) {
|
||||
.hero {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small screens */
|
||||
@media (max-width: 360px) {
|
||||
.hero-content h2 {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.terminal-body {
|
||||
font-size: 11px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.benefit-card, .service-card, .distro-card, .cta-benefit {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* High DPI screens */
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
.terminal-body {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
@ -14,13 +14,23 @@
|
||||
<div class="nav-logo">
|
||||
<h1><a href="/">{{.CompanyName}}</a></h1>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/#voordelen">Voordelen</a></li>
|
||||
<li><a href="/#distros">Linux Keuze</a></li>
|
||||
<li><a href="/#diensten">Diensten</a></li>
|
||||
<li><a href="/contact" class="active">Contact</a></li>
|
||||
</ul>
|
||||
<div class="nav-links">
|
||||
<a href="/">Home</a>
|
||||
<a href="/#voordelen">Voordelen</a>
|
||||
<a href="/#distros">Linux Keuze</a>
|
||||
<a href="/#diensten">Diensten</a>
|
||||
<a href="/contact" class="active">Contact</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="/#voordelen">Voordelen</a>
|
||||
<a href="/#distros">Linux Keuze</a>
|
||||
<a href="/#diensten">Diensten</a>
|
||||
<a href="/contact" class="active">Contact</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@ -166,5 +176,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function toggleMobileMenu() {
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
mobileMenu.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const menuToggle = document.querySelector('.mobile-menu-toggle');
|
||||
|
||||
if (!mobileMenu.contains(event.target) && !menuToggle.contains(event.target)) {
|
||||
mobileMenu.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking on a link
|
||||
document.querySelectorAll('.mobile-menu a').forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
document.getElementById('mobile-menu').classList.remove('active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -12,15 +12,23 @@
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<div class="nav-logo">
|
||||
<h1>{{.CompanyName}}</h1>
|
||||
<h1><a href="/">Hogeland Linux</a></h1>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="/">Home</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="/contact">Contact</a>
|
||||
<a href="#services">Diensten</a>
|
||||
<a href="#distros">Distributies</a>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#home">Home</a></li>
|
||||
<li><a href="#voordelen">Voordelen</a></li>
|
||||
<li><a href="#distros">Linux Keuze</a></li>
|
||||
<li><a href="#diensten">Diensten</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@ -394,5 +402,89 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function toggleMobileMenu() {
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
mobileMenu.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const menuToggle = document.querySelector('.mobile-menu-toggle');
|
||||
|
||||
if (!mobileMenu.contains(event.target) && !menuToggle.contains(event.target)) {
|
||||
mobileMenu.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking on a link
|
||||
document.querySelectorAll('.mobile-menu a').forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
document.getElementById('mobile-menu').classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user