💥 Update interactive Globe with latest features

It was long and painful but it's finally looking neat!

- place markers on globe following camera world matrix
- show location on marker hover instead of showing label near dot
- make marker easier to select
- make rotation constant no matter the monitor refresh rate by using a delta timed animation
- [wip] lighting the globe with a dark area / todo: how to get current sun lighting from a location?

Merci Julien :)
This commit is contained in:
2022-09-24 23:42:00 +02:00
parent b764b4d1d1
commit 71625dbce0
13 changed files with 394 additions and 422 deletions

View File

@@ -22,6 +22,7 @@
"motion": "^10.14.2", "motion": "^10.14.2",
"ogl": "^0.0.99", "ogl": "^0.0.99",
"sanitize.css": "^13.0.0", "sanitize.css": "^13.0.0",
"suncalc": "^1.9.0",
"swell-js": "^3.17.6", "swell-js": "^3.17.6",
"tweakpane": "^3.1.0" "tweakpane": "^3.1.0"
}, },
@@ -29,13 +30,13 @@
"@sveltejs/adapter-auto": "^1.0.0-next.80", "@sveltejs/adapter-auto": "^1.0.0-next.80",
"@sveltejs/adapter-node": "^1.0.0-next.95", "@sveltejs/adapter-node": "^1.0.0-next.95",
"@sveltejs/adapter-vercel": "^1.0.0-next.77", "@sveltejs/adapter-vercel": "^1.0.0-next.77",
"@sveltejs/kit": "^1.0.0-next.499", "@sveltejs/kit": "^1.0.0-next.503",
"@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0", "@typescript-eslint/parser": "^5.38.0",
"base-64": "^1.0.0", "base-64": "^1.0.0",
"browserslist": "^4.21.4", "browserslist": "^4.21.4",
"cssnano": "^5.1.13", "cssnano": "^5.1.13",
"eslint": "^8.23.1", "eslint": "^8.24.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"postcss": "^8.4.16", "postcss": "^8.4.16",
"postcss-focus-visible": "^7.1.0", "postcss-focus-visible": "^7.1.0",

88
pnpm-lock.yaml generated
View File

@@ -5,7 +5,7 @@ specifiers:
'@sveltejs/adapter-auto': ^1.0.0-next.80 '@sveltejs/adapter-auto': ^1.0.0-next.80
'@sveltejs/adapter-node': ^1.0.0-next.95 '@sveltejs/adapter-node': ^1.0.0-next.95
'@sveltejs/adapter-vercel': ^1.0.0-next.77 '@sveltejs/adapter-vercel': ^1.0.0-next.77
'@sveltejs/kit': ^1.0.0-next.499 '@sveltejs/kit': ^1.0.0-next.503
'@typescript-eslint/eslint-plugin': ^5.38.0 '@typescript-eslint/eslint-plugin': ^5.38.0
'@typescript-eslint/parser': ^5.38.0 '@typescript-eslint/parser': ^5.38.0
base-64: ^1.0.0 base-64: ^1.0.0
@@ -13,7 +13,7 @@ specifiers:
cssnano: ^5.1.13 cssnano: ^5.1.13
dayjs: ^1.11.5 dayjs: ^1.11.5
embla-carousel: ^7.0.3 embla-carousel: ^7.0.3
eslint: ^8.23.1 eslint: ^8.24.0
eslint-plugin-svelte3: ^4.0.0 eslint-plugin-svelte3: ^4.0.0
focus-visible: ^5.2.0 focus-visible: ^5.2.0
motion: ^10.14.2 motion: ^10.14.2
@@ -25,6 +25,7 @@ specifiers:
postcss-sort-media-queries: ^4.3.0 postcss-sort-media-queries: ^4.3.0
sanitize.css: ^13.0.0 sanitize.css: ^13.0.0
sass: ^1.55.0 sass: ^1.55.0
suncalc: ^1.9.0
svelte: ^3.50.1 svelte: ^3.50.1
svelte-check: ^2.9.0 svelte-check: ^2.9.0
svelte-preprocess: ^4.10.7 svelte-preprocess: ^4.10.7
@@ -42,6 +43,7 @@ dependencies:
motion: 10.14.2 motion: 10.14.2
ogl: 0.0.99 ogl: 0.0.99
sanitize.css: 13.0.0 sanitize.css: 13.0.0
suncalc: 1.9.0
swell-js: 3.17.6 swell-js: 3.17.6
tweakpane: 3.1.0 tweakpane: 3.1.0
@@ -49,14 +51,14 @@ devDependencies:
'@sveltejs/adapter-auto': 1.0.0-next.80 '@sveltejs/adapter-auto': 1.0.0-next.80
'@sveltejs/adapter-node': 1.0.0-next.95 '@sveltejs/adapter-node': 1.0.0-next.95
'@sveltejs/adapter-vercel': 1.0.0-next.77 '@sveltejs/adapter-vercel': 1.0.0-next.77
'@sveltejs/kit': 1.0.0-next.499_svelte@3.50.1+vite@3.1.3 '@sveltejs/kit': 1.0.0-next.503_svelte@3.50.1+vite@3.1.3
'@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry '@typescript-eslint/eslint-plugin': 5.38.0_4gkcvl6qsi23tqqawfqgcwtp54
'@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku
base-64: 1.0.0 base-64: 1.0.0
browserslist: 4.21.4 browserslist: 4.21.4
cssnano: 5.1.13_postcss@8.4.16 cssnano: 5.1.13_postcss@8.4.16
eslint: 8.23.1 eslint: 8.24.0
eslint-plugin-svelte3: 4.0.0_rhh4tpcyg7zwtq6pdnkzw5gxma eslint-plugin-svelte3: 4.0.0_snydkosur25h6rjxszquwacaua
postcss: 8.4.16 postcss: 8.4.16
postcss-focus-visible: 7.1.0_postcss@8.4.16 postcss-focus-visible: 7.1.0_postcss@8.4.16
postcss-normalize: 10.0.1_yroec54rl3ndwvbunmnefp5nvy postcss-normalize: 10.0.1_yroec54rl3ndwvbunmnefp5nvy
@@ -531,8 +533,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sveltejs/kit/1.0.0-next.499_svelte@3.50.1+vite@3.1.3: /@sveltejs/kit/1.0.0-next.503_svelte@3.50.1+vite@3.1.3:
resolution: {integrity: sha512-HoNsQ9CjdPLlDZLwcCsJTx5tW8YI3xhXEcJGuFHPLf600QcfeJWmQtE6mQF/SiykFkYR+2fD1GdRzKynJbZDMA==} resolution: {integrity: sha512-QSEHe40qMOYjXirxS57dIa9NU4FntlYh+KYslBzasjMCfSiUkHGaWMJRz8uU+R4BWnThD9SdCo7F/NwDxu5LRQ==}
engines: {node: '>=16.14'} engines: {node: '>=16.14'}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
@@ -604,8 +606,8 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
/@types/node/18.7.18: /@types/node/18.7.20:
resolution: {integrity: sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==} resolution: {integrity: sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==}
dev: true dev: true
/@types/pug/2.0.6: /@types/pug/2.0.6:
@@ -615,16 +617,16 @@ packages:
/@types/resolve/1.17.1: /@types/resolve/1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies: dependencies:
'@types/node': 18.7.18 '@types/node': 18.7.20
dev: true dev: true
/@types/sass/1.43.1: /@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies: dependencies:
'@types/node': 18.7.18 '@types/node': 18.7.20
dev: true dev: true
/@typescript-eslint/eslint-plugin/5.38.0_wsb62dxj2oqwgas4kadjymcmry: /@typescript-eslint/eslint-plugin/5.38.0_4gkcvl6qsi23tqqawfqgcwtp54:
resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==} resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -635,12 +637,12 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku
'@typescript-eslint/scope-manager': 5.38.0 '@typescript-eslint/scope-manager': 5.38.0
'@typescript-eslint/type-utils': 5.38.0_irgkl5vooow2ydyo6aokmferha '@typescript-eslint/type-utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku
'@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha '@typescript-eslint/utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku
debug: 4.3.4 debug: 4.3.4
eslint: 8.23.1 eslint: 8.24.0
ignore: 5.2.0 ignore: 5.2.0
regexpp: 3.2.0 regexpp: 3.2.0
semver: 7.3.7 semver: 7.3.7
@@ -650,7 +652,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser/5.38.0_irgkl5vooow2ydyo6aokmferha: /@typescript-eslint/parser/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku:
resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -664,7 +666,7 @@ packages:
'@typescript-eslint/types': 5.38.0 '@typescript-eslint/types': 5.38.0
'@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3
debug: 4.3.4 debug: 4.3.4
eslint: 8.23.1 eslint: 8.24.0
typescript: 4.8.3 typescript: 4.8.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -678,7 +680,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.38.0 '@typescript-eslint/visitor-keys': 5.38.0
dev: true dev: true
/@typescript-eslint/type-utils/5.38.0_irgkl5vooow2ydyo6aokmferha: /@typescript-eslint/type-utils/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku:
resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==} resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -689,9 +691,9 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3
'@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha '@typescript-eslint/utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku
debug: 4.3.4 debug: 4.3.4
eslint: 8.23.1 eslint: 8.24.0
tsutils: 3.21.0_typescript@4.8.3 tsutils: 3.21.0_typescript@4.8.3
typescript: 4.8.3 typescript: 4.8.3
transitivePeerDependencies: transitivePeerDependencies:
@@ -724,7 +726,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils/5.38.0_irgkl5vooow2ydyo6aokmferha: /@typescript-eslint/utils/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku:
resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==} resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -734,9 +736,9 @@ packages:
'@typescript-eslint/scope-manager': 5.38.0 '@typescript-eslint/scope-manager': 5.38.0
'@typescript-eslint/types': 5.38.0 '@typescript-eslint/types': 5.38.0
'@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3
eslint: 8.23.1 eslint: 8.24.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.23.1 eslint-utils: 3.0.0_eslint@8.24.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
@@ -859,7 +861,7 @@ packages:
postcss: ^8.1.0 postcss: ^8.1.0
dependencies: dependencies:
browserslist: 4.21.4 browserslist: 4.21.4
caniuse-lite: 1.0.30001410 caniuse-lite: 1.0.30001411
fraction.js: 4.2.0 fraction.js: 4.2.0
normalize-range: 0.1.2 normalize-range: 0.1.2
picocolors: 1.0.0 picocolors: 1.0.0
@@ -909,8 +911,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001410 caniuse-lite: 1.0.30001411
electron-to-chromium: 1.4.258 electron-to-chromium: 1.4.261
node-releases: 2.0.6 node-releases: 2.0.6
update-browserslist-db: 1.0.9_browserslist@4.21.4 update-browserslist-db: 1.0.9_browserslist@4.21.4
dev: true dev: true
@@ -940,13 +942,13 @@ packages:
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
dependencies: dependencies:
browserslist: 4.21.4 browserslist: 4.21.4
caniuse-lite: 1.0.30001410 caniuse-lite: 1.0.30001411
lodash.memoize: 4.1.2 lodash.memoize: 4.1.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
dev: true dev: true
/caniuse-lite/1.0.30001410: /caniuse-lite/1.0.30001411:
resolution: {integrity: sha512-QoblBnuE+rG0lc3Ur9ltP5q47lbguipa/ncNMyyGuqPk44FxbScWAeEO+k5fSQ8WekdAK4mWqNs1rADDAiN5xQ==} resolution: {integrity: sha512-HPnJKESKuhKpHvMY1/ux7J3nG7xG8jRuL4lbyCjDRm0doTNV91tcRk60xrP7Ym9DtJH/yuqntDWBJCqpXB4b7g==}
dev: true dev: true
/chalk/4.1.2: /chalk/4.1.2:
@@ -1256,8 +1258,8 @@ packages:
domhandler: 4.3.1 domhandler: 4.3.1
dev: true dev: true
/electron-to-chromium/1.4.258: /electron-to-chromium/1.4.261:
resolution: {integrity: sha512-vutF4q0dTUXoAFI7Vbtdwen/BJVwPgj8GRg/SElOodfH7VTX+svUe62A5BG41QRQGk5HsZPB0M++KH1lAlOt0A==} resolution: {integrity: sha512-fVXliNUGJ7XUVJSAasPseBbVgJIeyw5M1xIkgXdTSRjlmCqBbiSTsEdLOCJS31Fc8B7CaloQ/BFAg8By3ODLdg==}
dev: true dev: true
/embla-carousel/7.0.3: /embla-carousel/7.0.3:
@@ -1496,13 +1498,13 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/eslint-plugin-svelte3/4.0.0_rhh4tpcyg7zwtq6pdnkzw5gxma: /eslint-plugin-svelte3/4.0.0_snydkosur25h6rjxszquwacaua:
resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==}
peerDependencies: peerDependencies:
eslint: '>=8.0.0' eslint: '>=8.0.0'
svelte: ^3.2.0 svelte: ^3.2.0
dependencies: dependencies:
eslint: 8.23.1 eslint: 8.24.0
svelte: 3.50.1 svelte: 3.50.1
dev: true dev: true
@@ -1522,13 +1524,13 @@ packages:
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/eslint-utils/3.0.0_eslint@8.23.1: /eslint-utils/3.0.0_eslint@8.24.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies: peerDependencies:
eslint: '>=5' eslint: '>=5'
dependencies: dependencies:
eslint: 8.23.1 eslint: 8.24.0
eslint-visitor-keys: 2.1.0 eslint-visitor-keys: 2.1.0
dev: true dev: true
@@ -1542,8 +1544,8 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint/8.23.1: /eslint/8.24.0:
resolution: {integrity: sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==} resolution: {integrity: sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
@@ -1558,7 +1560,7 @@ packages:
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.1.1
eslint-utils: 3.0.0_eslint@8.23.1 eslint-utils: 3.0.0_eslint@8.24.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
espree: 9.4.0 espree: 9.4.0
esquery: 1.4.0 esquery: 1.4.0
@@ -3284,6 +3286,10 @@ packages:
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
dev: true dev: true
/suncalc/1.9.0:
resolution: {integrity: sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==}
dev: false
/supports-color/7.2.0: /supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'} engines: {node: '>=8'}

View File

@@ -4,16 +4,17 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount } from 'svelte' import { getContext, onMount } from 'svelte'
import { fly } from 'svelte/transition' import { fade, fly as flySvelte } from 'svelte/transition'
import { quartOut } from 'svelte/easing' import { quartOut } from 'svelte/easing'
import { Globe, type Marker } from '$modules/globe2' import { Globe, type Marker } from '$modules/globe2'
import { getRandomItem, debounce } from '$utils/functions' import { getRandomItem, debounce } from '$utils/functions'
import reveal from '$animations/reveal'
// Components // Components
import Image from '$components/atoms/Image.svelte' import SplitText from '$components/SplitText.svelte'
export let type: string = undefined export let type: string = undefined
export let enableMarkers: boolean = true export let enableMarkers: boolean = true
export let speed: number = 0.003 export let speed: number = 0.1
export let pane: boolean = import.meta.env.DEV export let pane: boolean = import.meta.env.DEV
export let width: number = undefined export let width: number = undefined
@@ -22,10 +23,7 @@
let globe: any let globe: any
let observer: IntersectionObserver let observer: IntersectionObserver
let animation: number let animation: number
let popinOpen: boolean = false let hoveredMarker: { name: string, country: string } = null
let clusterLocations: Marker[] = []
$: globeResolution = innerWidth > 1440 && window.devicePixelRatio > 1 ? '4k' : '2k'
const { continents, locations }: any = getContext('global') const { continents, locations }: any = getContext('global')
const randomContinent: any = getRandomItem(continents) const randomContinent: any = getRandomItem(continents)
@@ -39,35 +37,34 @@
onMount(() => { onMount(() => {
const globeResolution = innerWidth > 1440 && window.devicePixelRatio > 1 ? 4 : 2
globe = new Globe({ globe = new Globe({
el: globeEl, el: globeEl,
parent: globeParentEl, parent: globeParentEl,
mapFile: `/images/globe-map-${globeResolution}.png`, mapFile: `/images/globe-map-${globeResolution}k.png`,
mapFileDark: `/images/globe-map-dark-${globeResolution}k.png`,
dpr: Math.min(Math.round(window.devicePixelRatio), 2), dpr: Math.min(Math.round(window.devicePixelRatio), 2),
autoRotate: true, autoRotate: true,
speed, speed,
sunAngle: 2,
rotationStart: randomContinent.rotation, rotationStart: randomContinent.rotation,
enableMarkers, enableMarkers,
markers, markers,
pane, pane,
}) })
// TODO: Define cluster locations and position it
clusterLocations = locations.filter((loc: any) => loc.country.slug === 'france')
resize() resize()
// Render only if in viewport // Render only if in viewport
observer = new IntersectionObserver(entries => { observer = new IntersectionObserver(([{ isIntersecting }]) => {
entries.forEach(({ isIntersecting }: IntersectionObserverEntry) => { if (isIntersecting) {
if (isIntersecting) { update()
update() console.log('render globe2')
console.log('render globe2') } else {
} else { stop()
stop() console.log('stop globe2')
console.log('stop globe2') }
}
})
}, { threshold: 0 }) }, { threshold: 0 })
observer.observe(globeEl) observer.observe(globeEl)
@@ -114,55 +111,41 @@
class:is-cropped={type === 'cropped'} class:is-cropped={type === 'cropped'}
style:--width={width ? `${width}px` : null} style:--width={width ? `${width}px` : null}
> >
<div class="globe__inner"> <div class="globe__canvas" bind:this={globeEl}
<div class="globe__canvas" bind:this={globeEl} /> class:is-faded={hoveredMarker}
</div> >
<ul class="globe__markers">
{#if enableMarkers}
<ul class="globe__markers" data-sveltekit-noscroll>
{#each markers as { name, slug, country, lat, lng }} {#each markers as { name, slug, country, lat, lng }}
<li class="globe__marker" data-location={slug} data-lat={lat} data-lng={lng}> <li class="globe__marker" data-location={slug} data-lat={lat} data-lng={lng}>
<a href="/{country.slug}/{slug}"> <a href="/{country.slug}/{slug}" data-sveltekit-noscroll
<dl> on:mouseenter={() => hoveredMarker = { name, country: country.name }}
<dt class="title-small">{name}</dt> on:mouseleave={() => hoveredMarker = null}
<dd class="text-label text-label--small">{country.name}</dd> >
</dl> <i />
<span>{name}</span>
</a> </a>
</li> </li>
{/each} {/each}
<li class="globe__cluster">
<button on:click={() => popinOpen = !popinOpen} aria-label="{popinOpen ? 'Close' : 'Open'} cluster" />
</li>
</ul> </ul>
</div>
{#if popinOpen} {#if hoveredMarker}
<div class="globe__popin" transition:fly={{ y: 16, duration: 500, easing: quartOut }}> <div class="globe__location"
<ul data-sveltekit-noscroll> transition:fade={{ duration: 300, easing: quartOut }}
{#each clusterLocations as { name, slug, country }} use:reveal={{
<li> children: '.char',
<a href="/{country.slug}/{slug}" tabindex="0"> animation: { y: ['110%', 0] },
<Image options: {
class="flag" stagger: 0.04,
id={country.flag.id} duration: 1,
sizeKey="square-small" threshold: 0,
width={32} height={32} },
alt="Flag of {country.name}" }}
/> >
<dl> <SplitText text={hoveredMarker.name} mode="chars" class="name" />
<dt class="title-small">{name}</dt> <p class="country" in:flySvelte={{ y: 16, duration: 800, easing: quartOut, delay: 900 }}>
<dd class="text-label text-label--small">{country.name}</dd> {hoveredMarker.country}
</dl> </p>
</a> </div>
</li>
{/each}
</ul>
<button class="close" aria-label="Close" on:click={() => popinOpen = false}>
<svg width="9" height="9">
<use xlink:href="#cross" />
</svg>
</button>
</div>
{/if}
{/if} {/if}
</div> </div>

View File

@@ -1,37 +1,20 @@
precision highp float; precision highp float;
varying vec3 v_normal;
varying vec3 v_surfaceToLight; varying vec3 vNormal;
varying vec3 v_surfaceToView;
varying vec2 v_uv;
uniform float u_dt;
uniform float u_shininess;
uniform sampler2D map; uniform sampler2D map;
uniform sampler2D mapDark;
varying vec2 vUv;
varying vec3 vSunDir;
void main() { void main() {
// Re-normalize interpolated varyings float cosineAngleSunToNormal = dot(normalize(vNormal), normalize(vSunDir));
vec3 normal = normalize(v_normal); cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 1.0, -1.0, 1.0);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToViewDirection = normalize(v_surfaceToView);
// Calculate Half-Vector, Vector that bisects the angle of reflection.
// This vector indecates the "brightest point" A "refrence vector" if you will.
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
// Then we can get the brightness at any point by seeing "how similar" the surface normal is to the refrence vector.
float light = dot(normal, surfaceToLightDirection);
// By raising the specular vector to a power we can control the intensity of the light float mixAmount = cosineAngleSunToNormal * 0.666 + 0.333;
float specular = 0.0; vec3 dayColor = texture2D(map, vUv).rgb;
vec3 nightColor = texture2D(mapDark, vUv).rgb;
vec3 color = mix(nightColor, dayColor, mixAmount);
if (light > 0.0) { gl_FragColor = vec4(color, 1.0);
specular = pow(dot(normal, halfVector), u_shininess * 100.0); }
}
// Mapping textures
vec4 map = texture2D(map, v_uv).rgba;
// vec3 spec = texture2D(specMap, v_uv).rgb;
gl_FragColor.rgba = map;
// Add Point Lighting
gl_FragColor.rgba *= light;
// Add Specular Highlights
// gl_FragColor.rgb += specular * spec;
}

View File

@@ -1,8 +1,10 @@
// @ts-nocheck // @ts-nocheck
import { Renderer, Camera, Vec3, Orbit, Sphere, Transform, Program, Mesh, Texture } from 'ogl' import { Renderer, Camera, Vec3, Orbit, Sphere, Transform, Program, Mesh, Texture } from 'ogl'
import SunCalc from 'suncalc'
import { map } from '$utils/functions/index'
// Shaders // Shaders
import VERTEX_SHADER from '../../modules/globe2/vertex.glsl?raw' import VERTEX_SHADER from '$modules/globe2/vertex.glsl?raw'
import FRAGMENT_SHADER from '../../modules/globe2/frag.glsl?raw' import FRAGMENT_SHADER from '$modules/globe2/frag.glsl?raw'
export class Globe { export class Globe {
@@ -14,17 +16,47 @@ export class Globe {
this.width = this.el.offsetWidth this.width = this.el.offsetWidth
this.height = this.el.offsetHeight this.height = this.el.offsetHeight
this.markers = options.markers || [] this.markers = options.markers || []
this.globeRotation = {
lat: degToRad(-this.options.rotationStart.lat) || 0,
lng: degToRad(-this.options.rotationStart.lng) || 0,
}
// Calculate the current sun position from a given location
const locations = [
{
lat: -37.840935,
lng: 144.946457,
tz: 'Australia/Melbourne',
},
{
lat: 48.856614,
lng: 2.3522219,
tz: 'Europe/Paris',
}
]
const location = locations[1]
const localDate = new Date(now.toLocaleString('en-US', { timeZone: location.tz }))
this.sunPosition = SunCalc.getPosition(localDate, location.lat, location.lng)
var times = SunCalc.getTimes(new Date(), location.lat, location.lng);
var sunrisePos = SunCalc.getPosition(times.sunrise, location.lat, location.lng);
this.sunriseAzimuth = sunrisePos.azimuth * 180 / Math.PI;
// Parameters // Parameters
this.params = { this.params = {
autoRotate: options.autoRotate, autoRotate: options.autoRotate,
speed: options.speed, speed: options.speed,
enableMarkers: options.enableMarkers, enableMarkers: options.enableMarkers,
zoom: 1.305, zoom: 1.3075,
sunAngle: options.sunAngle || 0,
sunAngleDelta: 1.8,
} }
// Misc // Misc
this.hoveringMarker = false this.hoveringMarker = false
this.hoveringMarkerTimeout = 0
this.lastFrame = now()
this.dragging = false this.dragging = false
this.webgl = WebGLSupport() !== null this.webgl = WebGLSupport() !== null
this.pane = undefined this.pane = undefined
@@ -33,7 +65,6 @@ export class Globe {
if (this.webgl) { if (this.webgl) {
this.build() this.build()
this.resize() this.resize()
this.render()
} }
// Add GUI panel if activated // Add GUI panel if activated
@@ -60,19 +91,16 @@ export class Globe {
// Create camera // Create camera
this.camera = new Camera(this.gl) this.camera = new Camera(this.gl)
// TODO: Why 1.315? Is there a way to calculate this number?
this.camera.position.set(0, 0, this.params.zoom) this.camera.position.set(0, 0, this.params.zoom)
// Create controls // Create controls
this.controls = new Orbit(this.camera, { this.controls = new Orbit(this.camera, {
element: this.el, element: this.el,
target: new Vec3(0,0,0),
enableZoom: false, enableZoom: false,
enablePan: false, enablePan: false,
autoRotate: false,
ease: 0.2, ease: 0.2,
minPolarAngle: Math.PI / 4, minPolarAngle: Math.PI / 4,
maxPolarAngle: Math.PI / 1.5, maxPolarAngle: Math.PI / 1.85,
}) })
// Append canvas to scene // Append canvas to scene
@@ -81,51 +109,54 @@ export class Globe {
// Create scene and geometry // Create scene and geometry
this.scene = new Transform() this.scene = new Transform()
this.geometry = new Sphere(this.gl, { this.geometry = new Sphere(this.gl, {
widthSegments: 64, widthSegments: 75,
heightSegments: 64, heightSegments: 75,
}) })
// Create light
// TODO: How to create a nicer light that doesn't fade to 0? Just creating a "dark" area where you still can read markers and see countries/continents
// this.light = new Vec3(0, 50, 150)
this.light = new Vec3(0, 0, 15)
// Add map texture // Add map texture
const map = new Texture(this.gl) const mapWorld = new Texture(this.gl)
const img = new Image() const img = new Image()
img.onload = () => (map.image = img) img.onload = () => (mapWorld.image = img)
img.src = this.options.mapFile img.src = this.options.mapFile
// Dark map texture
const mapDark = new Texture(this.gl)
const imgDark = new Image()
imgDark.onload = () => (mapDark.image = imgDark)
imgDark.src = this.options.mapFileDark
const azimuthValue = map(this.sunriseAzimuth, -180, 180, -Math.PI, Math.PI);
// Create program // Create program
this.program = new Program(this.gl, { const program = new Program(this.gl, {
vertex: VERTEX_SHADER, vertex: VERTEX_SHADER,
fragment: FRAGMENT_SHADER, fragment: FRAGMENT_SHADER,
uniforms: { uniforms: {
u_dt: { value: 0 }, u_dt: { value: 0 },
u_lightWorldPosition: { value: this.light }, // Position of the Light map: { value: mapWorld }, // Map Texture
u_shininess: { value: 1.0 }, mapDark: { value: mapDark }, // Map Dark Texture
map: { value: map }, // Color Map altitude: { value: 0 },
azimuth: { value: 0 },
}, },
transparent: true, cullFace: null,
}) })
// Create mesh // Create light
this.mesh = new Mesh(this.gl, { program.uniforms.altitude.value = this.sunPosition.altitude
program.uniforms.azimuth.value = this.sunPosition.azimuth
// Create globe mesh
this.globe = new Mesh(this.gl, {
geometry: this.geometry, geometry: this.geometry,
program: this.program, program,
}) })
this.mesh.setParent(this.scene) this.globe.setParent(this.scene)
// Start globe angle with a random continent's position
if (this.options.rotationStart) {
this.mesh.rotation.y = degToRad(this.options.rotationStart * -1) || 0
}
// Add events // Add events
this.addEvents() this.addEvents()
// Setup markers // Setup markers
if (this.enableMarkers && this.markers) { if (this.markers) {
this.setupMarkers() this.setupMarkers()
} }
} }
@@ -165,45 +196,57 @@ export class Globe {
this.markers.forEach((marker: Marker) => { this.markers.forEach((marker: Marker) => {
const markerEl = this.getMarker(marker.slug) const markerEl = this.getMarker(marker.slug)
// Define position // Update marker position
const position = lonlatVec3(marker.lng, marker.lat) this.updateMarkerPosition(marker, markerEl)
// Scale marker position to fit globe size
marker.position = [position[0] *= 0.5, position[1] *= 0.5, position[2] *= 0.5]
// Position marker
const posX = (marker.position[0] + 1) * (this.width / this.params.zoom)
const posY = (1 - marker.position[1]) * (this.height / this.params.zoom)
markerEl.style.transform = `translate3d(${posX}px, ${posY}px, 0)`
// Entering marker // Entering marker
markerEl.addEventListener('mouseenter', () => { markerEl.addEventListener('mouseenter', () => {
this.hoveringMarker = true this.hoveringMarker = true
}, false) clearTimeout(this.hoveringMarkerTimeout)
// Leaving marker
markerEl.addEventListener('mouseleave', () => {
this.hoveringMarker = false
}, false) }, false)
// console.log(marker) // Leaving marker
markerEl.addEventListener('mouseleave', () => {
this.hoveringMarkerTimeout = setTimeout(() => {
this.hoveringMarker = false
}, 300)
}, false)
return marker return marker
}) })
} }
// Update marker position
updateMarkerPosition (marker: Marker, markerEl: HTMLElement) {
const position = latLonToVec3(marker.lat, marker.lng)
const screenVector = new Vec3(position.x, position.y, position.z)
screenVector.applyMatrix4(this.globe.worldMatrix)
this.camera.project(screenVector)
// Position marker
const posX = ((screenVector[0] + 1) / 2) * this.width
const posY = (1. - (screenVector[1] + 1) / 2) * this.height
markerEl.style.transform = `translate3d(${posX}px, ${posY}px, 0)`
// Hide marker if behind globe
markerEl.classList.toggle('is-hidden', screenVector[2] > 0.82)
}
// Update markers // Update markers
updateMarkers () { updateMarkers () {
this.markers.forEach((marker: Marker) => { this.markers.forEach((marker: Marker) => {
// const markerEl = this.getMarker(marker.slug) const markerEl = this.getMarker(marker.slug)
// const screenVector = new Vec3(0,0,0)
// screenVector.copy(marker.position)
// this.camera.project(screenVector)
// Update marker position
this.updateMarkerPosition(marker, markerEl)
})
}
// let posX = (screenVector.x + 1) * (this.options.width / this.params.zoom) // Disable markers
// // // posX /= this.mesh.rotation.y hideMarkers () {
// let posY = (1 - screenVector.y) * (this.options.height / this.params.zoom) this.markers.forEach((marker: Marker) => {
// markerEl.style.transform = `translate3d(${posX}px, ${posY}px, 0)` const markerEl = this.getMarker(marker.slug)
markerEl.classList.add('is-hidden')
}) })
} }
@@ -212,7 +255,6 @@ export class Globe {
* Resize method * Resize method
*/ */
resize () { resize () {
// this.renderer.setSize(window.innerWidth, window.innerHeight)
this.width = this.el.offsetWidth this.width = this.el.offsetWidth
this.height = this.el.offsetHeight this.height = this.el.offsetHeight
this.renderer.setSize(this.width, this.height) this.renderer.setSize(this.width, this.height)
@@ -226,27 +268,28 @@ export class Globe {
* Update method * Update method
*/ */
render () { render () {
// Stop render if not dragging but hovering marker const delta = (now() - this.lastFrame) / 1000
if (!this.dragging && this.hoveringMarker) return this.lastFrame = now()
// Update globe rotation // Rotate globe if not dragging neither hovering marker
if (this.params.autoRotate) { if (this.params.autoRotate && !this.hoveringMarker) {
this.mesh.rotation.y += this.params.speed this.globeRotation.lat += this.params.speed * delta
this.globe.rotation.y = this.globeRotation.lat
} }
// Update controls and renderer // Update controls and renderer
this.controls.update(this.params) this.controls.update()
this.renderer.render({ this.renderer.render({
scene: this.scene, scene: this.scene,
camera: this.camera, camera: this.camera,
}) })
// TODO: Update light
// this.light.set(this.camera.position)
// this.program.uniforms.u_lightWorldPosition.value = [this.mesh.rotation.y * 1, 50, 150]
// Update markers // Update markers
this.updateMarkers() if (this.params.enableMarkers) {
this.updateMarkers()
} else {
this.hideMarkers()
}
} }
@@ -259,7 +302,7 @@ export class Globe {
this.gl = null this.gl = null
this.scene = null this.scene = null
this.camera = null this.camera = null
this.mesh = null this.globe = null
this.renderer = null this.renderer = null
this.controls.remove() this.controls.remove()
@@ -277,9 +320,11 @@ type Options = {
el: HTMLElement el: HTMLElement
parent: HTMLElement parent: HTMLElement
mapFile: string mapFile: string
mapFileDark: string
dpr: number dpr: number
autoRotate: boolean autoRotate: boolean
speed: number speed: number
sunAngle: number
rotationStart?: number rotationStart?: number
enableMarkers?: boolean enableMarkers?: boolean
markers?: any[] markers?: any[]
@@ -297,7 +342,6 @@ export type Marker = {
} }
lat: number lat: number
lng: number lng: number
position?: number[]
} }
@@ -319,17 +363,24 @@ function WebGLSupport () {
/** /**
* Convert lat/lng to Vec3 * Convert lat/lng to Vec3
*/ */
function lonlatVec3 (longitude: number, latitude: number) { const latLonToVec3 = (lat: number, lng: number) => {
const lat = latitude * Math.PI / 180 const phi = (90 - lat) * (Math.PI / 180)
const lng = -longitude * Math.PI / 180 const theta = (lng + 180) * (Math.PI / 180)
return new Vec3(
Math.cos(lat) * Math.cos(lng), const x = -((0.5) * Math.sin(phi) * Math.cos(theta))
Math.sin(lat), const z = ((0.5) * Math.sin(phi) * Math.sin(theta))
Math.cos(lat) * Math.sin(lng) const y = ((0.5) * Math.cos(phi))
)
return new Vec3(x,y,z)
} }
/** /**
* Convert Degrees to Radians * Convert Degrees to Radians
*/ */
const degToRad = (deg: number) => deg * Math.PI / 180 const degToRad = (deg: number) => deg * Math.PI / 180
/**
* Get current timestamp (performance or Date)
*/
const now = () => (typeof performance === 'undefined' ? Date : performance).now()

View File

@@ -3,19 +3,50 @@ import { Pane } from 'tweakpane'
export const createPane = (ctx: any) => { export const createPane = (ctx: any) => {
ctx.pane = new Pane({ ctx.pane = new Pane({
container: ctx.parent, container: ctx.parent,
title: 'Settings', title: 'Globe Settings',
}) })
ctx.pane.addInput(ctx.params, 'autoRotate', {
/**
* Rotation
*/
const rotation = ctx.pane.addFolder({
title: 'Rotation',
})
rotation.addInput(ctx.params, 'autoRotate', {
label: 'Auto-rotate', label: 'Auto-rotate',
}) })
ctx.pane.addInput(ctx.params, 'speed', { rotation.addInput(ctx.params, 'speed', {
label: 'Rotation speed', label: 'Rotation speed',
min: 0.0005, min: 0.01,
max: 0.025, max: 2,
step: 0.00025, step: 0.05,
}) })
ctx.pane.addInput(ctx.params, 'enableMarkers', {
label: 'Enable markers',
/**
* Markers
*/
if (ctx.markers && ctx.markers.length > 0) {
const markers = ctx.pane.addFolder({
title: 'Markers',
})
markers.addInput(ctx.params, 'enableMarkers', {
label: 'Enable markers',
})
}
/**
* Others
*/
const misc = ctx.pane.addFolder({
title: 'Misc',
})
// Sun position
misc.addInput(ctx.params, 'sunAngleDelta', {
label: 'Sun angle delta',
min: 0,
max: 2 * Math.PI,
}) })
} }

View File

@@ -1,32 +1,29 @@
varying vec3 vNormal;
attribute vec2 uv; attribute vec2 uv;
attribute vec3 position; attribute vec3 position;
attribute vec3 normal; attribute vec3 normal;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix; uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix; uniform mat4 projectionMatrix;
uniform mat3 normalMatrix; uniform mat3 normalMatrix;
uniform vec3 u_lightWorldPosition; uniform float azimuth;
uniform vec3 cameraPosition; uniform float altitude;
varying vec2 vUv;
varying vec3 vSunDir;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
varying vec2 v_uv;
void main () { void main() {
// Pass UV information to Fragment Shader vUv = uv;
v_uv = uv; // float px = sin(rotation) * 1.0;
// float pz = cos(rotation) * 1.0;
float px = sin(0.0) * 1.0;
float pz = cos(0.0) * 1.0;
vec3 uLightPos = vec3(px, 0.0, pz);
// Calculate World Space Normal vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
v_normal = normalMatrix * normal;
// Compute the world position of the surface vNormal = normalMatrix * normal;
vec3 surfaceWorldPosition = mat3(modelMatrix) * position; vSunDir = mat3(normalMatrix) * uLightPos;
// Vector from the surface, to the light gl_Position = projectionMatrix * mvPosition;
v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition; }
// Vector from the surface, to the camera
v_surfaceToView = cameraPosition - surfaceWorldPosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

View File

@@ -4,46 +4,93 @@
position: relative; position: relative;
z-index: 10; z-index: 10;
user-select: none; user-select: none;
overflow: hidden;
// Inner // Canvas
&__inner { &__canvas {
position: relative; position: relative;
width: var(--width); z-index: 2;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%) translateZ(0);
width: var(--width);
// Responsive square padding
&:after { &:after {
content: ""; content: "";
display: block; display: block;
padding-bottom: 100%; padding-bottom: 100%;
} }
// Overlay
&:before {
content: "";
display: block;
position: absolute;
z-index: 21;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
pointer-events: none;
border-radius: 100%;
background: $color-primary;
transition: opacity 1.5s var(--ease-quart);
}
:global(canvas) {
position: absolute;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: grab;
}
// Is faded under name
&:global(.is-faded:before) {
opacity: 0.65;
}
} }
// Canvas
&__canvas { // Location name
&__location {
position: absolute; position: absolute;
z-index: 2; z-index: 30;
top: 0; top: 50%;
left: 50%;
transform: translate3d(-50%, 0, 0);
width: 100%;
height: 100%;
}
:global(canvas) {
position: absolute;
z-index: 10;
top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; overflow: hidden;
cursor: grab; transform: translateY(-50%) translateZ(0);
pointer-events: none;
text-align: center;
:global(.char) {
transition: none;
}
:global(.name) {
font-family: $font-serif;
font-weight: 100;
letter-spacing: -0.035em;
color: $color-secondary;
font-size: clamp(#{rem(88px)}, 20vw, #{rem(320px)});
}
.country {
display: block;
text-transform: uppercase;
font-size: rem(14px);
color: $color-tertiary;
letter-spacing: 0.1em;
font-weight: 500;
}
} }
// Markers // Markers
&__markers { &__markers {
position: absolute; position: relative;
z-index: 2; z-index: 20;
top: 0;
left: 0;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
@@ -61,178 +108,51 @@
left: 0; left: 0;
user-select: none; user-select: none;
transform: translate3d(var(--x), var(--y), 0); transform: translate3d(var(--x), var(--y), 0);
transition: opacity 0.4s var(--ease-quart);
a { a {
position: relative; position: relative;
top: -10px;
left: -10px;
display: block;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none; text-decoration: none;
color: $color-secondary; color: $color-secondary;
pointer-events: auto; pointer-events: auto;
dl > * {
transition: opacity 0.5s;
}
dt {
line-height: 1;
}
dd {
color: $color-gray;
margin-top: 4px;
line-height: 1;
opacity: 0;
}
// Dot // Dot
&:before { i {
content: "";
display: block; display: block;
position: absolute; width: 10px;
top: 10px; height: 10px;
left: -16px; border-radius: 32px;
width: 8px;
height: 8px;
border-radius: 100%;
background: $color-secondary; background: $color-secondary;
transition: box-shadow 0.4s var(--ease-quart), transform 0.4s var(--ease-quart);
transform-origin: 50% 50%;
} }
} // Name
span {
/* display: none;
** States
*/
// Has name
&:global(.is-dot-only) {
dt, dd {
opacity: 0;
} }
}
// Has country
&:global(.has-country) {
dd {
opacity: 1;
}
}
}
// Cluster
&__cluster {
position: absolute;
z-index: 10;
top: 300px;
left: 300px;
pointer-events: auto;
button {
width: 32px;
height: 32px;
padding: 0;
border: none;
border-radius: 100%;
background: rgba($color-secondary, 0.2);
transition: box-shadow 0.5s var(--ease-quart), background 0.5s var(--ease-quart);
}
&:hover {
button {
background: rgba($color-secondary, 0.3);
box-shadow: 0 0 0 8px rgba($color-secondary, 0.1);
}
}
}
// Popin
&__popin {
position: absolute;
z-index: 10;
top: 12vw;
left: 50%;
transform: translate3d(-50%, 0, 0);
pointer-events: auto;
width: 546px;
padding: 24px 32px;
border-radius: 16px;
background: #fff;
--shadow-color: #{rgba(45, 4, 88, 0.05)};
box-shadow:
0 6px 6px var(--shadow-color),
0 12px 12px var(--shadow-color),
0 24px 24px var(--shadow-color),
0 40px 40px var(--shadow-color);
ul {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px 16px;
}
li {
display: block;
transform: translateZ(0);
}
a {
display: flex;
align-items: center;
padding: 12px;
border-radius: 6px;
text-decoration: none;
transition: background 0.3s var(--ease-quart);
// Hover: Grow marker outline
&:hover { &:hover {
background: rgba($color-secondary, 0.1); i {
box-shadow: 0 0 0 10px rgba($color-tertiary, 0.25);
}
} }
} }
// Flag // State: Is hidden
:global(.flag) { &:global(.is-hidden) {
display: block; opacity: 0;
width: 28px;
height: 28px;
overflow: hidden;
border-radius: 100%;
transform: translateZ(0);
:global(img) { i {
display: block; transform: scale(0) translateZ(0);
width: 100%;
height: 100%;
}
}
// Details
dl {
margin-left: 16px;
}
dt {
margin-bottom: 4px;
line-height: 1.2;
}
dd {
color: $color-gray;
line-height: 1;
}
// Close buttom
.close {
position: absolute;
z-index: 2;
top: 12px;
right: 12px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
color: $color-primary-darker;
background: rgba($color-secondary, 0.15);
border-radius: 100%;
transition: background 0.3s var(--ease-quart);
svg {
display: block;
width: 9px;
height: 9px;
}
&:hover {
background: rgba($color-secondary, 0.3);
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB