Fertig
This commit is contained in:
parent
ecce65db79
commit
3cbe01e458
|
|
@ -1,24 +1,113 @@
|
||||||
# Logs
|
# Dependencies
|
||||||
logs
|
node_modules/
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
|
||||||
*.local
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
public
|
||||||
|
|
||||||
|
# Storybook build outputs
|
||||||
|
.out
|
||||||
|
.storybook-out
|
||||||
|
|
||||||
|
# Temporary folders
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# SSL certificates
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
ssl/
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Use Node.js 18 Alpine as base image
|
||||||
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies based on the preferred package manager
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy the built application
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Change ownership to nextjs user
|
||||||
|
RUN chown -R nextjs:nodejs /app
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npx", "serve", "-s", "dist", "-l", "3000"]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Development Dockerfile
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
CMD ["npm", "run", "dev"]
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Docker Setup für IIT Welders
|
||||||
|
|
||||||
|
Dieses Projekt kann mit Docker und Docker Compose ausgeführt werden.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Docker Compose
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
|
### Mit Docker Compose (empfohlen)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Entwicklungsumgebung starten
|
||||||
|
docker-compose -f docker-compose.dev.yml up
|
||||||
|
|
||||||
|
# Im Hintergrund ausführen
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
# Logs anzeigen
|
||||||
|
docker-compose -f docker-compose.dev.yml logs -f
|
||||||
|
|
||||||
|
# Stoppen
|
||||||
|
docker-compose -f docker-compose.dev.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Anwendung ist dann unter `http://localhost:8080` erreichbar.
|
||||||
|
|
||||||
|
### Manuell mit Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development Image bauen
|
||||||
|
docker build -f Dockerfile.dev -t iitwelders-dev .
|
||||||
|
|
||||||
|
# Container starten
|
||||||
|
docker run -p 8080:8080 -v $(pwd):/app -v /app/node_modules iitwelders-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Produktion
|
||||||
|
|
||||||
|
### Mit Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Produktionsumgebung starten
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Logs anzeigen
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stoppen
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Anwendung ist dann unter `http://localhost:3000` erreichbar.
|
||||||
|
|
||||||
|
### Mit Nginx (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mit Nginx Reverse Proxy
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Anwendung ist dann unter http://localhost erreichbar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manuell mit Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Produktions Image bauen
|
||||||
|
docker build -t iitwelders-prod .
|
||||||
|
|
||||||
|
# Container starten
|
||||||
|
docker run -p 3000:3000 iitwelders-prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nützliche Befehle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Container neu bauen
|
||||||
|
docker-compose build --no-cache
|
||||||
|
|
||||||
|
# Alle Container und Volumes entfernen
|
||||||
|
docker-compose down -v
|
||||||
|
|
||||||
|
# In laufenden Container einsteigen
|
||||||
|
docker exec -it iitwelders-web sh
|
||||||
|
|
||||||
|
# Images aufräumen
|
||||||
|
docker system prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Umgebungsvariablen
|
||||||
|
|
||||||
|
Kopieren Sie `.env.example` zu `.env` und passen Sie die Werte an:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Port bereits belegt
|
||||||
|
Falls Port 3000 oder 8080 bereits belegt ist, ändern Sie die Ports in der entsprechenden docker-compose.yml Datei.
|
||||||
|
|
||||||
|
### Node Modules Probleme
|
||||||
|
```bash
|
||||||
|
# Container und Volumes entfernen
|
||||||
|
docker-compose down -v
|
||||||
|
|
||||||
|
# Neu starten
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Probleme (Linux/Mac)
|
||||||
|
```bash
|
||||||
|
# Ownership korrigieren
|
||||||
|
sudo chown -R $USER:$USER .
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
# Server Deployment Guide - IIT Welders
|
||||||
|
|
||||||
|
## Voraussetzungen für Server-Deployment
|
||||||
|
|
||||||
|
### Server-Anforderungen
|
||||||
|
- **Betriebssystem**: Ubuntu 20.04+ / CentOS 7+ / Debian 10+
|
||||||
|
- **RAM**: Mindestens 2GB (4GB empfohlen)
|
||||||
|
- **CPU**: 1 Core (2 Cores empfohlen)
|
||||||
|
- **Speicher**: Mindestens 10GB freier Speicher
|
||||||
|
- **Docker**: Version 20.10+
|
||||||
|
- **Docker Compose**: Version 2.0+
|
||||||
|
|
||||||
|
### Domain & SSL (optional)
|
||||||
|
- Domain-Name für die Website
|
||||||
|
- SSL-Zertifikat (Let's Encrypt empfohlen)
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
### 1. Server vorbereiten
|
||||||
|
```bash
|
||||||
|
# Docker installieren (Ubuntu/Debian)
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
|
sudo sh get-docker.sh
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
|
||||||
|
# Docker Compose installieren
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
# Neustart erforderlich
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Projekt auf Server übertragen
|
||||||
|
```bash
|
||||||
|
# Mit Git (empfohlen)
|
||||||
|
git clone <your-repository-url>
|
||||||
|
cd iitwelders
|
||||||
|
|
||||||
|
# Oder mit SCP
|
||||||
|
scp -r ./iitwelders user@server:/home/user/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deployment ausführen
|
||||||
|
```bash
|
||||||
|
# Produktions-Deployment
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Oder manuell
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Detaillierte Anleitung
|
||||||
|
|
||||||
|
### Option 1: Einfaches Deployment (nur App)
|
||||||
|
```bash
|
||||||
|
# 1. Projekt klonen/übertragen
|
||||||
|
git clone <repository-url>
|
||||||
|
cd iitwelders
|
||||||
|
|
||||||
|
# 2. Dependencies installieren
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. Build erstellen
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 4. Mit Docker starten
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 5. Status prüfen
|
||||||
|
./deploy.sh status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Vollständiges Deployment (mit Nginx)
|
||||||
|
```bash
|
||||||
|
# 1. Alle Schritte aus Option 1
|
||||||
|
# 2. Nginx-Konfiguration anpassen (falls nötig)
|
||||||
|
# 3. SSL-Zertifikate hinzufügen (optional)
|
||||||
|
mkdir ssl
|
||||||
|
# SSL-Zertifikate in ssl/ Ordner kopieren
|
||||||
|
|
||||||
|
# 4. Mit Nginx starten
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 5. Nginx-Konfiguration testen
|
||||||
|
docker exec iitwelders-nginx nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Mit Reverse Proxy (Traefik/Nginx)
|
||||||
|
```bash
|
||||||
|
# Für Produktionsumgebung mit automatischem SSL
|
||||||
|
# docker-compose.prod.yml erstellen (siehe unten)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
### Umgebungsvariablen
|
||||||
|
```bash
|
||||||
|
# .env Datei erstellen
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Anpassen nach Bedarf
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx-Konfiguration anpassen
|
||||||
|
```bash
|
||||||
|
# nginx.conf bearbeiten
|
||||||
|
nano nginx.conf
|
||||||
|
|
||||||
|
# Neustart nach Änderungen
|
||||||
|
docker-compose restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL-Zertifikate (Let's Encrypt)
|
||||||
|
```bash
|
||||||
|
# Certbot installieren
|
||||||
|
sudo apt install certbot
|
||||||
|
|
||||||
|
# Zertifikat erstellen
|
||||||
|
sudo certbot certonly --standalone -d yourdomain.com
|
||||||
|
|
||||||
|
# Zertifikate in Docker-Container kopieren
|
||||||
|
sudo cp /etc/letsencrypt/live/yourdomain.com/*.pem ./ssl/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring & Wartung
|
||||||
|
|
||||||
|
### Logs anzeigen
|
||||||
|
```bash
|
||||||
|
# Alle Logs
|
||||||
|
./deploy.sh logs
|
||||||
|
|
||||||
|
# Nur App-Logs
|
||||||
|
docker logs iitwelders-web
|
||||||
|
|
||||||
|
# Nur Nginx-Logs
|
||||||
|
docker logs iitwelders-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
```bash
|
||||||
|
# Automatischer Health Check
|
||||||
|
./deploy.sh health
|
||||||
|
|
||||||
|
# Manueller Check
|
||||||
|
curl http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
```bash
|
||||||
|
# Code aktualisieren
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Neues Deployment
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Oder nur App neu starten
|
||||||
|
docker-compose restart iitwelders-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
```bash
|
||||||
|
# Container stoppen
|
||||||
|
./deploy.sh stop
|
||||||
|
|
||||||
|
# Backup erstellen
|
||||||
|
tar -czf backup-$(date +%Y%m%d).tar.gz .
|
||||||
|
|
||||||
|
# Container starten
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Häufige Probleme
|
||||||
|
|
||||||
|
#### Port bereits belegt
|
||||||
|
```bash
|
||||||
|
# Ports prüfen
|
||||||
|
netstat -tulpn | grep :3000
|
||||||
|
|
||||||
|
# Prozess beenden
|
||||||
|
sudo kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker-Probleme
|
||||||
|
```bash
|
||||||
|
# Docker neu starten
|
||||||
|
sudo systemctl restart docker
|
||||||
|
|
||||||
|
# Container bereinigen
|
||||||
|
docker system prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Speicherplatz
|
||||||
|
```bash
|
||||||
|
# Speicherplatz prüfen
|
||||||
|
df -h
|
||||||
|
|
||||||
|
# Docker-Images bereinigen
|
||||||
|
docker image prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logs prüfen
|
||||||
|
```bash
|
||||||
|
# System-Logs
|
||||||
|
journalctl -u docker
|
||||||
|
|
||||||
|
# Container-Logs
|
||||||
|
docker logs iitwelders-web --tail 100
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
### Firewall konfigurieren
|
||||||
|
```bash
|
||||||
|
# UFW aktivieren
|
||||||
|
sudo ufw enable
|
||||||
|
|
||||||
|
# Nur notwendige Ports öffnen
|
||||||
|
sudo ufw allow 22 # SSH
|
||||||
|
sudo ufw allow 80 # HTTP
|
||||||
|
sudo ufw allow 443 # HTTPS
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL/TLS
|
||||||
|
- Let's Encrypt für kostenlose SSL-Zertifikate
|
||||||
|
- Automatische Erneuerung einrichten
|
||||||
|
- HSTS-Header aktivieren
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Regelmäßige System-Updates
|
||||||
|
- Docker-Images aktualisieren
|
||||||
|
- Sicherheits-Patches installieren
|
||||||
|
|
||||||
|
## Performance-Optimierung
|
||||||
|
|
||||||
|
### Nginx-Optimierung
|
||||||
|
- Gzip-Kompression aktiviert
|
||||||
|
- Browser-Caching konfiguriert
|
||||||
|
- Rate Limiting aktiviert
|
||||||
|
|
||||||
|
### Docker-Optimierung
|
||||||
|
- Multi-stage Builds
|
||||||
|
- Alpine Linux Images
|
||||||
|
- Volume-Optimierung
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Bei Problemen:
|
||||||
|
1. Logs prüfen: `./deploy.sh logs`
|
||||||
|
2. Health Check: `./deploy.sh health`
|
||||||
|
3. Status prüfen: `./deploy.sh status`
|
||||||
|
4. Container neu starten: `./deploy.sh stop && ./deploy.sh`
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Deployment script for IIT Welders
|
||||||
|
# This script handles the deployment process
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
APP_NAME="iitwelders"
|
||||||
|
DOCKER_COMPOSE_FILE="docker-compose.yml"
|
||||||
|
DOCKER_COMPOSE_DEV_FILE="docker-compose.dev.yml"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_status() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if Docker is running
|
||||||
|
check_docker() {
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
print_error "Docker is not running. Please start Docker and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_success "Docker is running"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to build and deploy
|
||||||
|
deploy() {
|
||||||
|
local environment=${1:-production}
|
||||||
|
|
||||||
|
print_status "Starting deployment for $environment environment..."
|
||||||
|
|
||||||
|
# Check Docker
|
||||||
|
check_docker
|
||||||
|
|
||||||
|
# Stop existing containers
|
||||||
|
print_status "Stopping existing containers..."
|
||||||
|
if [ "$environment" = "development" ]; then
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_DEV_FILE down || true
|
||||||
|
else
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_FILE down || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove old images
|
||||||
|
print_status "Cleaning up old images..."
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
# Build and start containers
|
||||||
|
print_status "Building and starting containers..."
|
||||||
|
if [ "$environment" = "development" ]; then
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_DEV_FILE up --build -d
|
||||||
|
else
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_FILE up --build -d
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for application to start
|
||||||
|
print_status "Waiting for application to start..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Run health check
|
||||||
|
if [ -f "./healthcheck.sh" ]; then
|
||||||
|
print_status "Running health check..."
|
||||||
|
chmod +x ./healthcheck.sh
|
||||||
|
./healthcheck.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Deployment completed successfully!"
|
||||||
|
|
||||||
|
if [ "$environment" = "development" ]; then
|
||||||
|
print_status "Application is available at: http://localhost:8080"
|
||||||
|
else
|
||||||
|
print_status "Application is available at: http://localhost:3000"
|
||||||
|
print_status "With Nginx at: http://localhost"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show logs
|
||||||
|
show_logs() {
|
||||||
|
local environment=${1:-production}
|
||||||
|
|
||||||
|
if [ "$environment" = "development" ]; then
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_DEV_FILE logs -f
|
||||||
|
else
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_FILE logs -f
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to stop application
|
||||||
|
stop() {
|
||||||
|
local environment=${1:-production}
|
||||||
|
|
||||||
|
print_status "Stopping $environment environment..."
|
||||||
|
|
||||||
|
if [ "$environment" = "development" ]; then
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_DEV_FILE down
|
||||||
|
else
|
||||||
|
docker-compose -f $DOCKER_COMPOSE_FILE down
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Application stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show status
|
||||||
|
status() {
|
||||||
|
print_status "Container status:"
|
||||||
|
docker ps --filter "name=$APP_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script logic
|
||||||
|
case "${1:-deploy}" in
|
||||||
|
"deploy")
|
||||||
|
deploy "${2:-production}"
|
||||||
|
;;
|
||||||
|
"dev")
|
||||||
|
deploy "development"
|
||||||
|
;;
|
||||||
|
"logs")
|
||||||
|
show_logs "${2:-production}"
|
||||||
|
;;
|
||||||
|
"stop")
|
||||||
|
stop "${2:-production}"
|
||||||
|
;;
|
||||||
|
"status")
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
"help"|"-h"|"--help")
|
||||||
|
echo "Usage: $0 [command] [environment]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " deploy Deploy to production (default)"
|
||||||
|
echo " dev Deploy to development"
|
||||||
|
echo " logs Show application logs"
|
||||||
|
echo " stop Stop application"
|
||||||
|
echo " status Show container status"
|
||||||
|
echo " help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Environments:"
|
||||||
|
echo " production Production environment (default)"
|
||||||
|
echo " development Development environment"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 deploy production"
|
||||||
|
echo " $0 dev"
|
||||||
|
echo " $0 logs development"
|
||||||
|
echo " $0 stop"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown command: $1"
|
||||||
|
echo "Use '$0 help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
iitwelders-dev:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- /app/node_modules
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- CHOKIDAR_USEPOLLING=true
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: iitwelders-dev
|
||||||
|
command: npm run dev
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
node_modules:
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
iitwelders-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: iitwelders-web
|
||||||
|
|
||||||
|
# Optional: Add nginx reverse proxy for production
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
- ./ssl:/etc/nginx/ssl:ro
|
||||||
|
depends_on:
|
||||||
|
- iitwelders-app
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: iitwelders-nginx
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
node_modules:
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Health check script for IIT Welders application
|
||||||
|
# This script checks if the application is running and responding
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
HEALTH_URL="http://localhost:3000"
|
||||||
|
TIMEOUT=10
|
||||||
|
MAX_RETRIES=3
|
||||||
|
|
||||||
|
# Function to check health
|
||||||
|
check_health() {
|
||||||
|
local url=$1
|
||||||
|
local timeout=$2
|
||||||
|
|
||||||
|
if curl -f -s --max-time $timeout "$url" > /dev/null 2>&1; then
|
||||||
|
echo "✅ Health check passed: $url"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Health check failed: $url"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if port is listening
|
||||||
|
check_port() {
|
||||||
|
local port=$1
|
||||||
|
|
||||||
|
if netstat -tuln | grep -q ":$port "; then
|
||||||
|
echo "✅ Port $port is listening"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Port $port is not listening"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main health check
|
||||||
|
echo "🔍 Starting health check for IIT Welders..."
|
||||||
|
|
||||||
|
# Check if port is listening
|
||||||
|
if ! check_port 3000; then
|
||||||
|
echo "❌ Application is not running on port 3000"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check application health
|
||||||
|
retry_count=0
|
||||||
|
while [ $retry_count -lt $MAX_RETRIES ]; do
|
||||||
|
if check_health "$HEALTH_URL" $TIMEOUT; then
|
||||||
|
echo "✅ Application is healthy and responding"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
echo "⏳ Retry $retry_count/$MAX_RETRIES in 5 seconds..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "❌ Health check failed after $MAX_RETRIES retries"
|
||||||
|
exit 1
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_proxied expired no-cache no-store private must-revalidate auth;
|
||||||
|
gzip_types
|
||||||
|
text/plain
|
||||||
|
text/css
|
||||||
|
text/xml
|
||||||
|
text/javascript
|
||||||
|
application/javascript
|
||||||
|
application/xml+rss
|
||||||
|
application/json;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||||
|
|
||||||
|
upstream app {
|
||||||
|
server iitwelders-app:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||||
|
|
||||||
|
# Static files caching
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
proxy_pass http://app;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main application
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "healthy\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,11 @@
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:dev": "vite build --mode development",
|
"build:dev": "vite build --mode development",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"start": "serve -s dist -l 3000",
|
||||||
|
"deploy": "./deploy.sh",
|
||||||
|
"deploy:dev": "./deploy.sh dev",
|
||||||
|
"health": "./healthcheck.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
|
@ -59,7 +63,8 @@
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.9",
|
"vaul": "^0.9.9",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76",
|
||||||
|
"serve": "^14.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.32.0",
|
"@eslint/js": "^9.32.0",
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
|
@ -16,7 +16,7 @@ const Footer = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-slate-900 border-t border-slate-700">
|
<footer className="bg-slate-900 border-t border-slate-700 footer-transition">
|
||||||
<div className="container mx-auto px-4 py-16">
|
<div className="container mx-auto px-4 py-16">
|
||||||
{/* Main Footer Content */}
|
{/* Main Footer Content */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 mb-12">
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 mb-12">
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ const Hero = () => {
|
||||||
<Button
|
<Button
|
||||||
size="xl"
|
size="xl"
|
||||||
className="bg-white hover:bg-gray-100 text-slate-900 font-semibold text-lg px-8 py-4 h-auto shadow-lg rounded-lg hover-lift hover-glow"
|
className="bg-white hover:bg-gray-100 text-slate-900 font-semibold text-lg px-8 py-4 h-auto shadow-lg rounded-lg hover-lift hover-glow"
|
||||||
|
onClick={() => window.location.href = '/contact-us'}
|
||||||
>
|
>
|
||||||
Get Quote
|
Get Quote
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -58,6 +59,7 @@ const Hero = () => {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="xl"
|
size="xl"
|
||||||
className="border-slate-500/50 bg-slate-800/50 text-white hover:bg-slate-700/50 hover:border-slate-400 font-semibold text-lg px-8 py-4 h-auto rounded-lg backdrop-blur-sm hover-lift"
|
className="border-slate-500/50 bg-slate-800/50 text-white hover:bg-slate-700/50 hover:border-slate-400 font-semibold text-lg px-8 py-4 h-auto rounded-lg backdrop-blur-sm hover-lift"
|
||||||
|
onClick={() => window.location.href = '/services-and-specialties'}
|
||||||
>
|
>
|
||||||
Explore Services
|
Explore Services
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
475
src/index.css
475
src/index.css
|
|
@ -125,11 +125,247 @@
|
||||||
@apply bg-card/95 backdrop-blur-sm border border-border/50 shadow-xl;
|
@apply bg-card/95 backdrop-blur-sm border border-border/50 shadow-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dispel-style gradient backgrounds */
|
/* Enhanced gradient backgrounds */
|
||||||
.bg-dispel-gradient {
|
.bg-dispel-gradient {
|
||||||
background: linear-gradient(135deg, hsl(210 24% 8%) 0%, hsl(215 25% 10%) 50%, hsl(210 24% 8%) 100%);
|
background: linear-gradient(135deg, hsl(210 24% 8%) 0%, hsl(215 25% 10%) 50%, hsl(210 24% 8%) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-industrial-gradient {
|
||||||
|
background: linear-gradient(135deg, hsl(220 26% 14%) 0%, hsl(215 25% 12%) 50%, hsl(210 24% 10%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-section-gradient {
|
||||||
|
background: linear-gradient(180deg, hsl(220 26% 14%) 0%, hsl(215 25% 12%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-card-gradient {
|
||||||
|
background: linear-gradient(145deg, hsl(215 20% 12%) 0%, hsl(215 18% 14%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-accent-gradient {
|
||||||
|
background: linear-gradient(135deg, hsl(217 91% 60%) 0%, hsl(213 94% 68%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced color utilities */
|
||||||
|
.text-accent-blue {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-accent-cyan {
|
||||||
|
color: #22d3ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-accent-blue {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-accent-cyan {
|
||||||
|
background-color: #06b6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-accent-blue {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-accent-cyan {
|
||||||
|
border-color: #06b6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Soft Section Transition Effects */
|
||||||
|
.section-transition-soft {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-soft::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -60px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.1) 20%,
|
||||||
|
rgba(30, 41, 59, 0.3) 40%,
|
||||||
|
rgba(30, 41, 59, 0.6) 70%,
|
||||||
|
rgba(30, 41, 59, 0.9) 90%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gentle wave transition */
|
||||||
|
.section-transition-wave {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-wave::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -80px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 80px;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 120% 100% at center top,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.05) 10%,
|
||||||
|
rgba(30, 41, 59, 0.15) 25%,
|
||||||
|
rgba(30, 41, 59, 0.35) 50%,
|
||||||
|
rgba(30, 41, 59, 0.65) 75%,
|
||||||
|
rgba(30, 41, 59, 0.9) 95%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ultra-soft gradient transition */
|
||||||
|
.section-transition-gradient {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-gradient::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -100px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.02) 10%,
|
||||||
|
rgba(30, 41, 59, 0.08) 25%,
|
||||||
|
rgba(30, 41, 59, 0.18) 40%,
|
||||||
|
rgba(30, 41, 59, 0.35) 60%,
|
||||||
|
rgba(30, 41, 59, 0.55) 75%,
|
||||||
|
rgba(30, 41, 59, 0.75) 88%,
|
||||||
|
rgba(30, 41, 59, 0.9) 95%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Soft curve transition */
|
||||||
|
.section-transition-curve {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-curve::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -120px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 120px;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 150% 120% at center top,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.03) 8%,
|
||||||
|
rgba(30, 41, 59, 0.12) 20%,
|
||||||
|
rgba(30, 41, 59, 0.28) 40%,
|
||||||
|
rgba(30, 41, 59, 0.5) 65%,
|
||||||
|
rgba(30, 41, 59, 0.75) 85%,
|
||||||
|
rgba(30, 41, 59, 0.92) 96%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gentle slant transition */
|
||||||
|
.section-transition-slant {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-slant::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -80px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.05) 15%,
|
||||||
|
rgba(30, 41, 59, 0.2) 35%,
|
||||||
|
rgba(30, 41, 59, 0.45) 60%,
|
||||||
|
rgba(30, 41, 59, 0.7) 80%,
|
||||||
|
rgba(30, 41, 59, 0.9) 95%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
clip-path: polygon(0 100%, 100% 0%, 100% 100%, 0% 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Soft diagonal transition */
|
||||||
|
.section-transition-diagonal {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-transition-diagonal::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -100px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100px;
|
||||||
|
background: linear-gradient(45deg,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.03) 10%,
|
||||||
|
rgba(30, 41, 59, 0.15) 30%,
|
||||||
|
rgba(30, 41, 59, 0.35) 55%,
|
||||||
|
rgba(30, 41, 59, 0.6) 75%,
|
||||||
|
rgba(30, 41, 59, 0.8) 90%,
|
||||||
|
rgba(30, 41, 59, 0.95) 98%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
transform: skewY(-1deg);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer transition */
|
||||||
|
.footer-transition {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-transition::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -60px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(15, 23, 42, 0) 0%,
|
||||||
|
rgba(15, 23, 42, 0.1) 15%,
|
||||||
|
rgba(15, 23, 42, 0.25) 35%,
|
||||||
|
rgba(15, 23, 42, 0.45) 55%,
|
||||||
|
rgba(15, 23, 42, 0.65) 75%,
|
||||||
|
rgba(15, 23, 42, 0.8) 90%,
|
||||||
|
rgba(15, 23, 42, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero section transitions */
|
||||||
|
.hero-transition {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-transition::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(30, 41, 59, 0) 0%,
|
||||||
|
rgba(30, 41, 59, 0.05) 15%,
|
||||||
|
rgba(30, 41, 59, 0.15) 35%,
|
||||||
|
rgba(30, 41, 59, 0.3) 55%,
|
||||||
|
rgba(30, 41, 59, 0.5) 75%,
|
||||||
|
rgba(30, 41, 59, 0.7) 90%,
|
||||||
|
rgba(30, 41, 59, 0.85) 98%,
|
||||||
|
rgba(30, 41, 59, 1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dispel-style floating elements */
|
/* Dispel-style floating elements */
|
||||||
.floating-element {
|
.floating-element {
|
||||||
@apply absolute opacity-20;
|
@apply absolute opacity-20;
|
||||||
|
|
@ -143,6 +379,69 @@
|
||||||
@apply bottom-20 left-20 w-64 h-64 bg-accent/5 rounded-full blur-2xl;
|
@apply bottom-20 left-20 w-64 h-64 bg-accent/5 rounded-full blur-2xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Background Patterns */
|
||||||
|
.bg-pattern-dots {
|
||||||
|
background-image: radial-gradient(circle, rgba(59, 130, 246, 0.1) 1px, transparent 1px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-pattern-grid {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(59, 130, 246, 0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(59, 130, 246, 0.1) 1px, transparent 1px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-pattern-lines {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent,
|
||||||
|
transparent 10px,
|
||||||
|
rgba(59, 130, 246, 0.05) 10px,
|
||||||
|
rgba(59, 130, 246, 0.05) 20px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-pattern-industrial {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(59, 130, 246, 0.08) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(59, 130, 246, 0.08) 1px, transparent 1px),
|
||||||
|
linear-gradient(rgba(59, 130, 246, 0.04) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(59, 130, 246, 0.04) 1px, transparent 1px);
|
||||||
|
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced Card Designs */
|
||||||
|
.card-enhanced {
|
||||||
|
@apply bg-slate-800/60 backdrop-blur-sm border border-slate-600/50 rounded-xl shadow-xl;
|
||||||
|
background: linear-gradient(145deg, rgba(30, 41, 59, 0.8) 0%, rgba(51, 65, 85, 0.6) 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-enhanced:hover {
|
||||||
|
@apply border-blue-500/30 shadow-2xl;
|
||||||
|
background: linear-gradient(145deg, rgba(30, 41, 59, 0.9) 0%, rgba(51, 65, 85, 0.7) 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04),
|
||||||
|
0 0 0 1px rgba(59, 130, 246, 0.1),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism Effects */
|
||||||
|
.glass-card {
|
||||||
|
@apply bg-white/5 backdrop-blur-md border border-white/10 rounded-xl;
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card:hover {
|
||||||
|
@apply bg-white/10 border-white/20;
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Carousel specific styling - Dispel inspired */
|
/* Carousel specific styling - Dispel inspired */
|
||||||
.carousel-container {
|
.carousel-container {
|
||||||
@apply relative overflow-hidden rounded-2xl border border-border/30 bg-gradient-to-br from-bg-elevated/20 to-transparent backdrop-blur-sm;
|
@apply relative overflow-hidden rounded-2xl border border-border/30 bg-gradient-to-br from-bg-elevated/20 to-transparent backdrop-blur-sm;
|
||||||
|
|
@ -266,22 +565,164 @@
|
||||||
animation: bounceSubtle 2s ease-in-out infinite;
|
animation: bounceSubtle 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Simple hover animations that should be immediately visible */
|
/* Enhanced Statistics Styles */
|
||||||
|
.stat-number {
|
||||||
|
@apply text-4xl lg:text-5xl font-bold text-white mb-2;
|
||||||
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #1d4ed8 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
text-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
|
||||||
|
animation: statPulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
filter: brightness(1.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
@apply bg-slate-800/30 backdrop-blur-sm border border-slate-600/30 rounded-2xl p-6 text-center;
|
||||||
|
background: linear-gradient(145deg, rgba(30, 41, 59, 0.3) 0%, rgba(51, 65, 85, 0.2) 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||||
|
0 0 0 1px rgba(59, 130, 246, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
|
||||||
|
transition: left 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-8px) scale(1.05);
|
||||||
|
box-shadow:
|
||||||
|
0 20px 40px rgba(0, 0, 0, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||||
|
0 0 0 1px rgba(59, 130, 246, 0.3),
|
||||||
|
0 0 30px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
@apply text-slate-400 text-sm font-medium;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover .stat-label {
|
||||||
|
color: #60a5fa;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced hover animations */
|
||||||
.hover-lift {
|
.hover-lift {
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-lift:hover {
|
.hover-lift:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-4px) scale(1.02);
|
||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-glow {
|
.hover-glow {
|
||||||
transition: box-shadow 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-glow:hover {
|
.hover-glow:hover {
|
||||||
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
|
box-shadow: 0 0 30px rgba(59, 130, 246, 0.4);
|
||||||
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advanced hover effects */
|
||||||
|
.hover-slide {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-slide::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||||
|
transition: left 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-slide:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-rotate {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-rotate:hover {
|
||||||
|
transform: rotate(2deg) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-pulse {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-pulse:hover {
|
||||||
|
animation: pulse 0.6s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button hover effects */
|
||||||
|
.btn-hover-lift {
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hover-lift:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hover-lift:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card hover effects */
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-8px) scale(1.02);
|
||||||
|
box-shadow:
|
||||||
|
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text hover effects */
|
||||||
|
.text-hover-glow {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-hover-glow:hover {
|
||||||
|
text-shadow: 0 0 8px rgba(59, 130, 246, 0.6);
|
||||||
|
color: #60a5fa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,6 +798,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes statPulse {
|
||||||
|
0%, 100% {
|
||||||
|
text-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
text-shadow: 0 0 50px rgba(59, 130, 246, 0.8);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes statGlow {
|
||||||
|
0%, 100% {
|
||||||
|
text-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
text-shadow: 0 0 20px rgba(59, 130, 246, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
/* Text truncation utilities */
|
/* Text truncation utilities */
|
||||||
.line-clamp-1 {
|
.line-clamp-1 {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,42 @@ import React from 'react';
|
||||||
import Header from '@/components/Header';
|
import Header from '@/components/Header';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
import { Building, Users, Award, ChevronRight } from 'lucide-react';
|
import { Building, Users, Award, ChevronRight } from 'lucide-react';
|
||||||
|
import { useCountUp } from '@/hooks/useCountUp';
|
||||||
import content from '@/content/content.json';
|
import content from '@/content/content.json';
|
||||||
|
|
||||||
|
// Stat Card Component with Count Up Animation
|
||||||
|
const StatCard = ({
|
||||||
|
icon: Icon,
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
suffix = '',
|
||||||
|
delay = 0
|
||||||
|
}: {
|
||||||
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
suffix?: string;
|
||||||
|
delay?: number;
|
||||||
|
}) => {
|
||||||
|
const { count, ref } = useCountUp({
|
||||||
|
end: value,
|
||||||
|
duration: 2500,
|
||||||
|
delay: delay,
|
||||||
|
suffix: suffix
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="stat-card hover-lift">
|
||||||
|
<Icon className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
||||||
|
<div className="stat-number">{count}</div>
|
||||||
|
<div className="stat-label">{label}</div>
|
||||||
|
<div className="text-sm text-slate-500 mt-2">{description}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-900">
|
<div className="min-h-screen bg-slate-900">
|
||||||
|
|
@ -27,7 +61,7 @@ const About = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -56,7 +90,7 @@ const About = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 bg-pattern-dots section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
||||||
|
|
@ -102,8 +136,17 @@ const About = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Stats Section */}
|
{/* Stats Section */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900 bg-pattern-industrial section-transition-wave">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
|
|
@ -112,40 +155,47 @@ const About = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8 text-center">
|
<StatCard
|
||||||
<Users className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
icon={Users}
|
||||||
<div className="text-4xl font-bold text-white mb-2">800+</div>
|
value={800}
|
||||||
<div className="text-slate-400">Skilled Craftsmen</div>
|
label="Skilled Craftsmen"
|
||||||
<div className="text-sm text-slate-500 mt-2">Ready for mobilization 24/7</div>
|
description="Ready for mobilization 24/7"
|
||||||
</div>
|
suffix="+"
|
||||||
|
delay={0}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8 text-center">
|
<StatCard
|
||||||
<Building className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
icon={Building}
|
||||||
<div className="text-4xl font-bold text-white mb-2">13+</div>
|
value={13}
|
||||||
<div className="text-slate-400">Years Experience</div>
|
label="Years Experience"
|
||||||
<div className="text-sm text-slate-500 mt-2">Serving the industry since 2008</div>
|
description="Serving the industry since 2008"
|
||||||
</div>
|
suffix="+"
|
||||||
|
delay={200}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8 text-center">
|
<StatCard
|
||||||
<Award className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
icon={Award}
|
||||||
<div className="text-4xl font-bold text-white mb-2">3</div>
|
value={3}
|
||||||
<div className="text-slate-400">Texas Locations</div>
|
label="Texas Locations"
|
||||||
<div className="text-sm text-slate-500 mt-2">Brownsville, Ingleside, Corpus Christi</div>
|
description="Brownsville, Ingleside, Corpus Christi"
|
||||||
</div>
|
delay={400}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8 text-center">
|
<StatCard
|
||||||
<Users className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
icon={Users}
|
||||||
<div className="text-4xl font-bold text-white mb-2">24/7</div>
|
value={24}
|
||||||
<div className="text-slate-400">Project Support</div>
|
label="Project Support"
|
||||||
<div className="text-sm text-slate-500 mt-2">Always available for our clients</div>
|
description="Always available for our clients"
|
||||||
</div>
|
suffix="/7"
|
||||||
|
delay={600}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Commitment Section */}
|
{/* Commitment Section */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 bg-pattern-dots section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-4xl mx-auto text-center">
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
<h2 className="text-4xl font-bold text-white mb-8">Our Commitment</h2>
|
<h2 className="text-4xl font-bold text-white mb-8">Our Commitment</h2>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ const Careers = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -73,7 +73,7 @@ const Careers = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Current Openings */}
|
{/* Current Openings */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
|
|
@ -122,6 +122,15 @@ const Careers = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Benefits Section */}
|
{/* Benefits Section */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|
@ -144,7 +153,7 @@ const Careers = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Application Process */}
|
{/* Application Process */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
|
|
|
||||||
|
|
@ -564,7 +564,7 @@ const ClientsProjects = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -592,57 +592,75 @@ const ClientsProjects = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-900 via-slate-900/80 to-slate-800/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-900/30 to-slate-800/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-900/20 via-transparent to-slate-800/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Stats Section */}
|
{/* Stats Section */}
|
||||||
<section className="py-16 bg-slate-800">
|
<section className="py-16 bg-slate-800">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
<div className="text-center animate-fadeInUp">
|
<div className="stat-card animate-fadeInUp">
|
||||||
<div
|
<div
|
||||||
ref={clientsCount.ref}
|
ref={clientsCount.ref}
|
||||||
className="text-4xl lg:text-5xl font-bold text-white mb-2 animate-bounce-subtle"
|
className="stat-number"
|
||||||
>
|
>
|
||||||
{clientsCount.count}
|
{clientsCount.count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm">Major Clients Served</p>
|
<p className="stat-label">Major Clients Served</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center animate-fadeInUp" style={{animationDelay: '0.1s'}}>
|
|
||||||
|
<div className="stat-card animate-fadeInUp" style={{animationDelay: '0.1s'}}>
|
||||||
<div
|
<div
|
||||||
ref={projectsCount.ref}
|
ref={projectsCount.ref}
|
||||||
className="text-4xl lg:text-5xl font-bold text-white mb-2 animate-bounce-subtle"
|
className="stat-number"
|
||||||
style={{animationDelay: '0.2s'}}
|
|
||||||
>
|
>
|
||||||
{projectsCount.count}
|
{projectsCount.count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm">Projects Completed</p>
|
<p className="stat-label">Projects Completed</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center animate-fadeInUp" style={{animationDelay: '0.2s'}}>
|
|
||||||
|
<div className="stat-card animate-fadeInUp" style={{animationDelay: '0.2s'}}>
|
||||||
<div
|
<div
|
||||||
ref={longestProjectCount.ref}
|
ref={longestProjectCount.ref}
|
||||||
className="text-4xl lg:text-5xl font-bold text-white mb-2 animate-bounce-subtle"
|
className="stat-number"
|
||||||
style={{animationDelay: '0.4s'}}
|
|
||||||
>
|
>
|
||||||
{longestProjectCount.count}
|
{longestProjectCount.count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm">Months Longest Project</p>
|
<p className="stat-label">Months Longest Project</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center animate-fadeInUp" style={{animationDelay: '0.3s'}}>
|
|
||||||
|
<div className="stat-card animate-fadeInUp" style={{animationDelay: '0.3s'}}>
|
||||||
<div
|
<div
|
||||||
ref={maxPersonnelCount.ref}
|
ref={maxPersonnelCount.ref}
|
||||||
className="text-4xl lg:text-5xl font-bold text-white mb-2 animate-bounce-subtle"
|
className="stat-number"
|
||||||
style={{animationDelay: '0.6s'}}
|
|
||||||
>
|
>
|
||||||
{maxPersonnelCount.count}
|
{maxPersonnelCount.count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm">Max Personnel on Site</p>
|
<p className="stat-label">Max Personnel on Site</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-800/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-800/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-800/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Search and Filter Section */}
|
{/* Search and Filter Section */}
|
||||||
<section className="py-8 bg-slate-800">
|
<section className="py-8 bg-slate-800 bg-pattern-dots section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||||
|
|
@ -655,7 +673,7 @@ const ClientsProjects = () => {
|
||||||
placeholder="Search projects, clients, or locations..."
|
placeholder="Search projects, clients, or locations..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-3 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
|
className="w-full pl-10 pr-4 py-3 bg-slate-700/80 backdrop-blur-sm border border-slate-600/50 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:border-accent-blue focus:ring-2 focus:ring-accent-blue/20 transition-all duration-300 hover:border-accent-blue/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -665,7 +683,7 @@ const ClientsProjects = () => {
|
||||||
<select
|
<select
|
||||||
value={selectedIndustry}
|
value={selectedIndustry}
|
||||||
onChange={(e) => setSelectedIndustry(e.target.value)}
|
onChange={(e) => setSelectedIndustry(e.target.value)}
|
||||||
className="w-full py-3 px-4 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
|
className="w-full py-3 px-4 bg-slate-700/80 backdrop-blur-sm border border-slate-600/50 rounded-lg text-white focus:outline-none focus:border-accent-blue focus:ring-2 focus:ring-accent-blue/20 transition-all duration-300 hover:border-accent-blue/50"
|
||||||
>
|
>
|
||||||
{industries.map(industry => (
|
{industries.map(industry => (
|
||||||
<option key={industry} value={industry}>{industry}</option>
|
<option key={industry} value={industry}>{industry}</option>
|
||||||
|
|
@ -678,7 +696,7 @@ const ClientsProjects = () => {
|
||||||
<select
|
<select
|
||||||
value={selectedLocation}
|
value={selectedLocation}
|
||||||
onChange={(e) => setSelectedLocation(e.target.value)}
|
onChange={(e) => setSelectedLocation(e.target.value)}
|
||||||
className="w-full py-3 px-4 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
|
className="w-full py-3 px-4 bg-slate-700/80 backdrop-blur-sm border border-slate-600/50 rounded-lg text-white focus:outline-none focus:border-accent-blue focus:ring-2 focus:ring-accent-blue/20 transition-all duration-300 hover:border-accent-blue/50"
|
||||||
>
|
>
|
||||||
{locations.map(location => (
|
{locations.map(location => (
|
||||||
<option key={location} value={location}>{location}</option>
|
<option key={location} value={location}>{location}</option>
|
||||||
|
|
@ -697,8 +715,17 @@ const ClientsProjects = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Projects Grid */}
|
{/* Projects Grid */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900 bg-pattern-industrial section-transition-gradient">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
|
|
@ -737,13 +764,13 @@ const ClientsProjects = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredProjects.map((project, index) => (
|
filteredProjects.map((project, index) => (
|
||||||
<div key={index} className="bg-slate-800/50 border border-slate-600 rounded-xl p-6 hover:bg-slate-800/70 transition-all duration-300 hover:scale-[1.02] touch-manipulation">
|
<div key={index} className="card-enhanced card-hover hover-slide p-6 touch-manipulation">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Industry Badge */}
|
{/* Industry Badge */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg px-2 py-1 inline-block mb-3">
|
<div className="bg-accent-gradient border border-accent-blue/30 rounded-lg px-3 py-1 inline-block mb-3 shadow-lg">
|
||||||
<span className="text-blue-400 text-xs font-medium">{project.industry}</span>
|
<span className="text-white text-xs font-medium">{project.industry}</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-white mb-2 line-clamp-2">{project.client}</h3>
|
<h3 className="text-lg font-bold text-white mb-2 line-clamp-2">{project.client}</h3>
|
||||||
<div className="flex items-center space-x-2 text-slate-400">
|
<div className="flex items-center space-x-2 text-slate-400">
|
||||||
|
|
@ -751,8 +778,8 @@ const ClientsProjects = () => {
|
||||||
<span className="text-sm line-clamp-1">{project.location}</span>
|
<span className="text-sm line-clamp-1">{project.location}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-blue-500/20 rounded-lg px-3 py-1 ml-2">
|
<div className="bg-accent-blue/20 border border-accent-blue/30 rounded-lg px-3 py-1 ml-2 hover-glow">
|
||||||
<span className="text-blue-400 text-sm font-medium">{project.personnel}</span>
|
<span className="text-accent-blue text-sm font-medium">{project.personnel}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -790,7 +817,7 @@ const ClientsProjects = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Industry Sectors */}
|
{/* Industry Sectors */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-wave">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto text-center">
|
<div className="max-w-6xl mx-auto text-center">
|
||||||
<h2 className="text-4xl font-bold text-white mb-12">Industry Sectors</h2>
|
<h2 className="text-4xl font-bold text-white mb-12">Industry Sectors</h2>
|
||||||
|
|
@ -824,7 +851,7 @@ const ClientsProjects = () => {
|
||||||
{showBackToTop && (
|
{showBackToTop && (
|
||||||
<button
|
<button
|
||||||
onClick={scrollToTop}
|
onClick={scrollToTop}
|
||||||
className="fixed bottom-8 right-8 z-50 bg-blue-600 hover:bg-blue-700 text-white p-3 rounded-full shadow-lg transition-all duration-300 hover:scale-110 touch-manipulation"
|
className="fixed bottom-8 right-8 z-50 bg-accent-gradient hover:bg-blue-700 text-white p-4 rounded-full shadow-xl hover-lift btn-hover-lift touch-manipulation border border-accent-blue/30"
|
||||||
aria-label="Back to top"
|
aria-label="Back to top"
|
||||||
>
|
>
|
||||||
<ArrowUp className="h-6 w-6" />
|
<ArrowUp className="h-6 w-6" />
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const Contact = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -59,7 +59,7 @@ const Contact = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Contact Methods */}
|
{/* Contact Methods */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
|
||||||
|
|
@ -89,6 +89,15 @@ const Contact = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Office Locations */}
|
{/* Office Locations */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|
@ -171,7 +180,7 @@ const Contact = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Operations Manager */}
|
{/* Operations Manager */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
|
|
@ -238,7 +247,7 @@ const Contact = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Interactive Map Section */}
|
{/* Interactive Map Section */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
|
|
@ -338,6 +347,15 @@ const Contact = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Contact Forms Section */}
|
{/* Contact Forms Section */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const Locations = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -84,7 +84,7 @@ const Locations = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Locations Grid */}
|
{/* Locations Grid */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-7xl mx-auto space-y-16">
|
<div className="max-w-7xl mx-auto space-y-16">
|
||||||
{/* South Texas Office */}
|
{/* South Texas Office */}
|
||||||
|
|
@ -223,6 +223,15 @@ const Locations = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Operations Manager */}
|
{/* Operations Manager */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|
@ -297,7 +306,7 @@ const Locations = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Service Areas */}
|
{/* Service Areas */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto text-center">
|
<div className="max-w-6xl mx-auto text-center">
|
||||||
<h2 className="text-4xl font-bold text-white mb-12">Service Areas</h2>
|
<h2 className="text-4xl font-bold text-white mb-12">Service Areas</h2>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const Services = () => {
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{/* Hero Section with Image */}
|
{/* Hero Section with Image */}
|
||||||
<section className="relative py-32 overflow-hidden">
|
<section className="relative py-32 overflow-hidden hero-transition">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
|
@ -76,7 +76,7 @@ const Services = () => {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Services Section */}
|
{/* Services Section */}
|
||||||
<section className="py-24 bg-slate-800">
|
<section className="py-24 bg-slate-800 bg-pattern-grid section-transition-soft">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
||||||
|
|
@ -91,7 +91,7 @@ const Services = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
{services.map((service, index) => (
|
{services.map((service, index) => (
|
||||||
<div key={index} className="bg-slate-700/50 border border-slate-600 rounded-lg p-4 hover:bg-slate-700/70 transition-all duration-300">
|
<div key={index} className="card-enhanced card-hover hover-slide p-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<CheckCircle className="h-5 w-5 text-blue-400 flex-shrink-0" />
|
<CheckCircle className="h-5 w-5 text-blue-400 flex-shrink-0" />
|
||||||
<span className="text-white font-medium">{service}</span>
|
<span className="text-white font-medium">{service}</span>
|
||||||
|
|
@ -112,7 +112,7 @@ const Services = () => {
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{specialties.map((specialty, index) => (
|
{specialties.map((specialty, index) => (
|
||||||
<div key={index} className="bg-slate-700/50 border border-slate-600 rounded-lg p-6 hover:bg-slate-700/70 transition-all duration-300">
|
<div key={index} className="card-enhanced card-hover hover-slide p-6">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Award className="h-6 w-6 text-blue-400 flex-shrink-0" />
|
<Award className="h-6 w-6 text-blue-400 flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -129,8 +129,17 @@ const Services = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Soft Transition */}
|
||||||
|
<div className="relative h-40 bg-gradient-to-b from-slate-800 via-slate-800/80 to-slate-900/60">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-slate-800/30 to-slate-900/80"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 via-transparent to-slate-900/40"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-blue-400/20 to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Certifications Section */}
|
{/* Certifications Section */}
|
||||||
<section className="py-24 bg-slate-900">
|
<section className="py-24 bg-slate-900 bg-pattern-lines section-transition-gradient">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto text-center">
|
<div className="max-w-6xl mx-auto text-center">
|
||||||
<div className="flex items-center justify-center space-x-4 mb-12">
|
<div className="flex items-center justify-center space-x-4 mb-12">
|
||||||
|
|
@ -142,7 +151,7 @@ const Services = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-6">
|
||||||
{certifications.map((cert, index) => (
|
{certifications.map((cert, index) => (
|
||||||
<div key={index} className="bg-slate-800/50 border border-slate-600 rounded-xl p-6 hover:bg-slate-800/70 transition-all duration-300">
|
<div key={index} className="card-enhanced card-hover hover-slide p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center mx-auto">
|
<div className="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center mx-auto">
|
||||||
<Shield className="h-6 w-6 text-blue-400" />
|
<Shield className="h-6 w-6 text-blue-400" />
|
||||||
|
|
@ -154,19 +163,19 @@ const Services = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-16 grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="mt-16 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8">
|
<div className="card-enhanced card-hover hover-slide p-8">
|
||||||
<Users className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
<Users className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-white mb-2">Expert Team</h3>
|
<h3 className="text-xl font-bold text-white mb-2">Expert Team</h3>
|
||||||
<p className="text-slate-400">Certified professionals with decades of experience in industrial welding</p>
|
<p className="text-slate-400">Certified professionals with decades of experience in industrial welding</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8">
|
<div className="card-enhanced card-hover hover-slide p-8">
|
||||||
<Settings className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
<Settings className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-white mb-2">Modern Equipment</h3>
|
<h3 className="text-xl font-bold text-white mb-2">Modern Equipment</h3>
|
||||||
<p className="text-slate-400">State-of-the-art welding equipment and fabrication tools</p>
|
<p className="text-slate-400">State-of-the-art welding equipment and fabrication tools</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-600 rounded-xl p-8">
|
<div className="card-enhanced card-hover hover-slide p-8">
|
||||||
<Award className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
<Award className="h-12 w-12 text-blue-400 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-white mb-2">Quality Assurance</h3>
|
<h3 className="text-xl font-bold text-white mb-2">Quality Assurance</h3>
|
||||||
<p className="text-slate-400">Rigorous quality control and safety standards on every project</p>
|
<p className="text-slate-400">Rigorous quality control and safety standards on every project</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue