2026-07-05 06:41:32 +00:00
<!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" >
< style >
2026-07-07 07:11:50 +00:00
/*
2026-07-05 06:41:32 +00:00
LANDVEX ADMIN PORTAL — iOS Native
Wavult Group · 2026
2026-07-07 07:11:50 +00:00
*/
2026-07-05 06:41:32 +00:00
* { 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 : 100 vh ;
}
2026-07-07 07:11:50 +00:00
/* NAVIGATION BAR */
2026-07-05 06:41:32 +00:00
. nav-bar {
position : sticky ;
top : 0 ;
z-index : 100 ;
height : 44 px ;
background : var ( - - glass - bg ) ;
backdrop-filter : var ( - - glass - blur ) ;
-webkit- backdrop-filter : var ( - - glass - blur ) ;
border-bottom : 0.5 px solid var ( - - separator ) ;
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 0 16 px ;
safe-area-inset-top : env ( safe - area - inset - top ) ;
}
. nav-title {
font-family : var ( - - font - display ) ;
font-size : 17 px ;
font-weight : 600 ;
letter-spacing : -0.4 px ;
color : var ( - - label - primary ) ;
}
. nav-logo {
display : flex ;
align-items : center ;
gap : 8 px ;
}
. nav-logo . logo-icon {
width : 28 px ;
height : 28 px ;
background : linear-gradient ( 135 deg , #007AFF 0 % , #0055CC 100 % ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
display : flex ;
align-items : center ;
justify-content : center ;
font-size : 14 px ;
color : white ;
font-weight : 700 ;
letter-spacing : -0.5 px ;
}
. badge-admin {
font-size : 11 px ;
font-weight : 600 ;
padding : 3 px 8 px ;
background : rgba ( 255 , 149 , 0 , 0.15 ) ;
color : var ( - - ios - orange ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - xl ) ;
2026-07-05 06:41:32 +00:00
letter-spacing : 0.3 px ;
}
2026-07-07 07:11:50 +00:00
/* CONTENT */
2026-07-05 06:41:32 +00:00
. content {
padding : 0 16 px 120 px ;
max-width : 1100 px ;
margin : 0 auto ;
}
2026-07-07 07:11:50 +00:00
/* SECTION HEADER */
2026-07-05 06:41:32 +00:00
. section-header {
padding : 20 px 0 8 px ;
font-family : var ( - - font - display ) ;
font-size : 20 px ;
font-weight : 700 ;
letter-spacing : -0.4 px ;
color : var ( - - label - primary ) ;
}
. section-header-large {
padding : 28 px 0 4 px ;
font-family : var ( - - font - display ) ;
font-size : 28 px ;
font-weight : 700 ;
letter-spacing : -0.5 px ;
}
. section-sub {
font-size : 15 px ;
color : var ( - - label - secondary ) ;
padding-bottom : 12 px ;
}
2026-07-07 07:11:50 +00:00
/* METRICS GRID */
2026-07-05 06:41:32 +00:00
. metrics-grid {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 150 px , 1 fr ) ) ;
gap : 12 px ;
margin-bottom : 8 px ;
}
. metric-card {
background : var ( - - background - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
padding : 16 px ;
border : 0.5 px solid var ( - - separator ) ;
}
. metric-value {
font-family : var ( - - font - display ) ;
font-size : 32 px ;
font-weight : 700 ;
letter-spacing : -1 px ;
line-height : 1 ;
margin-bottom : 4 px ;
}
. metric-label {
font-size : 13 px ;
color : var ( - - label - secondary ) ;
font-weight : 500 ;
}
. metric-trend {
font-size : 12 px ;
font-weight : 600 ;
margin-top : 6 px ;
}
. 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 ) ; }
2026-07-07 07:11:50 +00:00
/* TABLE */
2026-07-05 06:41:32 +00:00
. table-card {
background : var ( - - background - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
border : 0.5 px solid var ( - - separator ) ;
overflow : hidden ;
margin-bottom : 12 px ;
}
. table-header {
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 14 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. table-title {
font-size : 17 px ;
font-weight : 600 ;
letter-spacing : -0.3 px ;
}
. table-action {
font-size : 15 px ;
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 : 14 px ;
}
thead th {
text-align : left ;
padding : 8 px 16 px ;
font-size : 12 px ;
font-weight : 600 ;
color : var ( - - label - secondary ) ;
text-transform : uppercase ;
letter-spacing : 0.5 px ;
background : var ( - - background - secondary ) ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
tbody tr {
border-bottom : 0.5 px solid var ( - - separator ) ;
transition : background 0.15 s ;
cursor : pointer ;
}
tbody tr : last-child { border-bottom : none ; }
tbody tr : hover { background : var ( - - fill - quaternary ) ; }
tbody td {
padding : 12 px 16 px ;
vertical-align : middle ;
}
. td-name {
font-weight : 600 ;
letter-spacing : -0.2 px ;
}
. td-sub {
font-size : 12 px ;
color : var ( - - label - secondary ) ;
margin-top : 2 px ;
}
2026-07-07 07:11:50 +00:00
/* STATUS BADGES */
2026-07-05 06:41:32 +00:00
. status-badge {
display : inline-flex ;
align-items : center ;
gap : 4 px ;
font-size : 12 px ;
font-weight : 600 ;
padding : 4 px 10 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - xl ) ;
2026-07-05 06:41:32 +00:00
}
. 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 : 6 px ;
height : 6 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - full ) ;
2026-07-05 06:41:32 +00:00
background : currentColor ;
}
2026-07-07 07:11:50 +00:00
/* RISK SCORE */
2026-07-05 06:41:32 +00:00
. risk-bar-wrap {
display : flex ;
align-items : center ;
gap : 8 px ;
}
. risk-bar {
flex : 1 ;
height : 4 px ;
background : var ( - - fill - tertiary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
overflow : hidden ;
min-width : 60 px ;
}
. risk-fill {
height : 100 % ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
transition : width 0.3 s ;
}
. 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 : 12 px ;
font-weight : 600 ;
min-width : 28 px ;
text-align : right ;
}
2026-07-07 07:11:50 +00:00
/* MAP PLACEHOLDER */
2026-07-05 06:41:32 +00:00
. map-card {
background : var ( - - background - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
border : 0.5 px solid var ( - - separator ) ;
overflow : hidden ;
margin-bottom : 12 px ;
}
. map-container {
height : 320 px ;
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 : 8 px ;
color : var ( - - label - secondary ) ;
}
. map-grid {
position : absolute ;
inset : 0 ;
opacity : 0.3 ;
background-image :
linear-gradient ( rgba ( 0 , 122 , 255 , 0.3 ) 1 px , transparent 1 px ) ,
linear-gradient ( 90 deg , rgba ( 0 , 122 , 255 , 0.3 ) 1 px , transparent 1 px ) ;
background-size : 40 px 40 px ;
}
. map-pin {
position : absolute ;
width : 28 px ;
height : 28 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - full ) 50 % 50 % 0 ;
2026-07-05 06:41:32 +00:00
transform : rotate ( -45 deg ) ;
display : flex ;
align-items : center ;
justify-content : center ;
cursor : pointer ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.25 ) ;
transition : transform 0.2 s ;
}
. map-pin : hover { transform : rotate ( -45 deg ) 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 ( 45 deg ) ;
font-size : 11 px ;
color : white ;
}
. map-legend {
position : absolute ;
bottom : 12 px ;
right : 12 px ;
background : var ( - - glass - bg ) ;
backdrop-filter : var ( - - glass - blur ) ;
-webkit- backdrop-filter : var ( - - glass - blur ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
padding : 8 px 12 px ;
border : 0.5 px solid var ( - - separator ) ;
display : flex ;
gap : 12 px ;
}
. legend-item {
display : flex ;
align-items : center ;
gap : 5 px ;
font-size : 11 px ;
color : var ( - - label - secondary ) ;
}
. legend-dot {
width : 8 px ;
height : 8 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - full ) ;
2026-07-05 06:41:32 +00:00
}
2026-07-07 07:11:50 +00:00
/* FAB BUTTON */
2026-07-05 06:41:32 +00:00
. fab {
position : fixed ;
bottom : calc ( 24 px + env ( safe - area - inset - bottom ) ) ;
right : 20 px ;
z-index : 200 ;
background : var ( - - ios - blue ) ;
color : white ;
border : none ;
border-radius : 28 px ;
height : 56 px ;
padding : 0 20 px ;
font-size : 15 px ;
font-weight : 600 ;
font-family : var ( - - font - text ) ;
cursor : pointer ;
display : flex ;
align-items : center ;
gap : 8 px ;
box-shadow : 0 4 px 20 px rgba ( 0 , 122 , 255 , 0.4 ) ;
transition : transform 0.15 s , box-shadow 0.15 s ;
}
. fab : hover {
transform : translateY ( -2 px ) ;
box-shadow : 0 8 px 28 px rgba ( 0 , 122 , 255 , 0.5 ) ;
}
. fab : active {
transform : translateY ( 0 ) scale ( 0.97 ) ;
box-shadow : 0 2 px 12 px rgba ( 0 , 122 , 255 , 0.35 ) ;
}
2026-07-07 07:11:50 +00:00
/* INSPECTION CELLS */
2026-07-05 06:41:32 +00:00
. inspection-list {
background : var ( - - background - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
border : 0.5 px solid var ( - - separator ) ;
overflow : hidden ;
}
. inspection-item {
display : flex ;
align-items : center ;
padding : 14 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
gap : 12 px ;
cursor : pointer ;
transition : background 0.15 s ;
}
. inspection-item : last-child { border-bottom : none ; }
. inspection-item : hover { background : var ( - - fill - quaternary ) ; }
. inspection-icon {
width : 40 px ;
height : 40 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
display : flex ;
align-items : center ;
justify-content : center ;
font-size : 18 px ;
flex-shrink : 0 ;
}
. inspection-body { flex : 1 ; min-width : 0 ; }
. inspection-title {
font-size : 15 px ;
font-weight : 600 ;
letter-spacing : -0.2 px ;
white-space : nowrap ;
overflow : hidden ;
text-overflow : ellipsis ;
}
. inspection-meta {
font-size : 13 px ;
color : var ( - - label - secondary ) ;
margin-top : 2 px ;
}
. inspection-chevron {
font-size : 17 px ;
color : var ( - - label - tertiary ) ;
flex-shrink : 0 ;
}
2026-07-07 07:11:50 +00:00
/* MODAL */
2026-07-05 06:41:32 +00:00
. modal-overlay {
position : fixed ;
inset : 0 ;
background : rgba ( 242 , 242 , 247 , 0.4 ) ;
backdrop-filter : blur ( 4 px ) ;
z-index : 300 ;
display : flex ;
align-items : flex-end ;
justify-content : center ;
opacity : 0 ;
pointer-events : none ;
transition : opacity 0.25 s ;
}
. modal-overlay . open {
opacity : 1 ;
pointer-events : all ;
}
. modal-sheet {
background : var ( - - background - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - xl ) 20 px 0 0 ;
2026-07-05 06:41:32 +00:00
width : 100 % ;
max-width : 640 px ;
padding : 8 px 0 env ( safe - area - inset - bottom ) ;
transform : translateY ( 100 % ) ;
transition : transform 0.3 s cubic-bezier ( 0.32 , 0.72 , 0 , 1 ) ;
}
. modal-overlay . open . modal-sheet {
transform : translateY ( 0 ) ;
}
. modal-handle {
width : 36 px ;
height : 4 px ;
background : var ( - - fill - primary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
margin : 8 px auto 16 px ;
}
. modal-title {
font-size : 20 px ;
font-weight : 700 ;
letter-spacing : -0.4 px ;
padding : 0 20 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. form-group {
padding : 16 px 20 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. form-label {
font-size : 13 px ;
font-weight : 600 ;
color : var ( - - label - secondary ) ;
margin-bottom : 6 px ;
}
. form-input {
width : 100 % ;
padding : 10 px 12 px ;
background : var ( - - background - secondary ) ;
border : 0.5 px solid var ( - - separator ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
font-size : 15 px ;
font-family : var ( - - font - text ) ;
color : var ( - - label - primary ) ;
outline : none ;
transition : border-color 0.2 s ;
}
. form-input : focus {
border-color : var ( - - ios - blue ) ;
}
. form-select {
width : 100 % ;
padding : 10 px 12 px ;
background : var ( - - background - secondary ) ;
border : 0.5 px solid var ( - - separator ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
font-size : 15 px ;
font-family : var ( - - font - text ) ;
color : var ( - - label - primary ) ;
outline : none ;
appearance : none ;
cursor : pointer ;
}
. modal-actions {
padding : 16 px 20 px ;
display : flex ;
gap : 10 px ;
}
. btn-primary {
flex : 1 ;
height : 50 px ;
background : var ( - - ios - blue ) ;
color : white ;
border : none ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
font-size : 17 px ;
font-weight : 600 ;
font-family : var ( - - font - text ) ;
cursor : pointer ;
transition : opacity 0.15 s ;
}
. btn-primary : active { opacity : 0.7 ; }
. btn-secondary {
flex : 1 ;
height : 50 px ;
background : var ( - - fill - secondary ) ;
color : var ( - - label - primary ) ;
border : none ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
font-size : 17 px ;
font-weight : 600 ;
font-family : var ( - - font - text ) ;
cursor : pointer ;
transition : opacity 0.15 s ;
}
. btn-secondary : active { opacity : 0.7 ; }
2026-07-07 07:11:50 +00:00
/* TAB BAR */
2026-07-05 06:41:32 +00:00
. tab-bar {
position : fixed ;
bottom : 0 ;
left : 0 ;
right : 0 ;
height : calc ( 49 px + 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.5 px solid var ( - - separator ) ;
display : flex ;
align-items : flex-start ;
justify-content : space-around ;
padding-top : 8 px ;
z-index : 90 ;
}
. tab-item {
display : flex ;
flex-direction : column ;
align-items : center ;
gap : 2 px ;
cursor : pointer ;
flex : 1 ;
padding : 0 8 px ;
opacity : 0.6 ;
transition : opacity 0.15 s ;
}
. tab-item . active { opacity : 1 ; }
. tab-icon { font-size : 22 px ; }
. tab-label { font-size : 10 px ; font-weight : 500 ; }
. tab-item . active . tab-label { color : var ( - - ios - blue ) ; }
. tab-item . active . tab-icon { filter : none ; }
2026-07-07 07:11:50 +00:00
/* DARK MODE */
2026-07-05 06:41:32 +00:00
@ media ( prefers-color-scheme : dark ) {
. map-container { background : #1A1E23 ; }
}
2026-07-07 07:11:50 +00:00
/* RESPONSIVE */
2026-07-05 06:41:32 +00:00
@ media ( min-width : 768px ) {
. metrics-grid { grid-template-columns : repeat ( 4 , 1 fr ) ; }
. content { padding : 0 24 px 80 px ; }
}
@ media ( max-width : 640px ) {
. hero h1 { font-size : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !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 : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !important ; }
}
< / style >
< / head >
< body >
<!-- NAV BAR -->
< nav class = "nav-bar" >
< div class = "nav-logo" >
< div class = "logo-icon" > LX< / div >
< span class = "nav-title" > LandveX Admin< / span >
< / div >
< div style = "display:flex;align-items:center;gap:8px;" >
< span class = "badge-admin" > ADMIN< / span >
2026-07-07 07:11:50 +00:00
< div style = "width:32px;height:32px;border-radius: var(--radius-full);background:linear-gradient(135deg,#007AFF,#0055CC);display:flex;align-items:center;justify-content:center;color:white;font-size:14px;font-weight:700;" > A< / div >
2026-07-05 06:41:32 +00:00
< / 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" style = "margin-bottom:12px;" >
< div class = "table-header" >
2026-07-07 07:11:50 +00:00
< span class = "table-title" > Realtids zoomers + objekt< / span >
2026-07-05 06:41:32 +00:00
< button class = "table-action" onclick = "cycleZoomerPos()" > ↻ Uppdatera< / button >
< / div >
< div class = "map-container" id = "map" style = "position:relative;min-height:260px;" >
< div class = "map-grid" > < / div >
<!-- Object pins -->
2026-07-07 07:11:50 +00:00
< div class = "map-pin map-pin-ok" style = "left:22%;top:30%;" 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 >
2026-07-05 06:41:32 +00:00
< div class = "map-pin map-pin-warn" style = "left:45%;top:45%;" title = "E4 Avsnitt 12 · Varning" > < div class = "map-pin-inner" > !< / div > < / div >
2026-07-07 07:11:50 +00:00
< div class = "map-pin map-pin-critical" style = "left:68%;top:25%;" title = "E4 Älvsjö · Kritisk" > < div class = "map-pin-inner" > < / div > < / div >
< div class = "map-pin map-pin-ok" style = "left:35%;top:65%;" 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 >
2026-07-05 06:41:32 +00:00
< div class = "map-pin map-pin-warn" style = "left:80%;top:55%;" title = "Öresundsbron · Varning" > < div class = "map-pin-inner" > !< / div > < / div >
2026-07-07 07:11:50 +00:00
< div class = "map-pin map-pin-ok" style = "left:55%;top:72%;" 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 >
2026-07-05 06:41:32 +00:00
<!-- Live zoomer pins -->
< div id = "zoomer-lx01" style = "position:absolute;left:48%;top:42%;z-index:10;transition:all 2s ease;" >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--ios-blue);color: #1d1d1f;border-radius: var(--radius-full);width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 8px rgba(0,122,255,0.5);animation:zoomer-pulse 2s infinite;" > < 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 style = "font-size:9px;font-weight:700;color:var(--ios-blue);text-align:center;margin-top:2px;background:rgba(0,0,0,0.9);border-radius: var(--radius-sm);padding:1px 4px;" > LX-01< / div >
2026-07-05 06:41:32 +00:00
< / div >
< div id = "zoomer-lx02" style = "position:absolute;left:24%;top:27%;z-index:10;transition:all 2s ease;" >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--ios-green);color: #1d1d1f;border-radius: var(--radius-full);width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 8px rgba(52,199,89,0.5);" > < 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 style = "font-size:9px;font-weight:700;color:var(--ios-green);text-align:center;margin-top:2px;background:rgba(0,0,0,0.9);border-radius: var(--radius-sm);padding:1px 4px;" > LX-02< / div >
2026-07-05 06:41:32 +00:00
< / div >
< div id = "zoomer-lx03" style = "position:absolute;left:72%;top:58%;z-index:10;transition:all 2s ease;" >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--ios-orange);color: #1d1d1f;border-radius: var(--radius-full);width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 8px rgba(255,149,0,0.5);" > < 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 style = "font-size:9px;font-weight:700;color:var(--ios-orange);text-align:center;margin-top:2px;background:rgba(0,0,0,0.9);border-radius: var(--radius-sm);padding:1px 4px;" > 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 >
2026-07-05 06:41:32 +00:00
< / div >
<!-- Live indicator -->
2026-07-07 07:11:50 +00:00
< div style = "position:absolute;top:10px;left:10px;z-index:11;display:flex;align-items:center;gap:5px;background:rgba(0,0,0,0.9);border-radius: var(--radius-xl);padding:4px 10px;font-size:11px;font-weight:600;color:var(--ios-green);" >
< div style = "width:6px;height:6px;border-radius: var(--radius-full);background:var(--ios-green);animation:zoomer-pulse 2s infinite;" > < / div >
2026-07-05 06:41:32 +00:00
LIVE
< / div >
< div class = "map-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--ios-green)" > < / div > OK (189)< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--ios-orange)" > < / div > Varning (41)< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--ios-red)" > < / div > Kritisk (17)< / div >
2026-07-07 07:11:50 +00:00
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--ios-blue)" > < / 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 >
2026-07-05 06:41:32 +00:00
< / div >
< / div >
< / div >
<!-- UPPDRAGSSTATUS -->
< div class = "section-header" > Uppdragsstatus< / div >
< div style = "display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px;" >
< div class = "metric-card metric-green" >
< div class = "metric-value" > 14< / div >
< div class = "metric-label" > Pågående< / div >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< / 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" style = "margin-bottom:12px;" >
< div class = "table-header" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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 >
2026-07-07 07:11:50 +00:00
< td > < span style = "font-weight:600;color:var(--ios-blue);" > < 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 >
2026-07-05 06:41:32 +00:00
< td > E4 Avsnitt 12< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(52,199,89,0.12);color:var(--ios-green);font-size:11px;font-weight:700;padding:3px 8px;border-radius: var(--radius-xl);" > PÅGÅR< / span > < / td >
2026-07-05 06:41:32 +00:00
< td >
2026-07-07 07:11:50 +00:00
< div style = "width:80px;height:5px;background:var(--fill-tertiary);border-radius: var(--radius-sm);overflow:hidden;" >
< div style = "width:72%;height:100%;background:var(--ios-blue);border-radius: var(--radius-sm);" > < / div >
2026-07-05 06:41:32 +00:00
< / div >
< div style = "font-size:10px;color:var(--label-tertiary);margin-top:2px;" > 72%< / div >
< / td >
< td style = "font-size:13px;" > 14 min< / td >
< td > < button style = "font-size:12px;color:var(--ios-blue);background:none;border:none;cursor:pointer;padding:4px 0;" > Följ live< / button > < / td >
< / tr >
< tr >
2026-07-07 07:11:50 +00:00
< td > < span style = "font-weight:600;color:var(--ios-green);" > < 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 >
2026-07-05 06:41:32 +00:00
< td > Gamla Stan Bron< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(52,199,89,0.12);color:var(--ios-green);font-size:11px;font-weight:700;padding:3px 8px;border-radius: var(--radius-xl);" > PÅGÅR< / span > < / td >
2026-07-05 06:41:32 +00:00
< td >
2026-07-07 07:11:50 +00:00
< div style = "width:80px;height:5px;background:var(--fill-tertiary);border-radius: var(--radius-sm);overflow:hidden;" >
< div style = "width:35%;height:100%;background:var(--ios-green);border-radius: var(--radius-sm);" > < / div >
2026-07-05 06:41:32 +00:00
< / div >
< div style = "font-size:10px;color:var(--label-tertiary);margin-top:2px;" > 35%< / div >
< / td >
< td style = "font-size:13px;" > 38 min< / td >
< td > < button style = "font-size:12px;color:var(--ios-blue);background:none;border:none;cursor:pointer;padding:4px 0;" > Följ live< / button > < / td >
< / tr >
< tr >
2026-07-07 07:11:50 +00:00
< td > < span style = "font-weight:600;color:var(--ios-orange);" > < 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 >
2026-07-05 06:41:32 +00:00
< td > Öresundsbron S3< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(255,149,0,0.12);color:var(--ios-orange);font-size:11px;font-weight:700;padding:3px 8px;border-radius: var(--radius-xl);" > AVVIKELSE< / span > < / td >
2026-07-05 06:41:32 +00:00
< td >
2026-07-07 07:11:50 +00:00
< div style = "width:80px;height:5px;background:var(--fill-tertiary);border-radius: var(--radius-sm);overflow:hidden;" >
< div style = "width:58%;height:100%;background:var(--ios-orange);border-radius: var(--radius-sm);" > < / div >
2026-07-05 06:41:32 +00:00
< / div >
< div style = "font-size:10px;color:var(--label-tertiary);margin-top:2px;" > 58%< / div >
< / td >
< td style = "font-size:13px;" > Försenad< / td >
< td > < button style = "font-size:12px;color:var(--ios-red);background:none;border:none;cursor:pointer;font-weight:600;padding:4px 0;" onclick = "openDispatch()" > Dispatch ny< / button > < / td >
< / tr >
< / tbody >
< / table >
< / div >
< / div >
<!-- KOSTNADSEFFEKTIVITET -->
< div class = "section-header" > Kostnadseffektivitet< / div >
< div style = "display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:12px;" >
< 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" style = "margin-bottom:12px;" >
< 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 style = "font-weight:600;" > Trafikverket< / td > < td > E4 Älvsjö · Kritisk< / td > < td > Fullständig< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(255,59,48,0.12);color:var(--ios-red);font-size:11px;font-weight:700;padding:2px 7px;border-radius: var(--radius-xl);" > KRITISK< / span > < / td >
2026-07-05 06:41:32 +00:00
< td style = "font-size:12px;color:var(--label-tertiary);" > 09:12< / td >
2026-07-07 07:11:50 +00:00
< td > < button style = "font-size:12px;color: #1d1d1f;background:var(--ios-red);border:none;border-radius: var(--radius-md);padding:4px 12px;cursor:pointer;font-weight:600;" onclick = "openDispatch()" > Dispatch nu< / button > < / td >
2026-07-05 06:41:32 +00:00
< / tr >
< tr >
< td style = "font-weight:600;" > Region Stockholm< / td > < td > Gamla Stan Bro< / td > < td > Ytinspektion< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(255,149,0,0.12);color:var(--ios-orange);font-size:11px;font-weight:700;padding:2px 7px;border-radius: var(--radius-xl);" > HÖG< / span > < / td >
2026-07-05 06:41:32 +00:00
< td style = "font-size:12px;color:var(--label-tertiary);" > 08:45< / td >
2026-07-07 07:11:50 +00:00
< td > < button style = "font-size:12px;color: #1d1d1f;background:var(--ios-orange);border:none;border-radius: var(--radius-md);padding:4px 12px;cursor:pointer;font-weight:600;" onclick = "openDispatch()" > Dispatch< / button > < / td >
2026-07-05 06:41:32 +00:00
< / tr >
< tr >
< td style = "font-weight:600;" > NCC Sverige< / td > < td > Rv 40 Borås< / td > < td > Strukturkontroll< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(0,122,255,0.10);color:var(--ios-blue);font-size:11px;font-weight:700;padding:2px 7px;border-radius: var(--radius-xl);" > NORMAL< / span > < / td >
2026-07-05 06:41:32 +00:00
< td style = "font-size:12px;color:var(--label-tertiary);" > Igår< / td >
2026-07-07 07:11:50 +00:00
< td > < button style = "font-size:12px;color:var(--ios-blue);background:none;border:1px solid var(--ios-blue);border-radius: var(--radius-md);padding:4px 12px;cursor:pointer;font-weight:600;" > Schemalägg< / button > < / td >
2026-07-05 06:41:32 +00:00
< / tr >
< tr >
< td style = "font-weight:600;" > Ellevio AB< / td > < td > Kraftledning N32< / td > < td > Optisk scanning< / td >
2026-07-07 07:11:50 +00:00
< td > < span style = "background:rgba(0,122,255,0.10);color:var(--ios-blue);font-size:11px;font-weight:700;padding:2px 7px;border-radius: var(--radius-xl);" > NORMAL< / span > < / td >
2026-07-05 06:41:32 +00:00
< td style = "font-size:12px;color:var(--label-tertiary);" > Igår< / td >
2026-07-07 07:11:50 +00:00
< td > < button style = "font-size:12px;color:var(--ios-blue);background:none;border:1px solid var(--ios-blue);border-radius: var(--radius-md);padding:4px 12px;cursor:pointer;font-weight:600;" > Schemalägg< / button > < / td >
2026-07-05 06:41:32 +00:00
< / tr >
< / tbody >
< / table >
< / div >
< / div >
<!-- KARTVY (original) -->
< div class = "section-header" style = "display:none;" > 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 >
2026-07-07 07:11:50 +00:00
< td > Väg< / td >
2026-07-05 06:41:32 +00:00
< 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" style = "width:58%" > < / 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 >
2026-07-07 07:11:50 +00:00
< td > Bro< / td >
2026-07-05 06:41:32 +00:00
< 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" style = "width:22%" > < / 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 >
2026-07-07 07:11:50 +00:00
< td > Byggnad< / td >
2026-07-05 06:41:32 +00:00
< 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" style = "width:87%" > < / div > < / div >
< span class = "risk-label" > 87< / span >
< / div >
< / td >
< td > 2026-05-01< / td >
2026-07-07 07:11:50 +00:00
< td > Omedelbart< / td >
2026-07-05 06:41:32 +00:00
< / 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 >
2026-07-07 07:11:50 +00:00
< td > Byggnad< / td >
2026-07-05 06:41:32 +00:00
< 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" style = "width:15%" > < / 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 >
2026-07-07 07:11:50 +00:00
< td > Bro< / td >
2026-07-05 06:41:32 +00:00
< 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" style = "width:63%" > < / 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" >
2026-07-07 07:11:50 +00:00
< div class = "inspection-icon" style = "background:rgba(52,199,89,0.15)" > < 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 >
2026-07-05 06:41:32 +00:00
< div class = "inspection-body" >
< div class = "inspection-title" > E4 Norrtull — Ytinspektion< / div >
< div class = "inspection-meta" > Anna Lindqvist · 2026-05-10 · < span style = "color:var(--ios-green)" > Godkänd< / span > < / div >
< / div >
< div class = "inspection-chevron" > › < / div >
< / div >
< div class = "inspection-item" >
2026-07-07 07:11:50 +00:00
< div class = "inspection-icon" style = "background:rgba(255,149,0,0.15)" > < / div >
2026-07-05 06:41:32 +00:00
< div class = "inspection-body" >
< div class = "inspection-title" > Öresundsbron — Strukturkontroll< / div >
< div class = "inspection-meta" > Marcus Holm · 2026-04-28 · < span style = "color:var(--ios-orange)" > Åtgärder krävs< / span > < / div >
< / div >
< div class = "inspection-chevron" > › < / div >
< / div >
< div class = "inspection-item" >
2026-07-07 07:11:50 +00:00
< div class = "inspection-icon" style = "background:rgba(255,59,48,0.15)" > < / div >
2026-07-05 06:41:32 +00:00
< div class = "inspection-body" >
< div class = "inspection-title" > Kungliga Slottet — Takinspektion< / div >
< div class = "inspection-meta" > Sofia Berg · 2026-05-01 · < span style = "color:var(--ios-red)" > Kritisk rapport< / span > < / div >
< / div >
< div class = "inspection-chevron" > › < / div >
< / div >
< div class = "inspection-item" >
2026-07-07 07:11:50 +00:00
< div class = "inspection-icon" style = "background:rgba(0,122,255,0.15)" > < / div >
2026-07-05 06:41:32 +00:00
< div class = "inspection-body" >
< div class = "inspection-title" > Arlanda T5 — Fasadinspektion< / div >
< div class = "inspection-meta" > Erik Svensson · 2026-05-16 · < span style = "color:var(--ios-green)" > 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" >
< div class = "tab-item active" >
2026-07-07 07:11:50 +00:00
< div class = "tab-icon" > < / div >
2026-07-05 06:41:32 +00:00
< div class = "tab-label" > Översikt< / div >
< / div >
< div class = "tab-item" onclick = "window.location.href='landvex-admin.html#objects'" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< div class = "tab-label" > Objekt< / div >
< / div >
< div class = "tab-item" onclick = "window.location.href='landvex-admin.html#inspections'" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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 6 px rgba ( 0 , 122 , 255 , 0 ) ; }
}
@ media ( max-width : 640px ) {
. hero h1 { font-size : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !important ; }
}
< / style >
<!-- DISPATCH MODAL -->
< div id = "dispatch-modal" style = "position:fixed;inset:0;background: rgba(242,242,247,0.5);display:flex;align-items:flex-end;z-index:1001;opacity:0;pointer-events:none;transition:opacity 0.25s;" >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--background-primary);border-radius: var(--radius-xl) 20px 0 0;padding:20px 20px 40px;width:100%;max-height:80vh;overflow-y:auto;transform:translateY(100%);transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1);" id = "dispatch-sheet" >
< div style = "width:36px;height:4px;background:var(--fill-secondary);border-radius: var(--radius-sm);margin:0 auto 16px;" > < / div >
< div style = "font-family:var(--font-display);font-size:22px;font-weight:700;letter-spacing:-0.4px;margin-bottom:16px;" > < 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 >
2026-07-05 06:41:32 +00:00
2026-07-07 07:11:50 +00:00
< div style = "background:var(--background-secondary);border-radius: var(--radius-md);padding:14px;margin-bottom:12px;" >
2026-07-05 06:41:32 +00:00
< div style = "font-size:13px;font-weight:600;color:var(--label-secondary);text-transform:uppercase;letter-spacing:0.4px;margin-bottom:10px;" > Närmaste lediga zoomer< / div >
2026-07-07 07:11:50 +00:00
< div style = "display:flex;align-items:center;gap:12px;padding:10px;background:rgba(52,199,89,0.08);border-radius: var(--radius-md);border:0.5px solid rgba(52,199,89,0.3);margin-bottom:8px;" >
< span style = "font-size:24px;" > < 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 >
2026-07-05 06:41:32 +00:00
< div style = "flex:1;" >
< div style = "font-size:16px;font-weight:600;" > LX-04 · Stockholm N< / div >
< div style = "font-size:12px;color:var(--label-secondary);" > 4.2 km bort · Laddad 100% · HD + Multispektral< / div >
< / div >
2026-07-07 07:11:50 +00:00
< button onclick = "confirmDispatch('LX-04')" style = "background:var(--ios-green);color: #1d1d1f;border:none;border-radius: var(--radius-md);padding:8px 14px;font-size:14px;font-weight:600;cursor:pointer;" > Skicka< / button >
2026-07-05 06:41:32 +00:00
< / div >
2026-07-07 07:11:50 +00:00
< div style = "display:flex;align-items:center;gap:12px;padding:10px;background:var(--fill-quaternary);border-radius: var(--radius-md);margin-bottom:8px;" >
< span style = "font-size:24px;" > < 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 >
2026-07-05 06:41:32 +00:00
< div style = "flex:1;" >
< div style = "font-size:16px;font-weight:600;" > LX-07 · Nacka< / div >
< div style = "font-size:12px;color:var(--label-secondary);" > 8.7 km bort · Laddad 87% · HD< / div >
< / div >
2026-07-07 07:11:50 +00:00
< button onclick = "confirmDispatch('LX-07')" style = "background:var(--ios-blue);color: #1d1d1f;border:none;border-radius: var(--radius-md);padding:8px 14px;font-size:14px;font-weight:600;cursor:pointer;" > Skicka< / button >
2026-07-05 06:41:32 +00:00
< / div >
< / div >
2026-07-07 07:11:50 +00:00
< div id = "dispatch-success" style = "display:none;text-align:center;padding:16px;background:rgba(52,199,89,0.1);border-radius: var(--radius-lg);color:var(--ios-green);font-size:16px;font-weight:600;" > < 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()" style = "width:100%;padding:14px;background:var(--fill-tertiary);color:var(--label-primary);border:none;border-radius: var(--radius-lg);font-size:17px;font-weight:600;cursor:pointer;margin-top:8px;" > Stäng< / button >
2026-07-05 06:41:32 +00:00
< / 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' ) ;
2026-07-07 07:11:50 +00:00
toast . style . cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:var(--ios-green);color:white;padding:12px 20px;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' ;
2026-07-05 06:41:32 +00:00
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 >