6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
1304 lines
41 KiB
HTML
1304 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="sv">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<meta name="color-scheme" content="light dark">
|
||
<title>LandveX Admin — Infrastrukturportal</title>
|
||
<link rel="stylesheet" >
|
||
<link rel="stylesheet" href="/assets/css/landvex-complete.css">
|
||
<style>
|
||
/*
|
||
LANDVEX ADMIN PORTAL — iOS Native
|
||
Wavult Group · 2026
|
||
*/
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-font-smoothing: antialiased; }
|
||
|
||
body {
|
||
font-family: var(--font-text);
|
||
background: var(--background-secondary);
|
||
color: var(--label-primary);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* NAVIGATION BAR */
|
||
.nav-bar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
height: 44px;
|
||
background: var(--glass-bg);
|
||
backdrop-filter: var(--glass-blur);
|
||
-webkit-backdrop-filter: var(--glass-blur);
|
||
border-bottom: 0.5px solid var(--separator);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 16px;
|
||
safe-area-inset-top: env(safe-area-inset-top);
|
||
}
|
||
|
||
.nav-title {
|
||
font-family: var(--font-display);
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
letter-spacing: -0.4px;
|
||
color: var(--label-primary);
|
||
}
|
||
|
||
.nav-logo {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.nav-logo .logo-icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
background: linear-gradient(135deg, #007AFF 0%, #0055CC 100%);
|
||
border-radius: var(--radius-sm);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 14px;
|
||
color: white;
|
||
font-weight: 700;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
|
||
.badge-admin {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
padding: 24px;
|
||
background: rgba(255, 149, 0, 0.15);
|
||
color: var(--ios-orange);
|
||
border-radius: var(--radius-xl);
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
/* CONTENT */
|
||
.content {
|
||
padding: 0 16px 120px;
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* SECTION HEADER */
|
||
.section-header {
|
||
padding: 20px 0 8px;
|
||
font-family: var(--font-display);
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.4px;
|
||
color: var(--label-primary);
|
||
}
|
||
|
||
.section-header-large {
|
||
padding: 28px 0 4px;
|
||
font-family: var(--font-display);
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
|
||
.section-sub {
|
||
font-size: 15px;
|
||
color: var(--label-secondary);
|
||
padding-bottom: 12px;
|
||
}
|
||
|
||
/* METRICS GRID */
|
||
.metrics-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.metric-card {
|
||
background: var(--background-primary);
|
||
border-radius: var(--radius-lg);
|
||
padding: 16px;
|
||
border: 0.5px solid var(--separator);
|
||
}
|
||
|
||
.metric-value {
|
||
font-family: var(--font-display);
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
letter-spacing: -1px;
|
||
line-height: 1;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.metric-label {
|
||
font-size: 13px;
|
||
color: var(--label-secondary);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.metric-trend {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.trend-up { color: var(--ios-green); }
|
||
.trend-down { color: var(--ios-red); }
|
||
.trend-flat { color: var(--label-tertiary); }
|
||
|
||
.metric-blue .metric-value { color: var(--ios-blue); }
|
||
.metric-green .metric-value { color: var(--ios-green); }
|
||
.metric-orange .metric-value { color: var(--ios-orange); }
|
||
.metric-red .metric-value { color: var(--ios-red); }
|
||
|
||
/* TABLE */
|
||
.table-card {
|
||
background: var(--background-primary);
|
||
border-radius: var(--radius-lg);
|
||
border: 0.5px solid var(--separator);
|
||
overflow: hidden;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.table-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
|
||
.table-title {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
letter-spacing: -0.3px;
|
||
}
|
||
|
||
.table-action {
|
||
font-size: 15px;
|
||
color: var(--ios-blue);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.table-scroll {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
|
||
thead th {
|
||
text-align: left;
|
||
padding: 24px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--label-secondary);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
background: var(--background-secondary);
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
|
||
tbody tr {
|
||
border-bottom: 0.5px solid var(--separator);
|
||
transition: background 0.15s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
tbody tr:last-child { border-bottom: none; }
|
||
|
||
tbody tr:hover { background: var(--fill-quaternary); }
|
||
|
||
tbody td {
|
||
padding: 24px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.td-name {
|
||
font-weight: 600;
|
||
letter-spacing: -0.2px;
|
||
}
|
||
|
||
.td-sub {
|
||
font-size: 12px;
|
||
color: var(--label-secondary);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* STATUS BADGES */
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
padding: 24px;
|
||
border-radius: var(--radius-xl);
|
||
}
|
||
|
||
.status-ok {
|
||
background: rgba(52, 199, 89, 0.15);
|
||
color: var(--ios-green);
|
||
}
|
||
|
||
.status-warn {
|
||
background: rgba(255, 149, 0, 0.15);
|
||
color: var(--ios-orange);
|
||
}
|
||
|
||
.status-critical {
|
||
background: rgba(255, 59, 48, 0.15);
|
||
color: var(--ios-red);
|
||
}
|
||
|
||
.status-pending {
|
||
background: rgba(142, 142, 147, 0.15);
|
||
color: var(--ios-gray);
|
||
}
|
||
|
||
.status-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: var(--radius-full);
|
||
background: currentColor;
|
||
}
|
||
|
||
/* RISK SCORE */
|
||
.risk-bar-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.risk-bar {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: var(--fill-tertiary);
|
||
border-radius: var(--radius-sm);
|
||
overflow: hidden;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.risk-fill {
|
||
height: 100%;
|
||
border-radius: var(--radius-sm);
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
.risk-low .risk-fill { background: var(--ios-green); }
|
||
.risk-medium .risk-fill { background: var(--ios-orange); }
|
||
.risk-high .risk-fill { background: var(--ios-red); }
|
||
|
||
.risk-label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
min-width: 28px;
|
||
text-align: right;
|
||
}
|
||
|
||
/* MAP PLACEHOLDER */
|
||
.map-card {
|
||
background: var(--background-primary);
|
||
border-radius: var(--radius-lg);
|
||
border: 0.5px solid var(--separator);
|
||
overflow: hidden;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.map-container {
|
||
height: 320px;
|
||
background: #E8EDF2;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
.map-container { background: #1C2328; }
|
||
}
|
||
|
||
.map-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
color: var(--label-secondary);
|
||
}
|
||
|
||
.map-grid {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.3;
|
||
background-image:
|
||
linear-gradient(rgba(0,122,255,0.3) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(0,122,255,0.3) 1px, transparent 1px);
|
||
background-size: 40px 40px;
|
||
}
|
||
|
||
.map-pin {
|
||
position: absolute;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: var(--radius-full) 50% 50% 0;
|
||
transform: rotate(-45deg);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
.map-pin:hover { transform: rotate(-45deg) scale(1.2); }
|
||
.map-pin-ok { background: var(--ios-green); }
|
||
.map-pin-warn { background: var(--ios-orange); }
|
||
.map-pin-critical { background: var(--ios-red); }
|
||
|
||
.map-pin-inner {
|
||
transform: rotate(45deg);
|
||
font-size: 11px;
|
||
color: white;
|
||
}
|
||
|
||
.map-legend {
|
||
position: absolute;
|
||
bottom: 12px;
|
||
right: 12px;
|
||
background: var(--glass-bg);
|
||
backdrop-filter: var(--glass-blur);
|
||
-webkit-backdrop-filter: var(--glass-blur);
|
||
border-radius: var(--radius-md);
|
||
padding: 24px;
|
||
border: 0.5px solid var(--separator);
|
||
display: flex;
|
||
gap: 16px;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
font-size: 11px;
|
||
color: var(--label-secondary);
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: var(--radius-full);
|
||
}
|
||
|
||
/* FAB BUTTON */
|
||
.fab {
|
||
position: fixed;
|
||
bottom: calc(24px + env(safe-area-inset-bottom));
|
||
right: 20px;
|
||
z-index: 200;
|
||
background: var(--ios-blue);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 28px;
|
||
height: 56px;
|
||
padding: 0 20px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
font-family: var(--font-text);
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
box-shadow: 0 4px 20px rgba(0, 122, 255, 0.4);
|
||
transition: transform 0.15s, box-shadow 0.15s;
|
||
}
|
||
|
||
.fab:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 28px rgba(0, 122, 255, 0.5);
|
||
}
|
||
|
||
.fab:active {
|
||
transform: translateY(0) scale(0.97);
|
||
box-shadow: 0 2px 12px rgba(0, 122, 255, 0.35);
|
||
}
|
||
|
||
/* INSPECTION CELLS */
|
||
.inspection-list {
|
||
background: var(--background-primary);
|
||
border-radius: var(--radius-lg);
|
||
border: 0.5px solid var(--separator);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.inspection-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 24px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
gap: 16px;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.inspection-item:last-child { border-bottom: none; }
|
||
.inspection-item:hover { background: var(--fill-quaternary); }
|
||
|
||
.inspection-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: var(--radius-md);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.inspection-body { flex: 1; min-width: 0; }
|
||
|
||
.inspection-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
letter-spacing: -0.2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.inspection-meta {
|
||
font-size: 13px;
|
||
color: var(--label-secondary);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.inspection-chevron {
|
||
font-size: 17px;
|
||
color: var(--label-tertiary);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* MODAL */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(242,242,247,0.4);
|
||
backdrop-filter: blur(4px);
|
||
z-index: 300;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.25s;
|
||
}
|
||
|
||
.modal-overlay.open {
|
||
opacity: 1;
|
||
pointer-events: all;
|
||
}
|
||
|
||
.modal-sheet {
|
||
background: var(--background-primary);
|
||
border-radius: var(--radius-xl) 20px 0 0;
|
||
width: 100%;
|
||
max-width: 640px;
|
||
padding: 8px 0 env(safe-area-inset-bottom);
|
||
transform: translateY(100%);
|
||
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
||
}
|
||
|
||
.modal-overlay.open .modal-sheet {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.modal-handle {
|
||
width: 36px;
|
||
height: 4px;
|
||
background: var(--fill-primary);
|
||
border-radius: var(--radius-sm);
|
||
margin: 8px auto 16px;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.4px;
|
||
padding: 0 20px 16px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
|
||
.form-group {
|
||
padding: 24px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--label-secondary);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 24px;
|
||
background: var(--background-secondary);
|
||
border: 0.5px solid var(--separator);
|
||
border-radius: var(--radius-md);
|
||
font-size: 15px;
|
||
font-family: var(--font-text);
|
||
color: var(--label-primary);
|
||
outline: none;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.form-input:focus {
|
||
border-color: var(--ios-blue);
|
||
}
|
||
|
||
.form-select {
|
||
width: 100%;
|
||
padding: 24px;
|
||
background: var(--background-secondary);
|
||
border: 0.5px solid var(--separator);
|
||
border-radius: var(--radius-md);
|
||
font-size: 15px;
|
||
font-family: var(--font-text);
|
||
color: var(--label-primary);
|
||
outline: none;
|
||
appearance: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.modal-actions {
|
||
padding: 24px;
|
||
display: flex;
|
||
gap: 16px;
|
||
}
|
||
|
||
.btn-primary {
|
||
flex: 1;
|
||
height: 50px;
|
||
background: var(--ios-blue);
|
||
color: white;
|
||
border: none;
|
||
border-radius: var(--radius-lg);
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
font-family: var(--font-text);
|
||
cursor: pointer;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.btn-primary:active { opacity: 0.7; }
|
||
|
||
.btn-secondary {
|
||
flex: 1;
|
||
height: 50px;
|
||
background: var(--fill-secondary);
|
||
color: var(--label-primary);
|
||
border: none;
|
||
border-radius: var(--radius-lg);
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
font-family: var(--font-text);
|
||
cursor: pointer;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.btn-secondary:active { opacity: 0.7; }
|
||
|
||
/* TAB BAR */
|
||
.tab-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: calc(49px + env(safe-area-inset-bottom));
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
background: var(--glass-bg);
|
||
backdrop-filter: var(--glass-blur);
|
||
-webkit-backdrop-filter: var(--glass-blur);
|
||
border-top: 0.5px solid var(--separator);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-around;
|
||
padding-top: 8px;
|
||
z-index: 90;
|
||
}
|
||
|
||
.tab-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16px;
|
||
cursor: pointer;
|
||
flex: 1;
|
||
padding: 0 8px;
|
||
opacity: 0.6;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.tab-item.active { opacity: 1; }
|
||
|
||
.tab-icon { font-size: 22px; }
|
||
.tab-label { font-size: 10px; font-weight: 500; }
|
||
.tab-item.active .tab-label { color: var(--ios-blue); }
|
||
.tab-item.active .tab-icon { filter: none; }
|
||
|
||
/* DARK MODE */
|
||
@media (prefers-color-scheme: dark) {
|
||
.map-container { background: #1A1E23; }
|
||
}
|
||
|
||
/* RESPONSIVE */
|
||
@media (min-width: 768px) {
|
||
.metrics-grid { grid-template-columns: repeat(4, 1fr); }
|
||
.content { padding: 0 24px 80px; }
|
||
}
|
||
@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>
|
||
|
||
<style id="wcag-contrast-enforcement">
|
||
/* AAMOS GLOBAL CONTRAST ENFORCEMENT — WCAG AA (4.5:1 minimum) */
|
||
/* Regel: Ingen text under 70% opacity. Aldrig. */
|
||
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
||
/* Fix common low-contrast patterns */
|
||
[style*="rgba(60,60,67,0.4)"] { color: rgba(60,60,67,0.65) !important; }
|
||
[style*="rgba(60,60,67,0.3)"] { color: rgba(60,60,67,0.65) !important; }
|
||
[style*="rgba(0,0,0,0.4)"] { color: rgba(0,0,0,0.72) !important; }
|
||
[style*="rgba(0,0,0,0.3)"] { color: rgba(0,0,0,0.72) !important; }
|
||
[style*="opacity: 0.4"] { opacity: 0.72 !important; }
|
||
[style*="opacity: 0.3"] { opacity: 0.72 !important; }
|
||
@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 BAR -->
|
||
<nav class="nav-bar" aria-label="Main navigation">
|
||
<div class="nav-logo">
|
||
<div class="logo-icon">LX</div>
|
||
<span class="nav-title">LandveX Admin</span>
|
||
</div>
|
||
<div>
|
||
<span class="badge-admin">ADMIN</span>
|
||
<div>A</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- MAIN CONTENT -->
|
||
<main class="content">
|
||
|
||
<!-- HEADER -->
|
||
<div class="section-header-large">Infrastrukturportal</div>
|
||
<div class="section-sub">LandveX · Administration · 18 maj 2026</div>
|
||
|
||
<!-- METRICS -->
|
||
<div class="section-header">Systemöversikt</div>
|
||
<div class="metrics-grid">
|
||
<div class="metric-card metric-blue">
|
||
<div class="metric-value">247</div>
|
||
<div class="metric-label">Registrerade objekt</div>
|
||
<div class="metric-trend trend-up">↑ 12 denna vecka</div>
|
||
</div>
|
||
<div class="metric-card metric-green">
|
||
<div class="metric-value">189</div>
|
||
<div class="metric-label">OK-status</div>
|
||
<div class="metric-trend trend-up">↑ 76%</div>
|
||
</div>
|
||
<div class="metric-card metric-orange">
|
||
<div class="metric-value">41</div>
|
||
<div class="metric-label">Kräver åtgärd</div>
|
||
<div class="metric-trend trend-flat">↔ Stabil</div>
|
||
</div>
|
||
<div class="metric-card metric-red">
|
||
<div class="metric-value">17</div>
|
||
<div class="metric-label">Kritiska</div>
|
||
<div class="metric-trend trend-down">↑ 3 sedan igår</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAP -->
|
||
<div class="section-header">Live Operationell Karta</div>
|
||
|
||
<!-- LIVE MAP WITH ZOOMER POSITIONS -->
|
||
<div class="map-card">
|
||
<div class="table-header">
|
||
<span class="table-title"> Realtids zoomers + objekt</span>
|
||
<button class="table-action" onclick="cycleZoomerPos()">↻ Uppdatera</button>
|
||
</div>
|
||
<div class="map-container" id="map">
|
||
<div class="map-grid"></div>
|
||
|
||
<!-- Object pins -->
|
||
<div class="map-pin map-pin-ok" title="Gamla Stan Bron · OK"><div class="map-pin-inner"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>
|
||
<div class="map-pin map-pin-warn" title="E4 Avsnitt 12 · Varning"><div class="map-pin-inner">!</div></div>
|
||
<div class="map-pin map-pin-critical" title="E4 Älvsjö · Kritisk"><div class="map-pin-inner"></div></div>
|
||
<div class="map-pin map-pin-ok" title="Arlanda Terminal 5 · OK"><div class="map-pin-inner"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>
|
||
<div class="map-pin map-pin-warn" title="Öresundsbron · Varning"><div class="map-pin-inner">!</div></div>
|
||
<div class="map-pin map-pin-ok" title="Göta Älvbron · OK"><div class="map-pin-inner"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>
|
||
|
||
<!-- Live zoomer pins -->
|
||
<div id="zoomer-lx01">
|
||
<div><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></div>
|
||
<div>LX-01</div>
|
||
</div>
|
||
<div id="zoomer-lx02">
|
||
<div><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></div>
|
||
<div>LX-02</div>
|
||
</div>
|
||
<div id="zoomer-lx03">
|
||
<div><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></div>
|
||
<div>LX-03 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
|
||
</div>
|
||
|
||
<!-- Live indicator -->
|
||
<div>
|
||
<div></div>
|
||
LIVE
|
||
</div>
|
||
|
||
<div class="map-legend">
|
||
<div class="legend-item"><div class="legend-dot"></div>OK (189)</div>
|
||
<div class="legend-item"><div class="legend-dot"></div>Varning (41)</div>
|
||
<div class="legend-item"><div class="legend-dot"></div>Kritisk (17)</div>
|
||
<div class="legend-item"><div class="legend-dot"></div><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg> Zoomer (3)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- UPPDRAGSSTATUS -->
|
||
<div class="section-header">Uppdragsstatus</div>
|
||
<div>
|
||
<div class="metric-card metric-green">
|
||
<div class="metric-value">14</div>
|
||
<div class="metric-label">Pågående</div>
|
||
<div class="metric-trend trend-up"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg> Aktiva uppdrag</div>
|
||
</div>
|
||
<div class="metric-card metric-blue">
|
||
<div class="metric-value">218</div>
|
||
<div class="metric-label">Klart idag</div>
|
||
<div class="metric-trend trend-up">↑ 23% vs plan</div>
|
||
</div>
|
||
<div class="metric-card metric-orange">
|
||
<div class="metric-value">3</div>
|
||
<div class="metric-label">Avvikelse</div>
|
||
<div class="metric-trend trend-down">↓ Kräver åtgärd</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ACTIVE MISSIONS TABLE -->
|
||
<div class="table-card">
|
||
<div class="table-header">
|
||
<span class="table-title"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M9 2L5 9H8L7 14L11 7H8L9 2Z" fill="#f59e0b"/></svg> Pågående uppdrag</span>
|
||
<button class="table-action" onclick="openDispatch()">Snabb-dispatch →</button>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead><tr>
|
||
<th>Zoomer</th><th>Objekt</th><th>Status</th><th>Progress</th><th>ETA</th><th>Åtgärd</th>
|
||
</tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg> LX-01</span></td>
|
||
<td>E4 Avsnitt 12</td>
|
||
<td><span>PÅGÅR</span></td>
|
||
<td>
|
||
<div>
|
||
<div></div>
|
||
</div>
|
||
<div>72%</div>
|
||
</td>
|
||
<td>14 min</td>
|
||
<td><button>Följ live</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg> LX-02</span></td>
|
||
<td>Gamla Stan Bron</td>
|
||
<td><span>PÅGÅR</span></td>
|
||
<td>
|
||
<div>
|
||
<div></div>
|
||
</div>
|
||
<div>35%</div>
|
||
</td>
|
||
<td>38 min</td>
|
||
<td><button>Följ live</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg> LX-03</span></td>
|
||
<td>Öresundsbron S3</td>
|
||
<td><span>AVVIKELSE</span></td>
|
||
<td>
|
||
<div>
|
||
<div></div>
|
||
</div>
|
||
<div>58%</div>
|
||
</td>
|
||
<td>Försenad</td>
|
||
<td><button onclick="openDispatch()">Dispatch ny</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- KOSTNADSEFFEKTIVITET -->
|
||
<div class="section-header">Kostnadseffektivitet</div>
|
||
<div>
|
||
<div class="metric-card metric-green">
|
||
<div class="metric-value">1 240</div>
|
||
<div class="metric-label">Pris/inspektion (SEK)</div>
|
||
<div class="metric-trend trend-up">↓ 62% vs marknad</div>
|
||
</div>
|
||
<div class="metric-card metric-blue">
|
||
<div class="metric-value">3 280</div>
|
||
<div class="metric-label">Marknadspris (SEK)</div>
|
||
<div class="metric-trend flat">→ Traditionell inspektion</div>
|
||
</div>
|
||
<div class="metric-card metric-green">
|
||
<div class="metric-value">218</div>
|
||
<div class="metric-label">Insp. idag</div>
|
||
<div class="metric-trend trend-up">Rekord <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0066FF" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg></div>
|
||
</div>
|
||
<div class="metric-card metric-orange">
|
||
<div class="metric-value">270k</div>
|
||
<div class="metric-label">Besparing idag (SEK)</div>
|
||
<div class="metric-trend trend-up">↑ vs traditionellt</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PIPELINE -->
|
||
<div class="section-header">Inkommande beställningar</div>
|
||
<div class="table-card">
|
||
<div class="table-header">
|
||
<span class="table-title">Pipeline · 7 väntande</span>
|
||
<button class="table-action">Hantera alla →</button>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead><tr>
|
||
<th>Kund</th><th>Objekt</th><th>Typ</th><th>Prio</th><th>Mottagen</th><th>Åtgärd</th>
|
||
</tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Trafikverket</td><td>E4 Älvsjö · Kritisk</td><td>Fullständig</td>
|
||
<td><span>KRITISK</span></td>
|
||
<td>09:12</td>
|
||
<td><button onclick="openDispatch()">Dispatch nu</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Region Stockholm</td><td>Gamla Stan Bro</td><td>Ytinspektion</td>
|
||
<td><span>HÖG</span></td>
|
||
<td>08:45</td>
|
||
<td><button onclick="openDispatch()">Dispatch</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NCC Sverige</td><td>Rv 40 Borås</td><td>Strukturkontroll</td>
|
||
<td><span>NORMAL</span></td>
|
||
<td>Igår</td>
|
||
<td><button>Schemalägg</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Ellevio AB</td><td>Kraftledning N32</td><td>Optisk scanning</td>
|
||
<td><span>NORMAL</span></td>
|
||
<td>Igår</td>
|
||
<td><button>Schemalägg</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- KARTVY (original) -->
|
||
<div class="section-header">Kartvy (Legacy)</div>
|
||
|
||
<!-- OBJECTS TABLE -->
|
||
<div class="section-header">Registrerade objekt</div>
|
||
<div class="table-card">
|
||
<div class="table-header">
|
||
<span class="table-title">Fastigheter & Infrastruktur</span>
|
||
<button class="table-action" onclick="openModal()">+ Nytt objekt</button>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Objekt</th>
|
||
<th>Typ</th>
|
||
<th>Status</th>
|
||
<th>Risk</th>
|
||
<th>Senaste inspektion</th>
|
||
<th>Nästa inspektion</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr onclick="alert('Öppnar objektdetaljer: E4 Norrtull')">
|
||
<td><div class="td-name">E4 Norrtull Avsnitt 12</div><div class="td-sub">REF-001 · Stockholm</div></td>
|
||
<td> Väg</td>
|
||
<td><span class="status-badge status-warn"><span class="status-dot"></span>Kräver åtgärd</span></td>
|
||
<td>
|
||
<div class="risk-bar-wrap risk-medium">
|
||
<div class="risk-bar"><div class="risk-fill"></div></div>
|
||
<span class="risk-label">58</span>
|
||
</div>
|
||
</td>
|
||
<td>2026-05-10</td>
|
||
<td>2026-05-25</td>
|
||
</tr>
|
||
<tr onclick="alert('Öppnar objektdetaljer: Gamla Stan')">
|
||
<td><div class="td-name">Gamla Stan Bro</div><div class="td-sub">REF-002 · Stockholm</div></td>
|
||
<td> Bro</td>
|
||
<td><span class="status-badge status-ok"><span class="status-dot"></span>OK</span></td>
|
||
<td>
|
||
<div class="risk-bar-wrap risk-low">
|
||
<div class="risk-bar"><div class="risk-fill"></div></div>
|
||
<span class="risk-label">22</span>
|
||
</div>
|
||
</td>
|
||
<td>2026-05-14</td>
|
||
<td>2026-08-14</td>
|
||
</tr>
|
||
<tr onclick="alert('Öppnar objektdetaljer: Kungliga Slottet')">
|
||
<td><div class="td-name">Kungliga Slottet Tak</div><div class="td-sub">REF-003 · Stockholm</div></td>
|
||
<td> Byggnad</td>
|
||
<td><span class="status-badge status-critical"><span class="status-dot"></span>Kritisk</span></td>
|
||
<td>
|
||
<div class="risk-bar-wrap risk-high">
|
||
<div class="risk-bar"><div class="risk-fill"></div></div>
|
||
<span class="risk-label">87</span>
|
||
</div>
|
||
</td>
|
||
<td>2026-05-01</td>
|
||
<td> Omedelbart</td>
|
||
</tr>
|
||
<tr onclick="alert('Öppnar objektdetaljer: Arlanda Terminal 5')">
|
||
<td><div class="td-name">Arlanda Terminal 5</div><div class="td-sub">REF-004 · Sigtuna</div></td>
|
||
<td> Byggnad</td>
|
||
<td><span class="status-badge status-ok"><span class="status-dot"></span>OK</span></td>
|
||
<td>
|
||
<div class="risk-bar-wrap risk-low">
|
||
<div class="risk-bar"><div class="risk-fill"></div></div>
|
||
<span class="risk-label">15</span>
|
||
</div>
|
||
</td>
|
||
<td>2026-05-16</td>
|
||
<td>2026-11-16</td>
|
||
</tr>
|
||
<tr onclick="alert('Öppnar objektdetaljer: Öresundsbron')">
|
||
<td><div class="td-name">Öresundsbron Sektion 3</div><div class="td-sub">REF-005 · Malmö</div></td>
|
||
<td> Bro</td>
|
||
<td><span class="status-badge status-warn"><span class="status-dot"></span>Kräver åtgärd</span></td>
|
||
<td>
|
||
<div class="risk-bar-wrap risk-medium">
|
||
<div class="risk-bar"><div class="risk-fill"></div></div>
|
||
<span class="risk-label">63</span>
|
||
</div>
|
||
</td>
|
||
<td>2026-04-28</td>
|
||
<td>2026-05-20</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- INSPECTIONS -->
|
||
<div class="section-header">Senaste inspektioner</div>
|
||
<div class="inspection-list">
|
||
<div class="inspection-item">
|
||
<div class="inspection-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 14H14M4 14V8L8 4L12 8V14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 14V10H10V14" stroke="#666" stroke-width="1.5"/></svg></div>
|
||
<div class="inspection-body">
|
||
<div class="inspection-title">E4 Norrtull — Ytinspektion</div>
|
||
<div class="inspection-meta">Anna Lindqvist · 2026-05-10 · <span>Godkänd</span></div>
|
||
</div>
|
||
<div class="inspection-chevron">›</div>
|
||
</div>
|
||
<div class="inspection-item">
|
||
<div class="inspection-icon"></div>
|
||
<div class="inspection-body">
|
||
<div class="inspection-title">Öresundsbron — Strukturkontroll</div>
|
||
<div class="inspection-meta">Marcus Holm · 2026-04-28 · <span>Åtgärder krävs</span></div>
|
||
</div>
|
||
<div class="inspection-chevron">›</div>
|
||
</div>
|
||
<div class="inspection-item">
|
||
<div class="inspection-icon"></div>
|
||
<div class="inspection-body">
|
||
<div class="inspection-title">Kungliga Slottet — Takinspektion</div>
|
||
<div class="inspection-meta">Sofia Berg · 2026-05-01 · <span>Kritisk rapport</span></div>
|
||
</div>
|
||
<div class="inspection-chevron">›</div>
|
||
</div>
|
||
<div class="inspection-item">
|
||
<div class="inspection-icon"></div>
|
||
<div class="inspection-body">
|
||
<div class="inspection-title">Arlanda T5 — Fasadinspektion</div>
|
||
<div class="inspection-meta">Erik Svensson · 2026-05-16 · <span>Godkänd</span></div>
|
||
</div>
|
||
<div class="inspection-chevron">›</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STATUS REPORTS -->
|
||
<div class="section-header">Statusrapporter</div>
|
||
<div class="table-card">
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Rapport</th>
|
||
<th>Period</th>
|
||
<th>Objekt</th>
|
||
<th>Status</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><div class="td-name">Månatlig Statusrapport</div><div class="td-sub">Automatisk</div></td>
|
||
<td>Maj 2026</td>
|
||
<td>247 objekt</td>
|
||
<td><span class="status-badge status-ok"><span class="status-dot"></span>Klar</span></td>
|
||
<td><button class="table-action">Ladda ned</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><div class="td-name">Kritiska avvikelser</div><div class="td-sub">Automatisk · Alert</div></td>
|
||
<td>2026-05-17</td>
|
||
<td>3 objekt</td>
|
||
<td><span class="status-badge status-critical"><span class="status-dot"></span>Åtgärda</span></td>
|
||
<td><button class="table-action">Visa</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><div class="td-name">Kvartal Q1 2026</div><div class="td-sub">Manuell</div></td>
|
||
<td>Jan–Mar 2026</td>
|
||
<td>231 objekt</td>
|
||
<td><span class="status-badge status-ok"><span class="status-dot"></span>Klar</span></td>
|
||
<td><button class="table-action">Ladda ned</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- FAB -->
|
||
<button class="fab" onclick="openModal()">
|
||
<span>+</span>
|
||
<span>Ny inspektion</span>
|
||
</button>
|
||
|
||
<!-- MODAL: Ny inspektion -->
|
||
<div class="modal-overlay" id="modal" onclick="closeModal(event)">
|
||
<div class="modal-sheet" onclick="event.stopPropagation()">
|
||
<div class="modal-handle"></div>
|
||
<div class="modal-title">Skapa ny inspektion</div>
|
||
<div class="form-group">
|
||
<div class="form-label">Objekt</div>
|
||
<select class="form-select">
|
||
<option>E4 Norrtull Avsnitt 12</option>
|
||
<option>Gamla Stan Bro</option>
|
||
<option>Kungliga Slottet Tak</option>
|
||
<option>Arlanda Terminal 5</option>
|
||
<option>Öresundsbron Sektion 3</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<div class="form-label">Inspektionstyp</div>
|
||
<select class="form-select">
|
||
<option>Ytinspektion</option>
|
||
<option>Strukturkontroll</option>
|
||
<option>Fasadinspektion</option>
|
||
<option>Takinspektion</option>
|
||
<option>Fullständig inspektion</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<div class="form-label">Ansvarig inspektion</div>
|
||
<input class="form-input" type="text" placeholder="Sök inspektör...">
|
||
</div>
|
||
<div class="form-group">
|
||
<div class="form-label">Datum</div>
|
||
<input class="form-input" type="date" value="2026-05-20">
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn-secondary" onclick="closeModal()">Avbryt</button>
|
||
<button class="btn-primary" onclick="submitInspection()">Skapa inspektion</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TAB BAR -->
|
||
<nav class="tab-bar" aria-label="Main navigation">
|
||
<div class="tab-item active">
|
||
<div class="tab-icon"></div>
|
||
<div class="tab-label">Översikt</div>
|
||
</div>
|
||
<div class="tab-item" onclick="window.location.href='landvex-admin.html#objects'">
|
||
<div class="tab-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H10M6 8H10M6 11H8" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div>
|
||
<div class="tab-label">Objekt</div>
|
||
</div>
|
||
<div class="tab-item" onclick="window.location.href='landvex-admin.html#inspections'">
|
||
<div class="tab-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="#666" stroke-width="2"/><path d="M11 11L14 14" stroke="#666" stroke-width="2" stroke-linecap="round"/></svg></div>
|
||
<div class="tab-label">Inspektioner</div>
|
||
</div>
|
||
<div class="tab-item">
|
||
<div class="tab-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0066FF" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></div>
|
||
<div class="tab-label">Rapporter</div>
|
||
</div>
|
||
<div class="tab-item">
|
||
<div class="tab-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="3" stroke="#666" stroke-width="1.5"/><path d="M8 2V4M8 12V14M2 8H4M12 8H14M4.3 4.3L5.7 5.7M10.3 10.3L11.7 11.7M4.3 11.7L5.7 10.3M10.3 5.7L11.7 4.3" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div>
|
||
<div class="tab-label">Inställningar</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<style>
|
||
@keyframes zoomer-pulse {
|
||
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0,122,255,0.4); }
|
||
50% { transform: scale(1.05); box-shadow: 0 0 0 6px rgba(0,122,255,0); }
|
||
}
|
||
@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>
|
||
|
||
<!-- DISPATCH MODAL -->
|
||
<div id="dispatch-modal">
|
||
<div id="dispatch-sheet">
|
||
<div></div>
|
||
<div><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M9 2L5 9H8L7 14L11 7H8L9 2Z" fill="#f59e0b"/></svg> Snabb-dispatch</div>
|
||
|
||
<div>
|
||
<div>Närmaste lediga zoomer</div>
|
||
<div>
|
||
<span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></span>
|
||
<div>
|
||
<div>LX-04 · Stockholm N</div>
|
||
<div>4.2 km bort · Laddad 100% · HD + Multispektral</div>
|
||
</div>
|
||
<button onclick="confirmDispatch('LX-04')">Skicka</button>
|
||
</div>
|
||
<div>
|
||
<span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></span>
|
||
<div>
|
||
<div>LX-07 · Nacka</div>
|
||
<div>8.7 km bort · Laddad 87% · HD</div>
|
||
</div>
|
||
<button onclick="confirmDispatch('LX-07')">Skicka</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="dispatch-success"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#00C853" stroke-width="3"><path d="M20 6L9 17l-5-5"/></svg> Dispatch bekräftad! Zoomer anländer inom 12 min.</div>
|
||
<button onclick="closeDispatch()">Stäng</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function openModal() {
|
||
document.getElementById('modal').classList.add('open');
|
||
}
|
||
|
||
function closeModal(e) {
|
||
if (!e || e.target === document.getElementById('modal')) {
|
||
document.getElementById('modal').classList.remove('open');
|
||
}
|
||
}
|
||
|
||
function submitInspection() {
|
||
document.getElementById('modal').classList.remove('open');
|
||
// Toast feedback
|
||
const toast = document.createElement('div');
|
||
toast.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:var(--ios-green);color:white;padding: 24px;border-radius: var(--radius-xl);font-size:15px;font-weight:600;z-index:500;box-shadow:0 4px 16px rgba(52,199,89,0.4);';
|
||
toast.textContent = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Inspektion skapad';
|
||
document.body.appendChild(toast);
|
||
setTimeout(() => toast.remove(), 2500);
|
||
}
|
||
|
||
// Tab switching
|
||
document.querySelectorAll('.tab-item').forEach((tab, i) => {
|
||
tab.addEventListener('click', () => {
|
||
document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
});
|
||
});
|
||
|
||
// Dispatch modal
|
||
function openDispatch() {
|
||
const m = document.getElementById('dispatch-modal');
|
||
const s = document.getElementById('dispatch-sheet');
|
||
document.getElementById('dispatch-success').style.display = 'none';
|
||
m.style.opacity = '1';
|
||
m.style.pointerEvents = 'all';
|
||
setTimeout(() => s.style.transform = 'translateY(0)', 10);
|
||
}
|
||
|
||
function closeDispatch() {
|
||
const m = document.getElementById('dispatch-modal');
|
||
const s = document.getElementById('dispatch-sheet');
|
||
s.style.transform = 'translateY(100%)';
|
||
setTimeout(() => { m.style.opacity = '0'; m.style.pointerEvents = 'none'; }, 300);
|
||
}
|
||
|
||
function confirmDispatch(zoomerId) {
|
||
setTimeout(() => {
|
||
document.getElementById('dispatch-success').style.display = 'block';
|
||
}, 800);
|
||
}
|
||
|
||
document.getElementById('dispatch-modal').addEventListener('click', (e) => {
|
||
if (e.target === document.getElementById('dispatch-modal')) closeDispatch();
|
||
});
|
||
|
||
// Zoomer live position animation
|
||
function cycleZoomerPos() {
|
||
const z1 = document.getElementById('zoomer-lx01');
|
||
const z2 = document.getElementById('zoomer-lx02');
|
||
const z3 = document.getElementById('zoomer-lx03');
|
||
if (z1) z1.style.left = (44 + Math.random() * 8) + '%';
|
||
if (z1) z1.style.top = (38 + Math.random() * 8) + '%';
|
||
if (z2) z2.style.left = (20 + Math.random() * 8) + '%';
|
||
if (z2) z2.style.top = (25 + Math.random() * 8) + '%';
|
||
if (z3) z3.style.left = (68 + Math.random() * 8) + '%';
|
||
if (z3) z3.style.top = (52 + Math.random() * 8) + '%';
|
||
}
|
||
|
||
// Auto-animate zoomers every 5s
|
||
setInterval(cycleZoomerPos, 5000);
|
||
</script>
|
||
</body>
|
||
</html>
|