1a12fb870b
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
899 lines
42 KiB
HTML
899 lines
42 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Data Quality Standards — Landvex</title>
|
|
<meta name="description" content="How Landvex ensures data quality: verification chains, confidence scoring, contradiction detection, and full audit trails for enterprise clients.">
|
|
<link rel="canonical" href="https://www.landvex.com/data-quality/">
|
|
<meta name="robots" content="index, follow">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="https://www.landvex.com/data-quality/">
|
|
<meta property="og:title" content="Data Quality Standards — Landvex">
|
|
<meta property="og:description" content="How Landvex ensures data quality: verification chains, confidence scoring, contradiction detection, and full audit trails for enterprise clients.">
|
|
<meta property="og:image" content="https://www.landvex.com/og-image.jpg">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg-dark: #f5f5f7;
|
|
--blue: #0066FF;
|
|
--blue-dark: #0052CC;
|
|
--blue-glow: rgba(0, 102, 255, 0.15);
|
|
--text-dark: #0a0a0a;
|
|
--text-body: #3a3a3a;
|
|
--text-muted: #6B6B6B;
|
|
--text-light: #1d1d1f;
|
|
--text-dim: #6B6B6B;
|
|
--surface: #ffffff;
|
|
--surface-2: #f5f5f7;
|
|
--border: rgba(0,0,0,0.08);
|
|
--radius-sm: 8px;
|
|
--radius-md: 14px;
|
|
--radius-lg: 24px;
|
|
}
|
|
|
|
html { scroll-behavior: smooth; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Helvetica Neue', sans-serif;
|
|
background: var(--bg-dark);
|
|
color: var(--text-light);
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
a { color: inherit; text-decoration: none; }
|
|
|
|
nav {
|
|
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 40px; height: 64px;
|
|
background: rgba(255,255,255,0.88);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.nav-logo {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.3px;
|
|
color: #1d1d1f; }
|
|
|
|
.btn {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 10px 22px;
|
|
background: var(--blue); color: #1d1d1f;
|
|
font-size: 14px; font-weight: 600;
|
|
border-radius: var(--radius-md);
|
|
border: none; cursor: pointer;
|
|
transition: background 0.2s, transform 0.15s;
|
|
}
|
|
.btn:hover { background: var(--blue-dark); transform: translateY(-1px); }
|
|
.btn-outline {
|
|
background: transparent;
|
|
border: 1.5px solid rgba(0,0,0,0.18);
|
|
color: var(--text-light);
|
|
}
|
|
.btn-outline:hover { background: rgba(0,0,0,0.04); transform: translateY(-1px); }
|
|
.btn-lg { padding: 14px 30px; font-size: 16px; border-radius: var(--radius-md); }
|
|
|
|
.hero {
|
|
min-height: 80vh;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
text-align: center;
|
|
padding: 120px 24px 80px;
|
|
position: relative; overflow: hidden;
|
|
}
|
|
|
|
.hero::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -200px; left: 50%; transform: translateX(-50%);
|
|
width: 900px; height: 600px;
|
|
background: radial-gradient(ellipse at center, rgba(0,102,255,0.12) 0%, transparent 70%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hero-eyebrow {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
background: rgba(0,102,255,0.1);
|
|
border: 1px solid rgba(0,102,255,0.25);
|
|
color: #5599ff;
|
|
font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
padding: 6px 16px; border-radius: 100px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.hero h1 {
|
|
font-size: clamp(36px, 6vw, 68px);
|
|
font-weight: 800; letter-spacing: -2px; line-height: 1.05;
|
|
color: #1d1d1f; max-width: 820px; margin-bottom: 24px;
|
|
}
|
|
|
|
.hero-sub {
|
|
font-size: clamp(16px, 2vw, 19px);
|
|
color: var(--text-dim); max-width: 660px; line-height: 1.65;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.hero-ctas { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }
|
|
|
|
section { padding: 96px 24px; }
|
|
.container { max-width: 1140px; margin: 0 auto; }
|
|
|
|
.section-label {
|
|
font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
|
|
text-transform: uppercase; color: var(--blue); margin-bottom: 16px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: clamp(28px, 4vw, 44px);
|
|
font-weight: 800; letter-spacing: -1.2px; line-height: 1.1;
|
|
color: #1d1d1f; margin-bottom: 16px;
|
|
}
|
|
|
|
.section-sub {
|
|
font-size: 17px; color: var(--text-dim);
|
|
max-width: 600px; line-height: 1.7;
|
|
}
|
|
|
|
/* VERIFICATION CHAIN */
|
|
.chain-wrap { margin-top: 48px; }
|
|
|
|
.chain-step {
|
|
display: flex; align-items: flex-start; gap: 24px;
|
|
padding: 28px 0; position: relative;
|
|
}
|
|
|
|
.chain-step:not(:last-child)::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 19px; top: 68px;
|
|
width: 2px; bottom: 0;
|
|
background: var(--border);
|
|
}
|
|
|
|
.chain-num {
|
|
width: 40px; height: 40px; flex-shrink: 0;
|
|
background: rgba(0,102,255,0.12);
|
|
border: 1px solid rgba(0,102,255,0.3);
|
|
border-radius: var(--radius-full);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 14px; font-weight: 800; color: var(--blue);
|
|
position: relative; z-index: 1;
|
|
}
|
|
|
|
.chain-body h3 {
|
|
font-size: 17px; font-weight: 700; color: #1d1d1f;
|
|
margin-bottom: 6px;
|
|
}
|
|
.chain-body p {
|
|
font-size: 14px; color: var(--text-dim); line-height: 1.6;
|
|
}
|
|
|
|
/* METRICS GRID */
|
|
.metrics-grid {
|
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
.metric-card {
|
|
background: var(--surface-2); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 28px 24px;
|
|
text-align: center;
|
|
transition: border-color 0.25s;
|
|
}
|
|
.metric-card:hover { border-color: rgba(0,102,255,0.3); }
|
|
|
|
.metric-icon {
|
|
width: 36px; height: 36px; margin: 0 auto 14px;
|
|
color: var(--blue);
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 36px; font-weight: 800; color: #1d1d1f;
|
|
letter-spacing: -1.5px; margin-bottom: 4px;
|
|
}
|
|
.metric-target {
|
|
font-size: 12px; font-weight: 700; color: #4ade80;
|
|
letter-spacing: 0.05em; text-transform: uppercase;
|
|
margin-bottom: 8px;
|
|
}
|
|
.metric-label { font-size: 14px; color: var(--text-dim); line-height: 1.5; }
|
|
|
|
/* CONFIDENCE SCORING */
|
|
.confidence-wrap {
|
|
background: var(--surface-2); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 40px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
.confidence-header {
|
|
display: flex; align-items: center; gap: 16px; margin-bottom: 32px;
|
|
}
|
|
|
|
.confidence-score-display {
|
|
width: 80px; height: 80px; flex-shrink: 0;
|
|
background: rgba(0,102,255,0.1);
|
|
border: 2px solid rgba(0,102,255,0.3);
|
|
border-radius: var(--radius-full);
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
}
|
|
.confidence-score-display .score-num {
|
|
font-size: 22px; font-weight: 800; color: var(--blue); line-height: 1;
|
|
}
|
|
.confidence-score-display .score-label { font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
|
|
.confidence-header-text h3 { font-size: 18px; font-weight: 700; color: #1d1d1f; margin-bottom: 4px; }
|
|
.confidence-header-text p { font-size: 14px; color: var(--text-dim); }
|
|
|
|
.confidence-factors {
|
|
display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
|
|
}
|
|
|
|
.factor-row {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
background: var(--bg-dark); border: 1px solid var(--border);
|
|
border-radius: var(--radius-md); padding: 14px 20px;
|
|
}
|
|
|
|
.factor-name { font-size: 14px; color: var(--text-light); font-weight: 500; }
|
|
.factor-weight {
|
|
font-size: 13px; font-weight: 700; color: var(--blue);
|
|
background: rgba(0,102,255,0.12);
|
|
padding: 2px 10px; border-radius: 100px;
|
|
}
|
|
|
|
/* CONTRADICTION DETECTION */
|
|
.contradiction-detail-card {
|
|
background: rgba(0,0,0,.03);
|
|
border: 1px solid rgba(0,0,0,.08);
|
|
border-radius: var(--radius-lg); padding: 32px 36px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
.cd-threshold {
|
|
display: flex; align-items: center; gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.cd-sigma {
|
|
font-size: 48px; font-weight: 800; color: #f87171;
|
|
letter-spacing: -2px; line-height: 1;
|
|
}
|
|
|
|
.cd-desc h3 { font-size: 17px; font-weight: 700; color: #1d1d1f; margin-bottom: 4px; }
|
|
.cd-desc p { font-size: 14px; color: rgba(0,0,0,.6); }
|
|
|
|
.cd-flags {
|
|
display: flex; gap: 10px; flex-wrap: wrap;
|
|
}
|
|
|
|
.cd-flag {
|
|
display: flex; align-items: center; gap: 8px;
|
|
background: rgba(248,113,113,0.1);
|
|
border: 1px solid rgba(248,113,113,0.2);
|
|
border-radius: var(--radius-md); padding: 10px 16px;
|
|
}
|
|
|
|
.cd-flag-icon { width: 16px; height: 16px; color: #f87171; flex-shrink: 0; }
|
|
.cd-flag span { font-size: 13px; color: rgba(0,0,0,.8); font-weight: 500; }
|
|
|
|
/* DATA RETENTION */
|
|
.retention-grid {
|
|
display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
.retention-card {
|
|
background: var(--surface-2); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 28px;
|
|
transition: border-color 0.25s, transform 0.2s;
|
|
}
|
|
.retention-card:hover { border-color: rgba(0,102,255,0.3); transform: translateY(-2px); }
|
|
|
|
.retention-icon {
|
|
width: 32px; height: 32px; margin-bottom: 14px;
|
|
color: var(--blue);
|
|
}
|
|
|
|
.retention-card h3 { font-size: 16px; font-weight: 700; color: #1d1d1f; margin-bottom: 8px; }
|
|
.retention-card p { font-size: 14px; color: var(--text-dim); line-height: 1.6; }
|
|
|
|
/* ENTERPRISE */
|
|
.enterprise-box {
|
|
background: #e8f0fe; border-radius: var(--radius-xl); padding: 48px;
|
|
margin-top: 48px;
|
|
}
|
|
|
|
.enterprise-box h3 { font-size: 24px; font-weight: 800; color: #1d1d1f; margin-bottom: 12px; }
|
|
.enterprise-box p { font-size: 16px; color: rgba(0,0,0,.7); line-height: 1.7; margin-bottom: 24px; max-width: 600px; }
|
|
|
|
.enterprise-features { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 32px; }
|
|
|
|
.ent-feature {
|
|
display: flex; align-items: center; gap: 10px;
|
|
background: rgba(0,0,0,.05);
|
|
border: 1px solid rgba(0,0,0,.1);
|
|
border-radius: var(--radius-md); padding: 12px 18px;
|
|
}
|
|
|
|
.ent-feature-icon { width: 18px; height: 18px; color: #5599ff; flex-shrink: 0; }
|
|
.ent-feature span { font-size: 14px; color: rgba(0,0,0,.85); font-weight: 500; }
|
|
|
|
/* CONTACT */
|
|
.contact-wrapper { max-width: 680px; margin: 0 auto; text-align: center; }
|
|
.contact-wrapper .section-title { margin-bottom: 10px; }
|
|
.contact-wrapper .section-sub { margin: 0 auto 48px; }
|
|
|
|
.contact-form {
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg); padding: 40px; text-align: left;
|
|
}
|
|
|
|
.form-group { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
|
.form-group label { font-size: 13px; font-weight: 600; color: var(--text-dim); }
|
|
.form-group input, .form-group textarea {
|
|
background: var(--bg-dark); border: 1.5px solid var(--border);
|
|
border-radius: var(--radius-md); color: #1d1d1f;
|
|
font-size: 15px; font-family: inherit; padding: 12px 16px;
|
|
outline: none; transition: border-color 0.2s; width: 100%;
|
|
}
|
|
.form-group input:focus, .form-group textarea:focus { border-color: var(--blue); }
|
|
.form-group input::placeholder, .form-group textarea::placeholder { color: var(--text-muted); }
|
|
.form-group textarea { min-height: 120px; resize: vertical; }
|
|
.form-note { font-size: 12px; color: var(--text-muted); margin-top: 12px; text-align: center; }
|
|
|
|
#form-message {
|
|
display: none; padding: 14px 20px; border-radius: var(--radius-md);
|
|
font-size: 14px; font-weight: 600; text-align: center; margin-top: 16px;
|
|
}
|
|
#form-message.success { background: rgba(0,200,80,0.1); border: 1px solid rgba(0,200,80,0.25); color: #4ade80; }
|
|
#form-message.error { background: rgba(255,60,60,0.1); border: 1px solid rgba(255,60,60,0.25); color: #f87171; }
|
|
|
|
/* FOOTER */
|
|
footer { border-top: 1px solid var(--border); padding: 40px; }
|
|
.footer-inner {
|
|
max-width: 1140px; margin: 0 auto;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
flex-wrap: wrap; gap: 20px;
|
|
}
|
|
.footer-logo { font-size: 17px; font-weight: 700; color: #1d1d1f; }
|
|
.footer-copy { font-size: 13px; color: var(--text-muted); }
|
|
.footer-tagline { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
|
.footer-links { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
|
|
.footer-links a { font-size: 14px; color: var(--text-muted); transition: color 0.2s; }
|
|
.footer-links a:hover { color: #1d1d1f; }
|
|
.footer-social {
|
|
width: 32px; height: 32px; border: 1px solid var(--border); border-radius: var(--radius-md);
|
|
display: flex; align-items: center; justify-content: center;
|
|
transition: border-color 0.2s, background 0.2s;
|
|
}
|
|
.footer-social:hover { border-color: rgba(0,0,0,0.25); background: rgba(0,0,0,0.04); }
|
|
|
|
@media (max-width: 900px) {
|
|
nav { padding: 0 20px; }
|
|
.metrics-grid { grid-template-columns: 1fr 1fr; }
|
|
.confidence-factors { grid-template-columns: 1fr; }
|
|
.retention-grid { grid-template-columns: 1fr; }
|
|
}
|
|
@media (max-width: 600px) {
|
|
section { padding: 72px 20px; }
|
|
.metrics-grid { grid-template-columns: 1fr; }
|
|
.contact-form { padding: 24px 20px; }
|
|
nav { padding: 0 16px; }
|
|
.hero h1 { letter-spacing: -1px; }
|
|
.enterprise-box { padding: 28px 24px; }
|
|
.confidence-wrap { padding: 24px; }
|
|
}
|
|
</style>
|
|
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "BreadcrumbList",
|
|
"itemListElement": [
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 1,
|
|
"name": "Landvex",
|
|
"item": "https://www.landvex.com/"
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 2,
|
|
"name": "Data Quality",
|
|
"item": "https://www.landvex.com/data-quality/"
|
|
}
|
|
]
|
|
}
|
|
</script>
|
|
<link rel="alternate" hreflang="x-default" href="https://www.landvex.com/">
|
|
<link rel="alternate" hreflang="en" href="https://www.landvex.com/">
|
|
<link rel="alternate" hreflang="sv" href="https://landvex.se/">
|
|
<link rel="alternate" hreflang="de" href="https://landvex.de/">
|
|
<link rel="alternate" hreflang="fr" href="https://landvex.fr/">
|
|
<link rel="alternate" hreflang="en-GB" href="https://landvex.co.uk/">
|
|
<link rel="alternate" hreflang="it" href="https://landvex.it/">
|
|
<style>
|
|
@media (max-width: 640px) {
|
|
.hero h1 { font-size: 2rem !important; }
|
|
.hero p { font-size: 1rem !important; }
|
|
.card { padding: 1.5rem !important; }
|
|
.grid { grid-template-columns: 1fr !important; }
|
|
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav aria-label="breadcrumb" style="padding: 10px 20px; color: rgba(235,235,245,0.6); font-size: 14px;">
|
|
<a href="/" style="color: #007AFF; text-decoration: none;">Home</a> / <span>data-quality</span>
|
|
</nav>
|
|
|
|
<nav>
|
|
<a class="nav-logo" href="https://landvex.com/">← Back to Landvex</a>
|
|
<a class="btn btn-outline" href="#contact">Get in touch →</a>
|
|
</nav>
|
|
|
|
<!-- HERO -->
|
|
<section class="hero">
|
|
<div class="hero-eyebrow">Landvex — Data Quality</div>
|
|
<h1>How we ensure data quality.</h1>
|
|
<p class="hero-sub">Every observation in Landvex passes through a multi-stage verification chain before it influences a score. Confidence is earned, not assumed.</p>
|
|
<div class="hero-ctas">
|
|
<a class="btn btn-lg" href="#verification">See the process →</a>
|
|
<a class="btn btn-outline btn-lg" href="#enterprise">Enterprise API</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- VERIFICATION CHAIN -->
|
|
<section id="verification" style="background:var(--surface)">
|
|
<div class="container">
|
|
<div class="section-label">Verification chain</div>
|
|
<h2 class="section-title">Five stages from observation to score</h2>
|
|
<p class="section-sub">Every data point undergoes sequential verification. A failure at any stage removes the observation from the scoring pool.</p>
|
|
<div class="chain-wrap">
|
|
<div class="chain-step">
|
|
<div class="chain-num">1</div>
|
|
<div class="chain-body">
|
|
<h3>Contributor identity verification</h3>
|
|
<p>Each contributor is identity-verified before any observations are accepted. Device binding, location history, and activity patterns are monitored continuously for anomalies.</p>
|
|
</div>
|
|
</div>
|
|
<div class="chain-step">
|
|
<div class="chain-num">2</div>
|
|
<div class="chain-body">
|
|
<h3>GPS validation</h3>
|
|
<p>GPS coordinates are validated against declared location, movement patterns, and historical data. Spoofed, interpolated, or implausible locations are automatically rejected.</p>
|
|
</div>
|
|
</div>
|
|
<div class="chain-step">
|
|
<div class="chain-num">3</div>
|
|
<div class="chain-body">
|
|
<h3>AI image review</h3>
|
|
<p>Computer vision models review submitted images for specification compliance, content authenticity, and data integrity. Manipulated or non-compliant images are flagged and excluded.</p>
|
|
</div>
|
|
</div>
|
|
<div class="chain-step">
|
|
<div class="chain-num">4</div>
|
|
<div class="chain-body">
|
|
<h3>Specification compliance</h3>
|
|
<p>Observations are checked against task specifications: correct asset type, required angles, mandatory data fields, and temporal constraints. Incomplete submissions are rejected, not approximated.</p>
|
|
</div>
|
|
</div>
|
|
<div class="chain-step">
|
|
<div class="chain-num">5</div>
|
|
<div class="chain-body">
|
|
<h3>Human spot-check (borderline cases)</h3>
|
|
<p>Edge cases, borderline confidence scores, and flagged observations are reviewed by trained human analysts before entering the scoring pipeline. Automated decisions are not final.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- QUALITY METRICS -->
|
|
<section id="metrics" style="background: var(--bg-dark)">
|
|
<div class="container">
|
|
<div class="section-label">Quality metrics</div>
|
|
<h2 class="section-title">Targets we hold ourselves to</h2>
|
|
<p class="section-sub">Published targets create accountability. These are the standards Landvex commits to across every data collection mission.</p>
|
|
<div class="metrics-grid">
|
|
<div class="metric-card" style="cursor:pointer;" onclick="location.href='#confidence'" role="button" tabindex="0">
|
|
<svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="20 6 9 17 4 12"/>
|
|
</svg>
|
|
<div class="metric-value">>85%</div>
|
|
<div class="metric-target">Approval rate target</div>
|
|
<div class="metric-label">Observations that pass all verification stages. Below-threshold batches trigger quality review.</div>
|
|
</div>
|
|
<div class="metric-card" style="cursor:pointer;" onclick="location.href='#confidence'" role="button" tabindex="0">
|
|
<svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
|
<circle cx="12" cy="10" r="3"/>
|
|
</svg>
|
|
<div class="metric-value"><10m</div>
|
|
<div class="metric-target">Geo-accuracy</div>
|
|
<div class="metric-label">GPS accuracy tolerance. Observations outside this threshold are flagged for manual review.</div>
|
|
</div>
|
|
<div class="metric-card" style="cursor:pointer;" onclick="location.href='#confidence'" role="button" tabindex="0">
|
|
<svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<polyline points="12 6 12 12 16 14"/>
|
|
</svg>
|
|
<div class="metric-value"><90 days</div>
|
|
<div class="metric-target">Temporal freshness</div>
|
|
<div class="metric-label">Maximum observation age per data point before requiring refresh. Older data is down-weighted in scoring.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- CONFIDENCE SCORING -->
|
|
<section id="confidence" style="background:var(--surface)">
|
|
<div class="container">
|
|
<div class="section-label">Confidence scoring</div>
|
|
<h2 class="section-title">Every data point has a confidence score</h2>
|
|
<p class="section-sub">Landvex attaches a 0–100 confidence score to every data point. Scores are transparent, composable, and auditable.</p>
|
|
|
|
<div class="confidence-wrap">
|
|
<div class="confidence-header">
|
|
<div class="confidence-score-display">
|
|
<span class="score-num">87</span>
|
|
<span class="score-label">confidence</span>
|
|
</div>
|
|
<div class="confidence-header-text">
|
|
<h3>Confidence score: 87/100</h3>
|
|
<p>Example: Norrmalm commercial vacancy observation, June 2026. Scored across four independent factors.</p>
|
|
</div>
|
|
</div>
|
|
<div class="confidence-factors">
|
|
<div class="factor-row">
|
|
<span class="factor-name">Recency of observation</span>
|
|
<span class="factor-weight">32 pts</span>
|
|
</div>
|
|
<div class="factor-row">
|
|
<span class="factor-name">Source count</span>
|
|
<span class="factor-weight">28 pts</span>
|
|
</div>
|
|
<div class="factor-row">
|
|
<span class="factor-name">Cross-validation against other sources</span>
|
|
<span class="factor-weight">15 pts</span>
|
|
</div>
|
|
<div class="factor-row">
|
|
<span class="factor-name">Reviewer consensus</span>
|
|
<span class="factor-weight">12 pts</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- CONTRADICTION DETECTION -->
|
|
<section id="contradiction" style="background:#001836">
|
|
<div class="container">
|
|
<div class="section-label" style="color:rgba(255,255,255,.75)">Contradiction detection</div>
|
|
<h2 class="section-title" style="color: #ffffff">Flagging what doesn’t add up</h2>
|
|
<p class="section-sub" style="color:rgba(255,255,255,.6)">When observed data conflicts with official sources by more than 2 standard deviations, the observation is automatically flagged for review rather than silently averaged away.</p>
|
|
|
|
<div class="contradiction-detail-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
|
|
<div class="cd-threshold">
|
|
<div class="cd-sigma">>2σ</div>
|
|
<div class="cd-desc">
|
|
<h3>Automatic contradiction flag threshold</h3>
|
|
<p>Any observation diverging from official sources by more than 2 standard deviations triggers a structured review process before inclusion in scores.</p>
|
|
</div>
|
|
</div>
|
|
<div class="cd-flags">
|
|
<div class="cd-flag">
|
|
<svg class="cd-flag-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
|
|
<line x1="4" y1="22" x2="4" y2="15"/>
|
|
</svg>
|
|
<span>Flagged for human review</span>
|
|
</div>
|
|
<div class="cd-flag">
|
|
<svg class="cd-flag-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
</svg>
|
|
<span>Source conflict logged</span>
|
|
</div>
|
|
<div class="cd-flag">
|
|
<svg class="cd-flag-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
<polyline points="14 2 14 8 20 8"/>
|
|
</svg>
|
|
<span>Contradiction report generated</span>
|
|
</div>
|
|
<div class="cd-flag">
|
|
<svg class="cd-flag-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
</svg>
|
|
<span>Included in client delivery</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- DATA RETENTION -->
|
|
<section id="retention" style="background:var(--surface)">
|
|
<div class="container">
|
|
<div class="section-label">Data retention</div>
|
|
<h2 class="section-title">Full provenance, every observation</h2>
|
|
<p class="section-sub">Landvex stores observations with a complete provenance chain. Nothing is anonymised away if it’s needed for audit.</p>
|
|
<div class="retention-grid">
|
|
<div class="retention-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
|
|
<svg class="retention-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
|
<circle cx="12" cy="7" r="4"/>
|
|
</svg>
|
|
<h3>Contributor ID hash</h3>
|
|
<p>Each observation is linked to a hashed contributor identity. Allows quality tracking without exposing personal data in standard outputs.</p>
|
|
</div>
|
|
<div class="retention-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
|
|
<svg class="retention-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<polyline points="12 6 12 12 16 14"/>
|
|
</svg>
|
|
<h3>Timestamp</h3>
|
|
<p>Precise capture timestamp stored with every observation. Temporal accuracy is critical for decay weighting and freshness scoring.</p>
|
|
</div>
|
|
<div class="retention-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
|
|
<svg class="retention-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
|
|
<line x1="12" y1="18" x2="12.01" y2="18"/>
|
|
</svg>
|
|
<h3>Device metadata</h3>
|
|
<p>Device model, OS version, and sensor data retained for quality auditing and anomaly detection. Used internally; not surfaced in standard reports.</p>
|
|
</div>
|
|
<div class="retention-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
|
|
<svg class="retention-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
|
<circle cx="12" cy="10" r="3"/>
|
|
</svg>
|
|
<h3>GPS trace</h3>
|
|
<p>Full GPS trace stored per observation session. Enables movement-pattern validation and retroactive quality review if anomalies are detected.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ENTERPRISE -->
|
|
<section id="enterprise" style="background: var(--bg-dark)">
|
|
<div class="container">
|
|
<div class="section-label">Enterprise</div>
|
|
<h2 class="section-title">Deeper access for enterprise clients</h2>
|
|
<p class="section-sub">Standard Landvex output includes final scores and contradiction flags. Enterprise clients can go deeper.</p>
|
|
<div class="enterprise-box">
|
|
<h3>Full audit trail on request</h3>
|
|
<p>Enterprise clients can access raw confidence scores via API, review individual observation-level provenance, and receive structured audit trails for regulatory or investment committee use.</p>
|
|
<div class="enterprise-features">
|
|
<div class="ent-feature">
|
|
<svg class="ent-feature-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="16 18 22 12 16 6"/>
|
|
<polyline points="8 6 2 12 8 18"/>
|
|
</svg>
|
|
<span>Raw confidence scores via API</span>
|
|
</div>
|
|
<div class="ent-feature">
|
|
<svg class="ent-feature-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
<polyline points="14 2 14 8 20 8"/>
|
|
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
<polyline points="10 9 9 9 8 9"/>
|
|
</svg>
|
|
<span>Full audit trail on request</span>
|
|
</div>
|
|
<div class="ent-feature">
|
|
<svg class="ent-feature-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
|
</svg>
|
|
<span>Observation-level provenance</span>
|
|
</div>
|
|
<div class="ent-feature">
|
|
<svg class="ent-feature-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
</svg>
|
|
<span>Regulatory-grade documentation</span>
|
|
</div>
|
|
</div>
|
|
<a class="btn btn-lg" href="#contact">Discuss enterprise access →</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- CONTACT -->
|
|
<section id="contact">
|
|
<div class="container">
|
|
<div class="contact-wrapper">
|
|
<div class="section-label">Get in touch</div>
|
|
<h2 class="section-title">Questions about data quality?</h2>
|
|
<p class="section-sub">Whether you need technical documentation, API access, or audit trails — get in touch and we’ll respond within one business day.</p>
|
|
|
|
<form class="contact-form" id="contact-form">
|
|
<div class="form-group">
|
|
<label for="name">Name</label>
|
|
<input type="text" id="name" name="name" placeholder="Your name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="email">Email</label>
|
|
<input type="email" id="email" name="email" placeholder="you@organisation.com" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="message">Message</label>
|
|
<textarea id="message" name="message" placeholder="Describe what you need — API access, audit documentation, methodology detail..."></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-lg" style="width:100%; justify-content: center;">
|
|
Send message →
|
|
</button>
|
|
<p class="form-note">We don’t spam. Your information stays private.</p>
|
|
<div id="form-message"></div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
<div class="footer-container" style="max-width:1200px;margin:0 auto;padding:48px 24px 32px;">
|
|
<!-- Main Footer Grid -->
|
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:40px;margin-bottom:48px;">
|
|
|
|
<!-- Column 1: Products -->
|
|
<div>
|
|
<h4 style="font-size:13px;font-weight:600;color:#0A0A0A;margin-bottom:16px;text-transform:uppercase;letter-spacing:0.5px;">Produkter</h4>
|
|
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px;">
|
|
<li><a href="/infrastructure-monitoring/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">VIMS</a></li>
|
|
<li><a href="/intelligence/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Intelligence</a></li>
|
|
<li><a href="/reality-alerts/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Reality Alerts</a></li>
|
|
<li><a href="/enterprise/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Enterprise</a></li>
|
|
<li><a href="/pilot/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Pilotprogram</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Column 2: Solutions -->
|
|
<div>
|
|
<h4 style="font-size:13px;font-weight:600;color:#0A0A0A;margin-bottom:16px;text-transform:uppercase;letter-spacing:0.5px;">Lösningar</h4>
|
|
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px;">
|
|
<li><a href="/verticals/bridge-inspection/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Broinspektion</a></li>
|
|
<li><a href="/verticals/road-monitoring/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Vägövervakning</a></li>
|
|
<li><a href="/verticals/infrastructure/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Infrastruktur</a></li>
|
|
<li><a href="/verticals/utilities/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Utilities</a></li>
|
|
<li><a href="/use-cases/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Användningsfall</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Column 3: Resources -->
|
|
<div>
|
|
<h4 style="font-size:13px;font-weight:600;color:#0A0A0A;margin-bottom:16px;text-transform:uppercase;letter-spacing:0.5px;">Resurser</h4>
|
|
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px;">
|
|
<li><a href="/blog/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Intelligence Insights</a></li>
|
|
<li><a href="/guides/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Guider</a></li>
|
|
<li><a href="/developers/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Developer API</a></li>
|
|
<li><a href="/status/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Systemstatus</a></li>
|
|
<li><a href="/reports/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Rapporter</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Column 4: Company -->
|
|
<div>
|
|
<h4 style="font-size:13px;font-weight:600;color:#0A0A0A;margin-bottom:16px;text-transform:uppercase;letter-spacing:0.5px;">Företag</h4>
|
|
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px;">
|
|
<li><a href="/about/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Om oss</a></li>
|
|
<li><a href="/careers/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Karriär</a></li>
|
|
<li><a href="/press/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Press</a></li>
|
|
<li><a href="/contact/" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">Kontakt</a></li>
|
|
<li><a href="https://www.quixzoom.com" target="_blank" rel="noopener" style="font-size:13px;color:#6E6E73;text-decoration:none;transition:color 0.2s;">quiXzoom →</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div style="border-top:1px solid #E5E5E5;padding-top:32px;">
|
|
|
|
<!-- Legal & Compliance Row -->
|
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:24px;margin-bottom:24px;">
|
|
<div style="display:flex;flex-wrap:wrap;gap:16px 24px;">
|
|
<a href="/privacy/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Privacy</a>
|
|
<a href="/terms/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Terms</a>
|
|
<a href="/cookie-policy/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Cookies</a>
|
|
<a href="/compliance/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Compliance</a>
|
|
<a href="/trust/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Trust Centre</a>
|
|
<a href="/dpa/" style="font-size:12px;color:#6E6E73;text-decoration:none;">DPA</a>
|
|
<a href="/sla/" style="font-size:12px;color:#6E6E73;text-decoration:none;">SLA</a>
|
|
<a href="/security/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Security</a>
|
|
<a href="/accessibility/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Accessibility</a>
|
|
<a href="/responsible-disclosure/" style="font-size:12px;color:#6E6E73;text-decoration:none;">Responsible Disclosure</a>
|
|
</div>
|
|
<div style="display:flex;gap:16px;">
|
|
<a href="https://www.linkedin.com/company/landvex" target="_blank" rel="noopener" style="color:#6E6E73;" title="LinkedIn">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg>
|
|
</a>
|
|
<a href="https://x.com/landvex" target="_blank" rel="noopener" style="color:#6E6E73;" title="X">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.253 5.622 5.912-5.622Zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Company Info Row -->
|
|
<div style="display:flex;justify-content:space-between;align-items:flex-end;flex-wrap:wrap;gap:16px;">
|
|
<div>
|
|
<div style="font-size:12px;color:#86868B;line-height:1.6;">
|
|
<strong style="color:#0A0A0A;">Landvex Inc.</strong> · Houston, Texas · USA<br>
|
|
<strong style="color:#0A0A0A;">Landvex AB</strong> · Org.nr 559141-7042 · Stockholm, Sweden · EU<br>
|
|
<a href="mailto:contact@landvex.com" style="color:#86868B;text-decoration:none;">contact@landvex.com</a> ·
|
|
<a href="mailto:security@landvex.com" style="color:#86868B;text-decoration:none;">security@landvex.com</a>
|
|
</div>
|
|
<div style="font-size:11px;color:#86868B;margin-top:8px;line-height:1.5;">
|
|
Field data powered by <a href="https://www.quixzoom.com" style="color:#86868B;text-decoration:underline;">quiXzoom</a> ·
|
|
<a href="/data-lineage/" style="color:#86868B;text-decoration:underline;">Data Lineage</a> ·
|
|
<a href="/subprocessors/" style="color:#86868B;text-decoration:underline;">Sub-processors</a> ·
|
|
<a href="/methodology/" style="color:#86868B;text-decoration:underline;">Methodology</a>
|
|
</div>
|
|
</div>
|
|
<div style="font-size:12px;color:#86868B;">
|
|
© 2026 Landvex Inc / Landvex AB. All rights reserved.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
document.getElementById('contact-form').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const btn = this.querySelector('button[type="submit"]');
|
|
const msgEl = document.getElementById('form-message');
|
|
btn.disabled = true; btn.textContent = 'Sending...';
|
|
|
|
const data = {
|
|
source: 'landvex-data-quality',
|
|
name: this.name.value,
|
|
email: this.email.value,
|
|
message: this.message.value
|
|
};
|
|
|
|
try {
|
|
const res = await fetch('https://api.quixzoom.com/api/qz/waitlist', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
if (res.ok || res.status === 201) {
|
|
msgEl.className = 'success';
|
|
msgEl.textContent = "\u2713 Message received. We\u2019ll be in touch within one business day.";
|
|
msgEl.style.display = 'block';
|
|
this.reset();
|
|
} else { throw new Error('Server error ' + res.status); }
|
|
} catch (err) {
|
|
msgEl.className = 'error';
|
|
msgEl.textContent = 'Something went wrong. Please email us directly at contact@landvex.com.';
|
|
msgEl.style.display = 'block';
|
|
}
|
|
|
|
btn.disabled = false; btn.textContent = 'Send message \u2192';
|
|
});
|
|
|
|
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
|
a.addEventListener('click', e => {
|
|
const target = document.querySelector(a.getAttribute('href'));
|
|
if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|