Files
boc/update-landing-pages.sh
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- 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
2026-06-29 16:24:48 +00:00

448 lines
19 KiB
Bash
Executable File

#!/bin/bash
# Update all landing pages with cross-links and synergies
set -e
PAGES=(
"bridge-inspection-sweden:🇸🇪 Sweden:Trafikverket Compliant"
"bridge-inspection-germany:🇩🇪 Germany:DIN Compliant"
"bridge-inspection-france:🇫🇷 France:CEREMA Aligned"
"bridge-inspection-uk:🇬🇧 UK:DfT Compliant"
"bridge-inspection-netherlands:🇳🇱 Netherlands:Rijkswaterstaat Compliant"
"infrastructure-monitoring-sweden:🇸🇪 Sweden:National Coverage"
"infrastructure-monitoring-germany:🇩🇪 Germany:Bundesweit"
"infrastructure-monitoring-france:🇫🇷 France:National Coverage"
"infrastructure-monitoring-uk:🇬🇧 UK:National Coverage"
"infrastructure-monitoring-netherlands:🇳🇱 Netherlands:National Coverage"
)
# Function to generate related services section
generate_related() {
local current_page="$1"
local current_type="$2"
local current_country="$3"
local related=""
# Add the opposite service type for same country
if [[ "$current_type" == "bridge-inspection" ]]; then
related+=" <a href=\"/infrastructure-monitoring/$current_country\" class=\"related-card\">\n"
related+=" <div class=\"related-flag\">$(get_flag "$current_country")</div>\n"
related+=" <h4>Infrastructure Monitoring — $(get_country_name "$current_country")</h4>\n"
related+=" <p>Continuous monitoring of roads, bridges, and utilities beyond single inspections.</p>\n"
related+=" <div class=\"related-arrow\">Explore →</div>\n"
related+=" </a>\n"
else
related+=" <a href=\"/bridge-inspection/$current_country\" class=\"related-card\">\n"
related+=" <div class=\"related-flag\">$(get_flag "$current_country")</div>\n"
related+=" <h4>Bridge Inspection — $(get_country_name "$current_country")</h4>\n"
related+=" <p>Professional bridge inspections with regulatory compliance.</p>\n"
related+=" <div class=\"related-arrow\">Explore →</div>\n"
related+=" </a>\n"
fi
# Add other countries for same service type
for page in "${PAGES[@]}"; do
local page_name=$(echo "$page" | cut -d: -f1)
local page_type=$(echo "$page_name" | cut -d- -f1-2)
local page_country=$(echo "$page_name" | sed 's/.*-//')
if [[ "$page_type" == "$current_type" && "$page_country" != "$current_country" && "$page_name" != "$current_page" ]]; then
related+=" <a href=\"/$page_type/$page_country\" class=\"related-card\">\n"
related+=" <div class=\"related-flag\">$(get_flag "$page_country")</div>\n"
related+=" <h4>$(echo "$current_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1')$(get_country_name "$page_country")</h4>\n"
related+=" <p>$(get_compliance "$page")</p>\n"
related+=" <div class=\"related-arrow\">Explore →</div>\n"
related+=" </a>\n"
fi
done
# Add enterprise solutions
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 inspection programs with API integration and SLA guarantees.</p>\n"
related+=" <div class=\"related-arrow\">Learn more →</div>\n"
related+=" </a>\n"
echo "$related"
}
get_flag() {
case "$1" in
sweden) echo "🇸🇪" ;;
germany) echo "🇩🇪" ;;
france) echo "🇫🇷" ;;
uk) echo "🇬🇧" ;;
netherlands) echo "🇳🇱" ;;
*) echo "🌍" ;;
esac
}
get_country_name() {
case "$1" in
sweden) echo "Sweden" ;;
germany) echo "Germany" ;;
france) echo "France" ;;
uk) echo "UK" ;;
netherlands) echo "Netherlands" ;;
*) echo "$1" ;;
esac
}
get_compliance() {
echo "$1" | cut -d: -f3
}
# Update each page
for page in "${PAGES[@]}"; do
page_name=$(echo "$page" | cut -d: -f1)
page_flag=$(echo "$page" | cut -d: -f2 | cut -d' ' -f1)
page_country=$(echo "$page" | cut -d: -f2 | cut -d' ' -f2)
page_compliance=$(echo "$page" | cut -d: -f3)
page_type=$(echo "$page_name" | cut -d- -f1-2)
echo "Updating $page_name..."
# Download current page
aws s3 cp "s3://quixzoom-landing-prod/$page_name.html" "/tmp/$page_name.html" 2>/dev/null || continue
# Create updated version with cross-links
cat > "/tmp/${page_name}-new.html" << EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#f5f5f7">
<title>$(echo "$page_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1') in $(get_country_name "$page_country") — quiXzoom</title>
<meta name="description" content="Professional $(echo "$page_type" | sed 's/-/ /g') services across $(get_country_name "$page_country"). $(get_compliance "$page"). Fast deployment, verified data, digital reporting.">
<link rel="canonical" href="https://www.quixzoom.com/$page_type/$page_country">
<!-- hreflang for regional variants -->
<link rel="alternate" hreflang="en-se" href="https://www.quixzoom.com/$page_type/sweden">
<link rel="alternate" hreflang="en-de" href="https://www.quixzoom.com/$page_type/germany">
<link rel="alternate" hreflang="en-fr" href="https://www.quixzoom.com/$page_type/france">
<link rel="alternate" hreflang="en-gb" href="https://www.quixzoom.com/$page_type/uk">
<link rel="alternate" hreflang="en-nl" href="https://www.quixzoom.com/$page_type/netherlands">
<link rel="alternate" hreflang="x-default" href="https://www.quixzoom.com/$page_type">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Service",
"name": "$(echo "$page_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1') in $(get_country_name "$page_country")",
"provider": {
"@type": "Organization",
"name": "quiXzoom",
"url": "https://www.quixzoom.com/"
},
"areaServed": {
"@type": "Country",
"name": "$(get_country_name "$page_country")"
},
"description": "Professional $(echo "$page_type" | sed 's/-/ /g') services across $(get_country_name "$page_country"). $(get_compliance "$page"). Fast deployment, verified data, digital reporting."
}
</script>
<style>
:root {
--bg-dark: #f5f5f7;
--blue: #0066FF;
--blue-dark: #0052CC;
--blue-glow: rgba(0,102,255,0.10);
--text-dark: #1d1d1f;
--text-body: #3a3a3a;
--text-muted: #6e6e73;
--surface: #ffffff;
--surface-2: #f5f5f7;
--border: rgba(0,0,0,0.09);
--green: #00C853;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Helvetica Neue', sans-serif;
background: var(--bg-dark);
color: var(--text-dark);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
/* Nav */
nav {
position: sticky; top: 0; z-index: 300;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 64px; }
.nav-logo { font-size: 1.375rem; font-weight: 900; color: var(--text-dark); text-decoration: none; }
.nav-logo span { color: var(--blue); }
.nav-links { display: flex; gap: 32px; }
.nav-links a { color: var(--text-muted); text-decoration: none; font-weight: 500; transition: color 0.15s; }
.nav-links a:hover { color: var(--text-dark); }
.nav-links a.active { color: var(--blue); }
/* Hero */
.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; line-height: 1.1; margin-bottom: 20px; }
.hero p { font-size: 1.25rem; color: var(--text-body); max-width: 600px; margin: 0 auto; }
.hero-badge {
display: inline-flex; align-items: center; gap: 8px;
background: var(--blue-glow); 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;
}
/* Content sections */
.section { padding: 60px 0; }
.section-title { text-align: center; margin-bottom: 48px; }
.section-title h2 { font-size: 2rem; font-weight: 700; margin-bottom: 12px; }
.section-title p { font-size: 1.1rem; color: var(--text-muted); }
.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: transform 0.2s, box-shadow 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 { font-size: 0.95rem; color: var(--text-muted); line-height: 1.6; }
.card .card-link {
display: inline-flex; align-items: center; gap: 6px;
color: var(--blue); font-size: 0.875rem; font-weight: 600;
margin-top: 16px; text-decoration: none;
}
.card .card-link:hover { gap: 10px; }
/* Related services */
.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; color: var(--text-dark); }
.related-card p { font-size: 0.875rem; color: var(--text-muted); line-height: 1.5; }
.related-card .related-arrow {
margin-top: auto; padding-top: 16px;
color: var(--blue); font-size: 0.875rem; font-weight: 600;
display: flex; align-items: center; gap: 6px;
}
/* Country selector */
.country-bar {
background: var(--surface); border-bottom: 1px solid var(--border);
padding: 16px 0;
}
.country-bar-inner {
display: flex; align-items: center; gap: 8px;
overflow-x: auto; scrollbar-width: none;
}
.country-bar-inner::-webkit-scrollbar { display: none; }
.country-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 8px 16px; border-radius: 20px;
border: 1.5px solid var(--border); background: var(--surface);
color: var(--text-muted); font-size: 0.875rem; font-weight: 500;
text-decoration: none; white-space: nowrap; transition: all 0.15s;
}
.country-chip:hover { border-color: var(--blue); color: var(--blue); }
.country-chip.active { background: var(--blue); color: #fff; border-color: var(--blue); }
/* CTA */
.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(--text-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; transition: background 0.2s;
}
.btn:hover { background: var(--blue-dark); }
.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;
transition: all 0.2s;
}
.btn-secondary:hover { background: var(--blue); color: #fff; }
/* Breadcrumb */
.breadcrumb {
padding: 20px 0 0;
font-size: 0.875rem; color: var(--text-muted);
}
.breadcrumb a { color: var(--text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--blue); }
.breadcrumb span { color: var(--text-dark); font-weight: 500; }
/* Footer */
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(--text-muted); text-decoration: none; font-size: 0.875rem; }
.footer-links a:hover { color: var(--text-dark); }
@media (max-width: 768px) {
.nav-links { display: none; }
.cta { padding: 40px 24px; }
.btn-secondary { margin-left: 0; margin-top: 12px; }
.footer-inner { flex-direction: column; text-align: center; }
}
</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>
<a href="/how-it-works">How It Works</a>
</div>
</div>
</div>
</nav>
<div class="country-bar">
<div class="container">
<div class="country-bar-inner">
<span style="font-size:0.8125rem;font-weight:600;color:var(--text-muted);white-space:nowrap;margin-right:8px;">$(echo "$page_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1'):</span>
<a href="/$page_type/sweden" class="country-chip $(if [[ "$page_country" == "sweden" ]]; then echo "active"; fi)">🇸🇪 Sweden</a>
<a href="/$page_type/germany" class="country-chip $(if [[ "$page_country" == "germany" ]]; then echo "active"; fi)">🇩🇪 Germany</a>
<a href="/$page_type/france" class="country-chip $(if [[ "$page_country" == "france" ]]; then echo "active"; fi)">🇫🇷 France</a>
<a href="/$page_type/uk" class="country-chip $(if [[ "$page_country" == "uk" ]]; then echo "active"; fi)">🇬🇧 UK</a>
<a href="/$page_type/netherlands" class="country-chip $(if [[ "$page_country" == "netherlands" ]]; then echo "active"; fi)">🇳🇱 Netherlands</a>
</div>
</div>
</div>
<section class="hero">
<div class="container">
<div class="breadcrumb">
<a href="/">Home</a> → <a href="/$page_type">$(echo "$page_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1')</a> → <span>$(get_country_name "$page_country")</span>
</div>
<div class="hero-badge">$page_flag $(get_country_name "$page_country") · $page_compliance</div>
<h1>$(echo "$page_type" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1') in $(get_country_name "$page_country")</h1>
<p>Nationwide coverage. Deploy inspection missions in hours, not weeks.</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-title">
<h2>Why choose quiXzoom</h2>
<p>Trusted by infrastructure operators across Europe</p>
</div>
<div class="grid">
<div class="card">
<h3>Regulatory Compliant</h3>
<p>All inspections follow local technical requirements and documentation standards. Digital reports ready for submission.</p>
<a href="/features/compliance" class="card-link">Learn about compliance →</a>
</div>
<div class="card">
<h3>Rapid Deployment</h3>
<p>Launch inspection missions across multiple sites simultaneously. Zoomers can be on-site within 24 hours.</p>
<a href="/how-it-works" class="card-link">See how deployment works →</a>
</div>
<div class="card">
<h3>Verified Data</h3>
<p>AI-powered quality control ensures every photo, measurement, and observation meets professional standards.</p>
<a href="/features/quality" class="card-link">Explore quality assurance →</a>
</div>
</div>
</div>
</section>
<section class="section" style="background: var(--surface);">
<div class="container">
<div class="section-title">
<h2>Coverage across $(get_country_name "$page_country")</h2>
<p>Comprehensive national coverage</p>
</div>
<div class="grid">
<div class="card">
<h3>Urban Areas</h3>
<p>Major cities and metropolitan regions with detailed analysis and high-frequency monitoring.</p>
</div>
<div class="card">
<h3>Rural Infrastructure</h3>
<p>Regional roads and smaller crossings with accessibility-focused inspection protocols.</p>
</div>
<div class="card">
<h3>Specialized Assets</h3>
<p>Railway bridges, tunnels, and critical infrastructure with specialized assessment protocols.</p>
</div>
</div>
</div>
</section>
<!-- Related Services -->
<section class="section">
<div class="container">
<div class="section-title">
<h2>Related services</h2>
<p>Complete infrastructure intelligence solutions</p>
</div>
<div class="related-grid">
$(generate_related "$page_name" "$page_type" "$page_country")
</div>
</div>
</section>
<section class="section" style="background: var(--surface);">
<div class="container">
<div class="cta">
<h2>Start today</h2>
<p>Get a custom quote for your inspection programme. 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>
<a href="/features">Features</a>
<a href="/how-it-works">How It Works</a>
</div>
<div class="footer-links">
<a href="/privacy">Privacy</a>
<a href="/terms">Terms</a>
<a href="/security">Security</a>
</div>
<p style="color:var(--text-muted);font-size:0.875rem;">&copy; 2026 quiXzoom. Operated by Landvex AB.</p>
</div>
</div>
</footer>
</body>
</html>
EOF
# Upload updated page
aws s3 cp "/tmp/${page_name}-new.html" "s3://quixzoom-landing-prod/$page_name.html" --content-type "text/html" --cache-control "max-age=3600"
echo "✅ Updated $page_name"
done
echo ""
echo "All pages updated!"
echo "Invalidating CloudFront cache..."
aws cloudfront create-invalidation --distribution-id EE30B9WM5ZYM7 --paths "/*" --query 'Invalidation.Id' --output text