diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b08dc7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +docker/apache/ssl/ +docker/mysql/data/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4c3f7ea --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2c57894 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/docker/apache/000-default.conf b/docker/apache/000-default.conf new file mode 100644 index 0000000..8979fa5 --- /dev/null +++ b/docker/apache/000-default.conf @@ -0,0 +1,35 @@ + + ServerAdmin webmaster@alwisp.net + DocumentRoot /var/www/html + + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + + + # 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 + + AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json + + + # Cache static assets + + 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" + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/docker/mysql/init.sql b/docker/mysql/init.sql new file mode 100644 index 0000000..970868a --- /dev/null +++ b/docker/mysql/init.sql @@ -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; diff --git a/docker/php/php.ini b/docker/php/php.ini new file mode 100644 index 0000000..be477f1 --- /dev/null +++ b/docker/php/php.ini @@ -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 diff --git a/www/.htaccess b/www/.htaccess new file mode 100644 index 0000000..75a419b --- /dev/null +++ b/www/.htaccess @@ -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] diff --git a/www/css/style.css b/www/css/style.css new file mode 100644 index 0000000..ae7b31b --- /dev/null +++ b/www/css/style.css @@ -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%; } +} diff --git a/www/includes/footer.php b/www/includes/footer.php new file mode 100644 index 0000000..72348ad --- /dev/null +++ b/www/includes/footer.php @@ -0,0 +1,53 @@ + + + + + + + + diff --git a/www/includes/header.php b/www/includes/header.php new file mode 100644 index 0000000..67ee92d --- /dev/null +++ b/www/includes/header.php @@ -0,0 +1,45 @@ + + + + + + + ALWISP – Mesh Network Solutions + + + + + + + + + + + + + + + + +
diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..e8a3c3a --- /dev/null +++ b/www/index.php @@ -0,0 +1,20 @@ + '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'; +?> diff --git a/www/js/main.js b/www/js/main.js new file mode 100644 index 0000000..cedd15e --- /dev/null +++ b/www/js/main.js @@ -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)); +}()); diff --git a/www/pages/404.php b/www/pages/404.php new file mode 100644 index 0000000..15d653f --- /dev/null +++ b/www/pages/404.php @@ -0,0 +1,9 @@ + +
+
+

Error 404

+

Page Not Found

+

The page you're looking for doesn't exist or has moved.

+ Back to Home +
+
diff --git a/www/pages/about.php b/www/pages/about.php new file mode 100644 index 0000000..913fafe --- /dev/null +++ b/www/pages/about.php @@ -0,0 +1,14 @@ + +
+
+ Who We Are +

About ALWISP

+

Alabama's locally-owned wireless internet and mesh networking company.

+
+
+ +
+
+
Company story, team bios, and mission statement will go here.
+
+
diff --git a/www/pages/contact.php b/www/pages/contact.php new file mode 100644 index 0000000..76bffe7 --- /dev/null +++ b/www/pages/contact.php @@ -0,0 +1,96 @@ + + +
+
+ Reach Out +

Contact Us

+

Questions about service, coverage, or your account? We're here to help.

+
+
+ +
+
+ +
+

Get In Touch

+ +
+ +
+ + + + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ +
+
diff --git a/www/pages/coverage.php b/www/pages/coverage.php new file mode 100644 index 0000000..2c03d94 --- /dev/null +++ b/www/pages/coverage.php @@ -0,0 +1,17 @@ + +
+
+ Service Area +

Coverage Map

+

See where ALWISP service is available.

+
+
+ +
+
+
+ Interactive coverage map will be embedded here.
+ (Leaflet.js / Google Maps integration — roadmap item) +
+
+
diff --git a/www/pages/home.php b/www/pages/home.php new file mode 100644 index 0000000..383f57c --- /dev/null +++ b/www/pages/home.php @@ -0,0 +1,170 @@ + + + +
+ +
+

Alabama's Wireless Network Provider

+

+ Fast, Reliable Internet
+ Built for Alabama +

+

+ Enterprise-grade mesh networking that reaches where fiber can't. + Residential plans, business solutions, and infrastructure buildouts across Alabama. +

+ +
+ +
+ + + +
+
+
+ 0+ + Active Subscribers +
+
+ 0 Counties + Coverage Area +
+
+ 0% + Uptime SLA +
+
+ 0Gbps + Backbone Capacity +
+
+
+ + + +
+
+
+ What We Offer +

Connectivity Solutions

+

From home internet to full network infrastructure buildouts.

+
+ +
+ +
+ +

Residential Internet

+

High-speed wireless internet for homes and small properties. No contracts, transparent pricing.

+ See Plans → +
+ +
+ +

Business Internet

+

Dedicated bandwidth, static IPs, and priority support for businesses of any size.

+ See Plans → +
+ +
+ +

Mesh Infrastructure

+

Full network design, tower buildouts, and mesh deployments for communities and enterprises.

+ Learn More → +
+ +
+ +

Managed Networking

+

Ongoing monitoring, maintenance, and support so your network runs itself.

+ Learn More → +
+ +
+
+
+ + + +
+
+
+ Why ALWISP +

Local Team.
Enterprise Tech.

+

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.

+
    +
  • Local 24/7 technical support
  • +
  • No data caps on most plans
  • +
  • Month-to-month options available
  • +
  • Self-healing mesh redundancy
  • +
  • Transparent pricing — no hidden fees
  • +
+ About Us +
+ +
+
+ + + +
+
+

Are you in our coverage area?

+

Enter your address to see available plans in your area.

+ +
+
diff --git a/www/pages/services.php b/www/pages/services.php new file mode 100644 index 0000000..0a24965 --- /dev/null +++ b/www/pages/services.php @@ -0,0 +1,45 @@ + +
+
+ What We Offer +

Our Services

+

Flexible plans for every need — from a farmhouse to a full enterprise campus.

+
+
+ +
+
+
+

Residential Plans

+

Content coming soon — plan tiers will be displayed here.

+
+
Plan cards will go here.
+
+
+ +
+
+
+

Business Plans

+
+
Business plan cards will go here.
+
+
+ +
+
+
+

Network Infrastructure

+
+
Infrastructure service details will go here.
+
+
+ +
+
+
+

Managed Networking

+
+
Managed service details will go here.
+
+