This commit is contained in:
2025-09-27 01:32:49 +01:00
commit b5e25b9fc4
12 changed files with 2392 additions and 0 deletions

203
README.md Normal file
View File

@@ -0,0 +1,203 @@
# SkyView Drones - Professional Drone Services Website
A modern, responsive website for your drone business specializing in aerial photography, video production, industrial inspections, and agricultural monitoring.
## Features
### 🚁 Complete Business Showcase
- **Hero Section** - Eye-catching introduction with call-to-action buttons
- **Services Overview** - Detailed sections for all your service offerings:
- Aerial Photography (Real estate, events, marketing)
- Video Production (4K recording, cinematic movements, editing)
- Industrial Inspections (Infrastructure, thermal imaging, safety assessments)
- Agricultural Monitoring (Crop analysis, livestock tracking, land surveying)
### 📱 Professional Design
- **Responsive Design** - Looks great on desktop, tablet, and mobile devices
- **Modern UI** - Clean, professional design with smooth animations
- **Interactive Portfolio** - Filterable gallery to showcase your work
- **Professional Branding** - Consistent color scheme and typography
### 📋 Lead Generation
- **Quote Request Form** - Comprehensive form to capture project details and client information
- **Contact Information** - Multiple ways for clients to reach you
- **Service-Specific Details** - Clear pricing expectations and service descriptions
### ⚡ Technical Features
- **Fast Loading** - Optimized for speed and performance
- **SEO Ready** - Proper meta tags and semantic HTML structure
- **Form Validation** - Real-time validation for better user experience
- **Smooth Animations** - Professional scroll animations and transitions
## Getting Started
### 1. Customize Your Information
Replace the placeholder information with your actual business details:
**In `index.html`:**
- Update company name "SkyView Drones" to your business name
- Replace phone number `(555) 123-4567` with your actual number
- Update email `info@skyviewdrones.com` with your business email
- Modify service area from "Nationwide Coverage" to your actual coverage area
- Update the hero title and descriptions to match your brand voice
**Key sections to customize:**
```html
<!-- Update these in the header -->
<div class="nav-logo">
<span>Your Business Name</span>
</div>
<!-- Update contact information -->
<p>(Your Phone Number)</p>
<p>your-email@yourdomain.com</p>
<p>Your Service Area</p>
```
### 2. Add Your Content
**Replace placeholder images:**
- Add your actual drone footage and photos
- Update the portfolio section with your real work
- Replace the about section image with your photo or team photo
**Portfolio Images:**
- Replace the placeholder URLs in the portfolio section
- Add your actual project images
- Update project descriptions
### 3. Customize Branding
**Colors (in `styles.css`):**
- Primary blue: `#0066cc` - used for buttons and accents
- Secondary red: `#ff6b6b` - used for call-to-action buttons
- Background gradient: `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`
**Fonts:**
- Current: Inter (clean, professional)
- Can be changed by updating the Google Fonts import
### 4. Set Up Form Handling
The forms currently show success messages but need backend integration:
**For Quote Form (`quoteForm`):**
- Collects: Name, email, phone, company, service type, location, date, budget, description
- Currently shows alert - replace with actual form submission
**For Contact Form (`contactForm`):**
- Collects: Name, email, subject, message
- Currently shows alert - replace with actual form submission
**Integration options:**
- **Netlify Forms** - Easy setup for static sites
- **Formspree** - Simple form handling service
- **EmailJS** - Send emails directly from JavaScript
- **Custom Backend** - PHP, Node.js, or other server-side solution
Example EmailJS integration:
```javascript
// Replace the setTimeout in script.js with:
emailjs.send('YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', data)
.then(() => {
alert('Thank you for your quote request!');
this.reset();
});
```
## File Structure
```
drone-website/
├── index.html # Main HTML file
├── styles.css # All CSS styles
├── script.js # JavaScript functionality
└── README.md # This file
```
## Customization Guide
### Adding New Services
1. Add a new service card in the services section
2. Create corresponding portfolio filter button
3. Add portfolio items with the new category
### Modifying Colors
Update the CSS custom properties or search and replace color values:
- Primary: `#0066cc`
- Secondary: `#ff6b6b`
- Background: `#f8f9fa`
- Text: `#333`
### Adding More Pages
Create new HTML files and link them in the navigation:
```html
<li><a href="services.html" class="nav-link">Services</a></li>
```
### SEO Optimization
1. Update meta descriptions for each page
2. Add proper alt tags to all images
3. Use semantic HTML structure
4. Add schema markup for local business
5. Create sitemap.xml
6. Set up Google Analytics
## Deployment Options
### Static Hosting (Recommended)
- **Netlify** - Free tier with form handling
- **Vercel** - Free with great performance
- **GitHub Pages** - Free for public repositories
- **Firebase Hosting** - Google's hosting solution
### Traditional Hosting
- Upload files to any web hosting provider
- Ensure HTTPS is enabled
- Set up form handling backend if needed
## Performance Tips
1. **Optimize Images**
- Compress photos before uploading
- Use WebP format when possible
- Add lazy loading for better performance
2. **Speed Optimization**
- Minify CSS and JavaScript for production
- Use a CDN for faster global loading
- Enable gzip compression
3. **SEO Best Practices**
- Add relevant keywords naturally
- Create descriptive page titles
- Use proper heading hierarchy (H1, H2, H3)
- Add local business schema markup
## Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
## License
This template is free to use for your drone business. Customize it as needed for your brand and services.
## Support
For customization help or questions about implementing specific features, feel free to ask for assistance with:
- Form integration
- Additional functionality
- Design modifications
- SEO optimization
- Performance improvements
---
**Ready to take your drone business online?** 🚁
This website template provides everything you need to establish a professional online presence and start capturing leads for your drone services business.

BIN
a2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

160
aws-ses-setup.md Normal file
View File

@@ -0,0 +1,160 @@
# AWS SES Setup Instructions
To enable email functionality for your PTSAerial contact forms using AWS SES, follow these steps:
## 1. AWS SES Setup
### Verify Email Addresses
1. Log into your AWS Console
2. Go to Amazon SES service
3. Navigate to "Verified identities"
4. Add and verify these email addresses:
- `oli@ptslondon.co.uk` (recipient)
- `noreply@ptslondon.co.uk` (sender - must be from your domain)
### Domain Verification (Recommended)
1. In SES, go to "Verified identities"
2. Click "Create identity" > "Domain"
3. Enter `ptslondon.co.uk`
4. Follow DNS verification steps
5. This allows sending from any address @ptslondon.co.uk
## 2. Create IAM User for Website
### Create IAM Policy
1. Go to IAM service in AWS Console
2. Click "Policies" > "Create policy"
3. Use JSON editor and paste:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
]
}
```
4. Name it `PTSAerial-SES-Send-Policy`
### Create IAM User
1. Go to "Users" > "Create user"
2. Username: `ptsaerial-website`
3. Attach the policy you just created
4. Create access keys for "Application running outside AWS"
5. **Save the Access Key ID and Secret Access Key securely**
## 3. Update JavaScript Configuration
In your `script.js` file, update these values:
```javascript
const awsConfig = {
region: 'eu-west-1', // Your SES region (e.g., us-east-1, eu-west-1, us-west-2)
accessKeyId: 'YOUR_ACCESS_KEY_ID', // From IAM user
secretAccessKey: 'YOUR_SECRET_ACCESS_KEY', // From IAM user
};
```
And update the sender email:
```javascript
Source: 'noreply@ptslondon.co.uk', // Must be verified in SES
```
## 4. SES Sandbox vs Production
### If in Sandbox Mode (default):
- Can only send to verified email addresses
- Limited to 200 emails per day
- Maximum 1 email per second
### To Move to Production:
1. In SES console, click "Request production access"
2. Fill out the form explaining your use case
3. AWS will review (usually approved within 24 hours)
4. Production allows sending to any email address
## 5. Security Considerations
### For Production Use (More Secure):
Instead of putting credentials in the frontend, consider:
1. **API Gateway + Lambda** (Recommended):
- Create a Lambda function to send emails
- Use API Gateway to expose an endpoint
- Call the API from your website
- Credentials stay server-side
2. **Cognito Identity Pools**:
- Use temporary credentials
- More secure than permanent access keys
### Basic Frontend Implementation:
For testing/simple use, the current implementation works but:
- Access keys are visible in the browser
- Only use this for low-security scenarios
- Consider IP restrictions in IAM policy
## 6. Test Configuration
1. Update the configuration values in `script.js`
2. Ensure SES is out of sandbox mode OR oli@ptslondon.co.uk is verified
3. Test both contact forms
4. Check oli@ptslondon.co.uk for emails
5. Monitor AWS SES console for send statistics
## 7. Monitoring & Troubleshooting
### SES Console Monitoring:
- Check "Sending statistics" for delivery rates
- Review "Suppression list" for bounced emails
- Monitor "Reputation metrics"
### Common Issues:
- **Invalid sender**: Ensure sender email is verified
- **Access denied**: Check IAM permissions
- **Sandbox restrictions**: Verify recipient or request production access
- **Region mismatch**: Ensure correct region in config
### Browser Console Errors:
Check for:
- AWS SDK loading errors
- CORS issues (not applicable for SES)
- Network connectivity
- Invalid credentials
## 8. Email Content Details
### Quote Request Emails Include:
- Customer name, email, phone, company
- Service type and location
- Preferred date and budget range
- Detailed project description
- HTML formatted table for easy reading
### Contact Form Emails Include:
- Customer name, email, subject
- Message content
- Professional HTML formatting
## 9. Cost Information
AWS SES Pricing (as of 2025):
- First 62,000 emails per month: $0.10 per 1,000 emails
- Additional emails: $0.10 per 1,000 emails
- No monthly fees
- Very cost-effective for business use
## 10. Regional Recommendations
Choose your region based on:
- **eu-west-1** (Ireland): Good for UK business
- **us-east-1** (N. Virginia): Lowest cost, highest feature availability
- **eu-west-2** (London): UK data residency if required
Your current configuration uses `eu-west-1` which is ideal for UK operations.

BIN
drone-op.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
drone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

199
emailjs-setup-guide.md Normal file
View File

@@ -0,0 +1,199 @@
# EmailJS Setup Guide for PTSAerial
I've switched your website back to EmailJS, which is much easier and more reliable than AWS SES for frontend forms. Here's how to set it up:
## 1. Create EmailJS Account
1. Go to [EmailJS.com](https://www.emailjs.com/)
2. Sign up for a free account (200 emails/month)
3. Verify your email address
## 2. Add Email Service
1. In your EmailJS dashboard, click "Email Services"
2. Click "Add New Service"
3. Choose "Gmail" (recommended) or your email provider
4. For Gmail:
- Enter your Gmail email (could be oli@ptslondon.co.uk if it's Gmail)
- Or create a dedicated Gmail account for website emails
5. Follow the OAuth setup process
6. **Copy your Service ID** (you'll need this)
## 3. Create Email Templates
### Template 1: Quote Request Template
1. Go to "Email Templates" → "Create New Template"
2. **Template ID**: `quote_template`
3. **Subject**: `New Quote Request - PTSAerial - {{service_type}}`
4. **Content**:
```
New Quote Request from PTSAerial Website
Customer Details:
Name: {{customer_name}}
Email: {{customer_email}}
Phone: {{customer_phone}}
Company: {{customer_company}}
Project Details:
Service Type: {{service_type}}
Location: {{project_location}}
Preferred Date: {{preferred_date}}
Budget Range: {{budget_range}}
Project Description:
{{project_description}}
---
Reply to this email to respond directly to the customer.
Website: PTSAerial.co.uk
```
### Template 2: Contact Form Template
1. Create another template
2. **Template ID**: `contact_template`
3. **Subject**: `New Contact Message - PTSAerial - {{message_subject}}`
4. **Content**:
```
New Contact Message from PTSAerial Website
From: {{customer_name}}
Email: {{customer_email}}
Subject: {{message_subject}}
Message:
{{message_content}}
---
Reply to this email to respond directly to the customer.
Website: PTSAerial.co.uk
```
## 4. Get Your Configuration
1. In EmailJS dashboard, go to "Account"
2. Copy your **Public Key** (also called User ID)
3. You'll also need your **Service ID** from step 2
## 5. Update Your Website
In your `script.js` file, replace these values:
```javascript
// Line ~69: Replace YOUR_PUBLIC_KEY
emailjs.init("YOUR_PUBLIC_KEY"); // Replace with your actual public key
// Line ~83 and ~127: Replace YOUR_SERVICE_ID
sendEmail('quote_template', templateParams)
// Should become:
emailjs.send('YOUR_SERVICE_ID', 'quote_template', templateParams)
```
**Example:**
```javascript
emailjs.init("user_abc123def456"); // Your public key
// and
return emailjs.send('service_gmail_xyz789', templateId, templateParams)
```
## 6. Test Your Setup
1. Save your script.js file with the correct keys
2. Open your website
3. Fill out both forms (quote and contact)
4. Check oli@ptslondon.co.uk for emails
5. Check browser console (F12) for any errors
## 7. EmailJS Dashboard Monitoring
After testing, check your EmailJS dashboard:
- "History" tab shows sent emails
- Monitor your monthly usage
- Check for any failed sends
## Configuration Summary
**What you need to replace in script.js:**
```javascript
// Replace this line:
emailjs.init("YOUR_PUBLIC_KEY");
// With:
emailjs.init("your_actual_public_key");
// And replace this line:
return emailjs.send('YOUR_SERVICE_ID', templateId, templateParams)
// With:
return emailjs.send('your_actual_service_id', templateId, templateParams)
```
## Email Template Variables
The website sends these variables to your templates:
### Quote Form (`quote_template`):
- `{{customer_name}}` - Customer's name
- `{{customer_email}}` - Customer's email
- `{{customer_phone}}` - Phone number
- `{{customer_company}}` - Company name
- `{{service_type}}` - Selected service
- `{{project_location}}` - Project location
- `{{preferred_date}}` - Preferred date
- `{{budget_range}}` - Budget range
- `{{project_description}}` - Project description
### Contact Form (`contact_template`):
- `{{customer_name}}` - Customer's name
- `{{customer_email}}` - Customer's email
- `{{message_subject}}` - Message subject
- `{{message_content}}` - Message content
## Troubleshooting
### Common Issues:
1. **"EmailJS not loaded"** - Check the script tag in index.html
2. **"Template not found"** - Ensure template IDs match exactly
3. **"Service not found"** - Check your Service ID is correct
4. **Emails not arriving** - Check spam folder first
### Testing in Console:
Open browser console (F12) and run:
```javascript
emailjs.send('your_service_id', 'quote_template', {
customer_name: 'Test User',
customer_email: 'test@example.com',
service_type: 'Aerial Photography'
});
```
## Benefits of EmailJS vs AWS SES
**Easier Setup**: No AWS account or IAM configuration needed
**Frontend Friendly**: Designed for browser use
**No CORS Issues**: Works directly from any website
**Free Tier**: 200 emails/month is usually sufficient
**Template Management**: Easy visual template editor
**Reliable Delivery**: Built-in email service integration
## Free Tier Limits
- **200 emails per month**
- **Basic templates**
- **Email history tracking**
For higher volume, upgrade to paid plans starting at $15/month.
---
**Quick Setup Checklist:**
- [ ] Create EmailJS account
- [ ] Add Gmail service and get Service ID
- [ ] Create quote_template and contact_template
- [ ] Get Public Key from Account settings
- [ ] Update script.js with your keys
- [ ] Test both forms
- [ ] Check oli@ptslondon.co.uk for emails
The setup should take about 15 minutes and is much more reliable than AWS SES for website forms!

0
emailjs-setup.md Normal file
View File

BIN
hero-video.mp4 Normal file

Binary file not shown.

417
index.html Normal file
View File

@@ -0,0 +1,417 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PTSAerial - Professional Aerial Photography & Industrial Solutions | Dartford, Kent</title>
<meta name="description" content="PTS London trading as PTSAerial - Professional drone services for aerial photography, video capture, industrial inspections, and agricultural monitoring across Kent and the UK. CAA licensed and fully insured.">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<!-- Header -->
<header class="header">
<nav class="nav">
<div class="nav-container">
<div class="nav-logo">
<img src="drone.png" alt="PTSAerial Drone" class="logo-icon">
<span>PTSAerial</span>
</div>
<ul class="nav-menu">
<li><a href="#home" class="nav-link">Home</a></li>
<li><a href="#services" class="nav-link">Services</a></li>
<!-- <li><a href="#portfolio" class="nav-link">Portfolio</a></li> -->
<li><a href="#about" class="nav-link">About</a></li>
<li><a href="#contact" class="nav-link">Contact</a></li>
<li><a href="#quote" class="nav-link nav-cta">Get Quote</a></li>
</ul>
<div class="hamburger">
<span></span>
<span></span>
<span></span>
</div>
</div>
</nav>
</header>
<!-- Hero Section -->
<section id="home" class="hero">
<div class="hero-content">
<h1 class="hero-title">Professional Drone Services</h1>
<p class="hero-subtitle">Capturing stunning aerial photography, providing industrial inspections, and agricultural monitoring with cutting-edge drone technology</p>
<div class="hero-buttons">
<a href="#quote" class="btn btn-primary">Get Free Quote</a>
<!-- <a href="#portfolio" class="btn btn-secondary">View Our Work</a> -->
</div>
</div>
<div class="hero-video">
<video autoplay muted loop>
<source src="hero-video.mp4" type="video/mp4">
<!-- Placeholder for hero video -->
</video>
</div>
</section>
<!-- Services Section -->
<section id="services" class="services">
<div class="container">
<h2 class="section-title">Our Services</h2>
<p class="section-subtitle">Professional drone solutions for every need</p>
<div class="services-grid">
<div class="service-card">
<div class="service-icon">
<i class="fas fa-camera"></i>
</div>
<h3>Aerial Photography</h3>
<p>Stunning high-resolution aerial photographs for real estate, events, marketing, and personal projects. Capture unique perspectives that showcase your property or event from above.</p>
<ul class="service-features">
<li>4K High-Resolution Images</li>
<li>Real Estate Photography</li>
<li>Event Coverage</li>
<li>Marketing Content</li>
</ul>
</div>
<div class="service-card">
<div class="service-icon">
<i class="fas fa-industry"></i>
</div>
<h3>Industrial</h3>
<p>Safe and efficient inspections of infrastructure, buildings, towers, and industrial facilities. Reduce costs and safety risks with our advanced drone technology.</p>
<ul class="service-features">
<li>Infrastructure Inspections</li>
<li>Thermal Imaging</li>
<li>Power Line Inspections</li>
<li>Detailed Reports</li>
</ul>
</div>
<div class="service-card">
<div class="service-icon">
<i class="fas fa-seedling"></i>
</div>
<h3>Agricultural</h3>
<p>Precision agriculture solutions including crop monitoring, livestock tracking, and land surveying. Optimize your farming operations with actionable aerial data.</p>
<ul class="service-features">
<li>Crop Health Analysis</li>
<li>Livestock Monitoring</li>
<li>Land Surveying</li>
<li>Irrigation Planning</li>
</ul>
</div>
<div class="service-card">
<div class="service-icon">
<i class="fas fa-hard-hat"></i>
</div>
<h3>Construction</h3>
<p>Advanced solutions for construction site monitoring, including progress tracking, safety inspections, and site surveys. Ensure your project stays on schedule and within budget.</p>
<ul class="service-features">
<li>Site Progress Tracking</li>
<li>Safety Inspections</li>
<li>3D Mapping</li>
<li>Site Surveys</li>
</ul>
</div>
<div class="service-card">
<div class="service-icon">
<i class="fas fa-drafting-compass"></i>
</div>
<h3>Architectural</h3>
<p>High-quality aerial imagery and 3D modeling for architectural projects. Showcase your designs with stunning visuals and accurate site assessments.</p>
<ul class="service-features">
<li>3D Modeling</li>
<li>Aerial Imagery</li>
<li>Site Assessments</li>
<li>Design Visualization</li>
</ul>
</div>
<div class="service-card">
<div class="service-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h3>Security Monitoring</h3>
<p>Professional security surveillance and monitoring services for commercial properties, events, and industrial sites. Real-time monitoring with advanced thermal imaging capabilities.</p>
<ul class="service-features">
<li>Perimeter Surveillance</li>
<li>Thermal Imaging</li>
<li>Real-time Monitoring</li>
<li>Incident Documentation</li>
</ul>
</div>
</div>
</div>
</section>
<!--
<section id="portfolio" class="portfolio">
<div class="container">
<h2 class="section-title">Our Work</h2>
<p class="section-subtitle">See the quality and creativity in our drone projects</p>
<div class="portfolio-filter">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="photography">Photography</button>
<button class="filter-btn" data-filter="industrial">Industrial</button>
<button class="filter-btn" data-filter="agriculture">Agriculture</button>
<button class="filter-btn" data-filter="security">Security</button>
</div>
<div class="portfolio-grid">
<div class="portfolio-item" data-category="photography">
<img src="https://via.placeholder.com/400x300/0066cc/ffffff?text=Real+Estate+Photo" alt="Real Estate Photography">
<div class="portfolio-overlay">
<h4>Real Estate Photography</h4>
<p>Luxury home showcase</p>
</div>
</div>
<div class="portfolio-item" data-category="industrial">
<img src="https://via.placeholder.com/400x300/cc0066/ffffff?text=Tower+Inspection" alt="Tower Inspection">
<div class="portfolio-overlay">
<h4>Tower Inspection</h4>
<p>Cell tower safety assessment</p>
</div>
</div>
<div class="portfolio-item" data-category="agriculture">
<img src="https://via.placeholder.com/400x300/66cc00/ffffff?text=Crop+Monitoring" alt="Crop Monitoring">
<div class="portfolio-overlay">
<h4>Crop Monitoring</h4>
<p>Precision agriculture analysis</p>
</div>
</div>
<div class="portfolio-item" data-category="security">
<img src="https://via.placeholder.com/400x300/990000/ffffff?text=Security+Monitoring" alt="Security Monitoring">
<div class="portfolio-overlay">
<h4>Security Monitoring</h4>
<p>Perimeter surveillance</p>
</div>
</div>
<div class="portfolio-item" data-category="photography">
<img src="https://via.placeholder.com/400x300/0066cc/ffffff?text=Construction+Site" alt="Construction Photography">
<div class="portfolio-overlay">
<h4>Construction Progress</h4>
<p>Development documentation</p>
</div>
</div>
</div>
</div>
</section> -->
<!-- About Section -->
<section id="about" class="about">
<div class="container">
<div class="about-content">
<div class="about-text">
<h2>About PTSAerial</h2>
<p>PTS London trading as PTSAerial provides professional, reliable, and innovative drone services across Kent and the UK. Based in Dartford, our CAA-licensed team is fully insured and equipped with the latest drone technology to deliver exceptional results for every project.</p>
<div class="about-stats">
<!-- <div class="stat">
<h3>500+</h3>
<p>Projects Completed</p>
</div> -->
<div class="stat">
<h3>100%</h3>
<p>Client Satisfaction</p>
</div>
<div class="stat">
<h3>24/7</h3>
<p>Support Available</p>
</div>
</div>
<div class="certifications">
<h4>Certifications & Insurance</h4>
<ul>
<li><i class="fas fa-check"></i> CAA Licensed (UK)</li>
<li><i class="fas fa-check"></i> Fully Insured Operations</li>
<li><i class="fas fa-check"></i> Professional Equipment</li>
<li><i class="fas fa-check"></i> Safety First Approach</li>
</ul>
</div>
</div>
<div class="about-image">
<img src="drone-op.jpg" alt="Professional Drone Operator">
</div>
</div>
</div>
</section>
<!-- Quote Form Section -->
<section id="quote" class="quote-section">
<div class="container">
<h2 class="section-title">Get Your Free Quote</h2>
<p class="section-subtitle">Tell us about your project and we'll provide a detailed quote within 24 hours</p>
<form id="quoteForm" class="quote-form">
<div class="form-row">
<div class="form-group">
<label for="name">Full Name *</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email Address *</label>
<input type="email" id="email" name="email" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone">
</div>
<div class="form-group">
<label for="company">Company/Organisation</label>
<input type="text" id="company" name="company">
</div>
</div>
<div class="form-group">
<label for="service">Service Type *</label>
<select id="service" name="service" required>
<option value="">Select a service</option>
<option value="aerial-photography">Aerial Photography</option>
<option value="industrial-inspection">Industrial Inspection</option>
<option value="agricultural-monitoring">Agricultural Monitoring</option>
<option value="security-monitoring">Security Monitoring</option>
<option value="construction">Construction</option>
<option value="architectural">Architectural</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label for="location">Project Location *</label>
<input type="text" id="location" name="location" placeholder="City / County" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="date">Preferred Date</label>
<input type="date" id="date" name="date">
</div>
</div>
<div class="form-group">
<label for="description">Project Description *</label>
<textarea id="description" name="description" rows="5" placeholder="Please describe your project, specific requirements, and any special considerations..." required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-large">Submit Quote Request</button>
</form>
</div>
</section>
<!-- Contact Section -->
<section id="contact" class="contact">
<div class="container">
<h2 class="section-title">Contact Us</h2>
<div class="contact-content">
<div class="contact-info">
<div class="contact-item">
<i class="fas fa-phone"></i>
<div>
<h4>Phone</h4>
<p>+44 (0)20 3883 7578</p>
</div>
</div>
<div class="contact-item">
<i class="fas fa-envelope"></i>
<div>
<h4>Email</h4>
<p>oli@ptslondon.co.uk</p>
</div>
</div>
<div class="contact-item">
<i class="fas fa-map-marker-alt"></i>
<div>
<h4>Location</h4>
<p>Dartford, Kent, UK</p>
</div>
</div>
</div>
<div class="contact-form">
<h3>Send us a message</h3>
<form id="contactForm">
<div class="form-group">
<input type="text" name="contact_name" placeholder="Your Name" required>
</div>
<div class="form-group">
<input type="email" name="contact_email" placeholder="Your Email" required>
</div>
<div class="form-group">
<input type="text" name="subject" placeholder="Subject" required>
</div>
<div class="form-group">
<textarea name="message" rows="5" placeholder="Your Message" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<div class="footer-logo">
<img src="drone.png" alt="PTSAerial Drone" class="logo-icon">
<span>PTSAerial</span>
</div>
<p>Professional drone services for aerial photography, industrial inspections, and agricultural monitoring across Kent and the UK.</p>
<!-- <div class="social-links">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-youtube"></i></a>
<a href="#"><i class="fab fa-linkedin"></i></a>
</div> -->
</div>
<div class="footer-section">
<h4>Services</h4>
<ul>
<li><a href="#services">Aerial Photography</a></li>
<li><a href="#services">Industrial Inspections</a></li>
<li><a href="#services">Agricultural Monitoring</a></li>
<li><a href="#services">Security Monitoring</a></li>
<li><a href="#services">Construction</a></li>
<li><a href="#services">Architectural</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Company</h4>
<ul>
<li><a href="#about">About Us</a></li>
<!-- <li><a href="#portfolio">Portfolio</a></li> -->
<li><a href="#contact">Contact</a></li>
<li><a href="#quote">Get Quote</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Contact Info</h4>
<p><i class="fas fa-phone"></i> +44 (0)20 3883 7578</p>
<p><i class="fas fa-envelope"></i> oli@ptslondon.co.uk</p>
<p><i class="fas fa-map-marker-alt"></i> Dartford, Kent, UK</p>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 PTSAerial (PTS London Ltd). All rights reserved. | CAA Licensed | Fully Insured | Based in Dartford, Kent</p>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/@emailjs/browser@4/dist/email.min.js"></script>
<script src="script.js"></script>
</body>
</html>

451
script.js Normal file
View File

@@ -0,0 +1,451 @@
// Mobile Navigation Toggle
document.addEventListener('DOMContentLoaded', function() {
const hamburger = document.querySelector('.hamburger');
const navMenu = document.querySelector('.nav-menu');
hamburger.addEventListener('click', function() {
hamburger.classList.toggle('active');
navMenu.classList.toggle('active');
});
// Close mobile menu when clicking on a link
document.querySelectorAll('.nav-link').forEach(n => n.addEventListener('click', () => {
hamburger.classList.remove('active');
navMenu.classList.remove('active');
}));
});
// Smooth Scrolling for Navigation Links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Portfolio Filter Functionality
document.addEventListener('DOMContentLoaded', function() {
const filterButtons = document.querySelectorAll('.filter-btn');
const portfolioItems = document.querySelectorAll('.portfolio-item');
filterButtons.forEach(button => {
button.addEventListener('click', function() {
// Remove active class from all buttons
filterButtons.forEach(btn => btn.classList.remove('active'));
// Add active class to clicked button
this.classList.add('active');
const filterValue = this.getAttribute('data-filter');
portfolioItems.forEach(item => {
if (filterValue === 'all' || item.getAttribute('data-category') === filterValue) {
item.style.display = 'block';
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = 'scale(1)';
}, 100);
} else {
item.style.opacity = '0';
item.style.transform = 'scale(0.8)';
setTimeout(() => {
item.style.display = 'none';
}, 300);
}
});
});
});
});
// Form Handling with EmailJS
document.addEventListener('DOMContentLoaded', function() {
// Check if EmailJS is loaded
if (typeof emailjs === 'undefined') {
console.error('EmailJS not loaded. Please check the script tag.');
return;
}
// Initialize EmailJS
emailjs.init("QznwPi5vZti7xnjRj"); // Replace with your EmailJS public key
console.log('EmailJS initialized successfully');
const quoteForm = document.getElementById('quoteForm');
const contactForm = document.getElementById('contactForm');
// Function to send email via EmailJS
function sendEmail(templateId, templateParams) {
console.log('Attempting to send email with EmailJS:', templateId);
console.log('Template parameters:', templateParams);
return emailjs.send('service_c51669d', templateId, templateParams)
.then(function(response) {
console.log('EmailJS Success:', response.status, response.text);
return response;
})
.catch(function(error) {
console.error('EmailJS Error:', error);
throw error;
});
}
// Quote Form Submission
if (quoteForm) {
quoteForm.addEventListener('submit', function(e) {
e.preventDefault();
// Get form data
const formData = new FormData(this);
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
// Show loading state
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Submitting...';
submitBtn.disabled = true;
// Prepare EmailJS template parameters
const templateParams = {
to_email: 'oli@ptslondon.co.uk',
from_name: data.name,
reply_to: data.email,
customer_name: data.name,
customer_email: data.email,
customer_phone: data.phone || 'Not provided',
customer_company: data.company || 'Not provided',
service_type: data.service,
project_location: data.location,
preferred_date: data.date || 'Not specified',
budget_range: data.budget || 'Not specified',
project_description: data.description,
form_type: 'Quote Request'
};
// Send email using EmailJS
sendEmail('quote_template', templateParams)
.then((result) => {
console.log('Quote email sent successfully:', result);
showNotification('Thank you for your quote request! We will contact you within 24 hours.', 'success');
this.reset();
})
.catch((error) => {
console.error('EmailJS error:', error);
let errorMessage = 'There was an error sending your quote request. ';
if (error.text && error.text.includes('template')) {
errorMessage += 'Email template not found. ';
} else if (error.text && error.text.includes('service')) {
errorMessage += 'Email service configuration error. ';
}
errorMessage += 'Please contact us directly at oli@ptslondon.co.uk.';
showNotification(errorMessage, 'error');
})
.finally(() => {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
});
});
}
// Contact Form Submission
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
// Get form data
const formData = new FormData(this);
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
// Show loading state
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Sending...';
submitBtn.disabled = true;
// Prepare EmailJS template parameters
const templateParams = {
to_email: 'oli@ptslondon.co.uk',
from_name: data.contact_name,
reply_to: data.contact_email,
customer_name: data.contact_name,
customer_email: data.contact_email,
message_subject: data.subject,
message_content: data.message,
form_type: 'Contact Message'
};
// Send email using EmailJS
sendEmail('contact_template', templateParams)
.then((result) => {
console.log('Contact email sent successfully:', result);
showNotification('Thank you for your message! We will get back to you soon.', 'success');
this.reset();
})
.catch((error) => {
console.error('Contact EmailJS error:', error);
let errorMessage = 'There was an error sending your message. ';
if (error.text && error.text.includes('template')) {
errorMessage += 'Email template not found. ';
} else if (error.text && error.text.includes('service')) {
errorMessage += 'Email service configuration error. ';
}
errorMessage += 'Please contact us directly at oli@ptslondon.co.uk.';
showNotification(errorMessage, 'error');
})
.finally(() => {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
});
});
}
});
// Navbar Background on Scroll
window.addEventListener('scroll', function() {
const header = document.querySelector('.header');
if (window.scrollY > 100) {
header.style.background = 'rgba(255, 255, 255, 0.98)';
header.style.boxShadow = '0 2px 20px rgba(0, 0, 0, 0.1)';
} else {
header.style.background = 'rgba(255, 255, 255, 0.95)';
header.style.boxShadow = 'none';
}
});
// Intersection Observer for Animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, observerOptions);
// Observe elements for animation
document.addEventListener('DOMContentLoaded', function() {
const animateElements = document.querySelectorAll('.service-card, .portfolio-item, .about-content, .contact-item');
animateElements.forEach(el => {
el.classList.add('fade-in');
observer.observe(el);
});
});
// Form Validation
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function validatePhone(phone) {
const re = /^[\+]?[1-9][\d]{0,15}$/;
return re.test(phone.replace(/\s/g, ''));
}
// Real-time form validation
document.addEventListener('DOMContentLoaded', function() {
const emailInputs = document.querySelectorAll('input[type="email"]');
const phoneInputs = document.querySelectorAll('input[type="tel"]');
emailInputs.forEach(input => {
input.addEventListener('blur', function() {
if (this.value && !validateEmail(this.value)) {
this.style.borderColor = '#ff6b6b';
this.style.boxShadow = '0 0 0 3px rgba(255, 107, 107, 0.1)';
} else {
this.style.borderColor = '#ddd';
this.style.boxShadow = 'none';
}
});
});
phoneInputs.forEach(input => {
input.addEventListener('blur', function() {
if (this.value && !validatePhone(this.value)) {
this.style.borderColor = '#ff6b6b';
this.style.boxShadow = '0 0 0 3px rgba(255, 107, 107, 0.1)';
} else {
this.style.borderColor = '#ddd';
this.style.boxShadow = 'none';
}
});
});
});
// Set minimum date for date inputs to today
document.addEventListener('DOMContentLoaded', function() {
const dateInput = document.getElementById('date');
if (dateInput) {
const today = new Date().toISOString().split('T')[0];
dateInput.setAttribute('min', today);
}
});
// Lazy loading for images
document.addEventListener('DOMContentLoaded', function() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
});
// Add loading states for buttons
function addLoadingState(button, loadingText = 'Loading...') {
const originalText = button.textContent;
button.textContent = loadingText;
button.disabled = true;
button.classList.add('loading');
return function removeLoadingState() {
button.textContent = originalText;
button.disabled = false;
button.classList.remove('loading');
};
}
// Enhanced form submission with better error handling
function submitForm(form, endpoint) {
const submitButton = form.querySelector('button[type="submit"]');
const removeLoading = addLoadingState(submitButton);
const formData = new FormData(form);
// Convert FormData to regular object
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
// Simulate API call (replace with actual endpoint)
return new Promise((resolve, reject) => {
setTimeout(() => {
// Simulate success/failure
if (Math.random() > 0.1) { // 90% success rate
resolve(data);
} else {
reject(new Error('Network error'));
}
}, 2000);
})
.then(result => {
removeLoading();
showNotification('Form submitted successfully!', 'success');
form.reset();
return result;
})
.catch(error => {
removeLoading();
showNotification('There was an error submitting the form. Please try again.', 'error');
throw error;
});
}
// Notification system
function showNotification(message, type = 'info') {
// Remove existing notifications
const existingNotifications = document.querySelectorAll('.notification');
existingNotifications.forEach(notification => notification.remove());
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<span>${message}</span>
<button class="notification-close">&times;</button>
`;
// Add styles
Object.assign(notification.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '1rem 1.5rem',
borderRadius: '5px',
color: 'white',
fontWeight: '500',
zIndex: '10000',
animation: 'slideInRight 0.3s ease',
display: 'flex',
alignItems: 'center',
gap: '1rem',
maxWidth: '300px'
});
// Set background color based on type
const colors = {
success: '#28a745',
error: '#dc3545',
warning: '#ffc107',
info: '#17a2b8'
};
notification.style.backgroundColor = colors[type] || colors.info;
// Add close functionality
const closeBtn = notification.querySelector('.notification-close');
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = 'white';
closeBtn.style.fontSize = '1.5rem';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '0';
closeBtn.style.marginLeft = 'auto';
closeBtn.addEventListener('click', () => notification.remove());
document.body.appendChild(notification);
// Auto remove after 5 seconds
setTimeout(() => {
if (document.body.contains(notification)) {
notification.remove();
}
}, 5000);
}
// Add CSS for notification animation
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.loading {
opacity: 0.7;
cursor: not-allowed;
}
`;
document.head.appendChild(style);

798
styles.css Normal file
View File

@@ -0,0 +1,798 @@
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
line-height: 1.6;
color: #333;
scroll-behavior: smooth;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header and Navigation */
.header {
position: fixed;
top: 0;
width: 100%;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
z-index: 1000;
transition: all 0.3s ease;
}
.nav-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
max-width: 1200px;
margin: 0 auto;
}
.nav-logo {
display: flex;
align-items: center;
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
text-decoration: none;
}
.nav-logo .logo-icon {
width: 32px;
height: 32px;
margin-right: 0.5rem;
object-fit: contain;
}
.nav-logo span {
color: var(--text-color);
}
.nav-menu {
display: flex;
list-style: none;
gap: 2rem;
align-items: center;
}
.nav-link {
text-decoration: none;
color: #333;
font-weight: 500;
transition: color 0.3s ease;
position: relative;
}
.nav-link:hover {
color: #0066cc;
}
.nav-link:after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: #0066cc;
transition: width 0.3s ease;
}
.nav-link:hover:after {
width: 100%;
}
.nav-cta {
background: #0066cc;
color: white !important;
padding: 0.5rem 1.5rem;
border-radius: 25px;
transition: all 0.3s ease;
}
.nav-cta:hover {
background: #0052a3;
transform: translateY(-2px);
}
.nav-cta:after {
display: none;
}
.hamburger {
display: none;
flex-direction: column;
cursor: pointer;
}
.hamburger span {
width: 25px;
height: 3px;
background: #333;
margin: 3px 0;
transition: 0.3s;
}
/* Hero Section */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: linear-gradient(135deg, #1b6799 0%, #15065a 70%);
color: white;
text-align: center;
overflow: hidden;
}
.hero-content {
z-index: 2;
max-width: 800px;
padding: 2rem;
}
.hero-title {
font-size: 3.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
animation: fadeInUp 1s ease;
}
.hero-subtitle {
font-size: 1.25rem;
margin-bottom: 2.5rem;
opacity: 0.9;
animation: fadeInUp 1s ease 0.2s both;
}
.hero-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
animation: fadeInUp 1s ease 0.4s both;
}
.btn {
display: inline-block;
padding: 1rem 2rem;
border-radius: 5px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
border: none;
cursor: pointer;
font-size: 1rem;
}
.btn-primary {
background: #ff6b6b;
color: white;
}
.btn-primary:hover {
background: #ff5252;
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(255, 107, 107, 0.3);
}
.btn-secondary {
background: transparent;
color: white;
border: 2px solid white;
}
.btn-secondary:hover {
background: white;
color: #667eea;
transform: translateY(-2px);
}
.btn-large {
padding: 1.25rem 2.5rem;
font-size: 1.1rem;
}
.hero-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
opacity: 0.4;
}
.hero-video video {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Sections */
.section-title {
font-size: 2.5rem;
font-weight: 700;
text-align: center;
margin-bottom: 1rem;
color: #333;
}
.section-subtitle {
font-size: 1.2rem;
text-align: center;
margin-bottom: 3rem;
color: #666;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
/* Services Section */
.services {
padding: 5rem 0;
background: #f8f9fa;
}
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 3rem;
}
.service-card {
background: white;
padding: 2.5rem;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
text-align: center;
}
.service-card:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
font-size: 2rem;
}
.service-card h3 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1rem;
color: #333;
}
.service-card p {
color: #666;
margin-bottom: 1.5rem;
line-height: 1.6;
}
.service-features {
list-style: none;
text-align: left;
}
.service-features li {
padding: 0.5rem 0;
color: #555;
position: relative;
padding-left: 1.5rem;
}
.service-features li:before {
content: '✓';
position: absolute;
left: 0;
color: #0066cc;
font-weight: bold;
}
/* Portfolio Section */
.portfolio {
padding: 5rem 0;
background: white;
}
.portfolio-filter {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.75rem 1.5rem;
border: 2px solid #0066cc;
background: transparent;
color: #0066cc;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
}
.filter-btn.active,
.filter-btn:hover {
background: #0066cc;
color: white;
}
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
position: relative;
border-radius: 10px;
overflow: hidden;
cursor: pointer;
transition: transform 0.3s ease;
}
.portfolio-item:hover {
transform: scale(1.05);
}
.portfolio-item img {
width: 100%;
height: 250px;
object-fit: cover;
transition: all 0.3s ease;
}
.portfolio-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
padding: 2rem;
transform: translateY(100%);
transition: all 0.3s ease;
}
.portfolio-item:hover .portfolio-overlay {
transform: translateY(0);
}
.portfolio-overlay h4 {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
/* About Section */
.about {
padding: 5rem 0;
background: #f8f9fa;
}
.about-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.about-text h2 {
font-size: 2.5rem;
margin-bottom: 1.5rem;
color: #333;
}
.about-text p {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.7;
}
.about-stats {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
}
.stat {
text-align: center;
}
.stat h3 {
font-size: 2rem;
color: #0066cc;
font-weight: 700;
}
.stat p {
color: #666;
margin: 0;
}
.certifications h4 {
margin-bottom: 1rem;
color: #333;
}
.certifications ul {
list-style: none;
}
.certifications li {
padding: 0.5rem 0;
color: #666;
}
.certifications i {
color: #28a745;
margin-right: 0.5rem;
}
.about-image img {
width: 100%;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
/* Quote Section */
.quote-section {
padding: 5rem 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.quote-section .section-title,
.quote-section .section-subtitle {
color: white;
}
.quote-form {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
padding: 3rem;
border-radius: 15px;
backdrop-filter: blur(10px);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 1rem;
border: none;
border-radius: 5px;
background: rgba(255, 255, 255, 0.9);
font-size: 1rem;
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
background: white;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.3);
}
.form-group textarea {
resize: vertical;
min-height: 120px;
}
/* Contact Section */
.contact {
padding: 5rem 0;
background: white;
}
.contact-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 2rem;
}
.contact-item {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.contact-item i {
font-size: 1.5rem;
color: #0066cc;
margin-top: 0.25rem;
}
.contact-item h4 {
margin-bottom: 0.5rem;
color: #333;
}
.contact-item p {
color: #666;
margin: 0;
}
.contact-form {
background: #f8f9fa;
padding: 2rem;
border-radius: 10px;
}
.contact-form h3 {
margin-bottom: 1.5rem;
color: #333;
}
.contact-form .form-group input,
.contact-form .form-group textarea {
background: white;
border: 1px solid #ddd;
color: #333;
}
/* Footer */
.footer {
background: #2c3e50;
color: white;
padding: 3rem 0 1rem;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-logo {
display: flex;
align-items: center;
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1rem;
}
.footer-logo .logo-icon {
width: 32px;
height: 32px;
margin-right: 0.5rem;
object-fit: contain;
filter: brightness(0) invert(1); /* Makes the icon white for footer */
}
.footer-logo span {
color: white;
}
.footer-section h4 {
margin-bottom: 1rem;
color: #0066cc;
}
.footer-section ul {
list-style: none;
}
.footer-section ul li {
margin-bottom: 0.5rem;
}
.footer-section ul li a {
color: #bbb;
text-decoration: none;
transition: color 0.3s ease;
}
.footer-section ul li a:hover {
color: white;
}
.footer-section p {
color: #bbb;
margin-bottom: 0.5rem;
}
.social-links {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
.social-links a {
width: 40px;
height: 40px;
background: #0066cc;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-decoration: none;
transition: all 0.3s ease;
}
.social-links a:hover {
background: #0052a3;
transform: translateY(-2px);
}
.footer-bottom {
border-top: 1px solid #444;
padding-top: 1rem;
text-align: center;
color: #bbb;
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.hamburger {
display: flex;
}
.nav-menu {
position: fixed;
left: -100%;
top: 70px;
flex-direction: column;
background-color: white;
width: 100%;
text-align: center;
transition: 0.3s;
box-shadow: 0 10px 27px rgba(0, 0, 0, 0.05);
padding: 2rem 0;
}
.nav-menu.active {
left: 0;
}
.hero-title {
font-size: 2.5rem;
}
.hero-subtitle {
font-size: 1.1rem;
}
.hero-buttons {
flex-direction: column;
align-items: center;
}
.services-grid {
grid-template-columns: 1fr;
}
.portfolio-grid {
grid-template-columns: 1fr;
}
.about-content {
grid-template-columns: 1fr;
text-align: center;
}
.about-stats {
justify-content: center;
}
.contact-content {
grid-template-columns: 1fr;
}
.form-row {
grid-template-columns: 1fr;
}
.section-title {
font-size: 2rem;
}
.nav-container {
padding: 1rem;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 2rem;
}
.service-card,
.quote-form {
padding: 1.5rem;
}
.about-stats {
flex-direction: column;
text-align: center;
}
.portfolio-filter {
gap: 0.5rem;
}
.filter-btn {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
}
/* Utility Classes */
.hidden {
display: none;
}
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}

164
troubleshooting.md Normal file
View File

@@ -0,0 +1,164 @@
# AWS SES Troubleshooting Guide
Your website now has enhanced error handling and logging. Here's how to troubleshoot email sending issues:
## 1. Check Browser Console
Open your website and:
1. Press F12 to open Developer Tools
2. Go to the "Console" tab
3. Submit a test form
4. Look for error messages
### Common Console Messages:
**Success:**
```
AWS SES configured successfully
Attempting to send email with subject: New Quote Request - PTSAerial - [service]
Email sent successfully: {MessageId: "..."}
```
**Common Errors:**
```
AWS SDK not loaded. Please check the script tag.
Error configuring AWS: [error details]
SES Error: MessageRejected
SES Error: AccessDenied
SES Error: InvalidParameterValue
```
## 2. Common Issues & Solutions
### Issue: "MessageRejected" Error
**Cause:** Email addresses not verified in SES
**Solution:**
1. Go to AWS SES Console
2. Navigate to "Verified identities"
3. Verify both `oli@ptslondon.co.uk` and `noreply@ptslondon.co.uk`
4. Check your email for verification links
### Issue: "AccessDenied" Error
**Cause:** IAM user lacks SES permissions
**Solution:**
1. Go to AWS IAM Console
2. Check your user has the SES policy attached
3. Ensure policy includes `ses:SendEmail` and `ses:SendRawEmail`
### Issue: "Sandbox" Restrictions
**Cause:** SES is in sandbox mode
**Solution:**
1. In SES Console, request production access
2. Or verify all recipient email addresses
3. Sandbox only allows verified recipients
### Issue: "Region" Mismatch
**Cause:** Wrong AWS region in config
**Solution:**
1. Check which region your SES is set up in
2. Update `region: 'eu-west-1'` in script.js to match
### Issue: "CORS" Errors
**Cause:** Browser security restrictions
**Solution:**
- This shouldn't occur with SES, but if it does, consider using a backend API instead
## 3. Verify SES Setup
### Check in AWS Console:
1. **SES Dashboard** → "Sending statistics"
- Should show attempted sends
- Check for bounce/complaint rates
2. **Verified identities**
- `oli@ptslondon.co.uk` should show "Verified"
- `noreply@ptslondon.co.uk` should show "Verified"
3. **Configuration sets** (optional)
- Can help with tracking delivery
## 4. Test with Minimal Example
Add this to your browser console to test SES directly:
```javascript
// Test AWS SES configuration
AWS.config.update({
region: 'eu-west-1',
accessKeyId: 'AKIA3C5Y7TIMPQ7PHLEM',
secretAccessKey: 'BPCPlS2X77KwgvFweOO3IebeT9U5gBNnZ5/7ZRX2WEtO'
});
const ses = new AWS.SES();
const params = {
Destination: { ToAddresses: ['oli@ptslondon.co.uk'] },
Message: {
Body: { Text: { Data: 'Test email from website' } },
Subject: { Data: 'Test Email' }
},
Source: 'noreply@ptslondon.co.uk'
};
ses.sendEmail(params, function(err, data) {
if (err) console.error('Error:', err);
else console.log('Success:', data);
});
```
## 5. Security Note
⚠️ **Important:** Your AWS credentials are currently visible in the browser source code. This is acceptable for testing but consider these alternatives for production:
### Option A: Backend API (Recommended)
Create a simple backend endpoint that handles email sending:
```javascript
// Instead of direct SES, call your API
fetch('/api/send-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
```
### Option B: Cognito Identity Pool
Use temporary credentials instead of permanent access keys.
## 6. Alternative: Quick Backend Solution
If SES continues to have issues, here's a simple Node.js backend you could deploy:
```javascript
// server.js
const express = require('express');
const AWS = require('aws-sdk');
const app = express();
AWS.config.update({
region: 'eu-west-1',
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY
});
app.post('/send-email', (req, res) => {
const ses = new AWS.SES();
// Email sending logic here
});
```
## 7. Monitoring
Check these regularly:
- AWS SES bounce rate (keep under 5%)
- AWS SES complaint rate (keep under 0.1%)
- Your website's email delivery success rate
## Next Steps
1. Check browser console for specific error messages
2. Verify email addresses in AWS SES
3. Ensure SES is out of sandbox mode or recipients are verified
4. Test with the minimal example above
5. Consider implementing a backend API for production use
Let me know what specific error messages you see in the console!