Merge pull request 'feat: 웹폰트 내장 + 이란 시설물 색상/가독성 개선' (#174) from feature/embed-fonts into develop
This commit is contained in:
커밋
6765c0bc5f
@ -4,6 +4,16 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 추가
|
||||
- 웹폰트 내장: @fontsource-variable Inter, Noto Sans KR, Fira Code 자체 호스팅
|
||||
- 폰트 상수 파일 (FONT_MONO, FONT_SANS) + 타입 선언
|
||||
|
||||
### 변경
|
||||
- 전체 font-family 통일: CSS 55곳 + deck.gl TextLayer 30곳 + 인라인 스타일 8곳
|
||||
- 이란 시설물 색상 사막 대비 고채도 팔레트 교체 (amber/orange/yellow → rose/sky/cyan/lime)
|
||||
- 이란 시설 라벨 fontWeight 600→700, alpha 200→255 (가독성 개선)
|
||||
- 접힘 패널 상하 패딩 균일화 (area-ship-header :last-child)
|
||||
|
||||
## [2026-03-24]
|
||||
|
||||
### 추가
|
||||
|
||||
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@ -11,6 +11,9 @@
|
||||
"@deck.gl/core": "^9.2.11",
|
||||
"@deck.gl/layers": "^9.2.11",
|
||||
"@deck.gl/mapbox": "^9.2.11",
|
||||
"@fontsource-variable/fira-code": "^5.2.7",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.10",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@turf/boolean-point-in-polygon": "^7.3.4",
|
||||
"@turf/helpers": "^7.3.4",
|
||||
@ -915,6 +918,33 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/fira-code": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/fira-code/-/fira-code-5.2.7.tgz",
|
||||
"integrity": "sha512-J2bxN7fz5rd8WpQYyau4o19WqTzxoTqaNj9jhsv4p21GSu1Rf34tbqsxqjyDCR+wDMHM3SajyFqtq+5uvRUQ7w==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/inter": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz",
|
||||
"integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/noto-sans-kr": {
|
||||
"version": "5.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-kr/-/noto-sans-kr-5.2.10.tgz",
|
||||
"integrity": "sha512-UZOO7HF44Rt5+7SCeFMHYVgbKu36Jet6IxrAd7jjEkQMVDmeefwd0H8V5pSZydvBOOxClk3V5cQsujJqGHhQMw==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"dev": true,
|
||||
|
||||
@ -13,6 +13,9 @@
|
||||
"@deck.gl/core": "^9.2.11",
|
||||
"@deck.gl/layers": "^9.2.11",
|
||||
"@deck.gl/mapbox": "^9.2.11",
|
||||
"@fontsource-variable/fira-code": "^5.2.7",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.10",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@turf/boolean-point-in-polygon": "^7.3.4",
|
||||
"@turf/helpers": "^7.3.4",
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
/* Map mode toggle */
|
||||
@ -47,7 +47,7 @@
|
||||
color: var(--kcg-dim);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.map-mode-btn:hover {
|
||||
@ -79,7 +79,7 @@
|
||||
.count-item {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
@ -103,7 +103,7 @@
|
||||
color: var(--kcg-text-secondary);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
@ -126,7 +126,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
@ -240,7 +240,7 @@
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.layer-items {
|
||||
@ -299,7 +299,7 @@
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
font-size: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.stat-cat {
|
||||
@ -318,7 +318,7 @@
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-secondary);
|
||||
padding: 2px 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
/* Layer tree */
|
||||
@ -434,7 +434,7 @@
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dash-tab:hover {
|
||||
@ -465,7 +465,7 @@
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 1px solid var(--kcg-border);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.event-list {
|
||||
@ -504,7 +504,7 @@
|
||||
white-space: nowrap;
|
||||
height: fit-content;
|
||||
margin-top: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.event-content {
|
||||
@ -521,7 +521,7 @@
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 1px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.event-desc {
|
||||
@ -559,7 +559,7 @@
|
||||
background: var(--kcg-danger);
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
animation: flash-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@ -573,7 +573,7 @@
|
||||
font-weight: 700;
|
||||
color: var(--text-secondary);
|
||||
letter-spacing: 0.5px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.breaking-news-list {
|
||||
@ -616,7 +616,7 @@
|
||||
.breaking-news-time {
|
||||
font-size: 9px;
|
||||
color: var(--kcg-dim);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.breaking-news-headline {
|
||||
@ -658,13 +658,13 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--kcg-danger);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.osint-count {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
color: var(--kcg-muted);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.osint-loading {
|
||||
margin-left: auto;
|
||||
@ -722,7 +722,7 @@
|
||||
font-size: 9px;
|
||||
color: var(--kcg-dim);
|
||||
white-space: nowrap;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.osint-item-title {
|
||||
font-size: 11px;
|
||||
@ -757,10 +757,17 @@
|
||||
.area-ship-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--kcg-hover);
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.area-ship-header:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.korean-highlight-toggle {
|
||||
@ -768,7 +775,7 @@
|
||||
padding: 1px 8px;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--kcg-border);
|
||||
background: transparent;
|
||||
@ -793,7 +800,11 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.area-ship-total {
|
||||
@ -801,7 +812,9 @@
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #fb923c;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.kr-ship-header {
|
||||
@ -820,7 +833,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-primary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.kr-total {
|
||||
@ -828,7 +841,7 @@
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--kcg-accent);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.kr-ship-breakdown {
|
||||
@ -859,7 +872,7 @@
|
||||
flex: 1;
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.kr-count {
|
||||
@ -867,7 +880,7 @@
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
text-align: right;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.kr-ship-list {
|
||||
@ -881,7 +894,7 @@
|
||||
gap: 6px;
|
||||
padding: 2px 0;
|
||||
font-size: 9px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.kr-ship-name {
|
||||
@ -1026,7 +1039,7 @@
|
||||
.korea-stat-num {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.korea-stat-card.total .korea-stat-num { color: var(--kcg-accent); }
|
||||
.korea-stat-card.anchored .korea-stat-num { color: var(--kcg-danger); }
|
||||
@ -1049,7 +1062,7 @@
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.korea-ship-section {
|
||||
flex: 1;
|
||||
@ -1067,7 +1080,7 @@
|
||||
color: var(--kcg-muted);
|
||||
border-bottom: 1px solid var(--kcg-border);
|
||||
flex-shrink: 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.korea-ship-section-count {
|
||||
color: var(--kcg-accent);
|
||||
@ -1131,7 +1144,7 @@
|
||||
.korea-ship-card-speed {
|
||||
margin-left: auto;
|
||||
color: var(--kcg-muted);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
.korea-ship-card-dest {
|
||||
font-size: 9px;
|
||||
@ -1161,7 +1174,7 @@
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.chart-grid {
|
||||
@ -1177,7 +1190,7 @@
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0;
|
||||
padding-left: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.chart-demo-label {
|
||||
@ -1193,7 +1206,7 @@
|
||||
|
||||
.ship-popup-body {
|
||||
width: 300px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@ -1347,12 +1360,12 @@
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.popup-body-sm {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
font-size: 11px;
|
||||
min-width: 220px;
|
||||
}
|
||||
@ -1446,7 +1459,7 @@
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.speed-btn:hover {
|
||||
@ -1485,7 +1498,7 @@
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.range-btn:hover {
|
||||
@ -1537,7 +1550,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.range-picker input[type="datetime-local"] {
|
||||
@ -1547,7 +1560,7 @@
|
||||
color: var(--text-primary);
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
@ -1571,7 +1584,7 @@
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: var(--kcg-success);
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@ -1591,7 +1604,7 @@
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.timeline-current {
|
||||
@ -1727,7 +1740,7 @@
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
color: var(--kcg-dim);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -1774,7 +1787,7 @@
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2px;
|
||||
padding-left: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
/* Aircraft, Satellite, Ship & Oil Tooltips - override Leaflet */
|
||||
@ -1788,7 +1801,7 @@
|
||||
border-radius: 3px !important;
|
||||
padding: 2px 6px !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5) !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace !important;
|
||||
}
|
||||
|
||||
.aircraft-tooltip::before,
|
||||
@ -1862,7 +1875,7 @@
|
||||
border-radius: 3px !important;
|
||||
padding: 2px 6px !important;
|
||||
box-shadow: 0 2px 12px rgba(255, 0, 0, 0.4) !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace !important;
|
||||
}
|
||||
|
||||
.impact-tooltip::before {
|
||||
@ -1924,7 +1937,7 @@
|
||||
pointer-events: none;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
text-shadow: var(--kcg-map-label-shadow);
|
||||
background: var(--kcg-glass);
|
||||
border: 1px solid var(--kcg-border);
|
||||
@ -1942,7 +1955,7 @@
|
||||
color: var(--kcg-event-impact);
|
||||
font-weight: 700;
|
||||
font-size: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
text-shadow: var(--kcg-map-impact-shadow);
|
||||
background: rgba(40, 0, 0, 0.85);
|
||||
border: 1px solid var(--kcg-event-impact);
|
||||
@ -2038,7 +2051,7 @@
|
||||
color: var(--kcg-dim);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.mode-btn:hover {
|
||||
@ -2100,14 +2113,14 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
color: var(--kcg-danger);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.live-clock {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
letter-spacing: 1px;
|
||||
padding: 4px 12px;
|
||||
background: var(--kcg-hover);
|
||||
@ -2126,7 +2139,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.history-presets {
|
||||
@ -2144,7 +2157,7 @@
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
}
|
||||
|
||||
.history-btn:hover {
|
||||
@ -2169,7 +2182,7 @@
|
||||
padding: 1px 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
@ -2335,7 +2348,7 @@
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
color: var(--kcg-text, #e2e8f0);
|
||||
font-family: monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { IconLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { PickingInfo, Layer } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { middleEastAirports } from '../../data/airports';
|
||||
import type { Airport } from '../../data/airports';
|
||||
|
||||
@ -42,9 +43,9 @@ export const IRAN_AIRPORT_COUNT = DEDUPLICATED_AIRPORTS.length;
|
||||
// ─── Colors ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function getAirportColor(airport: Airport): string {
|
||||
if (isUSBase(airport)) return '#3b82f6';
|
||||
if (airport.type === 'military') return '#ef4444';
|
||||
return '#f59e0b';
|
||||
if (isUSBase(airport)) return '#60a5fa';
|
||||
if (airport.type === 'military') return '#f87171';
|
||||
return '#38bdf8';
|
||||
}
|
||||
|
||||
// ─── SVG generators ──────────────────────────────────────────────────────────
|
||||
@ -140,7 +141,7 @@ export function createIranAirportLayers(config: AirportLayerConfig): Layer[] {
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
outlineWidth: 2,
|
||||
outlineColor: [0, 0, 0, 200],
|
||||
|
||||
@ -141,23 +141,23 @@ const IranDashboard = ({
|
||||
},
|
||||
{ key: 'events', label: t('layers.events'), color: '#a855f7' },
|
||||
{ key: 'koreanShips', label: `\u{1F1F0}\u{1F1F7} ${t('layers.koreanShips')}`, color: '#00e5ff', count: iranData.koreanShips.length },
|
||||
{ key: 'airports', label: t('layers.airports'), color: '#f59e0b', count: IRAN_AIRPORT_COUNT },
|
||||
{ key: 'oilFacilities', label: t('layers.oilFacilities'), color: '#d97706', count: IRAN_OIL_COUNT },
|
||||
{ key: 'meFacilities', label: '주요시설/군사', color: '#ef4444', count: ME_FACILITY_COUNT },
|
||||
{ key: 'airports', label: t('layers.airports'), color: '#38bdf8', count: IRAN_AIRPORT_COUNT },
|
||||
{ key: 'oilFacilities', label: t('layers.oilFacilities'), color: '#fb7185', count: IRAN_OIL_COUNT },
|
||||
{ key: 'meFacilities', label: '주요시설/군사', color: '#f87171', count: ME_FACILITY_COUNT },
|
||||
{ key: 'sensorCharts', label: t('layers.sensorCharts'), color: '#22c55e' },
|
||||
{
|
||||
key: 'overseas', label: '해외시설', color: '#f97316',
|
||||
key: 'overseas', label: '해외시설', color: '#c084fc',
|
||||
children: [
|
||||
{ key: 'overseasUS', label: '🇺🇸 미국', color: '#3b82f6', count: meCountByCountry('us') },
|
||||
{ key: 'overseasIsrael', label: '🇮🇱 이스라엘', color: '#dc2626', count: meCountByCountry('il') },
|
||||
{ key: 'overseasIran', label: '🇮🇷 이란', color: '#22c55e', count: meCountByCountry('ir') },
|
||||
{ key: 'overseasUAE', label: '🇦🇪 UAE', color: '#f59e0b', count: meCountByCountry('ae') },
|
||||
{ key: 'overseasSaudi', label: '🇸🇦 사우디아라비아', color: '#84cc16', count: meCountByCountry('sa') },
|
||||
{ key: 'overseasOman', label: '🇴🇲 오만', color: '#e11d48', count: meCountByCountry('om') },
|
||||
{ key: 'overseasQatar', label: '🇶🇦 카타르', color: '#8b5cf6', count: meCountByCountry('qa') },
|
||||
{ key: 'overseasKuwait', label: '🇰🇼 쿠웨이트', color: '#f97316', count: meCountByCountry('kw') },
|
||||
{ key: 'overseasIraq', label: '🇮🇶 이라크', color: '#65a30d', count: meCountByCountry('iq') },
|
||||
{ key: 'overseasBahrain', label: '🇧🇭 바레인', color: '#e11d48', count: meCountByCountry('bh') },
|
||||
{ key: 'overseasUS', label: '🇺🇸 미국', color: '#60a5fa', count: meCountByCountry('us') },
|
||||
{ key: 'overseasIsrael', label: '🇮🇱 이스라엘', color: '#ef4444', count: meCountByCountry('il') },
|
||||
{ key: 'overseasIran', label: '🇮🇷 이란', color: '#34d399', count: meCountByCountry('ir') },
|
||||
{ key: 'overseasUAE', label: '🇦🇪 UAE', color: '#38bdf8', count: meCountByCountry('ae') },
|
||||
{ key: 'overseasSaudi', label: '🇸🇦 사우디아라비아', color: '#a3e635', count: meCountByCountry('sa') },
|
||||
{ key: 'overseasOman', label: '🇴🇲 오만', color: '#fb7185', count: meCountByCountry('om') },
|
||||
{ key: 'overseasQatar', label: '🇶🇦 카타르', color: '#a78bfa', count: meCountByCountry('qa') },
|
||||
{ key: 'overseasKuwait', label: '🇰🇼 쿠웨이트', color: '#f472b6', count: meCountByCountry('kw') },
|
||||
{ key: 'overseasIraq', label: '🇮🇶 이라크', color: '#86efac', count: meCountByCountry('iq') },
|
||||
{ key: 'overseasBahrain', label: '🇧🇭 바레인', color: '#f87171', count: meCountByCountry('bh') },
|
||||
],
|
||||
},
|
||||
], [iranData, t, meCountByCountry]);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { IconLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { PickingInfo, Layer } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import {
|
||||
ME_ENERGY_HAZARD_FACILITIES,
|
||||
SUB_TYPE_META,
|
||||
@ -204,12 +205,12 @@ export function createMEEnergyHazardLayers(config: MELayerConfig): Layer[] {
|
||||
getText: (d) => d.nameKo.length > 12 ? d.nameKo.slice(0, 12) + '..' : d.nameKo,
|
||||
getSize: 12 * sc * fs,
|
||||
updateTriggers: { getSize: [sc] },
|
||||
getColor: (d) => hexToRgba(SUB_TYPE_META[d.subType].color, 200),
|
||||
getColor: (d) => hexToRgba(SUB_TYPE_META[d.subType].color),
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { IconLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { PickingInfo, Layer } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { ME_FACILITIES } from '../../data/middleEastFacilities';
|
||||
import type { MEFacility } from '../../data/middleEastFacilities';
|
||||
|
||||
@ -9,12 +10,12 @@ export const ME_FACILITY_COUNT = ME_FACILITIES.length;
|
||||
// ─── Type colors ──────────────────────────────────────────────────────────────
|
||||
|
||||
const TYPE_COLORS: Record<MEFacility['type'], string> = {
|
||||
naval: '#3b82f6',
|
||||
military_hq: '#ef4444',
|
||||
missile: '#dc2626',
|
||||
intelligence: '#8b5cf6',
|
||||
government: '#f59e0b',
|
||||
radar: '#06b6d4',
|
||||
naval: '#60a5fa',
|
||||
military_hq: '#f87171',
|
||||
missile: '#ef4444',
|
||||
intelligence: '#a78bfa',
|
||||
government: '#c084fc',
|
||||
radar: '#22d3ee',
|
||||
};
|
||||
|
||||
// ─── SVG generators ──────────────────────────────────────────────────────────
|
||||
@ -168,7 +169,7 @@ export function createMEFacilityLayers(config: MEFacilityLayerConfig): Layer[] {
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
outlineWidth: 2,
|
||||
outlineColor: [0, 0, 0, 200],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { IconLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { PickingInfo, Layer } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { iranOilFacilities } from '../../data/oilFacilities';
|
||||
import type { OilFacility, OilFacilityType } from '../../types';
|
||||
|
||||
@ -9,12 +10,12 @@ export const IRAN_OIL_COUNT = iranOilFacilities.length;
|
||||
// ─── Type colors ──────────────────────────────────────────────────────────────
|
||||
|
||||
const TYPE_COLORS: Record<OilFacilityType, string> = {
|
||||
refinery: '#f59e0b',
|
||||
oilfield: '#10b981',
|
||||
gasfield: '#6366f1',
|
||||
terminal: '#ec4899',
|
||||
petrochemical: '#8b5cf6',
|
||||
desalination: '#06b6d4',
|
||||
refinery: '#fb7185',
|
||||
oilfield: '#34d399',
|
||||
gasfield: '#818cf8',
|
||||
terminal: '#c084fc',
|
||||
petrochemical: '#f472b6',
|
||||
desalination: '#22d3ee',
|
||||
};
|
||||
|
||||
// ─── SVG generators ──────────────────────────────────────────────────────────
|
||||
@ -241,7 +242,7 @@ export function createIranOilLayers(config: OilLayerConfig): Layer[] {
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
outlineWidth: 2,
|
||||
outlineColor: [0, 0, 0, 200],
|
||||
|
||||
@ -529,8 +529,8 @@ export function ReplayMap({ events, currentTime, aircraft, satellites, ships, la
|
||||
const { kind, data } = iranPickedFacility;
|
||||
if (kind === 'oil') {
|
||||
const OIL_TYPE_COLORS: Record<string, string> = {
|
||||
refinery: '#f59e0b', oilfield: '#10b981', gasfield: '#6366f1',
|
||||
terminal: '#ec4899', petrochemical: '#8b5cf6', desalination: '#06b6d4',
|
||||
refinery: '#fb7185', oilfield: '#34d399', gasfield: '#818cf8',
|
||||
terminal: '#c084fc', petrochemical: '#f472b6', desalination: '#22d3ee',
|
||||
};
|
||||
const color = OIL_TYPE_COLORS[data.type] ?? '#888';
|
||||
return (
|
||||
@ -568,13 +568,13 @@ export function ReplayMap({ events, currentTime, aircraft, satellites, ships, la
|
||||
}
|
||||
if (kind === 'airport') {
|
||||
const isMil = data.type === 'military';
|
||||
const color = isMil ? '#ef4444' : '#f59e0b';
|
||||
const color = isMil ? '#f87171' : '#38bdf8';
|
||||
return (
|
||||
<Popup longitude={data.lng} latitude={data.lat}
|
||||
onClose={() => setIranPickedFacility(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="300px" className="gl-popup">
|
||||
<div className="popup-body-sm" style={{ minWidth: 240 }}>
|
||||
<div className="popup-header" style={{ background: isMil ? '#991b1b' : '#92400e', color: '#fff', gap: 6, padding: '6px 10px' }}>
|
||||
<div className="popup-header" style={{ background: isMil ? '#991b1b' : '#0369a1', color: '#fff', gap: 6, padding: '6px 10px' }}>
|
||||
<strong style={{ fontSize: 13 }}>{data.nameKo ?? data.name}</strong>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
|
||||
@ -598,7 +598,7 @@ export function ReplayMap({ events, currentTime, aircraft, satellites, ships, la
|
||||
);
|
||||
}
|
||||
if (kind === 'meFacility') {
|
||||
const meta = { naval: { label: 'Naval Base', color: '#3b82f6', icon: '⚓' }, military_hq: { label: 'Military HQ', color: '#ef4444', icon: '⭐' }, missile: { label: 'Missile/Air Defense', color: '#dc2626', icon: '🚀' }, intelligence: { label: 'Intelligence', color: '#8b5cf6', icon: '🔍' }, government: { label: 'Government', color: '#f59e0b', icon: '🏛️' }, radar: { label: 'Radar/C2', color: '#06b6d4', icon: '📡' } }[data.type] ?? { label: data.type, color: '#888', icon: '📍' };
|
||||
const meta = { naval: { label: 'Naval Base', color: '#60a5fa', icon: '⚓' }, military_hq: { label: 'Military HQ', color: '#f87171', icon: '⭐' }, missile: { label: 'Missile/Air Defense', color: '#ef4444', icon: '🚀' }, intelligence: { label: 'Intelligence', color: '#a78bfa', icon: '🔍' }, government: { label: 'Government', color: '#c084fc', icon: '🏛️' }, radar: { label: 'Radar/C2', color: '#22d3ee', icon: '📡' } }[data.type] ?? { label: data.type, color: '#888', icon: '📍' };
|
||||
return (
|
||||
<Popup longitude={data.lng} latitude={data.lat}
|
||||
onClose={() => setIranPickedFacility(null)} closeOnClick={false}
|
||||
|
||||
@ -359,8 +359,8 @@ export function SatelliteMap({ events, currentTime, aircraft, satellites, ships,
|
||||
const { kind, data } = iranPickedFacility;
|
||||
if (kind === 'oil') {
|
||||
const OIL_TYPE_COLORS: Record<string, string> = {
|
||||
refinery: '#f59e0b', oilfield: '#10b981', gasfield: '#6366f1',
|
||||
terminal: '#ec4899', petrochemical: '#8b5cf6', desalination: '#06b6d4',
|
||||
refinery: '#fb7185', oilfield: '#34d399', gasfield: '#818cf8',
|
||||
terminal: '#c084fc', petrochemical: '#f472b6', desalination: '#22d3ee',
|
||||
};
|
||||
const color = OIL_TYPE_COLORS[data.type] ?? '#888';
|
||||
return (
|
||||
@ -398,13 +398,13 @@ export function SatelliteMap({ events, currentTime, aircraft, satellites, ships,
|
||||
}
|
||||
if (kind === 'airport') {
|
||||
const isMil = data.type === 'military';
|
||||
const color = isMil ? '#ef4444' : '#f59e0b';
|
||||
const color = isMil ? '#f87171' : '#38bdf8';
|
||||
return (
|
||||
<Popup longitude={data.lng} latitude={data.lat}
|
||||
onClose={() => setIranPickedFacility(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="300px" className="gl-popup">
|
||||
<div className="popup-body-sm" style={{ minWidth: 240 }}>
|
||||
<div className="popup-header" style={{ background: isMil ? '#991b1b' : '#92400e', color: '#fff', gap: 6, padding: '6px 10px' }}>
|
||||
<div className="popup-header" style={{ background: isMil ? '#991b1b' : '#0369a1', color: '#fff', gap: 6, padding: '6px 10px' }}>
|
||||
<strong style={{ fontSize: 13 }}>{data.nameKo ?? data.name}</strong>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
|
||||
@ -428,7 +428,7 @@ export function SatelliteMap({ events, currentTime, aircraft, satellites, ships,
|
||||
);
|
||||
}
|
||||
if (kind === 'meFacility') {
|
||||
const meta = { naval: { label: 'Naval Base', color: '#3b82f6', icon: '⚓' }, military_hq: { label: 'Military HQ', color: '#ef4444', icon: '⭐' }, missile: { label: 'Missile/Air Defense', color: '#dc2626', icon: '🚀' }, intelligence: { label: 'Intelligence', color: '#8b5cf6', icon: '🔍' }, government: { label: 'Government', color: '#f59e0b', icon: '🏛️' }, radar: { label: 'Radar/C2', color: '#06b6d4', icon: '📡' } }[data.type] ?? { label: data.type, color: '#888', icon: '📍' };
|
||||
const meta = { naval: { label: 'Naval Base', color: '#60a5fa', icon: '⚓' }, military_hq: { label: 'Military HQ', color: '#f87171', icon: '⭐' }, missile: { label: 'Missile/Air Defense', color: '#ef4444', icon: '🚀' }, intelligence: { label: 'Intelligence', color: '#a78bfa', icon: '🔍' }, government: { label: 'Government', color: '#c084fc', icon: '🏛️' }, radar: { label: 'Radar/C2', color: '#22d3ee', icon: '📡' } }[data.type] ?? { label: data.type, color: '#888', icon: '📍' };
|
||||
return (
|
||||
<Popup longitude={data.lng} latitude={data.lat}
|
||||
onClose={() => setIranPickedFacility(null)} closeOnClick={false}
|
||||
|
||||
@ -3,6 +3,7 @@ import type { Layer, PickingInfo } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { middleEastAirports } from '../../data/airports';
|
||||
import type { Airport } from '../../data/airports';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
export { type Airport };
|
||||
|
||||
@ -15,10 +16,10 @@ const US_BASE_ICAOS = new Set([
|
||||
function getAirportColor(airport: Airport): string {
|
||||
const isMil = airport.type === 'military';
|
||||
const isUS = isMil && US_BASE_ICAOS.has(airport.icao);
|
||||
if (isUS) return '#3b82f6';
|
||||
if (isMil) return '#ef4444';
|
||||
if (airport.type === 'international') return '#f59e0b';
|
||||
return '#7c8aaa';
|
||||
if (isUS) return '#60a5fa'; // blue-400
|
||||
if (isMil) return '#f87171'; // red-400
|
||||
if (airport.type === 'international') return '#38bdf8'; // sky-400 (was amber)
|
||||
return '#a5b4fc'; // indigo-200 (was gray)
|
||||
}
|
||||
|
||||
function airportSvg(color: string, size: number): string {
|
||||
@ -92,8 +93,8 @@ export function createIranAirportLayers(config: IranAirportLayerConfig): Layer[]
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
|
||||
@ -3,18 +3,19 @@ import type { Layer, PickingInfo } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { iranOilFacilities } from '../../data/oilFacilities';
|
||||
import type { OilFacility, OilFacilityType } from '../../types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
export { type OilFacility };
|
||||
|
||||
export const IRAN_OIL_COUNT = iranOilFacilities.length;
|
||||
|
||||
const TYPE_COLORS: Record<OilFacilityType, string> = {
|
||||
refinery: '#f59e0b',
|
||||
oilfield: '#10b981',
|
||||
gasfield: '#6366f1',
|
||||
terminal: '#ec4899',
|
||||
petrochemical: '#8b5cf6',
|
||||
desalination: '#06b6d4',
|
||||
refinery: '#fb7185', // rose-400 (was amber — desert에 묻힘)
|
||||
oilfield: '#34d399', // emerald-400
|
||||
gasfield: '#818cf8', // indigo-400
|
||||
terminal: '#c084fc', // purple-400
|
||||
petrochemical: '#f472b6', // pink-400
|
||||
desalination: '#22d3ee', // cyan-400
|
||||
};
|
||||
|
||||
function refinerySvg(color: string, size: number): string {
|
||||
@ -142,8 +143,8 @@ export function createIranOilLayers(config: IranOilLayerConfig): Layer[] {
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
|
||||
@ -3,6 +3,7 @@ import type { Layer, PickingInfo } from '@deck.gl/core';
|
||||
import { svgToDataUri } from '../../utils/svgToDataUri';
|
||||
import { ME_FACILITIES, ME_FACILITY_TYPE_META } from '../../data/middleEastFacilities';
|
||||
import type { MEFacility } from '../../data/middleEastFacilities';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
export { type MEFacility };
|
||||
|
||||
@ -133,12 +134,12 @@ export function createMEFacilityLayers(config: MEFacilityLayerConfig): Layer[] {
|
||||
getText: (d) => d.nameKo.length > 12 ? d.nameKo.slice(0, 12) + '..' : d.nameKo,
|
||||
getSize: 12 * sc * fs,
|
||||
updateTriggers: { getSize: [sc] },
|
||||
getColor: (d) => hexToRgba(ME_FACILITY_TYPE_META[d.type].color, 200),
|
||||
getColor: (d) => hexToRgba(ME_FACILITY_TYPE_META[d.type].color),
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import type { VesselAnalysisDto, RiskLevel, Ship } from '../../types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import type { AnalysisStats } from '../../hooks/useVesselAnalysis';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import { fetchVesselTrack } from '../../services/vesselTrack';
|
||||
@ -143,7 +144,7 @@ export function AnalysisStatsPanel({ stats, lastUpdated, isLoading, analysisMap,
|
||||
border: '1px solid rgba(99, 179, 237, 0.25)',
|
||||
borderRadius: 8,
|
||||
color: '#e2e8f0',
|
||||
fontFamily: 'monospace, sans-serif',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: 11,
|
||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5)',
|
||||
overflow: 'hidden',
|
||||
@ -311,7 +312,7 @@ export function AnalysisStatsPanel({ stats, lastUpdated, isLoading, analysisMap,
|
||||
fontSize: 10,
|
||||
cursor: 'pointer',
|
||||
padding: '2px 4px',
|
||||
fontFamily: 'monospace, sans-serif',
|
||||
fontFamily: FONT_MONO,
|
||||
}}
|
||||
>
|
||||
<span>{RISK_EMOJI[level]}</span>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import type { Ship, ChnPrmShipInfo, VesselAnalysisDto } from '../../types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { classifyFishingZone } from '../../utils/fishingAnalysis';
|
||||
import { getMarineTrafficCategory } from '../../utils/marineTraffic';
|
||||
import { lookupPermittedShip } from '../../services/chnPrmShip';
|
||||
@ -330,7 +331,7 @@ export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) {
|
||||
position: 'absolute', inset: 0, zIndex: 2000,
|
||||
background: 'rgba(2,6,14,0.96)',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
fontFamily: "'IBM Plex Mono', 'Noto Sans KR', monospace",
|
||||
fontFamily: FONT_MONO,
|
||||
}}>
|
||||
{/* ── 헤더 */}
|
||||
<div style={{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { Source, Layer, Popup, useMap } from 'react-map-gl/maplibre';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import type { GeoJSON } from 'geojson';
|
||||
import type { MapLayerMouseEvent } from 'maplibre-gl';
|
||||
import type { Ship, VesselAnalysisDto } from '../../types';
|
||||
@ -458,7 +459,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, cluster
|
||||
border: '1px solid rgba(99, 179, 237, 0.25)',
|
||||
borderRadius: 8,
|
||||
color: '#e2e8f0',
|
||||
fontFamily: 'monospace, sans-serif',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: 11,
|
||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5)',
|
||||
overflow: 'hidden',
|
||||
@ -599,7 +600,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, cluster
|
||||
closeButton={false} closeOnClick={false} anchor="bottom"
|
||||
className="gl-popup" maxWidth="220px"
|
||||
>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: 10, padding: 4, color: '#e2e8f0' }}>
|
||||
<div style={{ fontFamily: FONT_MONO, fontSize: 10, padding: 4, color: '#e2e8f0' }}>
|
||||
<div style={{ fontWeight: 700, color: clusterColor(cid), marginBottom: 3 }}>
|
||||
{company?.nameCn || `선단 #${cid}`}
|
||||
</div>
|
||||
@ -628,7 +629,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, cluster
|
||||
closeButton={false} closeOnClick={false} anchor="bottom"
|
||||
className="gl-popup" maxWidth="220px"
|
||||
>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: 10, padding: 4, color: '#e2e8f0' }}>
|
||||
<div style={{ fontFamily: FONT_MONO, fontSize: 10, padding: 4, color: '#e2e8f0' }}>
|
||||
<div style={{ fontWeight: 700, color: '#f97316', marginBottom: 3 }}>
|
||||
{name} <span style={{ fontWeight: 400, color: '#94a3b8' }}>어구 {entry.gears.length}개</span>
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@ import { Map, NavigationControl, Marker, Source, Layer } from 'react-map-gl/mapl
|
||||
import type { MapRef } from 'react-map-gl/maplibre';
|
||||
import { ScatterplotLayer, TextLayer } from '@deck.gl/layers';
|
||||
import { useFontScale } from '../../hooks/useFontScale';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { DeckGLOverlay } from '../layers/DeckGLOverlay';
|
||||
import { useAnalysisDeckLayers } from '../../hooks/useAnalysisDeckLayers';
|
||||
import { useStaticDeckLayers } from '../../hooks/useStaticDeckLayers';
|
||||
@ -249,7 +250,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 14],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -287,7 +288,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
getColor: [255, 255, 255, 220],
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'center',
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -364,7 +365,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
getTextAnchor: 'middle' as const,
|
||||
getAlignmentBaseline: 'top' as const,
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 220],
|
||||
@ -399,7 +400,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
getTextAnchor: 'middle' as const,
|
||||
getAlignmentBaseline: 'top' as const,
|
||||
getPixelOffset: [0, 18],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -464,7 +465,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
getTextAnchor: 'middle' as const,
|
||||
getAlignmentBaseline: 'top' as const,
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 600,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Marker, Popup, Source, Layer } from 'react-map-gl/maplibre';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
import { KOREA_SUBMARINE_CABLES, KOREA_LANDING_POINTS } from '../../services/submarineCable';
|
||||
import type { SubmarineCable } from '../../services/submarineCable';
|
||||
|
||||
@ -90,7 +91,7 @@ export function SubmarineCableLayer() {
|
||||
<Marker key={`label-${cable.id}`} longitude={mid[0]} latitude={mid[1]} anchor="center"
|
||||
onClick={(e) => { e.originalEvent.stopPropagation(); setSelectedCable(cable); setSelectedPoint(null); }}>
|
||||
<div style={{
|
||||
fontSize: 7, fontFamily: 'monospace', fontWeight: 600,
|
||||
fontSize: 7, fontFamily: FONT_MONO, fontWeight: 600,
|
||||
color: cable.color, cursor: 'pointer',
|
||||
textShadow: '0 0 3px #000, 0 0 3px #000, 0 0 6px #000',
|
||||
whiteSpace: 'nowrap', opacity: 0.8,
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -19,14 +19,14 @@ export interface EnergyHazardFacility {
|
||||
}
|
||||
|
||||
export const SUB_TYPE_META: Record<FacilitySubType, { label: string; color: string; icon: string }> = {
|
||||
power: { label: '발전소', color: '#a855f7', icon: '⚡' },
|
||||
wind: { label: '풍력단지', color: '#22d3ee', icon: '🌬' },
|
||||
nuclear: { label: '원자력발전소', color: '#f59e0b', icon: '☢' },
|
||||
thermal: { label: '화력발전소', color: '#64748b', icon: '🏭' },
|
||||
petrochem: { label: '석유화학단지', color: '#f97316', icon: '🛢' },
|
||||
lng: { label: 'LNG저장기지', color: '#0ea5e9', icon: '❄' },
|
||||
oil_tank: { label: '유류저장탱크', color: '#eab308', icon: '🛢' },
|
||||
haz_port: { label: '위험물항만하역시설', color: '#dc2626', icon: '⚠' },
|
||||
power: { label: '발전소', color: '#c084fc', icon: '⚡' }, // purple-400
|
||||
wind: { label: '풍력단지', color: '#22d3ee', icon: '🌬' }, // cyan-400
|
||||
nuclear: { label: '원자력발전소', color: '#f472b6', icon: '☢' }, // pink-400 (was amber)
|
||||
thermal: { label: '화력발전소', color: '#94a3b8', icon: '🏭' }, // slate-400 (was 500)
|
||||
petrochem: { label: '석유화학단지', color: '#fb7185', icon: '🛢' }, // rose-400 (was orange)
|
||||
lng: { label: 'LNG저장기지', color: '#38bdf8', icon: '❄' }, // sky-400
|
||||
oil_tank: { label: '유류저장탱크', color: '#a3e635', icon: '🛢' }, // lime-400 (was yellow)
|
||||
haz_port: { label: '위험물항만하역시설', color: '#f87171', icon: '⚠' }, // red-400
|
||||
};
|
||||
|
||||
// layer key -> subType mapping
|
||||
|
||||
@ -14,12 +14,12 @@ export interface MEFacility {
|
||||
}
|
||||
|
||||
const TYPE_META: Record<string, { label: string; color: string; icon: string }> = {
|
||||
naval: { label: 'Naval Base', color: '#3b82f6', icon: '⚓' },
|
||||
military_hq: { label: 'Military HQ', color: '#ef4444', icon: '⭐' },
|
||||
missile: { label: 'Missile/Air Defense', color: '#dc2626', icon: '🚀' },
|
||||
intelligence: { label: 'Intelligence', color: '#8b5cf6', icon: '🔍' },
|
||||
government: { label: 'Government', color: '#f59e0b', icon: '🏛️' },
|
||||
radar: { label: 'Radar/C2', color: '#06b6d4', icon: '📡' },
|
||||
naval: { label: 'Naval Base', color: '#60a5fa', icon: '⚓' }, // blue-400
|
||||
military_hq: { label: 'Military HQ', color: '#f87171', icon: '⭐' }, // red-400
|
||||
missile: { label: 'Missile/Air Defense', color: '#ef4444', icon: '🚀' }, // red-500
|
||||
intelligence: { label: 'Intelligence', color: '#a78bfa', icon: '🔍' }, // violet-400
|
||||
government: { label: 'Government', color: '#c084fc', icon: '🏛️' }, // purple-400 (was amber)
|
||||
radar: { label: 'Radar/C2', color: '#22d3ee', icon: '📡' }, // cyan-400
|
||||
};
|
||||
|
||||
export { TYPE_META as ME_FACILITY_TYPE_META };
|
||||
|
||||
4
frontend/src/env.d.ts
vendored
4
frontend/src/env.d.ts
vendored
@ -1,5 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '@fontsource-variable/inter';
|
||||
declare module '@fontsource-variable/noto-sans-kr';
|
||||
declare module '@fontsource-variable/fira-code';
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SPG_API_KEY?: string;
|
||||
readonly VITE_GOOGLE_CLIENT_ID?: string;
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
type CnFacility,
|
||||
type JpFacility,
|
||||
} from './types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
// ─── Infra SVG ────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -359,8 +360,8 @@ export function createFacilityLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 8],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -415,8 +416,8 @@ export function createFacilityLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -469,8 +470,8 @@ export function createFacilityLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -522,8 +523,8 @@ export function createFacilityLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 12],
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
|
||||
@ -11,6 +11,7 @@ import { NK_MISSILE_EVENTS } from '../../data/nkMissileEvents';
|
||||
import type { NKMissileEvent } from '../../data/nkMissileEvents';
|
||||
import { hexToRgb } from './types';
|
||||
import type { LayerFactoryConfig } from './types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
// ─── NKMissile SVG ────────────────────────────────────────────────────────────
|
||||
|
||||
@ -318,7 +319,7 @@ export function createMilitaryLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 9],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -356,7 +357,7 @@ export function createMilitaryLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 8],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -394,7 +395,7 @@ export function createMilitaryLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 8],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -486,7 +487,7 @@ export function createMilitaryLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
|
||||
@ -11,6 +11,7 @@ import { PIRACY_ZONES, PIRACY_LEVEL_COLOR } from '../../services/piracy';
|
||||
import type { PiracyZone } from '../../services/piracy';
|
||||
import { hexToRgb } from './types';
|
||||
import type { LayerFactoryConfig } from './types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
// ─── CoastGuard ───────────────────────────────────────────────────────────────
|
||||
|
||||
@ -180,7 +181,7 @@ export function createNavigationLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 8],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -231,7 +232,7 @@ export function createNavigationLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -281,7 +282,7 @@ export function createNavigationLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 9],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -332,7 +333,7 @@ export function createNavigationLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 14],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
|
||||
@ -7,6 +7,7 @@ import { KOREA_WIND_FARMS } from '../../data/windFarms';
|
||||
import type { WindFarm } from '../../data/windFarms';
|
||||
import { hexToRgb } from './types';
|
||||
import type { LayerFactoryConfig } from './types';
|
||||
import { FONT_MONO } from '../../styles/fonts';
|
||||
|
||||
// ─── Port colors ──────────────────────────────────────────────────────────────
|
||||
|
||||
@ -101,7 +102,7 @@ export function createPortLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 8],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
@ -139,7 +140,7 @@ export function createPortLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 10],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
|
||||
@ -3,6 +3,7 @@ import { ScatterplotLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { Layer } from '@deck.gl/core';
|
||||
import type { Ship, VesselAnalysisDto } from '../types';
|
||||
import { useFontScale } from './useFontScale';
|
||||
import { FONT_MONO } from '../styles/fonts';
|
||||
|
||||
interface AnalyzedShip {
|
||||
ship: Ship;
|
||||
@ -132,7 +133,7 @@ export function useAnalysisDeckLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 16],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -176,7 +177,7 @@ export function useAnalysisDeckLayers(
|
||||
getTextAnchor: 'middle',
|
||||
getAlignmentBaseline: 'top',
|
||||
getPixelOffset: [0, 14],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
outlineColor: [0, 0, 0, 255],
|
||||
@ -198,7 +199,7 @@ export function useAnalysisDeckLayers(
|
||||
getColor: [239, 68, 68, 255],
|
||||
getTextAnchor: 'start',
|
||||
getPixelOffset: [12, -8],
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
fontSettings: { sdf: true },
|
||||
outlineWidth: 3,
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-family: 'Inter Variable', 'Noto Sans KR Variable', system-ui, -apple-system, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import '@fontsource-variable/inter'
|
||||
import '@fontsource-variable/noto-sans-kr'
|
||||
import '@fontsource-variable/fira-code'
|
||||
import './i18n'
|
||||
import './styles/tailwind.css'
|
||||
import './index.css'
|
||||
|
||||
3
frontend/src/styles/fonts.ts
Normal file
3
frontend/src/styles/fonts.ts
Normal file
@ -0,0 +1,3 @@
|
||||
/** Self-hosted web font stacks — @fontsource-variable 패키지 기반 */
|
||||
export const FONT_SANS = "'Inter Variable', 'Noto Sans KR Variable', system-ui, sans-serif";
|
||||
export const FONT_MONO = "'Fira Code Variable', 'Noto Sans KR Variable', monospace";
|
||||
@ -184,6 +184,6 @@
|
||||
--color-kcg-danger-bg: var(--kcg-danger-bg);
|
||||
|
||||
/* 폰트 */
|
||||
--font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'Fira Code Variable', 'Noto Sans KR Variable', monospace;
|
||||
--font-sans: 'Inter Variable', 'Noto Sans KR Variable', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user