diff --git a/package.json b/package.json
index d17834b..2fe30cd 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,9 @@
"dayjs": "^1.11.3",
"embla-carousel": "^6.2.0",
"focus-visible": "^5.2.0",
- "sanitize.css": "^13.0.0"
+ "ogl": "^0.0.97",
+ "sanitize.css": "^13.0.0",
+ "tweakpane": "^3.1.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^1.0.0-next.61",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c1c0246..19caf90 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,9 +1,9 @@
lockfileVersion: 5.4
specifiers:
- '@sveltejs/adapter-auto': ^1.0.0-next.60
+ '@sveltejs/adapter-auto': ^1.0.0-next.61
'@sveltejs/adapter-node': ^1.0.0-next.81
- '@sveltejs/adapter-vercel': ^1.0.0-next.62
+ '@sveltejs/adapter-vercel': ^1.0.0-next.63
'@sveltejs/kit': ^1.0.0-next.377
'@types/animejs': ^3.1.5
'@typescript-eslint/eslint-plugin': ^5.30.6
@@ -16,6 +16,7 @@ specifiers:
eslint: ^8.19.0
eslint-plugin-svelte3: ^4.0.0
focus-visible: ^5.2.0
+ ogl: ^0.0.97
postcss: ^8.4.14
postcss-focus-visible: ^7.0.0
postcss-normalize: ^10.0.1
@@ -28,6 +29,7 @@ specifiers:
svelte-preprocess: ^4.10.7
swell-node: ^4.0.10
tslib: ^2.4.0
+ tweakpane: ^3.1.0
typescript: ^4.7.4
vite: ^3.0.0
@@ -36,7 +38,9 @@ dependencies:
dayjs: 1.11.3
embla-carousel: 6.2.0
focus-visible: 5.2.0
+ ogl: 0.0.97
sanitize.css: 13.0.0
+ tweakpane: 3.1.0
devDependencies:
'@sveltejs/adapter-auto': 1.0.0-next.61
@@ -1875,6 +1879,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /ogl/0.0.97:
+ resolution: {integrity: sha512-8VGNwb+BnVgg80uF2MDJGX+rLja8DPvmSsW1a3KCZO4pQF8sszRCgQVQmUA2EnoIYXtMUEztChkB0fuoFcWLxw==}
+ dev: false
+
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -3029,6 +3037,10 @@ packages:
typescript: 4.7.4
dev: true
+ /tweakpane/3.1.0:
+ resolution: {integrity: sha512-PGAp/LPQdHwzL7/iAW4lV1p9iPQTti7YMjMWO48CoYjvZRS59RmgQnhEGzKzqST1JnmOYmQUjTe8bdhlZRJs5A==}
+ dev: false
+
/type-check/0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
diff --git a/src/components/organisms/InteractiveGlobe2.svelte b/src/components/organisms/InteractiveGlobe2.svelte
new file mode 100644
index 0000000..9918f28
--- /dev/null
+++ b/src/components/organisms/InteractiveGlobe2.svelte
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+ {#if popinOpen}
+
+
+
+
+ {/if}
+
\ No newline at end of file
diff --git a/src/modules/globe2/frag.glsl b/src/modules/globe2/frag.glsl
new file mode 100644
index 0000000..c07dcd4
--- /dev/null
+++ b/src/modules/globe2/frag.glsl
@@ -0,0 +1,37 @@
+precision highp float;
+varying vec3 v_normal;
+varying vec3 v_surfaceToLight;
+varying vec3 v_surfaceToView;
+varying vec2 v_uv;
+uniform float u_dt;
+uniform float u_shininess;
+uniform sampler2D map;
+
+void main() {
+ // Re-normalize interpolated varyings
+ vec3 normal = normalize(v_normal);
+ 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 specular = 0.0;
+
+ if (light > 0.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;
+}
\ No newline at end of file
diff --git a/src/modules/globe2/index.ts b/src/modules/globe2/index.ts
new file mode 100644
index 0000000..b2c03d0
--- /dev/null
+++ b/src/modules/globe2/index.ts
@@ -0,0 +1,324 @@
+// @ts-nocheck
+import { Renderer, Camera, Vec3, Orbit, Sphere, Transform, Program, Mesh, Texture } from 'ogl'
+// Shaders
+import VERTEX_SHADER from '../../modules/globe2/vertex.glsl?raw'
+import FRAGMENT_SHADER from '../../modules/globe2/frag.glsl?raw'
+
+
+export class Globe {
+ constructor (options: Options) {
+ // Options
+ this.options = options
+ this.el = options.el
+ this.parent = options.parent
+ this.markers = options.markers || []
+
+ // Parameters
+ this.params = {
+ autoRotate: options.autoRotate,
+ speed: options.speed,
+ }
+
+ // Misc
+ this.hoveringMarker = false
+ this.dragging = false
+ this.webgl = WebGLSupport() !== null
+ this.pane = undefined
+
+ // Run globe after check for WebGL support
+ if (this.webgl) {
+ this.build()
+ this.resize({
+ width: this.options.width,
+ height: this.options.height,
+ })
+ this.render()
+ }
+
+ // Add GUI panel if activated
+ if (this.options.pane) {
+ import('./pane').then(({ createPane }) => {
+ createPane(this)
+ })
+ }
+ }
+
+
+ /**
+ * Build scene
+ */
+ build () {
+ // Create renderer
+ this.renderer = new Renderer({
+ dpr: this.options.dpr || 1,
+ alpha: true,
+ premultiplyAlpha: false,
+ antialias: this.options.antialias || true,
+ })
+ this.gl = this.renderer.gl
+
+ // Create camera
+ this.camera = new Camera(this.gl)
+ this.camera.position.set(0, 0, 1.315)
+
+ // Create controls
+ this.controls = new Orbit(this.camera, {
+ element: this.el,
+ target: new Vec3(0,0,0),
+ enableZoom: false,
+ enablePan: false,
+ autoRotate: false,
+ ease: 0.2,
+ minPolarAngle: Math.PI / 4,
+ maxPolarAngle: Math.PI / 1.5,
+ })
+
+ // Append canvas to scene
+ this.el.appendChild(this.gl.canvas)
+
+ // Create scene and geometry
+ this.scene = new Transform()
+ this.geometry = new Sphere(this.gl, {
+ widthSegments: 64,
+ heightSegments: 64,
+ })
+
+ // Create light
+ // this.light = new Vec3(0, 50, 150)
+ this.light = new Vec3(0, 0, 1000)
+
+ // Add map texture
+ const map = new Texture(this.gl)
+ const img = new Image()
+ img.onload = () => (map.image = img)
+ img.src = this.options.mapFile
+
+ // Create program
+ this.program = new Program(this.gl, {
+ vertex: VERTEX_SHADER,
+ fragment: FRAGMENT_SHADER,
+ uniforms: {
+ u_dt: { value: 0 },
+ u_lightWorldPosition: { value: this.light }, // Position of the Light
+ u_shininess: { value: 1.0 },
+ map: { value: map }, // Color Map
+ },
+ transparent: true,
+ })
+
+ // Create mesh
+ this.mesh = new Mesh(this.gl, {
+ geometry: this.geometry,
+ program: this.program,
+ })
+ this.mesh.setParent(this.scene)
+
+ // Define random continent position
+ if (this.options.rotationStart) {
+ this.mesh.rotation.y = degToRad(this.options.rotationStart * -1) || 0
+ }
+
+ // Add events
+ this.addEvents()
+
+ // Setup markers
+ if (this.markers) {
+ this.setupMarkers()
+ }
+ }
+
+
+ /**
+ * Add events
+ */
+ addEvents () {
+ // When clicking on globe
+ this.gl.canvas.addEventListener('mousedown', () => {
+ this.dragging = true
+ this.gl.canvas.classList.add('is-grabbing')
+ }, false)
+
+ // When releasing globe click
+ this.gl.canvas.addEventListener('mouseup', () => {
+ this.dragging = false
+ this.gl.canvas.classList.remove('is-grabbing')
+ }, false)
+ }
+
+
+ /**
+ * Markers
+ */
+ // Get marker from DOM element
+ getMarker (id: string) {
+ const marker = this.parent.querySelector(`[data-location="${id}"]`)
+ if (marker) {
+ return marker
+ }
+ }
+
+ // Setup markers
+ setupMarkers () {
+ this.markers.forEach((marker: Marker) => {
+ const markerEl = this.getMarker(marker.slug)
+
+ // Entering marker
+ markerEl.addEventListener('mouseenter', () => {
+ this.hoveringMarker = true
+ }, false)
+ // Leaving marker
+ markerEl.addEventListener('mouseleave', () => {
+ this.hoveringMarker = false
+ }, false)
+
+ // Define position
+ const position = lonlatVec3(marker.lng, marker.lat)
+
+ // Scale marker position to fit globe size
+ marker.position = [position[0] *= 0.5, position[1] *= 0.5, position[2] *= 0.5]
+
+ console.log(marker)
+ return marker
+ })
+ }
+
+ // Update markers
+ updateMarkers () {
+ this.markers.forEach((marker: Marker) => {
+ const markerEl = this.getMarker(marker.slug)
+ const screenVector = new Vec3(0,0,0)
+ screenVector.copy(marker.position)
+ this.camera.project(screenVector)
+
+ // let posX = (screenVector.x + 1) * (this.options.width / 1.315)
+ // // posX /= this.mesh.rotation.y
+ // let posY = (1 - screenVector.y) * (this.options.height / 1.315)
+ // markerEl.style.transform = `translate3d(${posX}px, ${posY}px, 0)`
+ })
+ }
+
+
+ /**
+ * Resize method
+ */
+ resize (options: any) {
+ // this.renderer.setSize(window.innerWidth, window.innerHeight)
+ this.renderer.setSize(options.width || this.options.width, options.height || this.options.height)
+ this.camera.perspective({
+ aspect: this.gl.canvas.width / this.gl.canvas.height
+ })
+ }
+
+
+ /**
+ * Update method
+ */
+ render () {
+ // Stop render if not dragging but hovering marker
+ if (!this.dragging && this.hoveringMarker) return
+
+ // Update globe rotation
+ if (this.params.autoRotate) {
+ this.mesh.rotation.y += this.params.speed
+ }
+
+ // Update controls and renderer
+ this.controls.update(this.params)
+ this.renderer.render({
+ scene: this.scene,
+ camera: this.camera,
+ })
+
+ // Update light
+ // this.light.set(this.camera.position)
+ // this.program.uniforms.u_lightWorldPosition.value = [this.mesh.rotation.y * 1, 50, 150]
+
+ // Update markers
+ this.updateMarkers()
+ }
+
+
+ /**
+ * Destroy
+ */
+ destroy () {
+ console.log('destroy globe2')
+
+ this.gl = null
+ this.scene = null
+ this.camera = null
+ this.mesh = null
+ this.renderer = null
+ this.controls.remove()
+
+ if (this.pane) {
+ this.pane.dispose()
+ }
+ }
+}
+
+
+/**
+ * Types
+ */
+type Options = {
+ el: HTMLElement
+ parent: HTMLElement
+ width: number
+ height: number
+ mapFile: string
+ dpr: number
+ autoRotate: boolean
+ speed: number
+ rotationStart?: number
+ markers?: any[]
+ pane?: boolean
+}
+export type Marker = {
+ name: string
+ slug: string
+ country: {
+ name: string
+ slug: string
+ flag: {
+ id: string
+ }
+ }
+ lat: number
+ lng: number
+ position?: number[]
+}
+
+
+/* ==========================================================================
+ HELPERS
+========================================================================== */
+/**
+ * Detect WebGL support
+ */
+function WebGLSupport () {
+ try {
+ var canvas = document.createElement('canvas')
+ return !!window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
+ } catch(e) {
+ return false
+ }
+}
+
+/**
+ * Convert lat/lng to Vec3
+ */
+function lonlatVec3 (longitude: number, latitude: number) {
+ const lat = latitude * Math.PI / 180
+ const lng = -longitude * Math.PI / 180
+ return new Vec3(
+ Math.cos(lat) * Math.cos(lng),
+ Math.sin(lat),
+ Math.cos(lat) * Math.sin(lng)
+ )
+}
+
+/**
+ * Convert Degrees to Radians
+ */
+const degToRad = (deg: number) => deg * Math.PI / 180
\ No newline at end of file
diff --git a/src/modules/globe2/pane.ts b/src/modules/globe2/pane.ts
new file mode 100644
index 0000000..48e98c3
--- /dev/null
+++ b/src/modules/globe2/pane.ts
@@ -0,0 +1,18 @@
+import { Pane } from 'tweakpane'
+
+export const createPane = (ctx: any) => {
+ ctx.pane = new Pane({
+ container: ctx.parent,
+ title: 'Settings',
+ })
+
+ ctx.pane.addInput(ctx.params, 'autoRotate', {
+ label: 'Auto-rotate',
+ })
+ ctx.pane.addInput(ctx.params, 'speed', {
+ label: 'Rotation speed',
+ min: 0.0005,
+ max: 0.025,
+ step: 0.00025,
+ })
+}
\ No newline at end of file
diff --git a/src/modules/globe2/vertex.glsl b/src/modules/globe2/vertex.glsl
new file mode 100644
index 0000000..1d2c12d
--- /dev/null
+++ b/src/modules/globe2/vertex.glsl
@@ -0,0 +1,32 @@
+attribute vec2 uv;
+attribute vec3 position;
+attribute vec3 normal;
+uniform mat4 modelMatrix;
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+uniform mat3 normalMatrix;
+uniform vec3 u_lightWorldPosition;
+uniform vec3 cameraPosition;
+
+varying vec3 v_normal;
+varying vec3 v_surfaceToLight;
+varying vec3 v_surfaceToView;
+varying vec2 v_uv;
+
+void main () {
+ // Pass UV information to Fragment Shader
+ v_uv = uv;
+
+ // Calculate World Space Normal
+ v_normal = normalMatrix * normal;
+
+ // Compute the world position of the surface
+ vec3 surfaceWorldPosition = mat3(modelMatrix) * position;
+
+ // Vector from the surface, to the light
+ v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
+
+ // Vector from the surface, to the camera
+ v_surfaceToView = cameraPosition - surfaceWorldPosition;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
\ No newline at end of file
diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte
index 478fb5e..b18e230 100644
--- a/src/routes/__error.svelte
+++ b/src/routes/__error.svelte
@@ -10,7 +10,7 @@
import PageTransition from '$components/PageTransition.svelte'
import BoxCTA from '$components/atoms/BoxCTA.svelte'
import Heading from '$components/molecules/Heading.svelte'
- import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
+ import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
import ListCTAs from '$components/organisms/ListCTAs.svelte'
import Locations from '$components/organisms/Locations.svelte'
import ShopModule from '$components/organisms/ShopModule.svelte'
@@ -70,7 +70,7 @@
-
+
diff --git a/src/routes/credits.svelte b/src/routes/credits.svelte
index a37dd59..d918b29 100644
--- a/src/routes/credits.svelte
+++ b/src/routes/credits.svelte
@@ -9,7 +9,7 @@
import Metas from '$components/Metas.svelte'
import PageTransition from '$components/PageTransition.svelte'
import SiteTitle from '$components/atoms/SiteTitle.svelte'
- import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
+ import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
import Image from '$components/atoms/Image.svelte'
export let data: any
@@ -139,5 +139,5 @@
-
+
\ No newline at end of file
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
index 509f7e1..2e53532 100644
--- a/src/routes/index.svelte
+++ b/src/routes/index.svelte
@@ -18,7 +18,7 @@
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
import BoxCTA from '$components/atoms/BoxCTA.svelte'
import DiscoverText from '$components/atoms/DiscoverText.svelte'
- import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
+ import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
import Collage from '$components/organisms/Collage.svelte'
import Locations from '$components/organisms/Locations.svelte'
import ListCTAs from '$components/organisms/ListCTAs.svelte'
@@ -138,7 +138,7 @@
-
+
diff --git a/src/routes/locations.svelte b/src/routes/locations.svelte
index 016756b..da60d8f 100644
--- a/src/routes/locations.svelte
+++ b/src/routes/locations.svelte
@@ -7,7 +7,7 @@
// Components
import Metas from '$components/Metas.svelte'
import PageTransition from '$components/PageTransition.svelte'
- import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
+ import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
import Locations from '$components/organisms/Locations.svelte'
import ShopModule from '$components/organisms/ShopModule.svelte'
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
@@ -28,7 +28,7 @@
/>
diff --git a/src/routes/subscribe.svelte b/src/routes/subscribe.svelte
index 9fe0ca3..fac07bc 100644
--- a/src/routes/subscribe.svelte
+++ b/src/routes/subscribe.svelte
@@ -12,7 +12,7 @@
import Image from '$components/atoms/Image.svelte'
import Heading from '$components/molecules/Heading.svelte'
import EmailForm from '$components/molecules/EmailForm.svelte'
- import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
+ import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
export let data: any
export let issues: any[]
@@ -96,5 +96,5 @@
-
+
\ No newline at end of file
diff --git a/src/style/_typography.scss b/src/style/_typography.scss
index 72c3312..9853a6a 100644
--- a/src/style/_typography.scss
+++ b/src/style/_typography.scss
@@ -27,7 +27,6 @@
@include bp (sm) {
font-size: clamp(#{rem(40px)}, 7vw, #{rem(88px)});
}
-
}
// House Number
@@ -157,4 +156,8 @@
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
+
+ &--small {
+ font-size: rem(10px);
+ }
}
\ No newline at end of file
diff --git a/src/style/modules/_globe2.scss b/src/style/modules/_globe2.scss
new file mode 100644
index 0000000..657ba06
--- /dev/null
+++ b/src/style/modules/_globe2.scss
@@ -0,0 +1,257 @@
+// Globe
+.globe {
+ position: relative;
+ z-index: 10;
+ user-select: none;
+
+
+ // Inner
+ &__inner {
+ position: relative;
+ width: clamp(700px, 100vw, 1315px);
+ margin-left: auto;
+ margin-right: auto;
+
+ &:after {
+ content: "";
+ display: block;
+ padding-bottom: 100%;
+ }
+ }
+ // Canvas
+ &__canvas {
+ position: absolute;
+ z-index: 2;
+ top: 0;
+ left: 50%;
+ transform: translate3d(-50%, 0, 0);
+ width: 100%;
+ height: 100%;
+ }
+ :global(canvas) {
+ position: absolute;
+ z-index: 10;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ cursor: grab;
+ }
+
+ // Markers
+ &__markers {
+ position: absolute;
+ z-index: 2;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ user-select: none;
+
+ li {
+ display: block;
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ // Marker
+ &__marker {
+ position: absolute;
+ top: 0;
+ left: 0;
+ user-select: none;
+ transform: translate3d(var(--x), var(--y), 0);
+
+ a {
+ position: relative;
+ text-decoration: none;
+ color: $color-secondary;
+ 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
+ &:before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 10px;
+ left: -16px;
+ width: 8px;
+ height: 8px;
+ border-radius: 100%;
+ background: $color-secondary;
+ }
+ }
+
+ /*
+ ** States
+ */
+ // Has name
+ &.is-dot-only {
+ dt, dd {
+ opacity: 0;
+ }
+ }
+
+ // Has country
+ &.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 {
+ background: rgba($color-secondary, 0.1);
+ }
+ }
+
+ // Flag
+ :global(.flag) {
+ display: block;
+ width: 28px;
+ height: 28px;
+ overflow: hidden;
+ border-radius: 100%;
+ transform: translateZ(0);
+
+ :global(img) {
+ display: block;
+ 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);
+ }
+ }
+ }
+
+
+ /*
+ ** States and Variants
+ */
+ // When dragging
+ :global(.is-grabbing) {
+ cursor: grabbing;
+ }
+
+
+ // Tweakpane
+ :global(.tp-rotv) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 300px;
+ }
+}
\ No newline at end of file