diff --git a/app/assets/css/main.css b/app/assets/css/main.css new file mode 100644 index 0000000..66f7832 --- /dev/null +++ b/app/assets/css/main.css @@ -0,0 +1,128 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + .kestrel-page-heading { @apply text-xl font-semibold tracking-wide text-kestrel-text text-shadow-glow-sm; } + .kestrel-section-heading { @apply text-lg font-semibold tracking-wide text-kestrel-text text-shadow-glow-sm; } + .kestrel-panel-header { @apply flex items-center justify-between border-b border-kestrel-border px-4 py-3 shadow-border-header; } + .kestrel-video-frame { @apply relative aspect-video w-full overflow-hidden rounded border border-kestrel-border bg-black shadow-glow-inset-video; } + .kestrel-close-btn { @apply rounded p-1 text-kestrel-muted transition-colors hover:bg-kestrel-border hover:text-kestrel-accent; } + .kestrel-card { @apply rounded border border-kestrel-border bg-kestrel-surface shadow-glow-card; } + .kestrel-card-modal { @apply rounded-lg border border-kestrel-border bg-kestrel-surface shadow-glow-modal; } + .kestrel-label { @apply mb-1.5 block text-xs font-medium uppercase tracking-wider text-kestrel-muted; } + .kestrel-section-label { @apply mb-2 text-sm font-medium uppercase tracking-wider text-kestrel-muted; } + .kestrel-input { @apply w-full rounded border border-kestrel-border bg-kestrel-bg px-3 py-2 text-sm text-kestrel-text placeholder:text-kestrel-muted outline-none transition-colors focus:border-kestrel-accent; } + .kestrel-btn-secondary { @apply rounded border border-kestrel-border px-4 py-2 text-sm text-kestrel-text transition-colors hover:bg-kestrel-border; } + .kestrel-context-menu-item { @apply block w-full px-3 py-1.5 text-left text-sm text-kestrel-text transition-colors hover:bg-kestrel-border; } + .kestrel-context-menu-item-danger { @apply block w-full px-3 py-1.5 text-left text-sm text-red-400 transition-colors hover:bg-kestrel-border; } + .kestrel-panel-base { @apply flex flex-col border border-kestrel-border bg-kestrel-surface; } + .kestrel-panel-inline { @apply rounded-lg shadow-glow; } + .kestrel-panel-overlay { @apply absolute right-0 top-0 z-[1000] h-full w-full border-l shadow-glow md:w-[420px] shadow-glow-panel; } +} + +/* Transitions: modal + drawer-backdrop (same fade) */ +.modal-enter-active, .modal-leave-active, +.drawer-backdrop-enter-active, .drawer-backdrop-leave-active { transition: opacity 0.2s ease; } +.modal-enter-from, .modal-leave-to, +.drawer-backdrop-enter-from, .drawer-backdrop-leave-to { opacity: 0; } +.modal-enter-active .relative, .modal-leave-active .relative { transition: transform 0.2s ease; } +.modal-enter-from .relative, .modal-leave-to .relative { transform: scale(0.96); } + +.nav-drawer { box-shadow: 8px 0 24px -4px rgba(34, 201, 201, 0.12); } +@media (min-width: 768px) { .nav-drawer { box-shadow: none; } } + +/* Leaflet map */ +.kestrel-map-container { + background: #000 !important; +} +.kestrel-map-container .leaflet-tile-pane, +.kestrel-map-container .leaflet-map-pane, +.kestrel-map-container .leaflet-tile-container { + background: #000 !important; +} +.kestrel-map-container img.leaflet-tile { + background: #000 !important; + mix-blend-mode: normal; +} +.kestrel-map-container .poi-div-icon { + background: none; + border: none; +} +.kestrel-map-container .poi-icon-svg { + display: block; + width: 100%; + height: 100%; + pointer-events: none; +} +.kestrel-map-container .kestrel-poi-tooltip, +.kestrel-map-container .kestrel-live-popup-wrap .leaflet-popup-content-wrapper, +.kestrel-map-container .kestrel-live-popup-wrap .leaflet-popup-tip { + @apply bg-kestrel-surface-elevated border border-kestrel-glow rounded-md shadow-elevated; +} +.kestrel-map-container .kestrel-poi-tooltip { + @apply text-kestrel-text-bright text-xs font-[inherit] py-1.5 px-2.5; +} +.kestrel-map-container .kestrel-poi-tooltip::before, +.kestrel-map-container .kestrel-poi-tooltip::after { + border-color: #1e293b; +} +.kestrel-map-container .kestrel-live-popup-wrap .leaflet-popup-content { + @apply text-kestrel-text-bright my-2 mx-3 min-w-[200px]; +} +.kestrel-map-container .kestrel-live-popup { + @apply text-kestrel-text-bright text-xs; +} +.kestrel-map-container .kestrel-live-popup img { + @apply block max-h-40 w-auto rounded bg-kestrel-bg; +} +.kestrel-map-container .leaflet-control-zoom, +.kestrel-map-container .leaflet-control-locate, +.kestrel-map-container .savetiles.leaflet-bar { + @apply rounded-md overflow-hidden font-mono border border-kestrel-glow shadow-glow-sm; + border-color: rgba(34, 201, 201, 0.35) !important; +} +.kestrel-map-container .leaflet-control-zoom a, +.kestrel-map-container .leaflet-control-locate, +.kestrel-map-container .savetiles.leaflet-bar a { + @apply w-8 h-8 leading-8 bg-kestrel-surface text-kestrel-text border-none rounded-none text-lg font-semibold no-underline transition-all duration-150; + width: 32px !important; + height: 32px !important; + line-height: 32px !important; + background: #0d1424 !important; + color: #b8c9e0 !important; + text-decoration: none !important; +} +.kestrel-map-container .leaflet-control-zoom a + a, +.kestrel-map-container .savetiles.leaflet-bar a + a { + border-top: 1px solid rgba(34, 201, 201, 0.2); +} +.kestrel-map-container .leaflet-control-zoom a:hover, +.kestrel-map-container .leaflet-control-locate:hover, +.kestrel-map-container .savetiles.leaflet-bar a:hover { + @apply bg-kestrel-surface-hover text-kestrel-accent shadow-glow-md text-shadow-glow-md; +} +.kestrel-map-container .savetiles.leaflet-bar { + @apply flex flex-col; +} +.kestrel-map-container .savetiles.leaflet-bar a { + @apply min-w-[5.5em] leading-tight py-1.5 px-2.5 whitespace-nowrap text-center text-[11px] font-medium tracking-wide; + width: auto !important; + height: auto !important; + line-height: 1.25 !important; + padding: 6px 10px !important; + font-size: 11px !important; +} +.kestrel-map-container .leaflet-control-locate { + @apply flex items-center justify-center p-0 cursor-pointer; +} +.kestrel-map-container .leaflet-control-locate svg { + color: currentColor; +} +.kestrel-map-container .live-session-icon { + animation: live-pulse 1.5s ease-in-out infinite; +} +@keyframes live-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} diff --git a/app/components/CameraViewer.vue b/app/components/CameraViewer.vue index 3f61a5a..16be97c 100644 --- a/app/components/CameraViewer.vue +++ b/app/components/CameraViewer.vue @@ -7,18 +7,18 @@ />