bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
216 lines
12 KiB
Bash
Executable File
216 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# Generate standardized city inspection landing pages - v2
|
|
|
|
set -e
|
|
|
|
# Cities per country (slug|name)
|
|
SWEDEN_CITIES="stockholm|Stockholm goteborg|Göteborg malmo|Malmö uppsala|Uppsala linkoping|Linköping orebro|Örebro"
|
|
GERMANY_CITIES="berlin|Berlin hamburg|Hamburg munich|München cologne|Köln frankfurt|Frankfurt stuttgart|Stuttgart"
|
|
FRANCE_CITIES="paris|Paris marseille|Marseille lyon|Lyon toulouse|Toulouse nice|Nice nantes|Nantes"
|
|
UK_CITIES="london|London manchester|Manchester birmingham|Birmingham glasgow|Glasgow liverpool|Liverpool leeds|Leeds"
|
|
NETHERLANDS_CITIES="amsterdam|Amsterdam rotterdam|Rotterdam the-hague|Den_Haag utrecht|Utrecht eindhoven|Eindhoven tilburg|Tilburg"
|
|
|
|
# Services (slug|name|description)
|
|
SERVICES=(
|
|
"bridge-inspection|Bridge Inspection|Professional bridge inspection services in {city}. {compliance} compliant."
|
|
"facade-inspection|Facade Inspection|Building facade inspection and documentation in {city}. Identify maintenance needs."
|
|
"road-condition|Road Condition|AI-assisted road surface analysis and condition mapping across {city}."
|
|
"street-lighting|Street Lighting|Verify street light functionality and coverage across {city}."
|
|
"construction-monitoring|Construction Monitoring|Document construction progress and verify compliance in {city}."
|
|
"property-verification|Property Verification|Verify property condition and occupancy in {city}."
|
|
"retail-audit|Retail Audit|Document storefront conditions and competitor presence in {city}."
|
|
"accessibility-audit|Accessibility Audit|Verify accessibility compliance for public spaces in {city}."
|
|
"environmental-monitoring|Environmental Monitoring|Track environmental conditions and green space status in {city}."
|
|
"storm-damage|Storm Damage|Rapid post-storm documentation of damage across {city}."
|
|
)
|
|
|
|
# Flags
|
|
get_flag() {
|
|
case "$1" in
|
|
sweden) echo "🇸🇪" ;;
|
|
germany) echo "🇩🇪" ;;
|
|
france) echo "🇫🇷" ;;
|
|
uk) echo "🇬🇧" ;;
|
|
netherlands) echo "🇳🇱" ;;
|
|
esac
|
|
}
|
|
|
|
# Compliance
|
|
get_compliance() {
|
|
case "$1" in
|
|
sweden) echo "Trafikverket" ;;
|
|
germany) echo "DIN" ;;
|
|
france) echo "CEREMA" ;;
|
|
uk) echo "DfT" ;;
|
|
netherlands) echo "Rijkswaterstaat" ;;
|
|
esac
|
|
}
|
|
|
|
# Generate page
|
|
generate_page() {
|
|
local service_slug="$1"
|
|
local service_name="$2"
|
|
local service_desc="$3"
|
|
local city_slug="$4"
|
|
local city_name="$5"
|
|
local country="$6"
|
|
local flag="$7"
|
|
local compliance="$8"
|
|
|
|
# Build related services
|
|
local related=""
|
|
for svc in "${SERVICES[@]}"; do
|
|
local s_slug=$(echo "$svc" | cut -d'|' -f1)
|
|
local s_name=$(echo "$svc" | cut -d'|' -f2)
|
|
if [[ "$s_slug" != "$service_slug" ]]; then
|
|
related+=" <a href=\"/$s_slug/$city_slug\" class=\"related-card\">\n"
|
|
related+=" <div class=\"related-flag\">🏙️</div>\n"
|
|
related+=" <h4>$s_name — $city_name</h4>\n"
|
|
related+=" <p>Available in $city_name.</p>\n"
|
|
related+=" <div class=\"related-arrow\">Explore →</div>\n"
|
|
related+=" </a>\n"
|
|
fi
|
|
done
|
|
|
|
# Add country-level link
|
|
related+=" <a href=\"/$service_slug/$country\" class=\"related-card\">\n"
|
|
related+=" <div class=\"related-flag\">$flag</div>\n"
|
|
related+=" <h4>$service_name — $(echo "$country" | sed 's/.*/\u&/')</h4>\n"
|
|
related+=" <p>Nationwide coverage.</p>\n"
|
|
related+=" <div class=\"related-arrow\">Explore →</div>\n"
|
|
related+=" </a>\n"
|
|
|
|
# Add enterprise
|
|
related+=" <a href=\"/for-organisations\" class=\"related-card\">\n"
|
|
related+=" <div class=\"related-flag\">🏢</div>\n"
|
|
related+=" <h4>Enterprise Solutions</h4>\n"
|
|
related+=" <p>Custom programs with API and SLA.</p>\n"
|
|
related+=" <div class=\"related-arrow\">Learn more →</div>\n"
|
|
related+=" </a>\n"
|
|
|
|
# Build description
|
|
local desc=$(echo "$service_desc" | sed "s/{city}/$city_name/g; s/{compliance}/$compliance/g")
|
|
|
|
cat << EOF
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>$service_name in $city_name — quiXzoom</title>
|
|
<meta name="description" content="$desc">
|
|
<link rel="canonical" href="https://www.quixzoom.com/$service_slug/$city_slug">
|
|
<style>
|
|
:root { --bg: #f5f5f7; --blue: #0066FF; --text: #1d1d1f; --muted: #6e6e73; --surface: #fff; --border: rgba(0,0,0,0.09); }
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
|
|
.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
|
|
nav { position: sticky; top: 0; background: rgba(255,255,255,0.92); backdrop-filter: blur(20px); border-bottom: 1px solid var(--border); z-index: 300; }
|
|
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 64px; }
|
|
.nav-logo { font-size: 1.375rem; font-weight: 900; text-decoration: none; color: var(--text); }
|
|
.nav-logo span { color: var(--blue); }
|
|
.nav-links { display: flex; gap: 32px; }
|
|
.nav-links a { color: var(--muted); text-decoration: none; font-weight: 500; }
|
|
.nav-links a:hover { color: var(--text); }
|
|
.hero { padding: 80px 0 60px; text-align: center; background: var(--surface); }
|
|
.hero h1 { font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800; letter-spacing: -0.03em; margin-bottom: 20px; }
|
|
.hero p { font-size: 1.25rem; color: var(--muted); max-width: 600px; margin: 0 auto; }
|
|
.hero-badge { display: inline-flex; align-items: center; gap: 8px; background: rgba(0,102,255,0.1); color: var(--blue); border: 1px solid rgba(0,102,255,0.2); border-radius: 24px; padding: 7px 18px; font-size: 0.8125rem; font-weight: 700; margin-bottom: 24px; }
|
|
.section { padding: 60px 0; }
|
|
.section-title { text-align: center; margin-bottom: 48px; }
|
|
.section-title h2 { font-size: 2rem; font-weight: 700; margin-bottom: 12px; }
|
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; }
|
|
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 40px; transition: all 0.2s; }
|
|
.card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px rgba(0,0,0,0.08); }
|
|
.card h3 { font-size: 1.25rem; font-weight: 700; margin-bottom: 12px; }
|
|
.card p { color: var(--muted); font-size: 0.95rem; line-height: 1.6; }
|
|
.related-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
|
|
.related-card { background: var(--surface); border: 1.5px solid var(--border); border-radius: 16px; padding: 28px; transition: all 0.2s; text-decoration: none; color: inherit; display: flex; flex-direction: column; }
|
|
.related-card:hover { transform: translateY(-3px); box-shadow: 0 8px 30px rgba(0,0,0,0.08); border-color: rgba(0,102,255,0.25); }
|
|
.related-card .related-flag { font-size: 1.5rem; margin-bottom: 12px; }
|
|
.related-card h4 { font-size: 1rem; font-weight: 700; margin-bottom: 6px; }
|
|
.related-card p { font-size: 0.875rem; color: var(--muted); }
|
|
.related-card .related-arrow { margin-top: auto; padding-top: 16px; color: var(--blue); font-size: 0.875rem; font-weight: 600; }
|
|
.breadcrumb { padding: 20px 0 0; font-size: 0.875rem; color: var(--muted); }
|
|
.breadcrumb a { color: var(--muted); text-decoration: none; }
|
|
.breadcrumb a:hover { color: var(--blue); }
|
|
.breadcrumb span { color: var(--text); font-weight: 500; }
|
|
.cta { background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 60px; text-align: center; margin: 40px 0; }
|
|
.cta h2 { font-size: 2rem; font-weight: 700; margin-bottom: 16px; }
|
|
.cta p { font-size: 1.1rem; color: var(--muted); margin-bottom: 32px; }
|
|
.btn { display: inline-block; background: var(--blue); color: #fff; text-decoration: none; padding: 14px 32px; border-radius: 12px; font-weight: 600; }
|
|
.btn-secondary { display: inline-block; background: var(--surface); color: var(--blue); text-decoration: none; padding: 14px 32px; border-radius: 12px; font-weight: 600; border: 2px solid var(--blue); margin-left: 12px; }
|
|
footer { background: var(--surface); border-top: 1px solid var(--border); padding: 40px 0; }
|
|
.footer-inner { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; }
|
|
.footer-links { display: flex; gap: 24px; }
|
|
.footer-links a { color: var(--muted); text-decoration: none; font-size: 0.875rem; }
|
|
@media (max-width: 768px) { .nav-links { display: none; } .cta { padding: 40px 24px; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav><div class="container"><div class="nav-inner"><a href="/" class="nav-logo">qui<span>X</span>zoom</a><div class="nav-links"><a href="/">Home</a><a href="/about">About</a><a href="/pricing">Pricing</a><a href="/features">Features</a></div></div></div></nav>
|
|
<section class="hero"><div class="container">
|
|
<div class="breadcrumb"><a href="/">Home</a> → <a href="/$service_slug">$service_name</a> → <span>$city_name</span></div>
|
|
<div class="hero-badge">$flag $city_name · $compliance Compliant</div>
|
|
<h1>$service_name in $city_name</h1>
|
|
<p>$desc</p>
|
|
</div></section>
|
|
<section class="section"><div class="container">
|
|
<div class="section-title"><h2>Why choose quiXzoom in $city_name</h2><p>Local expertise, global standards</p></div>
|
|
<div class="grid">
|
|
<div class="card"><h3>Local Coverage</h3><p>Zoomers available throughout $city_name. Fast response times, local knowledge.</p></div>
|
|
<div class="card"><h3>$compliance Compliant</h3><p>All inspections follow $compliance regulations. Digital reports ready.</p></div>
|
|
<div class="card"><h3>Rapid Deployment</h3><p>Missions deployed within hours. Same-day for urgent requests.</p></div>
|
|
<div class="card"><h3>Verified Data</h3><p>AI quality control with geolocation and timestamps. Court-admissible.</p></div>
|
|
<div class="card"><h3>Cost Effective</h3><p>Up to 70% cheaper than traditional. Pay per mission.</p></div>
|
|
<div class="card"><h3>Scalable</h3><p>From single to city-wide campaigns. Handle any volume.</p></div>
|
|
</div>
|
|
</div></section>
|
|
<section class="section" style="background: var(--surface);"><div class="container">
|
|
<div class="section-title"><h2>Related services in $city_name</h2><p>Complete inspection solutions</p></div>
|
|
<div class="related-grid">
|
|
$related
|
|
</div>
|
|
</div></section>
|
|
<section class="section"><div class="container">
|
|
<div class="cta"><h2>Start inspecting in $city_name today</h2><p>Get a custom quote. No minimum commitment.</p><a href="/pricing" class="btn">View pricing</a><a href="mailto:sales@quixzoom.com" class="btn-secondary">Contact sales</a></div>
|
|
</div></section>
|
|
<footer><div class="container"><div class="footer-inner"><div class="footer-links"><a href="/">Home</a><a href="/about">About</a><a href="/pricing">Pricing</a></div><div class="footer-links"><a href="/privacy">Privacy</a><a href="/terms">Terms</a></div><p style="color:var(--muted);font-size:0.875rem;">© 2026 quiXzoom. Operated by Landvex AB.</p></div></div></footer>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
}
|
|
|
|
# Generate all pages
|
|
count=0
|
|
for country in sweden germany france uk netherlands; do
|
|
flag=$(get_flag "$country")
|
|
compliance=$(get_compliance "$country")
|
|
|
|
# Get cities for this country
|
|
cities_var="${country^^}_CITIES"
|
|
cities="${!cities_var}"
|
|
|
|
for city_info in $cities; do
|
|
city_slug=$(echo "$city_info" | cut -d'|' -f1)
|
|
city_name=$(echo "$city_info" | cut -d'|' -f2 | tr '_' ' ')
|
|
|
|
for service in "${SERVICES[@]}"; do
|
|
svc_slug=$(echo "$service" | cut -d'|' -f1)
|
|
svc_name=$(echo "$service" | cut -d'|' -f2)
|
|
svc_desc=$(echo "$service" | cut -d'|' -f3)
|
|
|
|
echo "Generating: $svc_slug/$city_slug ($city_name, $country)..."
|
|
|
|
page=$(generate_page "$svc_slug" "$svc_name" "$svc_desc" "$city_slug" "$city_name" "$country" "$flag" "$compliance")
|
|
echo "$page" | aws s3 cp - "s3://quixzoom-landing-prod/$svc_slug/$city_slug.html" --content-type "text/html" --cache-control "max-age=3600"
|
|
|
|
((count++))
|
|
done
|
|
done
|
|
done
|
|
|
|
echo ""
|
|
echo "✅ Generated $count city-specific landing pages"
|
|
aws cloudfront create-invalidation --distribution-id EE30B9WM5ZYM7 --paths "/*" --query 'Invalidation.Id' --output text
|