Merge pull request #1 from jasonMPM/claude/isp-website-docker-EB8pB
Add Dockerized LAMP stack and website skeleton for ALWISP
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
docker/apache/ssl/
|
||||
docker/mysql/data/
|
||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
# Install PHP extensions and utilities
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
libwebp-dev \
|
||||
libzip-dev \
|
||||
zip \
|
||||
unzip \
|
||||
&& docker-php-ext-configure gd --with-jpeg --with-webp \
|
||||
&& docker-php-ext-install gd pdo pdo_mysql mysqli zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable Apache modules
|
||||
RUN a2enmod rewrite ssl headers deflate expires
|
||||
|
||||
# Copy PHP config
|
||||
COPY docker/php/php.ini /usr/local/etc/php/conf.d/custom.ini
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /var/www/html
|
||||
|
||||
EXPOSE 80 443
|
||||
64
docker-compose.yml
Normal file
64
docker-compose.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: alwisp_web
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./www:/var/www/html
|
||||
- ./docker/apache/000-default.conf:/etc/apache2/sites-available/000-default.conf
|
||||
- ./docker/apache/ssl:/etc/apache2/ssl
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_NAME=${DB_NAME}
|
||||
- DB_USER=${DB_USER}
|
||||
- DB_PASS=${DB_PASS}
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- alwisp_net
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: alwisp_db
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USER}
|
||||
MYSQL_PASSWORD: ${DB_PASS}
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
networks:
|
||||
- alwisp_net
|
||||
restart: unless-stopped
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin:latest
|
||||
container_name: alwisp_pma
|
||||
ports:
|
||||
- "8080:80"
|
||||
environment:
|
||||
PMA_HOST: db
|
||||
PMA_USER: ${DB_USER}
|
||||
PMA_PASSWORD: ${DB_PASS}
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- alwisp_net
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- tools
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
|
||||
networks:
|
||||
alwisp_net:
|
||||
driver: bridge
|
||||
35
docker/apache/000-default.conf
Normal file
35
docker/apache/000-default.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@alwisp.net
|
||||
DocumentRoot /var/www/html
|
||||
|
||||
<Directory /var/www/html>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Security headers
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Compression
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json
|
||||
</IfModule>
|
||||
|
||||
# Cache static assets
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType image/webp "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
ExpiresByType text/css "access plus 1 week"
|
||||
ExpiresByType application/javascript "access plus 1 week"
|
||||
</IfModule>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
23
docker/mysql/init.sql
Normal file
23
docker/mysql/init.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- ALWISP Database Schema (skeleton)
|
||||
CREATE DATABASE IF NOT EXISTS `alwisp` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE `alwisp`;
|
||||
|
||||
-- Contact form submissions
|
||||
CREATE TABLE IF NOT EXISTS `contacts` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(120) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
`phone` VARCHAR(30),
|
||||
`subject` VARCHAR(255),
|
||||
`message` TEXT NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Service availability zones (placeholder)
|
||||
CREATE TABLE IF NOT EXISTS `coverage_zones` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`zone_name` VARCHAR(120) NOT NULL,
|
||||
`description` TEXT,
|
||||
`active` TINYINT(1) DEFAULT 1,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
22
docker/php/php.ini
Normal file
22
docker/php/php.ini
Normal file
@@ -0,0 +1,22 @@
|
||||
; Performance
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=128
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.revalidate_freq=60
|
||||
|
||||
; Security
|
||||
expose_php = Off
|
||||
display_errors = Off
|
||||
log_errors = On
|
||||
error_log = /var/log/php_errors.log
|
||||
|
||||
; Limits
|
||||
upload_max_filesize = 32M
|
||||
post_max_size = 32M
|
||||
max_execution_time = 60
|
||||
memory_limit = 256M
|
||||
|
||||
; Session
|
||||
session.cookie_httponly = 1
|
||||
session.cookie_secure = 1
|
||||
session.use_strict_mode = 1
|
||||
8
www/.htaccess
Normal file
8
www/.htaccess
Normal file
@@ -0,0 +1,8 @@
|
||||
Options -Indexes
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Route all requests through index.php (front controller)
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
|
||||
707
www/css/style.css
Normal file
707
www/css/style.css
Normal file
@@ -0,0 +1,707 @@
|
||||
/* ============================================================
|
||||
ALWISP – Mesh Network Solutions
|
||||
Brand palette extracted from logo:
|
||||
--navy: #0d1b3e (darkest bg)
|
||||
--blue: #1565c0 (primary mid)
|
||||
--teal: #00bcd4 (accent highlight)
|
||||
--orange: #f57c00 (mesh node accent)
|
||||
--white: #ffffff
|
||||
============================================================ */
|
||||
|
||||
/* ── RESET & BASE ──────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
/* Brand */
|
||||
--navy: #0d1b3e;
|
||||
--navy-mid: #132145;
|
||||
--blue: #1565c0;
|
||||
--blue-light:#1e88e5;
|
||||
--teal: #00bcd4;
|
||||
--teal-dark: #00acc1;
|
||||
--orange: #f57c00;
|
||||
--orange-lt: #ffb74d;
|
||||
|
||||
/* Neutrals */
|
||||
--white: #ffffff;
|
||||
--off-white: #f0f4f8;
|
||||
--gray-100: #e8edf3;
|
||||
--gray-300: #b0bec5;
|
||||
--gray-500: #607d8b;
|
||||
--gray-700: #37474f;
|
||||
--gray-900: #1a2332;
|
||||
|
||||
/* Gradients */
|
||||
--grad-hero: linear-gradient(135deg, #0d1b3e 0%, #1565c0 60%, #00bcd4 100%);
|
||||
--grad-text: linear-gradient(90deg, #00bcd4, #1565c0);
|
||||
--grad-cta: linear-gradient(135deg, #1565c0, #00bcd4);
|
||||
|
||||
/* Typography */
|
||||
--font-body: 'Inter', system-ui, sans-serif;
|
||||
--font-heading: 'Space Grotesk', system-ui, sans-serif;
|
||||
|
||||
/* Layout */
|
||||
--max-width: 1200px;
|
||||
--section-py: 5rem;
|
||||
--nav-h: 70px;
|
||||
|
||||
/* Radius */
|
||||
--r-sm: 6px;
|
||||
--r-md: 12px;
|
||||
--r-lg: 20px;
|
||||
--r-full: 9999px;
|
||||
|
||||
/* Shadow */
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,.12), 0 1px 2px rgba(0,0,0,.08);
|
||||
--shadow-md: 0 4px 16px rgba(0,0,0,.18);
|
||||
--shadow-lg: 0 10px 40px rgba(0,0,0,.28);
|
||||
|
||||
/* Transition */
|
||||
--t-fast: 0.18s ease;
|
||||
--t-base: 0.28s ease;
|
||||
}
|
||||
|
||||
html { scroll-behavior: smooth; font-size: 16px; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a { color: var(--teal); text-decoration: none; transition: color var(--t-fast); }
|
||||
a:hover { color: var(--orange-lt); }
|
||||
|
||||
img { max-width: 100%; display: block; }
|
||||
|
||||
ul { list-style: none; }
|
||||
|
||||
/* ── UTILITIES ──────────────────────────────────────────────── */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: var(--max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: var(--grad-text);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.accent { color: var(--teal); }
|
||||
|
||||
.section { padding-block: var(--section-py); }
|
||||
.section--alt { background: var(--navy-mid); }
|
||||
|
||||
.section__header { text-align: center; margin-bottom: 3rem; }
|
||||
.section__eyebrow {
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--teal);
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.section__heading {
|
||||
font-family: var(--font-heading);
|
||||
font-size: clamp(1.75rem, 3.5vw, 2.75rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--white);
|
||||
}
|
||||
.section__sub {
|
||||
margin-top: .75rem;
|
||||
color: var(--gray-300);
|
||||
font-size: 1.05rem;
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.placeholder-block {
|
||||
background: rgba(255,255,255,.05);
|
||||
border: 1px dashed rgba(255,255,255,.15);
|
||||
border-radius: var(--r-md);
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
color: var(--gray-300);
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
/* ── BUTTONS ────────────────────────────────────────────────── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding: .75rem 1.75rem;
|
||||
border-radius: var(--r-full);
|
||||
font-family: var(--font-heading);
|
||||
font-size: .95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: all var(--t-base);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: var(--grad-cta);
|
||||
color: var(--white);
|
||||
box-shadow: 0 4px 20px rgba(0,188,212,.3);
|
||||
}
|
||||
.btn--primary:hover {
|
||||
color: var(--white);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 28px rgba(0,188,212,.45);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
border-color: rgba(255,255,255,.35);
|
||||
color: var(--white);
|
||||
}
|
||||
.btn--ghost:hover {
|
||||
border-color: var(--teal);
|
||||
color: var(--teal);
|
||||
background: rgba(0,188,212,.08);
|
||||
}
|
||||
|
||||
/* ── NAV ────────────────────────────────────────────────────── */
|
||||
.site-header {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0;
|
||||
z-index: 100;
|
||||
height: var(--nav-h);
|
||||
background: rgba(13,27,62,.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(255,255,255,.07);
|
||||
transition: background var(--t-base), box-shadow var(--t-base);
|
||||
}
|
||||
.site-header.scrolled {
|
||||
background: rgba(13,27,62,.97);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav__logo-img { height: 38px; width: auto; }
|
||||
.nav__logo-text {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
color: var(--white);
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.nav__menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
}
|
||||
.nav__link {
|
||||
display: block;
|
||||
padding: .5rem .9rem;
|
||||
border-radius: var(--r-full);
|
||||
font-size: .9rem;
|
||||
font-weight: 500;
|
||||
color: rgba(255,255,255,.8);
|
||||
transition: color var(--t-fast), background var(--t-fast);
|
||||
}
|
||||
.nav__link:hover { color: var(--white); background: rgba(255,255,255,.08); }
|
||||
|
||||
.nav__link--cta {
|
||||
background: var(--grad-cta);
|
||||
color: var(--white) !important;
|
||||
padding: .5rem 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav__link--cta:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 16px rgba(0,188,212,.4);
|
||||
background: var(--grad-cta);
|
||||
}
|
||||
|
||||
/* Hamburger */
|
||||
.nav__toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: .5rem;
|
||||
}
|
||||
.nav__toggle-bar {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--white);
|
||||
border-radius: 2px;
|
||||
transition: transform var(--t-base), opacity var(--t-base);
|
||||
}
|
||||
|
||||
/* ── HERO ───────────────────────────────────────────────────── */
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding-top: var(--nav-h);
|
||||
background: var(--grad-hero);
|
||||
}
|
||||
|
||||
.hero__mesh-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 50%, rgba(0,188,212,.12) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(21,101,192,.18) 0%, transparent 45%),
|
||||
radial-gradient(circle at 60% 80%, rgba(245,124,0,.07) 0%, transparent 40%);
|
||||
/* Mesh dot pattern */
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 50%, rgba(0,188,212,.12) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(21,101,192,.18) 0%, transparent 45%),
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Ccircle cx='30' cy='30' r='1' fill='rgba(255,255,255,0.06)'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat, no-repeat, repeat;
|
||||
animation: meshPulse 8s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes meshPulse {
|
||||
from { opacity: .7; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 700px;
|
||||
padding-block: 4rem;
|
||||
}
|
||||
|
||||
.hero__eyebrow {
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--teal);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hero__heading {
|
||||
font-family: var(--font-heading);
|
||||
font-size: clamp(2.4rem, 6vw, 4rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.hero__sub {
|
||||
font-size: clamp(1rem, 2vw, 1.2rem);
|
||||
color: rgba(255,255,255,.78);
|
||||
max-width: 560px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.hero__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hero__scroll-hint {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: .4rem;
|
||||
}
|
||||
.hero__scroll-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--teal);
|
||||
animation: scrollBounce 1.8s ease-in-out infinite;
|
||||
opacity: .6;
|
||||
}
|
||||
@keyframes scrollBounce {
|
||||
0%, 100% { transform: translateY(0); opacity: .6; }
|
||||
50% { transform: translateY(8px); opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── STATS BAR ──────────────────────────────────────────────── */
|
||||
.stats-bar {
|
||||
background: rgba(255,255,255,.04);
|
||||
border-top: 1px solid rgba(255,255,255,.07);
|
||||
border-bottom: 1px solid rgba(255,255,255,.07);
|
||||
padding-block: 2.5rem;
|
||||
}
|
||||
.stats-bar__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.stat__value {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--teal);
|
||||
}
|
||||
.stat__unit {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: var(--teal);
|
||||
}
|
||||
.stat__label {
|
||||
display: block;
|
||||
font-size: .82rem;
|
||||
color: var(--gray-300);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
/* ── CARDS ──────────────────────────────────────────────────── */
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255,255,255,.04);
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
border-radius: var(--r-lg);
|
||||
padding: 2rem 1.75rem;
|
||||
transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
|
||||
}
|
||||
.card--hover:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 16px 40px rgba(0,0,0,.35);
|
||||
border-color: rgba(0,188,212,.3);
|
||||
}
|
||||
|
||||
.card__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 1.25rem;
|
||||
color: var(--teal);
|
||||
}
|
||||
.card__icon svg { width: 100%; height: 100%; }
|
||||
|
||||
.card__title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: .6rem;
|
||||
}
|
||||
.card__body {
|
||||
color: var(--gray-300);
|
||||
font-size: .93rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.card__link {
|
||||
font-weight: 600;
|
||||
font-size: .88rem;
|
||||
color: var(--teal);
|
||||
letter-spacing: .03em;
|
||||
}
|
||||
.card__link:hover { color: var(--orange-lt); }
|
||||
|
||||
/* ── WHY ALWISP ─────────────────────────────────────────────── */
|
||||
.why__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
}
|
||||
.why__text .section__heading { text-align: left; }
|
||||
.why__text p {
|
||||
color: var(--gray-300);
|
||||
margin-block: 1rem 1.5rem;
|
||||
max-width: 480px;
|
||||
}
|
||||
.why__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem;
|
||||
}
|
||||
.why__list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
font-size: .95rem;
|
||||
color: rgba(255,255,255,.85);
|
||||
}
|
||||
.why__check {
|
||||
color: var(--teal);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Mesh SVG diagram */
|
||||
.why__visual { display: flex; justify-content: center; }
|
||||
.mesh-diagram { width: 100%; max-width: 380px; }
|
||||
.mesh-svg { width: 100%; height: auto; }
|
||||
|
||||
.mesh-node {
|
||||
filter: drop-shadow(0 0 6px currentColor);
|
||||
}
|
||||
.mesh-node--tower {
|
||||
filter: drop-shadow(0 0 10px rgba(245,124,0,.8));
|
||||
animation: nodePulse 2.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes nodePulse {
|
||||
0%, 100% { r: 12; opacity: .9; }
|
||||
50% { r: 14; opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── CTA BAND ───────────────────────────────────────────────── */
|
||||
.cta-band {
|
||||
background: linear-gradient(135deg, rgba(21,101,192,.25), rgba(0,188,212,.15));
|
||||
border-top: 1px solid rgba(0,188,212,.2);
|
||||
border-bottom: 1px solid rgba(0,188,212,.2);
|
||||
}
|
||||
.cta-band__content { text-align: center; }
|
||||
.cta-band__heading {
|
||||
font-family: var(--font-heading);
|
||||
font-size: clamp(1.5rem, 3vw, 2.2rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: .6rem;
|
||||
}
|
||||
.cta-band__sub { color: var(--gray-300); margin-bottom: 1.75rem; }
|
||||
.cta-band__form {
|
||||
display: flex;
|
||||
gap: .75rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 560px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.cta-band__input {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
padding: .75rem 1.25rem;
|
||||
border-radius: var(--r-full);
|
||||
border: 1px solid rgba(255,255,255,.2);
|
||||
background: rgba(255,255,255,.07);
|
||||
color: var(--white);
|
||||
font-size: .95rem;
|
||||
outline: none;
|
||||
transition: border-color var(--t-fast), background var(--t-fast);
|
||||
}
|
||||
.cta-band__input::placeholder { color: var(--gray-300); }
|
||||
.cta-band__input:focus {
|
||||
border-color: var(--teal);
|
||||
background: rgba(255,255,255,.12);
|
||||
}
|
||||
|
||||
/* ── PAGE HERO ──────────────────────────────────────────────── */
|
||||
.page-hero {
|
||||
padding-top: calc(var(--nav-h) + 3rem);
|
||||
padding-bottom: 3rem;
|
||||
background: linear-gradient(135deg, #0d1b3e 0%, #132145 100%);
|
||||
border-bottom: 1px solid rgba(255,255,255,.06);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── CONTACT ────────────────────────────────────────────────── */
|
||||
.contact__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 4rem;
|
||||
align-items: start;
|
||||
}
|
||||
.contact__heading {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.contact__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
color: var(--gray-300);
|
||||
font-size: .95rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.contact__details a { color: var(--teal); }
|
||||
|
||||
/* ── FORM ───────────────────────────────────────────────────── */
|
||||
.form { display: flex; flex-direction: column; gap: 1.25rem; }
|
||||
.form__row { display: flex; gap: 1rem; }
|
||||
.form__row--2 > * { flex: 1; }
|
||||
.form__group { display: flex; flex-direction: column; gap: .4rem; }
|
||||
.form__label {
|
||||
font-size: .85rem;
|
||||
font-weight: 500;
|
||||
color: var(--gray-300);
|
||||
}
|
||||
.form__label span { color: var(--teal); }
|
||||
.form__input {
|
||||
padding: .7rem 1rem;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid rgba(255,255,255,.15);
|
||||
background: rgba(255,255,255,.06);
|
||||
color: var(--white);
|
||||
font-size: .95rem;
|
||||
font-family: var(--font-body);
|
||||
outline: none;
|
||||
transition: border-color var(--t-fast);
|
||||
}
|
||||
.form__input:focus { border-color: var(--teal); background: rgba(255,255,255,.1); }
|
||||
.form__textarea { resize: vertical; min-height: 130px; }
|
||||
select.form__input option { background: var(--navy); }
|
||||
|
||||
/* ── ALERTS ─────────────────────────────────────────────────── */
|
||||
.alert {
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: var(--r-sm);
|
||||
font-size: .93rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.alert--success { background: rgba(0,188,212,.15); border: 1px solid rgba(0,188,212,.4); color: #b2ebf2; }
|
||||
.alert--error { background: rgba(229,57,53,.15); border: 1px solid rgba(229,57,53,.4); color: #ffcdd2; }
|
||||
.alert ul { list-style: disc; padding-left: 1.25rem; }
|
||||
|
||||
/* ── FOOTER ─────────────────────────────────────────────────── */
|
||||
.site-footer {
|
||||
background: #080f1f;
|
||||
border-top: 1px solid rgba(255,255,255,.07);
|
||||
padding-top: 3.5rem;
|
||||
}
|
||||
.footer__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr 1fr 1.2fr;
|
||||
gap: 2.5rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
.footer__tagline {
|
||||
color: var(--gray-300);
|
||||
font-size: .9rem;
|
||||
line-height: 1.7;
|
||||
margin-top: .75rem;
|
||||
}
|
||||
.footer__heading {
|
||||
font-family: var(--font-heading);
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
color: var(--teal);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.footer__links ul { display: flex; flex-direction: column; gap: .5rem; }
|
||||
.footer__links a {
|
||||
color: var(--gray-300);
|
||||
font-size: .9rem;
|
||||
}
|
||||
.footer__links a:hover { color: var(--white); }
|
||||
.footer__contact p {
|
||||
color: var(--gray-300);
|
||||
font-size: .9rem;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.footer__contact a { color: var(--gray-300); }
|
||||
.footer__contact a:hover { color: var(--teal); }
|
||||
.footer__social {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.footer__social-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: var(--r-sm);
|
||||
background: rgba(255,255,255,.07);
|
||||
font-size: .72rem;
|
||||
font-weight: 700;
|
||||
color: var(--gray-300);
|
||||
transition: background var(--t-fast), color var(--t-fast);
|
||||
}
|
||||
.footer__social-link:hover { background: var(--teal); color: var(--white); }
|
||||
.footer__bottom {
|
||||
padding-block: 1.25rem;
|
||||
border-top: 1px solid rgba(255,255,255,.06);
|
||||
text-align: center;
|
||||
color: var(--gray-500);
|
||||
font-size: .82rem;
|
||||
}
|
||||
|
||||
/* ── RESPONSIVE ─────────────────────────────────────────────── */
|
||||
@media (max-width: 1024px) {
|
||||
.why__grid { grid-template-columns: 1fr; gap: 2.5rem; }
|
||||
.why__visual { display: none; }
|
||||
.footer__grid { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root { --section-py: 3.5rem; }
|
||||
|
||||
/* Mobile nav */
|
||||
.nav__toggle { display: flex; }
|
||||
|
||||
.nav__menu {
|
||||
position: fixed;
|
||||
top: var(--nav-h);
|
||||
left: 0; right: 0;
|
||||
background: rgba(13,27,62,.98);
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 1rem 1.5rem 2rem;
|
||||
gap: .25rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,.1);
|
||||
transform: translateY(-110%);
|
||||
opacity: 0;
|
||||
transition: transform var(--t-base), opacity var(--t-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
.nav__menu.is-open {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.nav__link { padding: .75rem 1rem; border-radius: var(--r-sm); }
|
||||
.nav__link--cta { text-align: center; margin-top: .5rem; }
|
||||
|
||||
.stats-bar__grid { grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }
|
||||
.contact__grid { grid-template-columns: 1fr; gap: 2rem; }
|
||||
.form__row--2 { flex-direction: column; }
|
||||
.footer__grid { grid-template-columns: 1fr; gap: 2rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hero__actions { flex-direction: column; }
|
||||
.hero__actions .btn { text-align: center; justify-content: center; }
|
||||
.stats-bar__grid { grid-template-columns: 1fr 1fr; }
|
||||
.cta-band__form { flex-direction: column; }
|
||||
.cta-band__input { min-width: 100%; }
|
||||
}
|
||||
53
www/includes/footer.php
Normal file
53
www/includes/footer.php
Normal file
@@ -0,0 +1,53 @@
|
||||
</main>
|
||||
|
||||
<!-- ===================== FOOTER ===================== -->
|
||||
<footer class="site-footer">
|
||||
<div class="container footer__grid">
|
||||
|
||||
<div class="footer__brand">
|
||||
<a href="/" class="nav__logo" aria-label="ALWISP Home">
|
||||
<span class="nav__logo-text">AL<span class="accent">WISP</span></span>
|
||||
</a>
|
||||
<p class="footer__tagline">Connecting Alabama,<br>one node at a time.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer__links">
|
||||
<h4 class="footer__heading">Services</h4>
|
||||
<ul>
|
||||
<li><a href="/services#residential">Residential Internet</a></li>
|
||||
<li><a href="/services#business">Business Internet</a></li>
|
||||
<li><a href="/services#infrastructure">Network Infrastructure</a></li>
|
||||
<li><a href="/services#managed">Managed Networking</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer__links">
|
||||
<h4 class="footer__heading">Company</h4>
|
||||
<ul>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/coverage">Coverage Map</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer__contact">
|
||||
<h4 class="footer__heading">Contact</h4>
|
||||
<p>📞 <a href="tel:+1-000-000-0000">(000) 000-0000</a></p>
|
||||
<p>✉ <a href="mailto:info@alwisp.net">info@alwisp.net</a></p>
|
||||
<div class="footer__social">
|
||||
<!-- Social icons — add links when ready -->
|
||||
<a href="#" aria-label="Facebook" class="footer__social-link">FB</a>
|
||||
<a href="#" aria-label="Twitter/X" class="footer__social-link">X</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer__bottom container">
|
||||
<p>© <?= date('Y') ?> Alabama WISP – Mesh Network Solutions. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/js/main.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
45
www/includes/header.php
Normal file
45
www/includes/header.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Alabama WISP – Mesh Network Solutions. High-speed wireless internet and enterprise networking for rural and underserved communities.">
|
||||
<title>ALWISP – Mesh Network Solutions</title>
|
||||
|
||||
<!-- Preconnect for performance -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<!-- Google Fonts: Inter (body) + Space Grotesk (headings) -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ===================== NAV ===================== -->
|
||||
<header class="site-header" id="site-header">
|
||||
<nav class="nav container">
|
||||
<a href="/" class="nav__logo" aria-label="ALWISP Home">
|
||||
<img src="/assets/logo-condensed.png" alt="ALWISP Logo" class="nav__logo-img" onerror="this.style.display='none'">
|
||||
<span class="nav__logo-text">AL<span class="accent">WISP</span></span>
|
||||
</a>
|
||||
|
||||
<button class="nav__toggle" id="nav-toggle" aria-label="Open menu" aria-expanded="false" aria-controls="nav-menu">
|
||||
<span class="nav__toggle-bar"></span>
|
||||
<span class="nav__toggle-bar"></span>
|
||||
<span class="nav__toggle-bar"></span>
|
||||
</button>
|
||||
|
||||
<ul class="nav__menu" id="nav-menu" role="menubar">
|
||||
<li role="none"><a href="/" class="nav__link" role="menuitem">Home</a></li>
|
||||
<li role="none"><a href="/services" class="nav__link" role="menuitem">Services</a></li>
|
||||
<li role="none"><a href="/coverage" class="nav__link" role="menuitem">Coverage</a></li>
|
||||
<li role="none"><a href="/about" class="nav__link" role="menuitem">About</a></li>
|
||||
<li role="none"><a href="/contact" class="nav__link nav__link--cta" role="menuitem">Get Connected</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
20
www/index.php
Normal file
20
www/index.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// Simple front controller — expand routing here later
|
||||
$path = trim($_GET['path'] ?? '', '/');
|
||||
|
||||
// Map paths to page includes
|
||||
$pages = [
|
||||
'' => 'pages/home.php',
|
||||
'services' => 'pages/services.php',
|
||||
'coverage' => 'pages/coverage.php',
|
||||
'about' => 'pages/about.php',
|
||||
'contact' => 'pages/contact.php',
|
||||
];
|
||||
|
||||
$page = $pages[$path] ?? 'pages/404.php';
|
||||
$pageFile = __DIR__ . '/' . $page;
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
include file_exists($pageFile) ? $pageFile : __DIR__ . '/pages/404.php';
|
||||
include __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
114
www/js/main.js
Normal file
114
www/js/main.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/* ============================================================
|
||||
ALWISP – main.js
|
||||
============================================================ */
|
||||
|
||||
'use strict';
|
||||
|
||||
// ── NAV: scroll shadow + mobile toggle ──────────────────────
|
||||
(function () {
|
||||
const header = document.getElementById('site-header');
|
||||
const toggle = document.getElementById('nav-toggle');
|
||||
const menu = document.getElementById('nav-menu');
|
||||
const navLinks = menu ? menu.querySelectorAll('.nav__link') : [];
|
||||
|
||||
// Scroll shadow
|
||||
if (header) {
|
||||
const onScroll = () => header.classList.toggle('scrolled', window.scrollY > 10);
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
}
|
||||
|
||||
// Mobile toggle
|
||||
if (toggle && menu) {
|
||||
toggle.addEventListener('click', () => {
|
||||
const isOpen = menu.classList.toggle('is-open');
|
||||
toggle.setAttribute('aria-expanded', isOpen);
|
||||
});
|
||||
|
||||
// Close on nav link click
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
menu.classList.remove('is-open');
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', e => {
|
||||
if (!header.contains(e.target)) {
|
||||
menu.classList.remove('is-open');
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Active nav link
|
||||
const currentPath = window.location.pathname.replace(/\/$/, '') || '/';
|
||||
navLinks.forEach(link => {
|
||||
const href = link.getAttribute('href').replace(/\/$/, '') || '/';
|
||||
if (href === currentPath) link.classList.add('nav__link--active');
|
||||
});
|
||||
}());
|
||||
|
||||
|
||||
// ── COUNTER ANIMATION (stats bar) ───────────────────────────
|
||||
(function () {
|
||||
const counters = document.querySelectorAll('.stat__value[data-count]');
|
||||
if (!counters.length) return;
|
||||
|
||||
const easeOut = t => 1 - Math.pow(1 - t, 3);
|
||||
|
||||
function animateCounter(el) {
|
||||
const target = parseInt(el.dataset.count, 10);
|
||||
const duration = 1400;
|
||||
const start = performance.now();
|
||||
|
||||
function step(now) {
|
||||
const elapsed = now - start;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
el.textContent = Math.floor(easeOut(progress) * target);
|
||||
if (progress < 1) requestAnimationFrame(step);
|
||||
else el.textContent = target;
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateCounter(entry.target);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.4 });
|
||||
|
||||
counters.forEach(c => observer.observe(c));
|
||||
}());
|
||||
|
||||
|
||||
// ── SCROLL REVEAL (cards, sections) ─────────────────────────
|
||||
(function () {
|
||||
const revealEls = document.querySelectorAll(
|
||||
'.card, .stat, .why__text, .section__header, .cta-band__content'
|
||||
);
|
||||
|
||||
if (!revealEls.length || !('IntersectionObserver' in window)) return;
|
||||
|
||||
revealEls.forEach((el, i) => {
|
||||
el.style.opacity = '0';
|
||||
el.style.transform = 'translateY(22px)';
|
||||
el.style.transition = `opacity 0.55s ease ${i * 0.07}s, transform 0.55s ease ${i * 0.07}s`;
|
||||
});
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12 });
|
||||
|
||||
revealEls.forEach(el => observer.observe(el));
|
||||
}());
|
||||
9
www/pages/404.php
Normal file
9
www/pages/404.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php http_response_code(404); ?>
|
||||
<section class="section page-hero" style="text-align:center; min-height:60vh; display:flex; align-items:center;">
|
||||
<div class="container">
|
||||
<p class="hero__eyebrow">Error 404</p>
|
||||
<h1 class="section__heading">Page Not Found</h1>
|
||||
<p class="section__sub">The page you're looking for doesn't exist or has moved.</p>
|
||||
<a href="/" class="btn btn--primary" style="margin-top:2rem">Back to Home</a>
|
||||
</div>
|
||||
</section>
|
||||
14
www/pages/about.php
Normal file
14
www/pages/about.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<!-- ABOUT PAGE — placeholder -->
|
||||
<section class="section page-hero">
|
||||
<div class="container">
|
||||
<span class="section__eyebrow">Who We Are</span>
|
||||
<h1 class="section__heading">About ALWISP</h1>
|
||||
<p class="section__sub">Alabama's locally-owned wireless internet and mesh networking company.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container" style="max-width:800px">
|
||||
<div class="placeholder-block">Company story, team bios, and mission statement will go here.</div>
|
||||
</div>
|
||||
</section>
|
||||
96
www/pages/contact.php
Normal file
96
www/pages/contact.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
$success = false;
|
||||
$errors = [];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim(htmlspecialchars($_POST['name'] ?? '', ENT_QUOTES));
|
||||
$email = trim(htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES));
|
||||
$phone = trim(htmlspecialchars($_POST['phone'] ?? '', ENT_QUOTES));
|
||||
$subject = trim(htmlspecialchars($_POST['subject'] ?? '', ENT_QUOTES));
|
||||
$message = trim(htmlspecialchars($_POST['message'] ?? '', ENT_QUOTES));
|
||||
|
||||
if (!$name) $errors[] = 'Name is required.';
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'A valid email is required.';
|
||||
if (!$message) $errors[] = 'Message is required.';
|
||||
|
||||
if (empty($errors)) {
|
||||
// TODO: swap for DB insert + email once credentials are configured
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<section class="section page-hero">
|
||||
<div class="container">
|
||||
<span class="section__eyebrow">Reach Out</span>
|
||||
<h1 class="section__heading">Contact Us</h1>
|
||||
<p class="section__sub">Questions about service, coverage, or your account? We're here to help.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container contact__grid">
|
||||
|
||||
<div class="contact__info">
|
||||
<h2 class="contact__heading">Get In Touch</h2>
|
||||
<ul class="contact__details">
|
||||
<li>📞 <a href="tel:+10000000000">(000) 000-0000</a></li>
|
||||
<li>✉ <a href="mailto:info@alwisp.net">info@alwisp.net</a></li>
|
||||
<li>🕐 Mon–Fri 8am–6pm CST<br>Emergency support 24/7</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="contact__form-wrap">
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert--success" role="alert">
|
||||
Thanks! We'll be in touch within one business day.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<ul><?php foreach ($errors as $e) echo "<li>" . $e . "</li>"; ?></ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/contact" class="form" novalidate>
|
||||
<div class="form__row form__row--2">
|
||||
<div class="form__group">
|
||||
<label for="name" class="form__label">Name <span aria-hidden="true">*</span></label>
|
||||
<input type="text" id="name" name="name" class="form__input" required
|
||||
value="<?= htmlspecialchars($_POST['name'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="form__group">
|
||||
<label for="email" class="form__label">Email <span aria-hidden="true">*</span></label>
|
||||
<input type="email" id="email" name="email" class="form__input" required
|
||||
value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__row form__row--2">
|
||||
<div class="form__group">
|
||||
<label for="phone" class="form__label">Phone</label>
|
||||
<input type="tel" id="phone" name="phone" class="form__input"
|
||||
value="<?= htmlspecialchars($_POST['phone'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="form__group">
|
||||
<label for="subject" class="form__label">Subject</label>
|
||||
<select id="subject" name="subject" class="form__input">
|
||||
<option value="">Select a topic…</option>
|
||||
<option value="new-service">New Service Inquiry</option>
|
||||
<option value="support">Technical Support</option>
|
||||
<option value="billing">Billing Question</option>
|
||||
<option value="coverage">Coverage Question</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__group">
|
||||
<label for="message" class="form__label">Message <span aria-hidden="true">*</span></label>
|
||||
<textarea id="message" name="message" class="form__input form__textarea" rows="5" required><?= htmlspecialchars($_POST['message'] ?? '') ?></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn--primary">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
17
www/pages/coverage.php
Normal file
17
www/pages/coverage.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- COVERAGE PAGE — placeholder -->
|
||||
<section class="section page-hero">
|
||||
<div class="container">
|
||||
<span class="section__eyebrow">Service Area</span>
|
||||
<h1 class="section__heading">Coverage Map</h1>
|
||||
<p class="section__sub">See where ALWISP service is available.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="placeholder-block" style="min-height:420px; display:flex; align-items:center; justify-content:center;">
|
||||
Interactive coverage map will be embedded here.<br>
|
||||
(Leaflet.js / Google Maps integration — roadmap item)
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
170
www/pages/home.php
Normal file
170
www/pages/home.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<!-- ====================================================
|
||||
HOME PAGE
|
||||
===================================================== -->
|
||||
|
||||
<!-- HERO -->
|
||||
<section class="hero" aria-labelledby="hero-heading">
|
||||
<div class="hero__mesh-bg" aria-hidden="true"></div>
|
||||
<div class="container hero__content">
|
||||
<p class="hero__eyebrow">Alabama's Wireless Network Provider</p>
|
||||
<h1 id="hero-heading" class="hero__heading">
|
||||
Fast, Reliable Internet<br>
|
||||
<span class="gradient-text">Built for Alabama</span>
|
||||
</h1>
|
||||
<p class="hero__sub">
|
||||
Enterprise-grade mesh networking that reaches where fiber can't.
|
||||
Residential plans, business solutions, and infrastructure buildouts across Alabama.
|
||||
</p>
|
||||
<div class="hero__actions">
|
||||
<a href="/coverage" class="btn btn--primary">Check My Coverage</a>
|
||||
<a href="/services" class="btn btn--ghost">View Plans</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero__scroll-hint" aria-hidden="true">
|
||||
<span class="hero__scroll-dot"></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- STATS BAR -->
|
||||
<section class="stats-bar" aria-label="Quick statistics">
|
||||
<div class="container stats-bar__grid">
|
||||
<div class="stat">
|
||||
<span class="stat__value" data-count="500">0</span><span class="stat__unit">+</span>
|
||||
<span class="stat__label">Active Subscribers</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat__value" data-count="15">0</span><span class="stat__unit"> Counties</span>
|
||||
<span class="stat__label">Coverage Area</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat__value" data-count="99">0</span><span class="stat__unit">%</span>
|
||||
<span class="stat__label">Uptime SLA</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat__value" data-count="1">0</span><span class="stat__unit">Gbps</span>
|
||||
<span class="stat__label">Backbone Capacity</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- SERVICES PREVIEW -->
|
||||
<section class="section services-preview" aria-labelledby="services-heading">
|
||||
<div class="container">
|
||||
<div class="section__header">
|
||||
<span class="section__eyebrow">What We Offer</span>
|
||||
<h2 id="services-heading" class="section__heading">Connectivity Solutions</h2>
|
||||
<p class="section__sub">From home internet to full network infrastructure buildouts.</p>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<div class="card card--hover">
|
||||
<div class="card__icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>
|
||||
</div>
|
||||
<h3 class="card__title">Residential Internet</h3>
|
||||
<p class="card__body">High-speed wireless internet for homes and small properties. No contracts, transparent pricing.</p>
|
||||
<a href="/services#residential" class="card__link">See Plans →</a>
|
||||
</div>
|
||||
|
||||
<div class="card card--hover">
|
||||
<div class="card__icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="12" y1="12" x2="12" y2="16"/><line x1="10" y1="14" x2="14" y2="14"/></svg>
|
||||
</div>
|
||||
<h3 class="card__title">Business Internet</h3>
|
||||
<p class="card__body">Dedicated bandwidth, static IPs, and priority support for businesses of any size.</p>
|
||||
<a href="/services#business" class="card__link">See Plans →</a>
|
||||
</div>
|
||||
|
||||
<div class="card card--hover">
|
||||
<div class="card__icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2"/><circle cx="12" cy="5" r="1"/><circle cx="19" cy="9" r="1"/><circle cx="19" cy="15" r="1"/><circle cx="12" cy="19" r="1"/><circle cx="5" cy="15" r="1"/><circle cx="5" cy="9" r="1"/><path d="M12 7v3m5.2.8-2.5 1.5m2.5 4.7-2.5-1.5M12 17v-3m-5.2-.8 2.5-1.5m-2.5-4.7 2.5 1.5"/></svg>
|
||||
</div>
|
||||
<h3 class="card__title">Mesh Infrastructure</h3>
|
||||
<p class="card__body">Full network design, tower buildouts, and mesh deployments for communities and enterprises.</p>
|
||||
<a href="/services#infrastructure" class="card__link">Learn More →</a>
|
||||
</div>
|
||||
|
||||
<div class="card card--hover">
|
||||
<div class="card__icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
</div>
|
||||
<h3 class="card__title">Managed Networking</h3>
|
||||
<p class="card__body">Ongoing monitoring, maintenance, and support so your network runs itself.</p>
|
||||
<a href="/services#managed" class="card__link">Learn More →</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- WHY ALWISP -->
|
||||
<section class="section section--alt why" aria-labelledby="why-heading">
|
||||
<div class="container why__grid">
|
||||
<div class="why__text">
|
||||
<span class="section__eyebrow">Why ALWISP</span>
|
||||
<h2 id="why-heading" class="section__heading">Local Team.<br>Enterprise Tech.</h2>
|
||||
<p>We're an Alabama company solving Alabama's connectivity gap. We deploy the same equipment used by major carriers — without the corporate red tape or out-of-state call centers.</p>
|
||||
<ul class="why__list">
|
||||
<li><span class="why__check" aria-hidden="true">✓</span> Local 24/7 technical support</li>
|
||||
<li><span class="why__check" aria-hidden="true">✓</span> No data caps on most plans</li>
|
||||
<li><span class="why__check" aria-hidden="true">✓</span> Month-to-month options available</li>
|
||||
<li><span class="why__check" aria-hidden="true">✓</span> Self-healing mesh redundancy</li>
|
||||
<li><span class="why__check" aria-hidden="true">✓</span> Transparent pricing — no hidden fees</li>
|
||||
</ul>
|
||||
<a href="/about" class="btn btn--primary" style="margin-top:1.5rem">About Us</a>
|
||||
</div>
|
||||
<div class="why__visual" aria-hidden="true">
|
||||
<div class="mesh-diagram">
|
||||
<!-- SVG mesh nodes — decorative -->
|
||||
<svg viewBox="0 0 400 340" xmlns="http://www.w3.org/2000/svg" class="mesh-svg">
|
||||
<defs>
|
||||
<linearGradient id="lineGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#00bcd4" stop-opacity="0.6"/>
|
||||
<stop offset="100%" stop-color="#1565c0" stop-opacity="0.3"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Edges -->
|
||||
<g stroke="url(#lineGrad)" stroke-width="1.5" fill="none">
|
||||
<line x1="200" y1="60" x2="320" y2="140"/>
|
||||
<line x1="200" y1="60" x2="80" y2="140"/>
|
||||
<line x1="320" y1="140" x2="80" y2="140"/>
|
||||
<line x1="320" y1="140" x2="280" y2="280"/>
|
||||
<line x1="80" y1="140" x2="120" y2="280"/>
|
||||
<line x1="280" y1="280" x2="120" y2="280"/>
|
||||
<line x1="200" y1="60" x2="200" y2="200"/>
|
||||
<line x1="200" y1="200" x2="320" y2="140"/>
|
||||
<line x1="200" y1="200" x2="80" y2="140"/>
|
||||
<line x1="200" y1="200" x2="280" y2="280"/>
|
||||
<line x1="200" y1="200" x2="120" y2="280"/>
|
||||
</g>
|
||||
<!-- Nodes -->
|
||||
<g>
|
||||
<circle cx="200" cy="60" r="12" fill="#f57c00" opacity="0.9" class="mesh-node mesh-node--tower"/>
|
||||
<circle cx="320" cy="140" r="9" fill="#00bcd4" opacity="0.85" class="mesh-node"/>
|
||||
<circle cx="80" cy="140" r="9" fill="#00bcd4" opacity="0.85" class="mesh-node"/>
|
||||
<circle cx="200" cy="200" r="10" fill="#1565c0" opacity="0.9" class="mesh-node"/>
|
||||
<circle cx="280" cy="280" r="8" fill="#00acc1" opacity="0.8" class="mesh-node"/>
|
||||
<circle cx="120" cy="280" r="8" fill="#00acc1" opacity="0.8" class="mesh-node"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- COVERAGE CTA -->
|
||||
<section class="section cta-band" aria-labelledby="cta-heading">
|
||||
<div class="container cta-band__content">
|
||||
<h2 id="cta-heading" class="cta-band__heading">Are you in our coverage area?</h2>
|
||||
<p class="cta-band__sub">Enter your address to see available plans in your area.</p>
|
||||
<form class="cta-band__form" action="/coverage" method="get" role="search" aria-label="Coverage check">
|
||||
<input type="text" name="address" placeholder="Enter your address…" class="cta-band__input" aria-label="Street address" required>
|
||||
<button type="submit" class="btn btn--primary">Check Now</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
45
www/pages/services.php
Normal file
45
www/pages/services.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- SERVICES PAGE — placeholder -->
|
||||
<section class="section page-hero page-hero--services">
|
||||
<div class="container">
|
||||
<span class="section__eyebrow">What We Offer</span>
|
||||
<h1 class="section__heading">Our Services</h1>
|
||||
<p class="section__sub">Flexible plans for every need — from a farmhouse to a full enterprise campus.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="residential">
|
||||
<div class="container">
|
||||
<div class="section__header">
|
||||
<h2 class="section__heading">Residential Plans</h2>
|
||||
<p class="section__sub">Content coming soon — plan tiers will be displayed here.</p>
|
||||
</div>
|
||||
<div class="placeholder-block">Plan cards will go here.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section--alt" id="business">
|
||||
<div class="container">
|
||||
<div class="section__header">
|
||||
<h2 class="section__heading">Business Plans</h2>
|
||||
</div>
|
||||
<div class="placeholder-block">Business plan cards will go here.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="infrastructure">
|
||||
<div class="container">
|
||||
<div class="section__header">
|
||||
<h2 class="section__heading">Network Infrastructure</h2>
|
||||
</div>
|
||||
<div class="placeholder-block">Infrastructure service details will go here.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section--alt" id="managed">
|
||||
<div class="container">
|
||||
<div class="section__header">
|
||||
<h2 class="section__heading">Managed Networking</h2>
|
||||
</div>
|
||||
<div class="placeholder-block">Managed service details will go here.</div>
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user