diff --git a/src/components/organisms/InteractiveGlobe.svelte b/src/components/organisms/InteractiveGlobe.svelte new file mode 100644 index 0000000..04790aa --- /dev/null +++ b/src/components/organisms/InteractiveGlobe.svelte @@ -0,0 +1,130 @@ + + + + + +
+ {#if type === 'cropped'} +
+
+
+ {:else} +
+ {/if} +
\ No newline at end of file diff --git a/src/modules/globe/index.js b/src/modules/globe/index.js index 238b920..a4538b5 100644 --- a/src/modules/globe/index.js +++ b/src/modules/globe/index.js @@ -24,7 +24,6 @@ const degToRad = deg => deg * Math.PI / 180 class WebglGlobe { - // Constructor constructor (options) { this.$el = options.el // The DOM reference node @@ -34,7 +33,7 @@ class WebglGlobe { this.options.cameraDistance = 1 // this.options.cameraDistance || 1 // A multiplier to move camera backward or forward this.options.opacity = this.options.opacity || 1 - this.cities = options.markers // List of cities with their options + this.locations = options.markers // List of locations with their options this._canUpdate = false this.hasUpdateCameraPos = false this.referenceHeight = 1 // Used to set camera distance from globe where referenceHeight == window height @@ -157,7 +156,7 @@ class WebglGlobe { * Create DOM nodes for markers and 3D positions */ this.markers = [] - let markers = this.cities + let markers = this.locations // Instance all markers for (let i = 0; i < markers.length; i++) { @@ -362,7 +361,7 @@ class WebglGlobe { vec2.set(dir, x, y) let center = vec2.create() vec2.set(center, this.width/2, this.height/2) - let dir2d = vec2.clone(dir, dir) + let dir2d = vec2.clone(dir) // vec2.clone(dir, dir) vec2.subtract(dir2d, dir2d, center) vec2.normalize(dir2d, dir2d) vec2.scale(dir2d, dir2d, this.circleScreenSize) diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 951e563..ca269bd 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -28,6 +28,7 @@ location { name slug + coordinates country { name slug @@ -58,6 +59,7 @@ continent { name slug + rotation countries { slug } diff --git a/src/routes/index.svelte b/src/routes/index.svelte index 89d7f57..b04be02 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -53,6 +53,8 @@ + + diff --git a/src/style/_animations.scss b/src/style/_animations.scss index 905ca1d..126ba00 100644 --- a/src/style/_animations.scss +++ b/src/style/_animations.scss @@ -1,119 +1,3 @@ -/* ========================================================================== - PAGE TRANSITION -========================================================================== */ -.transition { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 400; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - cursor: wait; - - &, * { - will-change: transform, opacity; - } - - // Content - &__loader { - position: relative; - z-index: 2; - } - - // Background - &__background { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: $color-primary; - transform-origin: 50% 0; - } - - // Hidden - &.hidden { - display: none; - } -} - - - -/* ========================================================================== - REVEAL ANIMATIONS -========================================================================== */ -.anim-mask { - display: block; - overflow: hidden; - white-space: nowrap; - - span { - display: inline-block; - } -} - - - -/* ========================================================================== - KEYFRAMES ANIMATIONS -========================================================================== */ -// Rotate button dashes -@keyframes rotateDashes { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - - -/* -** Spinning globe -*/ -@keyframes moveContinents { - 0% { transform: translate(0,0); } - 100% { transform: translate(-80.26px, 28.2px); } -} -.anim-spinGlobe { - animation: moveContinents 1.7s linear infinite; - animation-play-state: paused; -} -// Small -@keyframes moveContinentsSmall { - 0% { transform: translate(0,0); } - 100% { transform: translate(-96.95px, 0); } -} -.anim-spinGlobeSmall { - animation: moveContinentsSmall 1.5s linear infinite; - animation-play-state: paused; -} - - -/* -** Layout -*/ -// List -@keyframes layoutListOdd { - 0% { transform: translateX(0); } - 50% { transform: translateX(2px); } -} -@keyframes layoutListEven { - 0% { transform: translateX(0); } - 50% { transform: translateX(-4px); } -} - -// Grid -@keyframes layoutGridOdd { - 0% { transform: translateY(0); } - 50% { transform: translateY(2px); } -} -@keyframes layoutGridEven { - 0% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - - /* ** Globe */ diff --git a/src/style/modules/_globe.scss b/src/style/modules/_globe.scss new file mode 100644 index 0000000..d8b82b2 --- /dev/null +++ b/src/style/modules/_globe.scss @@ -0,0 +1,187 @@ +// Globe +.globe { + position: relative; + z-index: 2; + width: 1315px; + height: clamp(700px, 100vw, 1315px); + overflow: hidden; + cursor: grab; + user-select: none; + + @include bp (sm) { + // height: 130vw; + } + @include bp (md) { + // height: 112vw; + } + @include bp (xl) { + // height: 100vw; + } + + // DEBUG // + // background: rgba(red, 0.2); + // &:after { + // content: ""; + // display: block; + // position: absolute; + // top: 50%; + // left: 0; + // background: blue; + // width: 100%; + // height: 2px; + // margin-top: -1px; + // } + // END DEBUG // + + /* + ** Cropped globe + */ + &--cropped { + overflow: hidden; + height: clamp(300px, 30vw, 500px); + } + + /* + ** Markers + */ + &__markers { + z-index: 210; + + // When dragging + &.is-grabbing { + cursor: grabbing; + } + + // Marker + .marker { + position: absolute; + z-index: 2; + cursor: pointer; + display: block; + top: -4px; + left: -4px; + padding: 4px; + opacity: 1; + will-change: transform; + + // Dot + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 8px; + background: $color-secondary; + border-radius: 100%; + } + + span { + transition: color 0.4s $ease-quart, opacity 0.3s $ease-inout; + } + + // Hover glow effect + &.hover { + &:before { + animation: globeMarkerPulse 1s; + } + } + + // Label + &__label { + position: absolute; + bottom: -16px; + left: 16px; + color: transparent; + } + // Location city + &__city { + font-family: $font-serif; + font-size: rem(18px); + line-height: 1; + + @include bp (sm) { + font-size: rem(24px); + } + } + // Location country + &__country { + display: block; + opacity: 0.8; + font-family: $font-sans; + font-size: rem(8px); + line-height: 1; + text-transform: uppercase; + letter-spacing: 1px; + + @include bp (sm) { + font-size: rem(10px); + } + } + + // Active + &.is-active { + &, span { + opacity: 1; + } + .marker { + &__city { + color: $color-secondary; + } + &__country { + color: $color-text; + } + } + } + + // Is light + &.is-light { + &.is-active { + .marker { + &__city { + color: #fff; + } + &__country { + color: #d2b7e4; + } + } + } + } + + // Left positioned + &.is-left { + .marker { + &__label { + left: auto; + right: 32px; + } + &__country { + text-align: right; + } + } + } + + // Marker is close to another one + // Show the marker infos only on hover + &.is-close { + // Dot + &:before { + width: 7px; + height: 7px; + } + // Label + .marker__label { + opacity: 0; + } + + // Show labels on hover + &:hover { + .marker__label { + opacity: 1; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/style/style.scss b/src/style/style.scss index c0fc64a..cc74ed4 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -22,6 +22,9 @@ // Pages @import "pages/homepage"; +// Modules +@import "modules/globe"; + // Atomic Design System // Atoms @@ -46,4 +49,4 @@ // @import "pages/page"; // Misc -// @import "animations"; \ No newline at end of file +@import "animations"; \ No newline at end of file diff --git a/src/utils/functions.ts b/src/utils/functions.ts index fe9ebc0..94f2aa8 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -3,4 +3,31 @@ */ export const lerp = (start: number, end: number, amt: number): number => { return (1 - amt) * start + amt * end +} + + +/** + * Return a random element from an array + */ +export const getRandomElement = (array: any[]): any => { + return ~~(array.length * Math.random()) +} + + +/** + * Get a DOM element's position + */ +export const getPosition = (node, scope?: HTMLElement) => { + const root = scope || document + let offsetTop = node.offsetTop + let offsetLeft = node.offsetLeft + while (node && node.offsetParent && node.offsetParent != document && node !== root && root !== node.offsetParent) { + offsetTop += node.offsetParent.offsetTop + offsetLeft += node.offsetParent.offsetLeft + node = node.offsetParent + } + return { + top: offsetTop, + left: offsetLeft + } } \ No newline at end of file