diff --git a/apps/web/src/app/styles.css b/apps/web/src/app/styles.css index 1037b54..3d864f8 100644 --- a/apps/web/src/app/styles.css +++ b/apps/web/src/app/styles.css @@ -971,7 +971,7 @@ body { } .map-settings-panel .ms-title { - font-size: 10px; + font-size: 11px; font-weight: 700; color: var(--text); letter-spacing: 1px; @@ -983,11 +983,11 @@ body { } .map-settings-panel .ms-label { - font-size: 8px; - font-weight: 700; - color: var(--muted); - letter-spacing: 1px; - margin-bottom: 4px; + font-size: 10px; + font-weight: 600; + color: var(--text); + letter-spacing: 0.5px; + margin-bottom: 5px; } .map-settings-panel .ms-row { @@ -1019,12 +1019,12 @@ body { .map-settings-panel .ms-hex { font-size: 9px; - color: var(--muted); + color: #94a3b8; font-family: monospace; } .map-settings-panel .ms-depth-label { - font-size: 9px; + font-size: 10px; color: var(--text); min-width: 48px; text-align: right; diff --git a/apps/web/src/features/mapSettings/MapSettingsPanel.tsx b/apps/web/src/features/mapSettings/MapSettingsPanel.tsx index f7dc386..592fb60 100644 --- a/apps/web/src/features/mapSettings/MapSettingsPanel.tsx +++ b/apps/web/src/features/mapSettings/MapSettingsPanel.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import type { MapStyleSettings, MapLabelLanguage, DepthFontSize } from './types'; +import type { MapStyleSettings, MapLabelLanguage, DepthFontSize, DepthColorStop } from './types'; import { DEFAULT_MAP_STYLE_SETTINGS } from './types'; interface MapSettingsPanelProps { @@ -25,8 +25,42 @@ function depthLabel(depth: number): string { return `${Math.abs(depth).toLocaleString()}m`; } +function hexToRgb(hex: string): [number, number, number] { + return [ + parseInt(hex.slice(1, 3), 16), + parseInt(hex.slice(3, 5), 16), + parseInt(hex.slice(5, 7), 16), + ]; +} + +function rgbToHex(r: number, g: number, b: number): string { + return `#${[r, g, b].map((c) => Math.round(Math.max(0, Math.min(255, c))).toString(16).padStart(2, '0')).join('')}`; +} + +function interpolateGradient(stops: DepthColorStop[]): DepthColorStop[] { + if (stops.length < 2) return stops; + const sorted = [...stops].sort((a, b) => a.depth - b.depth); + const first = sorted[0]; + const last = sorted[sorted.length - 1]; + const [r1, g1, b1] = hexToRgb(first.color); + const [r2, g2, b2] = hexToRgb(last.color); + return sorted.map((stop, i) => { + if (i === 0 || i === sorted.length - 1) return stop; + const t = i / (sorted.length - 1); + return { + depth: stop.depth, + color: rgbToHex( + r1 + (r2 - r1) * t, + g1 + (g2 - g1) * t, + b1 + (b2 - b1) * t, + ), + }; + }); +} + export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) { const [open, setOpen] = useState(false); + const [autoGradient, setAutoGradient] = useState(false); const update = (key: K, val: MapStyleSettings[K]) => { onChange({ ...value, [key]: val }); @@ -34,7 +68,19 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) { const updateDepthStop = (index: number, color: string) => { const next = value.depthStops.map((s, i) => (i === index ? { ...s, color } : s)); - update('depthStops', next); + if (autoGradient && (index === 0 || index === next.length - 1)) { + update('depthStops', interpolateGradient(next)); + } else { + update('depthStops', next); + } + }; + + const toggleAutoGradient = () => { + const next = !autoGradient; + setAutoGradient(next); + if (next) { + update('depthStops', interpolateGradient(value.depthStops)); + } }; return ( @@ -97,19 +143,34 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) { {/* ── Depth gradient ────────────────────────────── */}
-
수심 구간 색상
- {value.depthStops.map((stop, i) => ( -
- {depthLabel(stop.depth)} - updateDepthStop(i, e.target.value)} - /> - {stop.color} -
- ))} +
+ 수심 구간 색상 + + 자동채우기 + +
+ {value.depthStops.map((stop, i) => { + const isEdge = i === 0 || i === value.depthStops.length - 1; + const dimmed = autoGradient && !isEdge; + return ( +
+ {depthLabel(stop.depth)} + updateDepthStop(i, e.target.value)} + disabled={dimmed} + /> + {stop.color} +
+ ); + })}
{/* ── Depth font size ───────────────────────────── */} @@ -146,7 +207,10 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) { diff --git a/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts b/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts index 1f6fece..2cf0724 100644 --- a/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts +++ b/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts @@ -51,14 +51,21 @@ function applyLabelLanguage(map: maplibregl.Map, lang: MapLabelLanguage) { function applyLandColor(map: maplibregl.Map, color: string) { const style = map.getStyle(); if (!style?.layers) return; - const landRegex = /(land|landcover|landuse|earth|continent|terrain|park)/i; + const waterRegex = /(water|sea|ocean|river|lake|coast|bay)/i; + const darkVariant = darkenHex(color, 0.8); for (const layer of style.layers) { - if (layer.type !== 'fill') continue; const id = layer.id; + if (id.startsWith('bathymetry-')) continue; + if (id.startsWith('subcables-')) continue; const sourceLayer = String((layer as Record)['source-layer'] ?? ''); - if (!landRegex.test(id) && !landRegex.test(sourceLayer)) continue; + const isWater = waterRegex.test(id) || waterRegex.test(sourceLayer); + if (isWater) continue; try { - map.setPaintProperty(id, 'fill-color', color); + if (layer.type === 'background') { + map.setPaintProperty(id, 'background-color', color); + } else if (layer.type === 'fill') { + map.setPaintProperty(id, 'fill-color', darkVariant); + } } catch { // ignore }