diff --git a/apps/web/src/app/styles.css b/apps/web/src/app/styles.css index 9e8c645..a57393e 100644 --- a/apps/web/src/app/styles.css +++ b/apps/web/src/app/styles.css @@ -1,1814 +1,19 @@ -@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;800;900&display=swap"); - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --bg: #020617; - --panel: #0f172a; - --card: #1e293b; - --border: #1e3a5f; - --text: #e2e8f0; - --muted: #64748b; - --accent: #3b82f6; - - --crit: #ef4444; - --high: #f59e0b; -} - -html, -body { - height: 100%; -} - -body { - font-family: "Noto Sans KR", sans-serif; - background: var(--bg); - color: var(--text); - overflow: hidden; -} - -#root { - height: 100%; -} - -.app { - display: grid; - grid-template-columns: 310px 1fr; - grid-template-rows: 44px 1fr; - height: 100vh; -} - -.topbar { - grid-column: 1/-1; - background: var(--panel); - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - padding: 0 14px; - gap: 10px; - z-index: 1000; -} - -.topbar .logo { - font-size: 14px; - font-weight: 800; - display: flex; - align-items: center; - gap: 6px; - white-space: nowrap; -} - -.topbar .logo span { - color: var(--accent); -} - -.topbar .stats { - display: flex; - gap: 14px; - margin-left: auto; - flex-wrap: wrap; - justify-content: flex-end; -} - -.topbar .stat { - font-size: 10px; - color: var(--muted); - display: flex; - align-items: center; - gap: 4px; -} - -.topbar .stat b { - color: var(--text); - font-size: 12px; -} - -.topbar .time { - font-size: 10px; - color: var(--accent); - font-weight: 600; - margin-left: 10px; - white-space: nowrap; -} - -.sidebar { - background: var(--panel); - border-right: 1px solid var(--border); - overflow-y: auto; - display: flex; - flex-direction: column; -} - -.map-area { - position: relative; - background: #010610; -} - -.sb { - padding: 10px 12px; - border-bottom: 1px solid var(--border); -} - -.sb-t { - font-size: 9px; - font-weight: 700; - color: var(--muted); - letter-spacing: 1.5px; - text-transform: uppercase; - margin-bottom: 6px; -} - -.sb-t-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.relation-sort { - display: flex; - align-items: center; - gap: 6px; - font-size: 8px; - color: var(--muted); - white-space: nowrap; -} - -.relation-sort__option { - display: inline-flex; - align-items: center; - gap: 3px; - cursor: pointer; - user-select: none; -} - -/* Type grid */ -.tg { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 3px; -} - -.tb { - background: var(--card); - border: 1px solid transparent; - border-radius: 5px; - padding: 4px; - cursor: pointer; - text-align: center; - transition: all 0.15s; - user-select: none; -} - -.tb:hover { - border-color: var(--border); -} - -.tb.on { - border-color: var(--accent); - background: rgba(59, 130, 246, 0.1); -} - -.tb .c { - font-size: 11px; - font-weight: 800; -} - -.tb .n { - font-size: 8px; - color: var(--muted); -} - -/* Speed bar */ -.sbar { - position: relative; - height: 24px; - background: var(--bg); - border-radius: 5px; - overflow: hidden; - margin: 4px 0; -} - -.sseg { - position: absolute; - border-radius: 3px; - display: flex; - align-items: center; - justify-content: center; - font-size: 7px; - color: #fff; - font-weight: 600; -} - -.sseg.p { - height: 24px; - top: 0; - border: 1.5px solid rgba(255, 255, 255, 0.25); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); -} - -.sseg:not(.p) { - height: 16px; - top: 4px; - opacity: 0.6; -} - -/* Vessel list */ -.vlist { - flex: 1; - min-height: 0; - overflow-y: auto; -} - -.vi { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 3px; - cursor: pointer; - font-size: 10px; - transition: background 0.1s; - user-select: none; -} - -.vi:hover { - background: var(--card); -} - -.vi.sel { - background: rgba(14, 234, 255, 0.16); - border-color: rgba(14, 234, 255, 0.55); -} - -.vi.hl { - background: rgba(245, 158, 11, 0.16); - border: 1px solid rgba(245, 158, 11, 0.4); -} - -.vi.sel.hl { - background: linear-gradient(90deg, rgba(14, 234, 255, 0.16), rgba(245, 158, 11, 0.16)); - border-color: rgba(14, 234, 255, 0.7); -} - -.vi .dot { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; -} - -.vi .nm { - flex: 1; - font-weight: 500; -} - -.vi .sp { - font-weight: 700; - font-size: 9px; -} - -.vi .st { - font-size: 7px; - padding: 1px 3px; - border-radius: 2px; - font-weight: 600; -} - -/* AIS target list */ -.ais-q { - flex: 1; - font-size: 10px; - padding: 6px 8px; - border-radius: 6px; - border: 1px solid var(--border); - background: rgba(30, 41, 59, 0.75); - color: var(--text); - outline: none; -} - -.ais-q::placeholder { - color: rgba(100, 116, 139, 0.9); -} - -.ais-mode { - display: flex; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; -} - -.ais-mode-btn { - font-size: 9px; - padding: 0 8px; - height: 28px; - border: none; - background: var(--card); - color: var(--muted); - cursor: pointer; -} - -.ais-mode-btn.on { - background: rgba(59, 130, 246, 0.18); - color: var(--text); -} - -.ais-list { - flex: 1; - min-height: 0; - overflow-y: auto; -} - -.ais-row { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 6px; - border-radius: 6px; - cursor: pointer; - user-select: none; - transition: background 0.12s, border-color 0.12s; - border: 1px solid transparent; -} - -.ais-row:hover { - background: rgba(30, 41, 59, 0.6); - border-color: rgba(30, 58, 95, 0.8); -} - -.ais-row.sel { - background: rgba(59, 130, 246, 0.14); - border-color: rgba(59, 130, 246, 0.55); -} - -.ais-dot { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; -} - -.ais-nm { - flex: 1; - min-width: 0; -} - -.ais-nm1 { - font-size: 10px; - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ais-nm2 { - font-size: 9px; - color: var(--muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ais-right { - text-align: right; - flex-shrink: 0; -} - -.ais-badges { - display: flex; - gap: 4px; - justify-content: flex-end; - margin-bottom: 2px; - flex-wrap: wrap; -} - -.ais-badge { - font-size: 8px; - padding: 1px 5px; - border-radius: 5px; - border: 1px solid rgba(255, 255, 255, 0.08); - font-weight: 800; - letter-spacing: 0.2px; - color: #fff; - background: rgba(100, 116, 139, 0.22); -} - -.ais-badge.pn { - color: var(--muted); - background: rgba(30, 41, 59, 0.55); - border-color: rgba(30, 58, 95, 0.9); - font-weight: 700; -} - -.ais-badge.PT { - background: rgba(30, 64, 175, 0.28); - border-color: rgba(30, 64, 175, 0.7); -} -.ais-badge.PT-S { - background: rgba(234, 88, 12, 0.22); - border-color: rgba(234, 88, 12, 0.7); -} -.ais-badge.GN { - background: rgba(16, 185, 129, 0.22); - border-color: rgba(16, 185, 129, 0.7); -} -.ais-badge.OT { - background: rgba(139, 92, 246, 0.22); - border-color: rgba(139, 92, 246, 0.7); -} -.ais-badge.PS { - background: rgba(239, 68, 68, 0.22); - border-color: rgba(239, 68, 68, 0.7); -} -.ais-badge.FC { - background: rgba(245, 158, 11, 0.22); - border-color: rgba(245, 158, 11, 0.7); -} - -.ais-sp { - font-size: 10px; - font-weight: 800; -} - -.ais-ts { - font-size: 9px; - color: rgba(100, 116, 139, 0.9); -} - -/* Alarm */ -.ai { - display: flex; - gap: 6px; - padding: 4px 6px; - border-radius: 3px; - margin-bottom: 2px; - font-size: 9px; - border-left: 3px solid; -} - -.ai.cr { - border-color: var(--crit); - background: rgba(239, 68, 68, 0.07); -} - -.ai.hi { - border-color: var(--high); - background: rgba(245, 158, 11, 0.05); -} - -.ai .at { - color: var(--muted); - font-size: 8px; - white-space: nowrap; -} - -/* Alarm filter (dropdown) */ -.alarm-filter { - position: relative; -} - -.alarm-filter__summary { - list-style: none; - cursor: pointer; - padding: 2px 8px; - border-radius: 6px; - border: 1px solid var(--border); - background: rgba(30, 41, 59, 0.55); - color: var(--text); - font-size: 8px; - font-weight: 700; - letter-spacing: 0.4px; - user-select: none; - white-space: nowrap; -} - -.alarm-filter__summary::-webkit-details-marker { - display: none; -} - -.alarm-filter__menu { - position: absolute; - right: 0; - top: 22px; - z-index: 2000; - min-width: 170px; - padding: 6px; - border-radius: 10px; - border: 1px solid var(--border); - background: rgba(15, 23, 42, 0.98); - box-shadow: 0 16px 50px rgba(0, 0, 0, 0.55); -} - -.alarm-filter__row { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 6px; - cursor: pointer; - font-size: 10px; - color: var(--text); - user-select: none; -} - -.alarm-filter__row:hover { - background: rgba(59, 130, 246, 0.08); -} - -.alarm-filter__row input { - cursor: pointer; -} - -.alarm-filter__cnt { - margin-left: auto; - font-size: 9px; - color: var(--muted); -} - -.alarm-filter__sep { - height: 1px; - background: rgba(30, 58, 95, 0.85); - margin: 4px 0; -} - -/* Relation panel */ -.rel-panel { - background: var(--card); - border-radius: 6px; - padding: 8px; - margin-top: 4px; -} - -.rel-header { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 6px; -} - -.rel-badge { - font-size: 9px; - padding: 1px 5px; - border-radius: 3px; - font-weight: 700; -} - -.rel-line { - display: flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 2px 0; -} - -.rel-line .dot { - width: 6px; - height: 6px; - border-radius: 50%; - flex-shrink: 0; -} - -.rel-link { - width: 20px; - display: flex; - align-items: center; - justify-content: center; - color: var(--muted); - font-size: 10px; -} - -.rel-dist { - font-size: 8px; - padding: 1px 4px; - border-radius: 2px; - font-weight: 600; -} - -/* Fleet network */ -.fleet-card { - background: rgba(30, 41, 59, 0.8); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px; - margin-bottom: 4px; -} - -.fleet-card.hl, -.fleet-card:hover { - border-color: rgba(245, 158, 11, 0.75); - background: rgba(251, 191, 36, 0.09); - box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.25) inset; -} - -.fleet-owner { - font-size: 10px; - font-weight: 700; - color: var(--accent); - margin-bottom: 4px; -} - -.fleet-owner.hl { - color: rgba(245, 158, 11, 1); -} - -.fleet-vessel { - display: flex; - align-items: center; - gap: 4px; - font-size: 9px; - padding: 1px 0; -} - -.fleet-vessel.hl { - color: rgba(245, 158, 11, 1); -} - -.fleet-dot.hl { - box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.45); -} - -/* Toggles */ -.tog { - display: flex; - gap: 3px; - flex-wrap: wrap; - margin-bottom: 6px; -} - -.tog.tog-map { - /* Keep "지도 표시 설정" buttons in a predictable 2-row layout (4 columns). */ - gap: 4px; -} - -.tog.tog-map .tog-btn { - flex: 1 1 calc(25% - 4px); - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.tog-btn { - font-size: 8px; - padding: 2px 6px; - border-radius: 3px; - border: 1px solid var(--border); - background: var(--card); - color: var(--muted); - cursor: pointer; - transition: all 0.15s; - user-select: none; -} - -.tog-btn.on { - background: var(--accent); - color: #fff; - border-color: var(--accent); -} - -/* Map panels */ -.map-legend { - position: absolute; - /* Keep attribution visible (bottom-right) for licensing/compliance. */ - bottom: 44px; - right: 12px; - z-index: 800; - background: rgba(15, 23, 42, 0.92); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 10px; - font-size: 9px; - min-width: 180px; -} - -.map-legend .lt { - font-size: 8px; - font-weight: 700; - color: var(--muted); - margin-bottom: 4px; - letter-spacing: 1px; -} - -.map-legend .li { - display: flex; - align-items: center; - gap: 5px; - margin-bottom: 2px; -} - -.map-legend .ls { - width: 12px; - height: 12px; - border-radius: 3px; - flex-shrink: 0; -} - -.map-info { - position: absolute; - top: 12px; - right: 12px; - z-index: 800; - background: rgba(15, 23, 42, 0.95); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - width: 270px; -} - -.map-info .ir { - display: flex; - justify-content: space-between; - font-size: 10px; - padding: 2px 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.03); -} - -.map-info .il { - color: var(--muted); -} - -.map-info .iv { - font-weight: 600; -} - -.map-loader-overlay { - position: absolute; - inset: 0; - z-index: 950; - display: flex; - align-items: center; - justify-content: center; - background: rgba(2, 6, 23, 0.42); - pointer-events: auto; -} - -.map-loader-overlay__panel { - width: min(72vw, 320px); - background: rgba(15, 23, 42, 0.94); - border: 1px solid var(--border); - border-radius: 12px; - padding: 14px 16px; - display: grid; - gap: 10px; - justify-items: center; -} - -.map-loader-overlay__spinner { - width: 28px; - height: 28px; - border: 3px solid rgba(148, 163, 184, 0.28); - border-top-color: var(--accent); - border-radius: 50%; - animation: map-loader-spin 0.7s linear infinite; -} - -.map-loader-overlay__text { - font-size: 12px; - color: var(--text); - letter-spacing: 0.2px; -} - -.map-loader-overlay__bar { - width: 100%; - height: 6px; - border-radius: 999px; - background: rgba(148, 163, 184, 0.2); - overflow: hidden; - position: relative; -} - -.map-loader-overlay__fill { - width: 28%; - height: 100%; - border-radius: inherit; - background: var(--accent); - animation: map-loader-fill 1.2s ease-in-out infinite; -} - -.maplibre-tooltip-popup .maplibregl-popup-content { - color: #f8fafc !important; - background: rgba(2, 6, 23, 0.98) !important; - border: 1px solid rgba(148, 163, 184, 0.4) !important; - box-shadow: 0 8px 26px rgba(2, 6, 23, 0.55) !important; - border-radius: 8px !important; - font-size: 11px !important; - line-height: 1.35 !important; - padding: 7px 9px !important; - color: #f8fafc !important; - min-width: 180px; -} - -.maplibre-tooltip-popup .maplibregl-popup-tip { - border-top-color: rgba(2, 6, 23, 0.97) !important; -} - -.maplibre-tooltip-popup__content { - color: #f8fafc; - font-family: Pretendard, Inter, ui-sans-serif, -apple-system, Segoe UI, sans-serif; - font-size: 11px; - line-height: 1.35; -} - -.maplibre-tooltip-popup__content div, -.maplibre-tooltip-popup__content span, -.maplibre-tooltip-popup__content p { - color: inherit; -} - -.maplibre-tooltip-popup__content div { - word-break: break-word; -} - -.maplibre-tooltip-popup .maplibregl-popup-content div, -.maplibre-tooltip-popup .maplibregl-popup-content span, -.maplibre-tooltip-popup .maplibregl-popup-content p { - color: inherit !important; -} - -.maplibre-tooltip-popup .maplibregl-popup-close-button { - color: #94a3b8 !important; -} - -@keyframes map-loader-spin { - to { - transform: rotate(360deg); - } -} - -@keyframes map-loader-fill { - 0% { - transform: translateX(-40%); - } - - 50% { - transform: translateX(220%); - } - - 100% { - transform: translateX(-40%); - } -} - -.close-btn { - position: absolute; - top: 6px; - right: 8px; - background: none; - border: none; - color: var(--muted); - cursor: pointer; - font-size: 13px; -} - -.month-row { - display: flex; - gap: 1px; -} - -.month-cell { - flex: 1; - height: 12px; - border-radius: 2px; - display: flex; - align-items: center; - justify-content: center; - font-size: 6px; - font-weight: 600; -} - -::-webkit-scrollbar { - width: 3px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 2px; -} - -.maplibregl-ctrl-group { - border: 1px solid var(--border) !important; - background: rgba(15, 23, 42, 0.92) !important; - backdrop-filter: blur(8px); -} - -.maplibregl-ctrl-group button { - background: transparent !important; -} - -.maplibregl-ctrl-group button + button { - border-top: 1px solid var(--border) !important; -} - -.maplibregl-ctrl-group button span { - filter: invert(1); - opacity: 0.9; -} - -.maplibregl-ctrl-attrib { - font-size: 10px !important; - background: rgba(15, 23, 42, 0.75) !important; - color: var(--text) !important; - border: 1px solid var(--border) !important; - border-radius: 8px; -} - -/* ── Map Settings Panel ────────────────────────────────────────────── */ - -.map-settings-gear { - position: absolute; - top: 100px; - left: 10px; - z-index: 850; - width: 29px; - height: 29px; - border-radius: 4px; - border: 1px solid var(--border); - background: rgba(15, 23, 42, 0.92); - backdrop-filter: blur(8px); - color: var(--muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - transition: color 0.15s, border-color 0.15s; - user-select: none; - padding: 0; -} - -.map-settings-gear:hover { - color: var(--text); - border-color: var(--accent); -} - -.map-settings-gear.open { - color: var(--accent); - border-color: var(--accent); -} - -.map-settings-panel { - position: absolute; - top: 10px; - left: 48px; - z-index: 850; - background: rgba(15, 23, 42, 0.95); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - width: 240px; - max-height: calc(100vh - 80px); - overflow-y: auto; -} - -.map-settings-panel .ms-title { - font-size: 11px; - font-weight: 700; - color: var(--text); - letter-spacing: 1px; - margin-bottom: 10px; -} - -.map-settings-panel .ms-section { - margin-bottom: 10px; -} - -.map-settings-panel .ms-label { - font-size: 10px; - font-weight: 600; - color: var(--text); - letter-spacing: 0.5px; - margin-bottom: 5px; -} - -.map-settings-panel .ms-row { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; -} - -.map-settings-panel .ms-color-input { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 4px; - padding: 0; - cursor: pointer; - background: transparent; - flex-shrink: 0; -} - -.map-settings-panel .ms-color-input::-webkit-color-swatch-wrapper { - padding: 1px; -} - -.map-settings-panel .ms-color-input::-webkit-color-swatch { - border: none; - border-radius: 2px; -} - -.map-settings-panel .ms-hex { - font-size: 9px; - color: #94a3b8; - font-family: monospace; -} - -.map-settings-panel .ms-depth-label { - font-size: 10px; - color: var(--text); - min-width: 48px; - text-align: right; -} - -.map-settings-panel select { - font-size: 10px; - padding: 4px 8px; - border-radius: 4px; - border: 1px solid var(--border); - background: var(--card); - color: var(--text); - cursor: pointer; - outline: none; - width: 100%; -} - -.map-settings-panel select:focus { - border-color: var(--accent); -} - -.map-settings-panel .ms-reset { - width: 100%; - font-size: 9px; - padding: 5px 8px; - border-radius: 4px; - border: 1px solid var(--border); - background: var(--card); - color: var(--muted); - cursor: pointer; - transition: all 0.15s; - margin-top: 4px; -} - -.map-settings-panel .ms-reset:hover { - color: var(--text); - border-color: var(--accent); -} - -/* ── Depth Legend ──────────────────────────────────────────────────── */ - -.depth-legend { - position: absolute; - bottom: 44px; - left: 10px; - z-index: 800; - background: rgba(15, 23, 42, 0.92); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px; - display: flex; - gap: 6px; - align-items: stretch; -} - -.depth-legend__bar { - width: 14px; - border-radius: 3px; - min-height: 120px; -} - -.depth-legend__ticks { - display: flex; - flex-direction: column; - justify-content: space-between; - font-size: 8px; - color: var(--muted); - font-family: monospace; - padding: 1px 0; -} - -/* ── Auth pages ──────────────────────────────────────────────────── */ - -.auth-page { - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #020617 0%, #0f172a 50%, #020617 100%); -} - -.auth-card { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 16px; - padding: 40px 36px; - width: 360px; - text-align: center; -} - -.auth-logo { - font-size: 36px; - font-weight: 900; - color: var(--accent); - letter-spacing: 4px; - margin-bottom: 4px; -} - -.auth-title { - font-size: 16px; - font-weight: 700; - color: var(--text); - margin-bottom: 8px; -} - -.auth-subtitle { - font-size: 12px; - color: var(--muted); - margin-bottom: 24px; -} - -.auth-error { - font-size: 11px; - color: var(--crit); - background: rgba(239, 68, 68, 0.08); - border: 1px solid rgba(239, 68, 68, 0.2); - border-radius: 8px; - padding: 8px 12px; - margin-bottom: 16px; -} - -.auth-google-btn { - display: flex; - justify-content: center; - margin-bottom: 12px; -} - -.auth-dev-btn { - width: 100%; - padding: 10px; - border-radius: 8px; - border: 1px solid var(--border); - background: var(--card); - color: var(--muted); - font-size: 12px; - cursor: pointer; - transition: all 0.15s; - margin-bottom: 12px; -} - -.auth-dev-btn:hover { - color: var(--text); - border-color: var(--accent); -} - -.auth-footer { - font-size: 10px; - color: var(--muted); - margin-top: 16px; -} - -.auth-status-icon { - font-size: 48px; - margin-bottom: 12px; -} - -.auth-message { - font-size: 13px; - color: var(--muted); - line-height: 1.6; - margin-bottom: 16px; -} - -.auth-message b { - color: var(--text); -} - -.auth-link-btn { - background: none; - border: none; - color: var(--accent); - font-size: 12px; - cursor: pointer; - text-decoration: underline; - padding: 4px 8px; -} - -.auth-link-btn:hover { - color: var(--text); -} - -.auth-loading { - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg); -} - -.auth-loading__spinner { - width: 32px; - height: 32px; - border: 3px solid rgba(148, 163, 184, 0.28); - border-top-color: var(--accent); - border-radius: 50%; - animation: map-loader-spin 0.7s linear infinite; -} - -/* ── Topbar user ─────────────────────────────────────────────────── */ - -.topbar-user { - display: flex; - align-items: center; - gap: 8px; - margin-left: 10px; - flex-shrink: 0; -} - -.topbar-user__name { - font-size: 10px; - color: var(--text); - font-weight: 500; - white-space: nowrap; -} - -.topbar-user__logout { - font-size: 9px; - color: var(--muted); - background: none; - border: 1px solid var(--border); - border-radius: 3px; - padding: 2px 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; -} - -.topbar-user__logout:hover { - color: var(--text); - border-color: var(--accent); -} - -/* ── Weather Overlay Panel ─────────────────────────────────── */ - -.weather-gear { - position: absolute; - top: 140px; - left: 10px; - z-index: 850; - width: 29px; - height: 29px; - border-radius: 4px; - border: 1px solid var(--border); - background: rgba(15, 23, 42, 0.92); - backdrop-filter: blur(8px); - color: var(--muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - transition: color 0.15s, border-color 0.15s; - user-select: none; - padding: 0; -} - -.weather-gear:hover { - color: var(--text); - border-color: var(--accent); -} - -.weather-gear.open { - color: var(--accent); - border-color: var(--accent); -} - -.weather-panel { - position: absolute; - top: 130px; - left: 48px; - z-index: 850; - background: rgba(15, 23, 42, 0.95); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 10px; - width: 260px; - max-height: calc(100vh - 200px); - overflow-y: auto; -} - -.wp-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.wp-title { - font-size: 11px; - font-weight: 700; - color: var(--text); - letter-spacing: 1px; -} - -.wp-loading { - font-size: 9px; - color: var(--accent); -} - -.wp-error { - font-size: 9px; - color: #f87171; - margin-bottom: 6px; - padding: 4px 6px; - background: rgba(248, 113, 113, 0.1); - border-radius: 4px; -} - -.wp-empty { - font-size: 10px; - color: var(--muted); - text-align: center; - padding: 12px 0; -} - -.wz-card { - border-left: 3px solid var(--border); - padding: 6px 8px; - margin-bottom: 6px; - border-radius: 0 4px 4px 0; - background: rgba(255, 255, 255, 0.03); - transition: background 0.15s; -} - -.wz-card:last-of-type { - margin-bottom: 0; -} - -.wz-card.wz-warn { - background: rgba(248, 113, 113, 0.08); -} - -.wz-name { - font-size: 10px; - font-weight: 600; - color: var(--text); - margin-bottom: 4px; - letter-spacing: 0.3px; -} - -.wz-row { - display: flex; - gap: 10px; - flex-wrap: wrap; - margin-bottom: 2px; -} - -.wz-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 10px; - color: var(--muted); - white-space: nowrap; -} - -.wz-icon { - font-size: 10px; - color: var(--accent); -} - -.wz-label { - font-size: 9px; - color: var(--muted); -} - -.wz-value { - font-size: 10px; - font-weight: 600; - color: var(--text); -} - -.wz-weather { - font-weight: 500; - color: var(--muted); -} - -.wp-footer { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 8px; - padding-top: 6px; - border-top: 1px solid var(--border); -} - -.wp-time { - font-size: 9px; - color: var(--muted); -} - -.wp-refresh { - font-size: 14px; - color: var(--muted); - background: none; - border: 1px solid var(--border); - border-radius: 3px; - width: 22px; - height: 22px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: color 0.15s, border-color 0.15s; - padding: 0; -} - -.wp-refresh:hover { - color: var(--text); - border-color: var(--accent); -} - -.wp-refresh:disabled { - opacity: 0.5; - cursor: default; -} - -/* ── Weather Overlay Panel (MapTiler) ────────────────────────────── */ - -.wo-gear { - position: absolute; - top: 180px; - left: 10px; - z-index: 850; - width: 29px; - height: 29px; - border-radius: 4px; - border: 1px solid var(--border); - background: rgba(15, 23, 42, 0.92); - backdrop-filter: blur(8px); - color: var(--muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - transition: color 0.15s, border-color 0.15s; - user-select: none; - padding: 0; -} - -.wo-gear:hover { - color: var(--text); - border-color: var(--accent); -} - -.wo-gear.open { - color: var(--accent); - border-color: var(--accent); -} - -.wo-gear.active { - border-color: #22c55e; -} - -.wo-gear.active.open { - border-color: var(--accent); -} - -.wo-gear-badge { - position: absolute; - top: -4px; - right: -4px; - background: #22c55e; - color: #fff; - font-size: 8px; - font-weight: 700; - width: 14px; - height: 14px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -.wo-stack { - position: absolute; - top: 170px; - left: 48px; - z-index: 850; - display: flex; - flex-direction: column; - gap: 6px; - width: 280px; - pointer-events: none; -} -.wo-stack > * { - pointer-events: auto; -} - -.wo-panel { - background: rgba(15, 23, 42, 0.95); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 10px; - width: 100%; - max-height: calc(100vh - 240px); - overflow-y: auto; -} - -.wo-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.wo-title { - font-size: 11px; - font-weight: 700; - color: var(--text); - letter-spacing: 1px; -} - -.wo-loading { - font-size: 9px; - color: var(--accent); - animation: wo-pulse 1.2s ease-in-out infinite; -} - -@keyframes wo-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } -} - -.wo-layers { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 4px; - margin-bottom: 10px; -} - -.wo-layer-btn { - display: flex; - flex-direction: column; - align-items: center; - gap: 2px; - padding: 6px 4px; - border-radius: 6px; - border: 1px solid var(--border); - background: rgba(255, 255, 255, 0.03); - color: var(--muted); - cursor: pointer; - transition: all 0.15s; - font-size: 10px; -} - -.wo-layer-btn:hover { - background: rgba(255, 255, 255, 0.06); - color: var(--text); - border-color: rgba(59, 130, 246, 0.4); -} - -.wo-layer-btn.on { - background: rgba(59, 130, 246, 0.15); - color: var(--text); - border-color: var(--accent); -} - -.wo-layer-icon { - font-size: 16px; - line-height: 1; -} - -.wo-layer-name { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.3px; -} - -.wo-section { - margin-bottom: 8px; -} - -.wo-label { - font-size: 9px; - font-weight: 600; - color: var(--text); - margin-bottom: 4px; - display: flex; - align-items: center; - justify-content: space-between; -} - -.wo-val { - font-weight: 400; - color: var(--muted); - font-size: 9px; -} -.wo-offset { - color: #4fc3f7; - font-weight: 600; -} - -.wo-slider { - width: 100%; - height: 4px; - -webkit-appearance: none; - appearance: none; - background: var(--border); - border-radius: 2px; - outline: none; - cursor: pointer; -} - -.wo-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - border: 2px solid var(--panel); - cursor: pointer; -} - -.wo-slider::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - border: 2px solid var(--panel); - cursor: pointer; -} - -.wo-slider:disabled { - opacity: 0.4; - cursor: default; -} - -.wo-timeline { - padding-top: 8px; - border-top: 1px solid var(--border); -} - -.wo-step-slider-wrap { - position: relative; - padding-bottom: 10px; -} - -.wo-time-slider { - margin-bottom: 2px; -} - -.wo-step-ticks { - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 8px; - pointer-events: none; -} - -.wo-step-tick { - position: absolute; - bottom: 0; - width: 1px; - height: 4px; - background: var(--muted); - opacity: 0.4; - transform: translateX(-0.5px); -} - -.wo-step-tick.day { - height: 8px; - opacity: 0.8; - background: var(--accent); -} - -.wo-time-range { - display: flex; - justify-content: space-between; - font-size: 8px; - color: var(--muted); - margin-bottom: 6px; -} - -.wo-playback { - display: flex; - align-items: center; - gap: 6px; -} - -.wo-play-btn { - width: 26px; - height: 26px; - border-radius: 50%; - border: 1px solid var(--border); - background: var(--card); - color: var(--text); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - transition: all 0.15s; - padding: 0; - flex-shrink: 0; -} - -.wo-play-btn:hover { - border-color: var(--accent); - background: rgba(59, 130, 246, 0.12); -} - -.wo-play-btn:disabled { - opacity: 0.4; - cursor: default; -} - -.wo-speed-btns { - display: flex; - gap: 2px; -} - -.wo-speed-btn { - font-size: 8px; - padding: 3px 6px; - border-radius: 3px; - border: 1px solid var(--border); - background: transparent; - color: var(--muted); - cursor: pointer; - transition: all 0.15s; -} - -.wo-speed-btn:hover { - color: var(--text); - border-color: rgba(59, 130, 246, 0.4); -} - -.wo-speed-btn.on { - background: rgba(59, 130, 246, 0.15); - color: var(--text); - border-color: var(--accent); -} - -.wo-hint { - font-size: 8px; - color: var(--muted); - text-align: right; - margin-top: 4px; - opacity: 0.6; -} - -/* ── Weather Legend ── */ -.wo-legend { - background: rgba(15, 23, 42, 0.85); - backdrop-filter: blur(8px); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px 4px; - width: 100%; -} -.wo-legend-header { - font-size: 9px; - color: var(--muted); - margin-bottom: 4px; - text-align: center; -} -.wo-legend-bar { - height: 10px; - border-radius: 3px; - width: 100%; -} -.wo-legend-ticks { - display: flex; - justify-content: space-between; - font-size: 8px; - color: var(--muted); - margin-top: 2px; -} - -@media (max-width: 920px) { - .app { - grid-template-columns: 1fr; - grid-template-rows: 44px 1fr; - } - - .sidebar { - display: none; - } -} +/* ── Wing Fleet Dashboard – Style Entry Point ─────────────────── */ + +@import "./styles/base.css"; +@import "./styles/layout.css"; + +/* Components */ +@import "./styles/components/topbar.css"; +@import "./styles/components/panels.css"; +@import "./styles/components/toggles.css"; +@import "./styles/components/speed.css"; +@import "./styles/components/vessel-list.css"; +@import "./styles/components/ais-list.css"; +@import "./styles/components/alarms.css"; +@import "./styles/components/relations.css"; +@import "./styles/components/map-panels.css"; +@import "./styles/components/map-settings.css"; +@import "./styles/components/auth.css"; +@import "./styles/components/weather.css"; +@import "./styles/components/weather-overlay.css"; diff --git a/apps/web/src/app/styles/base.css b/apps/web/src/app/styles/base.css new file mode 100644 index 0000000..1902d59 --- /dev/null +++ b/apps/web/src/app/styles/base.css @@ -0,0 +1,36 @@ +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;800;900&display=swap"); + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --bg: #020617; + --panel: #0f172a; + --card: #1e293b; + --border: #1e3a5f; + --text: #e2e8f0; + --muted: #64748b; + --accent: #3b82f6; + + --crit: #ef4444; + --high: #f59e0b; +} + +html, +body { + height: 100%; +} + +body { + font-family: "Noto Sans KR", sans-serif; + background: var(--bg); + color: var(--text); + overflow: hidden; +} + +#root { + height: 100%; +} diff --git a/apps/web/src/app/styles/components/ais-list.css b/apps/web/src/app/styles/components/ais-list.css new file mode 100644 index 0000000..3897468 --- /dev/null +++ b/apps/web/src/app/styles/components/ais-list.css @@ -0,0 +1,159 @@ +/* AIS target list */ +.ais-q { + flex: 1; + font-size: 10px; + padding: 6px 8px; + border-radius: 6px; + border: 1px solid var(--border); + background: rgba(30, 41, 59, 0.75); + color: var(--text); + outline: none; +} + +.ais-q::placeholder { + color: rgba(100, 116, 139, 0.9); +} + +.ais-mode { + display: flex; + border: 1px solid var(--border); + border-radius: 6px; + overflow: hidden; +} + +.ais-mode-btn { + font-size: 9px; + padding: 0 8px; + height: 28px; + border: none; + background: var(--card); + color: var(--muted); + cursor: pointer; +} + +.ais-mode-btn.on { + background: rgba(59, 130, 246, 0.18); + color: var(--text); +} + +.ais-list { + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.ais-row { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 6px; + border-radius: 6px; + cursor: pointer; + user-select: none; + transition: background 0.12s, border-color 0.12s; + border: 1px solid transparent; +} + +.ais-row:hover { + background: rgba(30, 41, 59, 0.6); + border-color: rgba(30, 58, 95, 0.8); +} + +.ais-row.sel { + background: rgba(59, 130, 246, 0.14); + border-color: rgba(59, 130, 246, 0.55); +} + +.ais-dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.ais-nm { + flex: 1; + min-width: 0; +} + +.ais-nm1 { + font-size: 10px; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ais-nm2 { + font-size: 9px; + color: var(--muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ais-right { + text-align: right; + flex-shrink: 0; +} + +.ais-badges { + display: flex; + gap: 4px; + justify-content: flex-end; + margin-bottom: 2px; + flex-wrap: wrap; +} + +.ais-badge { + font-size: 8px; + padding: 1px 5px; + border-radius: 5px; + border: 1px solid rgba(255, 255, 255, 0.08); + font-weight: 800; + letter-spacing: 0.2px; + color: #fff; + background: rgba(100, 116, 139, 0.22); +} + +.ais-badge.pn { + color: var(--muted); + background: rgba(30, 41, 59, 0.55); + border-color: rgba(30, 58, 95, 0.9); + font-weight: 700; +} + +.ais-badge.PT { + background: rgba(30, 64, 175, 0.28); + border-color: rgba(30, 64, 175, 0.7); +} +.ais-badge.PT-S { + background: rgba(234, 88, 12, 0.22); + border-color: rgba(234, 88, 12, 0.7); +} +.ais-badge.GN { + background: rgba(16, 185, 129, 0.22); + border-color: rgba(16, 185, 129, 0.7); +} +.ais-badge.OT { + background: rgba(139, 92, 246, 0.22); + border-color: rgba(139, 92, 246, 0.7); +} +.ais-badge.PS { + background: rgba(239, 68, 68, 0.22); + border-color: rgba(239, 68, 68, 0.7); +} +.ais-badge.FC { + background: rgba(245, 158, 11, 0.22); + border-color: rgba(245, 158, 11, 0.7); +} + +.ais-sp { + font-size: 10px; + font-weight: 800; +} + +.ais-ts { + font-size: 9px; + color: rgba(100, 116, 139, 0.9); +} diff --git a/apps/web/src/app/styles/components/alarms.css b/apps/web/src/app/styles/components/alarms.css new file mode 100644 index 0000000..0b98003 --- /dev/null +++ b/apps/web/src/app/styles/components/alarms.css @@ -0,0 +1,95 @@ +/* Alarm */ +.ai { + display: flex; + gap: 6px; + padding: 4px 6px; + border-radius: 3px; + margin-bottom: 2px; + font-size: 9px; + border-left: 3px solid; +} + +.ai.cr { + border-color: var(--crit); + background: rgba(239, 68, 68, 0.07); +} + +.ai.hi { + border-color: var(--high); + background: rgba(245, 158, 11, 0.05); +} + +.ai .at { + color: var(--muted); + font-size: 8px; + white-space: nowrap; +} + +/* Alarm filter (dropdown) */ +.alarm-filter { + position: relative; +} + +.alarm-filter__summary { + list-style: none; + cursor: pointer; + padding: 2px 8px; + border-radius: 6px; + border: 1px solid var(--border); + background: rgba(30, 41, 59, 0.55); + color: var(--text); + font-size: 8px; + font-weight: 700; + letter-spacing: 0.4px; + user-select: none; + white-space: nowrap; +} + +.alarm-filter__summary::-webkit-details-marker { + display: none; +} + +.alarm-filter__menu { + position: absolute; + right: 0; + top: 22px; + z-index: 2000; + min-width: 170px; + padding: 6px; + border-radius: 10px; + border: 1px solid var(--border); + background: rgba(15, 23, 42, 0.98); + box-shadow: 0 16px 50px rgba(0, 0, 0, 0.55); +} + +.alarm-filter__row { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-radius: 6px; + cursor: pointer; + font-size: 10px; + color: var(--text); + user-select: none; +} + +.alarm-filter__row:hover { + background: rgba(59, 130, 246, 0.08); +} + +.alarm-filter__row input { + cursor: pointer; +} + +.alarm-filter__cnt { + margin-left: auto; + font-size: 9px; + color: var(--muted); +} + +.alarm-filter__sep { + height: 1px; + background: rgba(30, 58, 95, 0.85); + margin: 4px 0; +} diff --git a/apps/web/src/app/styles/components/auth.css b/apps/web/src/app/styles/components/auth.css new file mode 100644 index 0000000..930b931 --- /dev/null +++ b/apps/web/src/app/styles/components/auth.css @@ -0,0 +1,126 @@ +/* ── Auth pages ──────────────────────────────────────────────────── */ + +.auth-page { + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #020617 0%, #0f172a 50%, #020617 100%); +} + +.auth-card { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 16px; + padding: 40px 36px; + width: 360px; + text-align: center; +} + +.auth-logo { + font-size: 36px; + font-weight: 900; + color: var(--accent); + letter-spacing: 4px; + margin-bottom: 4px; +} + +.auth-title { + font-size: 16px; + font-weight: 700; + color: var(--text); + margin-bottom: 8px; +} + +.auth-subtitle { + font-size: 12px; + color: var(--muted); + margin-bottom: 24px; +} + +.auth-error { + font-size: 11px; + color: var(--crit); + background: rgba(239, 68, 68, 0.08); + border: 1px solid rgba(239, 68, 68, 0.2); + border-radius: 8px; + padding: 8px 12px; + margin-bottom: 16px; +} + +.auth-google-btn { + display: flex; + justify-content: center; + margin-bottom: 12px; +} + +.auth-dev-btn { + width: 100%; + padding: 10px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--card); + color: var(--muted); + font-size: 12px; + cursor: pointer; + transition: all 0.15s; + margin-bottom: 12px; +} + +.auth-dev-btn:hover { + color: var(--text); + border-color: var(--accent); +} + +.auth-footer { + font-size: 10px; + color: var(--muted); + margin-top: 16px; +} + +.auth-status-icon { + font-size: 48px; + margin-bottom: 12px; +} + +.auth-message { + font-size: 13px; + color: var(--muted); + line-height: 1.6; + margin-bottom: 16px; +} + +.auth-message b { + color: var(--text); +} + +.auth-link-btn { + background: none; + border: none; + color: var(--accent); + font-size: 12px; + cursor: pointer; + text-decoration: underline; + padding: 4px 8px; +} + +.auth-link-btn:hover { + color: var(--text); +} + +.auth-loading { + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg); +} + +.auth-loading__spinner { + width: 32px; + height: 32px; + border: 3px solid rgba(148, 163, 184, 0.28); + border-top-color: var(--accent); + border-radius: 50%; + animation: map-loader-spin 0.7s linear infinite; +} diff --git a/apps/web/src/app/styles/components/map-panels.css b/apps/web/src/app/styles/components/map-panels.css new file mode 100644 index 0000000..a2824cd --- /dev/null +++ b/apps/web/src/app/styles/components/map-panels.css @@ -0,0 +1,211 @@ +/* Map panels */ +.map-legend { + position: absolute; + /* Keep attribution visible (bottom-right) for licensing/compliance. */ + bottom: 44px; + right: 12px; + z-index: 800; + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 10px; + font-size: 9px; + min-width: 180px; +} + +.map-legend .lt { + font-size: 8px; + font-weight: 700; + color: var(--muted); + margin-bottom: 4px; + letter-spacing: 1px; +} + +.map-legend .li { + display: flex; + align-items: center; + gap: 5px; + margin-bottom: 2px; +} + +.map-legend .ls { + width: 12px; + height: 12px; + border-radius: 3px; + flex-shrink: 0; +} + +.map-info { + position: absolute; + top: 12px; + right: 12px; + z-index: 800; + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + width: 270px; +} + +.map-info .ir { + display: flex; + justify-content: space-between; + font-size: 10px; + padding: 2px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.map-info .il { + color: var(--muted); +} + +.map-info .iv { + font-weight: 600; +} + +.map-loader-overlay { + position: absolute; + inset: 0; + z-index: 950; + display: flex; + align-items: center; + justify-content: center; + background: rgba(2, 6, 23, 0.42); + pointer-events: auto; +} + +.map-loader-overlay__panel { + width: min(72vw, 320px); + background: rgba(15, 23, 42, 0.94); + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px 16px; + display: grid; + gap: 10px; + justify-items: center; +} + +.map-loader-overlay__spinner { + width: 28px; + height: 28px; + border: 3px solid rgba(148, 163, 184, 0.28); + border-top-color: var(--accent); + border-radius: 50%; + animation: map-loader-spin 0.7s linear infinite; +} + +.map-loader-overlay__text { + font-size: 12px; + color: var(--text); + letter-spacing: 0.2px; +} + +.map-loader-overlay__bar { + width: 100%; + height: 6px; + border-radius: 999px; + background: rgba(148, 163, 184, 0.2); + overflow: hidden; + position: relative; +} + +.map-loader-overlay__fill { + width: 28%; + height: 100%; + border-radius: inherit; + background: var(--accent); + animation: map-loader-fill 1.2s ease-in-out infinite; +} + +.maplibre-tooltip-popup .maplibregl-popup-content { + color: #f8fafc !important; + background: rgba(2, 6, 23, 0.98) !important; + border: 1px solid rgba(148, 163, 184, 0.4) !important; + box-shadow: 0 8px 26px rgba(2, 6, 23, 0.55) !important; + border-radius: 8px !important; + font-size: 11px !important; + line-height: 1.35 !important; + padding: 7px 9px !important; + color: #f8fafc !important; + min-width: 180px; +} + +.maplibre-tooltip-popup .maplibregl-popup-tip { + border-top-color: rgba(2, 6, 23, 0.97) !important; +} + +.maplibre-tooltip-popup__content { + color: #f8fafc; + font-family: Pretendard, Inter, ui-sans-serif, -apple-system, Segoe UI, sans-serif; + font-size: 11px; + line-height: 1.35; +} + +.maplibre-tooltip-popup__content div, +.maplibre-tooltip-popup__content span, +.maplibre-tooltip-popup__content p { + color: inherit; +} + +.maplibre-tooltip-popup__content div { + word-break: break-word; +} + +.maplibre-tooltip-popup .maplibregl-popup-content div, +.maplibre-tooltip-popup .maplibregl-popup-content span, +.maplibre-tooltip-popup .maplibregl-popup-content p { + color: inherit !important; +} + +.maplibre-tooltip-popup .maplibregl-popup-close-button { + color: #94a3b8 !important; +} + +@keyframes map-loader-spin { + to { + transform: rotate(360deg); + } +} + +@keyframes map-loader-fill { + 0% { + transform: translateX(-40%); + } + + 50% { + transform: translateX(220%); + } + + 100% { + transform: translateX(-40%); + } +} + +.maplibregl-ctrl-group { + border: 1px solid var(--border) !important; + background: rgba(15, 23, 42, 0.92) !important; + backdrop-filter: blur(8px); +} + +.maplibregl-ctrl-group button { + background: transparent !important; +} + +.maplibregl-ctrl-group button + button { + border-top: 1px solid var(--border) !important; +} + +.maplibregl-ctrl-group button span { + filter: invert(1); + opacity: 0.9; +} + +.maplibregl-ctrl-attrib { + font-size: 10px !important; + background: rgba(15, 23, 42, 0.75) !important; + color: var(--text) !important; + border: 1px solid var(--border) !important; + border-radius: 8px; +} diff --git a/apps/web/src/app/styles/components/map-settings.css b/apps/web/src/app/styles/components/map-settings.css new file mode 100644 index 0000000..23a8147 --- /dev/null +++ b/apps/web/src/app/styles/components/map-settings.css @@ -0,0 +1,175 @@ +/* ── Map Settings Panel ──────────────────────────────────────────── */ + +.map-settings-gear { + position: absolute; + top: 100px; + left: 10px; + z-index: 850; + width: 29px; + height: 29px; + border-radius: 4px; + border: 1px solid var(--border); + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(8px); + color: var(--muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: color 0.15s, border-color 0.15s; + user-select: none; + padding: 0; +} + +.map-settings-gear:hover { + color: var(--text); + border-color: var(--accent); +} + +.map-settings-gear.open { + color: var(--accent); + border-color: var(--accent); +} + +.map-settings-panel { + position: absolute; + top: 10px; + left: 48px; + z-index: 850; + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + width: 240px; + max-height: calc(100vh - 80px); + overflow-y: auto; +} + +.map-settings-panel .ms-title { + font-size: 11px; + font-weight: 700; + color: var(--text); + letter-spacing: 1px; + margin-bottom: 10px; +} + +.map-settings-panel .ms-section { + margin-bottom: 10px; +} + +.map-settings-panel .ms-label { + font-size: 10px; + font-weight: 600; + color: var(--text); + letter-spacing: 0.5px; + margin-bottom: 5px; +} + +.map-settings-panel .ms-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.map-settings-panel .ms-color-input { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 4px; + padding: 0; + cursor: pointer; + background: transparent; + flex-shrink: 0; +} + +.map-settings-panel .ms-color-input::-webkit-color-swatch-wrapper { + padding: 1px; +} + +.map-settings-panel .ms-color-input::-webkit-color-swatch { + border: none; + border-radius: 2px; +} + +.map-settings-panel .ms-hex { + font-size: 9px; + color: #94a3b8; + font-family: monospace; +} + +.map-settings-panel .ms-depth-label { + font-size: 10px; + color: var(--text); + min-width: 48px; + text-align: right; +} + +.map-settings-panel select { + font-size: 10px; + padding: 4px 8px; + border-radius: 4px; + border: 1px solid var(--border); + background: var(--card); + color: var(--text); + cursor: pointer; + outline: none; + width: 100%; +} + +.map-settings-panel select:focus { + border-color: var(--accent); +} + +.map-settings-panel .ms-reset { + width: 100%; + font-size: 9px; + padding: 5px 8px; + border-radius: 4px; + border: 1px solid var(--border); + background: var(--card); + color: var(--muted); + cursor: pointer; + transition: all 0.15s; + margin-top: 4px; +} + +.map-settings-panel .ms-reset:hover { + color: var(--text); + border-color: var(--accent); +} + +/* ── Depth Legend ──────────────────────────────────────────────────── */ + +.depth-legend { + position: absolute; + bottom: 44px; + left: 10px; + z-index: 800; + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px; + display: flex; + gap: 6px; + align-items: stretch; +} + +.depth-legend__bar { + width: 14px; + border-radius: 3px; + min-height: 120px; +} + +.depth-legend__ticks { + display: flex; + flex-direction: column; + justify-content: space-between; + font-size: 8px; + color: var(--muted); + font-family: monospace; + padding: 1px 0; +} diff --git a/apps/web/src/app/styles/components/panels.css b/apps/web/src/app/styles/components/panels.css new file mode 100644 index 0000000..f9ad84e --- /dev/null +++ b/apps/web/src/app/styles/components/panels.css @@ -0,0 +1,77 @@ +.sb { + padding: 10px 12px; + border-bottom: 1px solid var(--border); +} + +.sb-t { + font-size: 9px; + font-weight: 700; + color: var(--muted); + letter-spacing: 1.5px; + text-transform: uppercase; + margin-bottom: 6px; +} + +.sb-t-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.relation-sort { + display: flex; + align-items: center; + gap: 6px; + font-size: 8px; + color: var(--muted); + white-space: nowrap; +} + +.relation-sort__option { + display: inline-flex; + align-items: center; + gap: 3px; + cursor: pointer; + user-select: none; +} + +.close-btn { + position: absolute; + top: 6px; + right: 8px; + background: none; + border: none; + color: var(--muted); + cursor: pointer; + font-size: 13px; +} + +.month-row { + display: flex; + gap: 1px; +} + +.month-cell { + flex: 1; + height: 12px; + border-radius: 2px; + display: flex; + align-items: center; + justify-content: center; + font-size: 6px; + font-weight: 600; +} + +::-webkit-scrollbar { + width: 3px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 2px; +} diff --git a/apps/web/src/app/styles/components/relations.css b/apps/web/src/app/styles/components/relations.css new file mode 100644 index 0000000..3cc2596 --- /dev/null +++ b/apps/web/src/app/styles/components/relations.css @@ -0,0 +1,95 @@ +/* Relation panel */ +.rel-panel { + background: var(--card); + border-radius: 6px; + padding: 8px; + margin-top: 4px; +} + +.rel-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; +} + +.rel-badge { + font-size: 9px; + padding: 1px 5px; + border-radius: 3px; + font-weight: 700; +} + +.rel-line { + display: flex; + align-items: center; + gap: 4px; + font-size: 10px; + padding: 2px 0; +} + +.rel-line .dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.rel-link { + width: 20px; + display: flex; + align-items: center; + justify-content: center; + color: var(--muted); + font-size: 10px; +} + +.rel-dist { + font-size: 8px; + padding: 1px 4px; + border-radius: 2px; + font-weight: 600; +} + +/* Fleet network */ +.fleet-card { + background: rgba(30, 41, 59, 0.8); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px; + margin-bottom: 4px; +} + +.fleet-card.hl, +.fleet-card:hover { + border-color: rgba(245, 158, 11, 0.75); + background: rgba(251, 191, 36, 0.09); + box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.25) inset; +} + +.fleet-owner { + font-size: 10px; + font-weight: 700; + color: var(--accent); + margin-bottom: 4px; +} + +.fleet-owner.hl { + color: rgba(245, 158, 11, 1); +} + +.fleet-vessel { + display: flex; + align-items: center; + gap: 4px; + font-size: 9px; + padding: 1px 0; +} + +.fleet-vessel.hl { + color: rgba(245, 158, 11, 1); +} + +.fleet-dot.hl { + box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.45); +} diff --git a/apps/web/src/app/styles/components/speed.css b/apps/web/src/app/styles/components/speed.css new file mode 100644 index 0000000..284d1b1 --- /dev/null +++ b/apps/web/src/app/styles/components/speed.css @@ -0,0 +1,33 @@ +/* Speed bar */ +.sbar { + position: relative; + height: 24px; + background: var(--bg); + border-radius: 5px; + overflow: hidden; + margin: 4px 0; +} + +.sseg { + position: absolute; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + font-size: 7px; + color: #fff; + font-weight: 600; +} + +.sseg.p { + height: 24px; + top: 0; + border: 1.5px solid rgba(255, 255, 255, 0.25); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); +} + +.sseg:not(.p) { + height: 16px; + top: 4px; + opacity: 0.6; +} diff --git a/apps/web/src/app/styles/components/toggles.css b/apps/web/src/app/styles/components/toggles.css new file mode 100644 index 0000000..328818c --- /dev/null +++ b/apps/web/src/app/styles/components/toggles.css @@ -0,0 +1,75 @@ +/* Type grid */ +.tg { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 3px; +} + +.tb { + background: var(--card); + border: 1px solid transparent; + border-radius: 5px; + padding: 4px; + cursor: pointer; + text-align: center; + transition: all 0.15s; + user-select: none; +} + +.tb:hover { + border-color: var(--border); +} + +.tb.on { + border-color: var(--accent); + background: rgba(59, 130, 246, 0.1); +} + +.tb .c { + font-size: 11px; + font-weight: 800; +} + +.tb .n { + font-size: 8px; + color: var(--muted); +} + +/* Toggles */ +.tog { + display: flex; + gap: 3px; + flex-wrap: wrap; + margin-bottom: 6px; +} + +.tog.tog-map { + /* Keep "지도 표시 설정" buttons in a predictable 2-row layout (4 columns). */ + gap: 4px; +} + +.tog.tog-map .tog-btn { + flex: 1 1 calc(25% - 4px); + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tog-btn { + font-size: 8px; + padding: 2px 6px; + border-radius: 3px; + border: 1px solid var(--border); + background: var(--card); + color: var(--muted); + cursor: pointer; + transition: all 0.15s; + user-select: none; +} + +.tog-btn.on { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} diff --git a/apps/web/src/app/styles/components/topbar.css b/apps/web/src/app/styles/components/topbar.css new file mode 100644 index 0000000..dcb52c8 --- /dev/null +++ b/apps/web/src/app/styles/components/topbar.css @@ -0,0 +1,84 @@ +.topbar { + grid-column: 1/-1; + background: var(--panel); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 14px; + gap: 10px; + z-index: 1000; +} + +.topbar .logo { + font-size: 14px; + font-weight: 800; + display: flex; + align-items: center; + gap: 6px; + white-space: nowrap; +} + +.topbar .logo span { + color: var(--accent); +} + +.topbar .stats { + display: flex; + gap: 14px; + margin-left: auto; + flex-wrap: wrap; + justify-content: flex-end; +} + +.topbar .stat { + font-size: 10px; + color: var(--muted); + display: flex; + align-items: center; + gap: 4px; +} + +.topbar .stat b { + color: var(--text); + font-size: 12px; +} + +.topbar .time { + font-size: 10px; + color: var(--accent); + font-weight: 600; + margin-left: 10px; + white-space: nowrap; +} + +.topbar-user { + display: flex; + align-items: center; + gap: 8px; + margin-left: 10px; + flex-shrink: 0; +} + +.topbar-user__name { + font-size: 10px; + color: var(--text); + font-weight: 500; + white-space: nowrap; +} + +.topbar-user__logout { + font-size: 9px; + color: var(--muted); + background: none; + border: 1px solid var(--border); + border-radius: 3px; + padding: 2px 6px; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; +} + +.topbar-user__logout:hover { + color: var(--text); + border-color: var(--accent); +} diff --git a/apps/web/src/app/styles/components/vessel-list.css b/apps/web/src/app/styles/components/vessel-list.css new file mode 100644 index 0000000..11e4308 --- /dev/null +++ b/apps/web/src/app/styles/components/vessel-list.css @@ -0,0 +1,61 @@ +/* Vessel list */ +.vlist { + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.vi { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-radius: 3px; + cursor: pointer; + font-size: 10px; + transition: background 0.1s; + user-select: none; +} + +.vi:hover { + background: var(--card); +} + +.vi.sel { + background: rgba(14, 234, 255, 0.16); + border-color: rgba(14, 234, 255, 0.55); +} + +.vi.hl { + background: rgba(245, 158, 11, 0.16); + border: 1px solid rgba(245, 158, 11, 0.4); +} + +.vi.sel.hl { + background: linear-gradient(90deg, rgba(14, 234, 255, 0.16), rgba(245, 158, 11, 0.16)); + border-color: rgba(14, 234, 255, 0.7); +} + +.vi .dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.vi .nm { + flex: 1; + font-weight: 500; +} + +.vi .sp { + font-weight: 700; + font-size: 9px; +} + +.vi .st { + font-size: 7px; + padding: 1px 3px; + border-radius: 2px; + font-weight: 600; +} diff --git a/apps/web/src/app/styles/components/weather-overlay.css b/apps/web/src/app/styles/components/weather-overlay.css new file mode 100644 index 0000000..3a3e5b7 --- /dev/null +++ b/apps/web/src/app/styles/components/weather-overlay.css @@ -0,0 +1,356 @@ +/* ── Weather Overlay Panel (MapTiler) ────────────────────────────── */ + +.wo-gear { + position: absolute; + top: 180px; + left: 10px; + z-index: 850; + width: 29px; + height: 29px; + border-radius: 4px; + border: 1px solid var(--border); + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(8px); + color: var(--muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: color 0.15s, border-color 0.15s; + user-select: none; + padding: 0; +} + +.wo-gear:hover { + color: var(--text); + border-color: var(--accent); +} + +.wo-gear.open { + color: var(--accent); + border-color: var(--accent); +} + +.wo-gear.active { + border-color: #22c55e; +} + +.wo-gear.active.open { + border-color: var(--accent); +} + +.wo-gear-badge { + position: absolute; + top: -4px; + right: -4px; + background: #22c55e; + color: #fff; + font-size: 8px; + font-weight: 700; + width: 14px; + height: 14px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.wo-stack { + position: absolute; + top: 170px; + left: 48px; + z-index: 850; + display: flex; + flex-direction: column; + gap: 6px; + width: 280px; + pointer-events: none; +} +.wo-stack > * { + pointer-events: auto; +} + +.wo-panel { + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 10px; + width: 100%; + max-height: calc(100vh - 240px); + overflow-y: auto; +} + +.wo-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; +} + +.wo-title { + font-size: 11px; + font-weight: 700; + color: var(--text); + letter-spacing: 1px; +} + +.wo-loading { + font-size: 9px; + color: var(--accent); + animation: wo-pulse 1.2s ease-in-out infinite; +} + +@keyframes wo-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.wo-layers { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; + margin-bottom: 10px; +} + +.wo-layer-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + padding: 6px 4px; + border-radius: 6px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.03); + color: var(--muted); + cursor: pointer; + transition: all 0.15s; + font-size: 10px; +} + +.wo-layer-btn:hover { + background: rgba(255, 255, 255, 0.06); + color: var(--text); + border-color: rgba(59, 130, 246, 0.4); +} + +.wo-layer-btn.on { + background: rgba(59, 130, 246, 0.15); + color: var(--text); + border-color: var(--accent); +} + +.wo-layer-icon { + font-size: 16px; + line-height: 1; +} + +.wo-layer-name { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.3px; +} + +.wo-section { + margin-bottom: 8px; +} + +.wo-label { + font-size: 9px; + font-weight: 600; + color: var(--text); + margin-bottom: 4px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.wo-val { + font-weight: 400; + color: var(--muted); + font-size: 9px; +} +.wo-offset { + color: #4fc3f7; + font-weight: 600; +} + +.wo-slider { + width: 100%; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: var(--border); + border-radius: 2px; + outline: none; + cursor: pointer; +} + +.wo-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + border: 2px solid var(--panel); + cursor: pointer; +} + +.wo-slider::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + border: 2px solid var(--panel); + cursor: pointer; +} + +.wo-slider:disabled { + opacity: 0.4; + cursor: default; +} + +.wo-timeline { + padding-top: 8px; + border-top: 1px solid var(--border); +} + +.wo-step-slider-wrap { + position: relative; + padding-bottom: 10px; +} + +.wo-time-slider { + margin-bottom: 2px; +} + +.wo-step-ticks { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 8px; + pointer-events: none; +} + +.wo-step-tick { + position: absolute; + bottom: 0; + width: 1px; + height: 4px; + background: var(--muted); + opacity: 0.4; + transform: translateX(-0.5px); +} + +.wo-step-tick.day { + height: 8px; + opacity: 0.8; + background: var(--accent); +} + +.wo-time-range { + display: flex; + justify-content: space-between; + font-size: 8px; + color: var(--muted); + margin-bottom: 6px; +} + +.wo-playback { + display: flex; + align-items: center; + gap: 6px; +} + +.wo-play-btn { + width: 26px; + height: 26px; + border-radius: 50%; + border: 1px solid var(--border); + background: var(--card); + color: var(--text); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + transition: all 0.15s; + padding: 0; + flex-shrink: 0; +} + +.wo-play-btn:hover { + border-color: var(--accent); + background: rgba(59, 130, 246, 0.12); +} + +.wo-play-btn:disabled { + opacity: 0.4; + cursor: default; +} + +.wo-speed-btns { + display: flex; + gap: 2px; +} + +.wo-speed-btn { + font-size: 8px; + padding: 3px 6px; + border-radius: 3px; + border: 1px solid var(--border); + background: transparent; + color: var(--muted); + cursor: pointer; + transition: all 0.15s; +} + +.wo-speed-btn:hover { + color: var(--text); + border-color: rgba(59, 130, 246, 0.4); +} + +.wo-speed-btn.on { + background: rgba(59, 130, 246, 0.15); + color: var(--text); + border-color: var(--accent); +} + +.wo-hint { + font-size: 8px; + color: var(--muted); + text-align: right; + margin-top: 4px; + opacity: 0.6; +} + +/* ── Weather Legend ── */ +.wo-legend { + background: rgba(15, 23, 42, 0.85); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 10px 4px; + width: 100%; +} +.wo-legend-header { + font-size: 9px; + color: var(--muted); + margin-bottom: 4px; + text-align: center; +} +.wo-legend-bar { + height: 10px; + border-radius: 3px; + width: 100%; +} +.wo-legend-ticks { + display: flex; + justify-content: space-between; + font-size: 8px; + color: var(--muted); + margin-top: 2px; +} diff --git a/apps/web/src/app/styles/components/weather.css b/apps/web/src/app/styles/components/weather.css new file mode 100644 index 0000000..f957d7a --- /dev/null +++ b/apps/web/src/app/styles/components/weather.css @@ -0,0 +1,185 @@ +/* ── Weather Panel (Open-Meteo) ───────────────────────────────────── */ + +.weather-gear { + position: absolute; + top: 140px; + left: 10px; + z-index: 850; + width: 29px; + height: 29px; + border-radius: 4px; + border: 1px solid var(--border); + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(8px); + color: var(--muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: color 0.15s, border-color 0.15s; + user-select: none; + padding: 0; +} + +.weather-gear:hover { + color: var(--text); + border-color: var(--accent); +} + +.weather-gear.open { + color: var(--accent); + border-color: var(--accent); +} + +.weather-panel { + position: absolute; + top: 130px; + left: 48px; + z-index: 850; + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(8px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 10px; + width: 260px; + max-height: calc(100vh - 200px); + overflow-y: auto; +} + +.wp-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.wp-title { + font-size: 11px; + font-weight: 700; + color: var(--text); + letter-spacing: 1px; +} + +.wp-loading { + font-size: 9px; + color: var(--accent); +} + +.wp-error { + font-size: 9px; + color: #f87171; + margin-bottom: 6px; + padding: 4px 6px; + background: rgba(248, 113, 113, 0.1); + border-radius: 4px; +} + +.wp-empty { + font-size: 10px; + color: var(--muted); + text-align: center; + padding: 12px 0; +} + +.wz-card { + border-left: 3px solid var(--border); + padding: 6px 8px; + margin-bottom: 6px; + border-radius: 0 4px 4px 0; + background: rgba(255, 255, 255, 0.03); + transition: background 0.15s; +} + +.wz-card:last-of-type { + margin-bottom: 0; +} + +.wz-card.wz-warn { + background: rgba(248, 113, 113, 0.08); +} + +.wz-name { + font-size: 10px; + font-weight: 600; + color: var(--text); + margin-bottom: 4px; + letter-spacing: 0.3px; +} + +.wz-row { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 2px; +} + +.wz-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 10px; + color: var(--muted); + white-space: nowrap; +} + +.wz-icon { + font-size: 10px; + color: var(--accent); +} + +.wz-label { + font-size: 9px; + color: var(--muted); +} + +.wz-value { + font-size: 10px; + font-weight: 600; + color: var(--text); +} + +.wz-weather { + font-weight: 500; + color: var(--muted); +} + +.wp-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 8px; + padding-top: 6px; + border-top: 1px solid var(--border); +} + +.wp-time { + font-size: 9px; + color: var(--muted); +} + +.wp-refresh { + font-size: 14px; + color: var(--muted); + background: none; + border: 1px solid var(--border); + border-radius: 3px; + width: 22px; + height: 22px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.15s, border-color 0.15s; + padding: 0; +} + +.wp-refresh:hover { + color: var(--text); + border-color: var(--accent); +} + +.wp-refresh:disabled { + opacity: 0.5; + cursor: default; +} diff --git a/apps/web/src/app/styles/layout.css b/apps/web/src/app/styles/layout.css new file mode 100644 index 0000000..18b1ec4 --- /dev/null +++ b/apps/web/src/app/styles/layout.css @@ -0,0 +1,30 @@ +.app { + display: grid; + grid-template-columns: 310px 1fr; + grid-template-rows: 44px 1fr; + height: 100vh; +} + +.sidebar { + background: var(--panel); + border-right: 1px solid var(--border); + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.map-area { + position: relative; + background: #010610; +} + +@media (max-width: 920px) { + .app { + grid-template-columns: 1fr; + grid-template-rows: 44px 1fr; + } + + .sidebar { + display: none; + } +}