feat(map): generate mathematically accurate 100x50 world dot matrix and align country pinpoint coordinates

This commit is contained in:
ssbg04 committed 2026-10-18 22:12:40 +08:00
1 parent eb09f43454
commit c3f9ab43ab
3 files changed
+114 -77

No files matched your search

+64 -51
View File
@@ -31,51 +31,62 @@ interface GeoPayload {
}
/**
* 80x40 Equirectangular World Map Dot-Matrix Bitmask
* 100x50 High-Accuracy Equirectangular World Map Dot-Matrix Bitmask
* Mathematically aligned with Natural Earth 50m physical geography
* '#' = Landmass dot | '.' = Ocean
* Matches 2:1 aspect ratio (1000x500 SVG viewBox)
*/
const WORLD_DOT_MATRIX: string[] = [
"................................................................................",
"..........................##########............................................",
".................#####...############......................#####................",
"........#####...######...############.........####.......##############.........",
".....#################....##########........#######...####################......",
"....###################....########........#########.#######################....",
"....###################......####.........###################################...",
"......#################...................####################################..",
"........###############..................######################################.",
".........##############.................#####.################################..",
".........#############..................####..###############################...",
"..........###########...................#####..##############################...",
"..........##########....................######.############################.....",
"...........########.......#.............##################################......",
"............#######.....####.............################################.......",
".............###..##......###...........#########..#####################........",
"................####....................#########....###################........",
".................###...................###########....#####...#########.........",
"..................###..................############....###.....#######..........",
"...................###................#############....##.......#####...........",
"....................#######...........#############.............#####.#.........",
"....................#########.........#############..............####.##........",
"....................##########.........###########................######........",
"....................###########.........#########..................#######......",
"....................############.........########.................####.###......",
".....................###########..........#######...............................",
"......................#########...........######......................#####.....",
"......................########............######....................#########...",
".......................######.............#####....................##########...",
".......................#####...............####....................##########...",
".......................####................###.....................#########....",
".......................###..................#.......................#######.....",
".......................##................................................#..##..",
".......................##...................................................#...",
".......................#........................................................",
"................................................................................",
"................................................................................",
"................................................................................",
"................................................................................",
"................................................................................",
"....................................................................................................",
"...........................######....#######........................................................",
"....................###########################.....########..#######..##.#####.....................",
"...............###############################......#######.......####....########.....###.##.......",
"...............###############...#############.........#........####.#################.#####........",
"#..#############################..###########..#.....########.######################################",
"#################################..#######.####.....################################################",
".################################..####....######..#################################################",
".########################..######...###........###.###############################################..",
"..######....#######################............###..######################################...####...",
"####.........######################............###########################################...##..###",
"..............######################............###########################################.##......",
"...............#####################...........##############################################.......",
"...............################................#################.##########################.........",
"...............###############...........###...###########################################..........",
"................##############...............#.#####################################.#####..........",
".................###########...##............########################################.#.............",
".................#######..###...............########################################.##..#..........",
".....##...........#####..######..............########################################...............",
"......#............##############............######################..#############.#......#.........",
".....................######.#.####........##.#####################....####..#####..##.....#.........",
"........................###..#####...........#####################....###..#.####..##.....#.....#...",
"..........................########...........####################.....###..######.####.##...#...#...",
".....#......................########..........##################......#.#...###.######.......#.#.#..",
"......#.................#..##########..............#############......#.....###########..........##.",
"..#.....................##.#############............##########...............################...###.",
".##...#....................##############............#########...#............################......",
"..#........#...............##############....##......#########........#........########.#########..#",
"###....#....................############.............###########....................#######.#.###..#",
"#......####.................############........#....############..................########.....#..#",
"###..#....###.................#########..............########.##.##...............##########..####.#",
"..............#...............#########..............#######..##.................############..##.#.",
"...................#..........#######.................######..#..................############.......",
"..............................#######.................#####......................############.......",
"............................########..................####.......................####..######....##.",
".............................######....................................................#####......##",
".............................####........................................................###.....###",
"##..........................####................................#.........................##....###.",
"............................####............................#........#..........................#...",
".............................###.#.....#..............................#.........................##..",
".............................####......##.....................................................#.....",
"..................................#..#....#.........................................................",
"................................###.................................................................",
"..............................####...........................#########.######################..#....",
".....................###....######............####################################################..",
".........#########################.........#######################################################..",
"....#############################..###.###########################################################..",
".#..#############################################################################################...",
"#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.",
"....................................................................................................",
];
export default function VisitorWorldMap() {
@@ -568,6 +579,7 @@ export default function VisitorWorldMap() {
{/* Expand to Fullscreen War-Room Button */}
<button
id="world-map-header-expand-btn"
type="button"
onClick={() => {
haptic.openModal();
@@ -601,14 +613,14 @@ export default function VisitorWorldMap() {
id="ocean-dot-matrix"
x="0"
y="0"
width="12.5"
height="12.5"
width="10"
height="10"
patternUnits="userSpaceOnUse"
>
<circle
cx="6.25"
cy="6.25"
r="1"
cx="5"
cy="5"
r="0.85"
className="fill-current opacity-[0.06] dark:opacity-[0.08]"
/>
</pattern>
@@ -624,7 +636,7 @@ export default function VisitorWorldMap() {
key={dot.key}
cx={dot.cx}
cy={dot.cy}
r="2.6"
r="2.2"
/>
))}
</g>
@@ -677,6 +689,7 @@ export default function VisitorWorldMap() {
<span>Your Location</span>
</span>
<button
id="world-map-legend-expand-btn"
type="button"
onClick={() => {
haptic.tap();
@@ -913,11 +926,11 @@ export default function VisitorWorldMap() {
id="fullscreen-ocean-matrix"
x="0"
y="0"
width="12.5"
height="12.5"
width="10"
height="10"
patternUnits="userSpaceOnUse"
>
<circle cx="6.25" cy="6.25" r="1" className="fill-current opacity-[0.06] dark:opacity-[0.10]" />
<circle cx="5" cy="5" r="0.85" className="fill-current opacity-[0.06] dark:opacity-[0.10]" />
</pattern>
</defs>
@@ -925,7 +938,7 @@ export default function VisitorWorldMap() {
<g className="fill-current opacity-[0.25] dark:opacity-[0.35]">
{landDots.map((dot) => (
<circle key={dot.key} cx={dot.cx} cy={dot.cy} r="2.6" />
<circle key={dot.key} cx={dot.cx} cy={dot.cy} r="2.2" />
))}
</g>
</svg>
+49 -25
View File
@@ -5,31 +5,55 @@ export const prerender = false;
// Country code to coordinates (normalized 0-100% on Equirectangular / Mercator map)
// and country display metadata
export const COUNTRY_COORDS: Record<string, { name: string; flag: string; x: number; y: number }> = {
PH: { name: 'Philippines', flag: '🇵🇭', x: 81.5, y: 55.2 },
US: { name: 'United States', flag: '🇺🇸', x: 23.5, y: 38.0 },
CA: { name: 'Canada', flag: '🇨🇦', x: 22.0, y: 27.0 },
GB: { name: 'United Kingdom', flag: '🇬🇧', x: 48.0, y: 28.5 },
DE: { name: 'Germany', flag: '🇩🇪', x: 51.5, y: 29.5 },
FR: { name: 'France', flag: '🇫🇷', x: 49.5, y: 33.0 },
NL: { name: 'Netherlands', flag: '🇳🇱', x: 50.2, y: 28.8 },
SG: { name: 'Singapore', flag: '🇸🇬', x: 78.5, y: 59.5 },
JP: { name: 'Japan', flag: '🇯🇵', x: 86.8, y: 40.0 },
KR: { name: 'South Korea', flag: '🇰🇷', x: 84.0, y: 40.5 },
AU: { name: 'Australia', flag: '🇦🇺', x: 87.0, y: 76.0 },
NZ: { name: 'New Zealand', flag: '🇳🇿', x: 94.5, y: 84.0 },
IN: { name: 'India', flag: '🇮🇳', x: 72.0, y: 48.5 },
BR: { name: 'Brazil', flag: '🇧🇷', x: 34.5, y: 68.0 },
AE: { name: 'United Arab Emirates', flag: '🇦🇪', x: 63.8, y: 46.5 },
VN: { name: 'Vietnam', flag: '🇻🇳', x: 79.5, y: 53.0 },
ID: { name: 'Indonesia', flag: '🇮🇩', x: 81.0, y: 63.5 },
MY: { name: 'Malaysia', flag: '🇲🇾', x: 78.0, y: 58.0 },
ES: { name: 'Spain', flag: '🇪🇸', x: 47.0, y: 36.5 },
IT: { name: 'Italy', flag: '🇮🇹', x: 52.5, y: 34.8 },
SE: { name: 'Sweden', flag: '🇸🇪', x: 53.0, y: 22.0 },
CH: { name: 'Switzerland', flag: '🇨🇭', x: 50.8, y: 32.5 },
IE: { name: 'Ireland', flag: '🇮🇪', x: 46.2, y: 29.0 },
HK: { name: 'Hong Kong', flag: '🇭🇰', x: 80.5, y: 49.5 },
TW: { name: 'Taiwan', flag: '🇹🇼', x: 82.8, y: 49.0 },
PH: { name: 'Philippines', flag: '🇵🇭', x: 83.8, y: 42.9 },
US: { name: 'United States', flag: '🇺🇸', x: 22.6, y: 27.9 },
CA: { name: 'Canada', flag: '🇨🇦', x: 20.5, y: 18.8 },
GB: { name: 'United Kingdom', flag: '🇬🇧', x: 49.4, y: 20.3 },
DE: { name: 'Germany', flag: '🇩🇪', x: 52.9, y: 21.6 },
FR: { name: 'France', flag: '🇫🇷', x: 50.6, y: 24.3 },
NL: { name: 'Netherlands', flag: '🇳🇱', x: 51.5, y: 21.0 },
SG: { name: 'Singapore', flag: '🇸🇬', x: 78.8, y: 49.3 },
JP: { name: 'Japan', flag: '🇯🇵', x: 88.4, y: 29.9 },
KR: { name: 'South Korea', flag: '🇰🇷', x: 85.5, y: 30.1 },
AU: { name: 'Australia', flag: '🇦🇺', x: 87.2, y: 64.0 },
NZ: { name: 'New Zealand', flag: '🇳🇿', x: 98.6, y: 72.7 },
IN: { name: 'India', flag: '🇮🇳', x: 71.9, y: 38.6 },
BR: { name: 'Brazil', flag: '🇧🇷', x: 35.6, y: 57.9 },
AE: { name: 'United Arab Emirates', flag: '🇦🇪', x: 65.0, y: 37.0 },
VN: { name: 'Vietnam', flag: '🇻🇳', x: 80.1, y: 42.2 },
ID: { name: 'Indonesia', flag: '🇮🇩', x: 81.6, y: 50.4 },
MY: { name: 'Malaysia', flag: '🇲🇾', x: 78.3, y: 47.7 },
ES: { name: 'Spain', flag: '🇪🇸', x: 49.0, y: 27.5 },
IT: { name: 'Italy', flag: '🇮🇹', x: 53.5, y: 26.7 },
SE: { name: 'Sweden', flag: '🇸🇪', x: 55.2, y: 16.6 },
CH: { name: 'Switzerland', flag: '🇨🇭', x: 52.3, y: 24.0 },
IE: { name: 'Ireland', flag: '🇮🇪', x: 47.7, y: 20.3 },
HK: { name: 'Hong Kong', flag: '🇭🇰', x: 81.7, y: 37.6 },
TW: { name: 'Taiwan', flag: '🇹🇼', x: 83.6, y: 36.8 },
CN: { name: 'China', flag: '🇨🇳', x: 78.9, y: 30.1 },
MX: { name: 'Mexico', flag: '🇲🇽', x: 21.5, y: 36.9 },
PL: { name: 'Poland', flag: '🇵🇱', x: 55.3, y: 21.2 },
NO: { name: 'Norway', flag: '🇳🇴', x: 52.4, y: 16.4 },
FI: { name: 'Finland', flag: '🇫🇮', x: 57.2, y: 15.6 },
TH: { name: 'Thailand', flag: '🇹🇭', x: 78.1, y: 41.2 },
ZA: { name: 'South Africa', flag: '🇿🇦', x: 56.4, y: 67.0 },
AR: { name: 'Argentina', flag: '🇦🇷', x: 32.3, y: 71.3 },
CL: { name: 'Chile', flag: '🇨🇱', x: 30.1, y: 69.8 },
EG: { name: 'Egypt', flag: '🇪🇬', x: 58.6, y: 35.1 },
SA: { name: 'Saudi Arabia', flag: '🇸🇦', x: 62.5, y: 36.7 },
TR: { name: 'Turkey', flag: '🇹🇷', x: 59.8, y: 28.4 },
IL: { name: 'Israel', flag: '🇮🇱', x: 59.7, y: 32.8 },
UA: { name: 'Ukraine', flag: '🇺🇦', x: 58.7, y: 23.1 },
RO: { name: 'Romania', flag: '🇷🇴', x: 56.9, y: 24.5 },
BE: { name: 'Belgium', flag: '🇧🇪', x: 51.2, y: 21.9 },
AT: { name: 'Austria', flag: '🇦🇹', x: 54.0, y: 23.6 },
CZ: { name: 'Czechia', flag: '🇨🇿', x: 54.3, y: 22.3 },
PT: { name: 'Portugal', flag: '🇵🇹', x: 47.7, y: 28.1 },
GR: { name: 'Greece', flag: '🇬🇷', x: 56.1, y: 28.3 },
DK: { name: 'Denmark', flag: '🇩🇰', x: 52.6, y: 18.7 },
PK: { name: 'Pakistan', flag: '🇵🇰', x: 69.3, y: 33.1 },
BD: { name: 'Bangladesh', flag: '🇧🇩', x: 75.1, y: 36.8 },
RU: { name: 'Russia', flag: '🇷🇺', x: 79.3, y: 15.8 },
};
// In-memory country visitor counters across edge runtime instances
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "curved-cloud",
"name": "portfolioo",
"compatibility_date": "2026-10-12",
"kv_namespaces": [
{