Add Fullscreen in viewer, Track links with Google Analytics, Use .env file
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Fullscreen is a component that watches a store value set by the Carousel component on a picture click - Use a .env file for API and website related settings and informations - Google Analytics is now in place, tracking each routes link and viewer photo change
This commit is contained in:
10
.env
Normal file
10
.env
Normal file
@@ -0,0 +1,10 @@
|
||||
# Website
|
||||
PROD_URL=https://housesof.world
|
||||
|
||||
# API
|
||||
API_TOKEN=NJk0urljsdSvApUDzWxGgoO6
|
||||
API_URL_DEV=http://api.housesof.localhost/how
|
||||
API_URL_PROD=https://api.housesof.world/_
|
||||
|
||||
# Tracking
|
||||
GA_TRACKER_ID=UA-4060922-27
|
||||
@@ -37,6 +37,7 @@
|
||||
"@rollup/plugin-replace": "^2.3.1",
|
||||
"autoprefixer": "^9.7.5",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -19,6 +19,7 @@ devDependencies:
|
||||
'@rollup/plugin-replace': 2.3.1_rollup@2.2.0
|
||||
autoprefixer: 9.7.5
|
||||
babel-plugin-module-resolver: 4.0.0
|
||||
dotenv: 8.2.0
|
||||
eslint-config-standard: 14.1.1_13a54f81caffeb9134dc06c172bdde71
|
||||
eslint-plugin-import: 2.20.1
|
||||
eslint-plugin-node: 11.0.0
|
||||
@@ -1649,6 +1650,12 @@ packages:
|
||||
node: '>=6.0.0'
|
||||
resolution:
|
||||
integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
/dotenv/8.2.0:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
/ecc-jsbn/0.1.2:
|
||||
dependencies:
|
||||
jsbn: 0.1.1
|
||||
@@ -4574,6 +4581,7 @@ specifiers:
|
||||
autoprefixer: ^9.7.5
|
||||
babel-plugin-module-resolver: ^4.0.0
|
||||
compression: ^1.7.4
|
||||
dotenv: ^8.2.0
|
||||
eslint-config-standard: ^14.1.1
|
||||
eslint-plugin-import: ^2.20.1
|
||||
eslint-plugin-node: ^11.0.0
|
||||
|
||||
188
rollup.config.js
188
rollup.config.js
@@ -7,121 +7,121 @@ import babel from 'rollup-plugin-babel'
|
||||
// import browsersync from 'rollup-plugin-browsersync'
|
||||
import autoPreprocess from 'svelte-preprocess'
|
||||
import { terser } from 'rollup-plugin-terser'
|
||||
import config from 'sapper/config/rollup'
|
||||
import sapperConfig from 'sapper/config/rollup'
|
||||
import { config } from 'dotenv'
|
||||
import pkg from './package.json'
|
||||
|
||||
// Define environment and things
|
||||
const mode = process.env.NODE_ENV
|
||||
const dev = mode === 'development'
|
||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD
|
||||
const replaceOptions = {
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
'process.env.CONFIG': JSON.stringify(config().parsed)
|
||||
}
|
||||
|
||||
// Svelte
|
||||
const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning)
|
||||
|
||||
// Preprocessors
|
||||
const preprocess = autoPreprocess({
|
||||
scss: {
|
||||
includePaths: ['src', 'node_modules'],
|
||||
renderSync: true
|
||||
},
|
||||
postcss: true
|
||||
scss: {
|
||||
includePaths: ['src', 'node_modules'],
|
||||
renderSync: true
|
||||
},
|
||||
postcss: true
|
||||
})
|
||||
|
||||
|
||||
export default {
|
||||
/*
|
||||
** Client
|
||||
*/
|
||||
client: {
|
||||
input: config.client.input(),
|
||||
output: {
|
||||
...config.client.output(),
|
||||
// ...dev && { exports: 'named' }
|
||||
},
|
||||
// experimentalCodeSplitting: true,
|
||||
plugins: [
|
||||
// Javascript
|
||||
replace({
|
||||
'process.browser': true,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
hydratable: true,
|
||||
emitCss: true,
|
||||
// css: css => css.write('static/bundle.css'),
|
||||
preprocess
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
extensions: ['.mjs', '.js', '.svelte', '.scss', '.json', '.html'],
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
// dev && eslint(),
|
||||
legacy && babel({
|
||||
extensions: ['.js', '.mjs', '.html', '.svelte'],
|
||||
exclude: ['*.scss', '*.css', 'node_modules/@babel/**'],
|
||||
runtimeHelpers: true
|
||||
}),
|
||||
/*
|
||||
** Client
|
||||
*/
|
||||
client: {
|
||||
input: sapperConfig.client.input(),
|
||||
output: sapperConfig.client.output(),
|
||||
plugins: [
|
||||
// Javascript
|
||||
replace({
|
||||
'process.browser': true,
|
||||
...replaceOptions
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
preprocess,
|
||||
hydratable: true,
|
||||
emitCss: true,
|
||||
// css: css => css.write('static/bundle.css')
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
extensions: ['.mjs', '.js', '.svelte', '.scss', '.json', '.html'],
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
// dev && eslint(),
|
||||
legacy && babel({
|
||||
extensions: ['.js', '.mjs', '.html', '.svelte'],
|
||||
exclude: ['*.scss', '*.css', 'node_modules/@babel/**'],
|
||||
runtimeHelpers: true
|
||||
}),
|
||||
|
||||
// Compress Javascript
|
||||
!dev && terser({
|
||||
module: true
|
||||
}),
|
||||
],
|
||||
// Compress Javascript
|
||||
!dev && terser({
|
||||
module: true
|
||||
}),
|
||||
],
|
||||
|
||||
onwarn,
|
||||
},
|
||||
onwarn,
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
** Server
|
||||
*/
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
'process.browser': false,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
generate: 'ssr',
|
||||
preprocess
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
extensions: ['.mjs', '.js', '.json', '.html', '.svelte', '.scss'],
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require('module').builtinModules || Object.keys(process.binding('natives'))
|
||||
),
|
||||
|
||||
onwarn,
|
||||
},
|
||||
/*
|
||||
** Server
|
||||
*/
|
||||
server: {
|
||||
input: sapperConfig.server.input(),
|
||||
output: sapperConfig.server.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
'process.browser': false,
|
||||
...replaceOptions
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
preprocess,
|
||||
generate: 'ssr'
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
extensions: ['.mjs', '.js', '.json', '.html', '.svelte', '.scss'],
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require('module').builtinModules || Object.keys(process.binding('natives'))
|
||||
),
|
||||
onwarn,
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
** Service worker
|
||||
*/
|
||||
// serviceworker: {
|
||||
// input: config.serviceworker.input(),
|
||||
// output: config.serviceworker.output(),
|
||||
// plugins: [
|
||||
// resolve(),
|
||||
// replace({
|
||||
// 'process.browser': true,
|
||||
// 'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
// }),
|
||||
// commonjs(),
|
||||
// !dev && terser()
|
||||
// ],
|
||||
/*
|
||||
** Service worker
|
||||
*/
|
||||
// serviceworker: {
|
||||
// input: sapperConfig.serviceworker.input(),
|
||||
// output: sapperConfig.serviceworker.output(),
|
||||
// plugins: [
|
||||
// resolve(),
|
||||
// replace({
|
||||
// 'process.browser': true,
|
||||
// ...replaceOptions
|
||||
// }),
|
||||
// commonjs(),
|
||||
// !dev && terser()
|
||||
// ],
|
||||
|
||||
// onwarn,
|
||||
// }
|
||||
// onwarn,
|
||||
// }
|
||||
}
|
||||
|
||||
9
src/atoms/IconZoomOut.svelte
Normal file
9
src/atoms/IconZoomOut.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<script>
|
||||
export let width = 18
|
||||
export let color = '#fff'
|
||||
export let hidden = undefined
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 18" {width} fill={color} class={$$props.class} aria-hidden={hidden}>
|
||||
<path fill-rule="evenodd" d="M8.36 0a7.76 7.76 0 016.02 12.63l3.99 3.99a.81.81 0 11-1.15 1.14l-3.99-3.99A7.76 7.76 0 118.35 0zm0 1.62a6.14 6.14 0 10.01 12.28 6.14 6.14 0 00-.01-12.28zm2.38 5.32a.81.81 0 110 1.62H5.98a.81.81 0 110-1.62z"/>
|
||||
</svg>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import { currentLocation } from '../utils/store'
|
||||
import { currentLocation, fullscreen } from '../utils/store'
|
||||
import { getThumbnail, formatDate } from '../utils/functions'
|
||||
const dispatch = createEventDispatcher()
|
||||
const { page } = stores()
|
||||
@@ -50,6 +50,8 @@
|
||||
|
||||
// Dispatch current photo
|
||||
sendCurrentPhoto()
|
||||
// Reset fullscreen value if open
|
||||
fullscreen.set()
|
||||
}
|
||||
|
||||
// Send current photo to event
|
||||
@@ -145,6 +147,7 @@
|
||||
class:gallery__photo--prev={photo === prevPhoto}
|
||||
class:gallery__photo--active={photo === currentPhoto}
|
||||
class:gallery__photo--next={photo === nextPhoto}
|
||||
on:click={event => viewer && fullscreen.set(currentPhoto)}
|
||||
>
|
||||
<source media="(min-width: 968px)" srcset={getThumbnail(photo.image.private_hash, 1400)}>
|
||||
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
|
||||
|
||||
82
src/organisms/Fullscreen.svelte
Normal file
82
src/organisms/Fullscreen.svelte
Normal file
@@ -0,0 +1,82 @@
|
||||
<script>
|
||||
import { fullscreen } from '../utils/store'
|
||||
import { throttle, getThumbnail } from '../utils/functions'
|
||||
|
||||
// Dependencies
|
||||
import imagesLoaded from 'imagesloaded'
|
||||
|
||||
// Components
|
||||
import IconGlobe from '../atoms/IconGlobe'
|
||||
import IconZoomOut from '../atoms/IconZoomOut'
|
||||
|
||||
// Variables
|
||||
let scope
|
||||
let loading = false
|
||||
let open = false
|
||||
let closed = false
|
||||
|
||||
// Wait for fullscreen store value
|
||||
$: {
|
||||
if ($fullscreen) openFullscreen($fullscreen)
|
||||
else closeFullscreen()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Functions
|
||||
*/
|
||||
// Open fullscreen
|
||||
const openFullscreen = throttle(currentPhoto => {
|
||||
loading = true
|
||||
|
||||
if (!open) {
|
||||
const img = document.createElement('img')
|
||||
const imgContainer = scope.querySelector('.fullscreen__image')
|
||||
img.src = getThumbnail(currentPhoto.image.private_hash, null, 1600, 'crop', 80)
|
||||
img.alt = `${currentPhoto.name}, ${currentPhoto.location.name}, ${currentPhoto.location.country.name}`
|
||||
img.width = 2400
|
||||
img.height = 1600
|
||||
imgContainer.append(img)
|
||||
|
||||
// Show fullscreen when new image is loaded
|
||||
imagesLoaded(scope, instance => {
|
||||
open = true
|
||||
loading = false
|
||||
closed = false
|
||||
|
||||
// Scroll to photo's center
|
||||
imgContainer.scrollTo((img.clientWidth - scope.clientWidth) / 2, 0)
|
||||
})
|
||||
}
|
||||
}, 800)
|
||||
|
||||
// Close fullscreen
|
||||
const closeFullscreen = throttle(() => {
|
||||
// Reset values
|
||||
open = false
|
||||
closed = true
|
||||
|
||||
// Clear image and reset fullscreen store value
|
||||
setTimeout(() => {
|
||||
scope.querySelector('.fullscreen__image').innerHTML = ''
|
||||
fullscreen.set()
|
||||
}, 800) // Transition duration
|
||||
}, 800)
|
||||
</script>
|
||||
|
||||
<div class="fullscreen" bind:this={scope}>
|
||||
<div class="fullscreen__image" class:is-open={open} on:click={closeFullscreen} />
|
||||
|
||||
<div class="fullscreen__loading" class:is-hidden={!loading}>
|
||||
<IconGlobe width="24" color="#fff" animated="true" />
|
||||
</div>
|
||||
|
||||
<div class="fullscreen__close" class:is-visible={open}>
|
||||
<button class="button-control button-control--gray button-control--shadow dir-top" aria-label="Close"
|
||||
on:click={closeFullscreen}
|
||||
>
|
||||
<IconZoomOut color="#fff" width="22" class="icon" />
|
||||
<IconZoomOut color="#fff" width="22" class="icon" hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,9 +71,11 @@
|
||||
countries,
|
||||
locations
|
||||
} from '../utils/store'
|
||||
import { stores } from '@sapper/app'
|
||||
|
||||
// Components
|
||||
import Transition from '../utils/Transition'
|
||||
import AnalyticsTracker from '../utils/AnalyticsTracker'
|
||||
|
||||
// Props
|
||||
export const segment = null
|
||||
@@ -96,7 +98,6 @@
|
||||
$locations.forEach(loc => loc.country = $countries.find(cont => cont.id === loc.country.id))
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" global>
|
||||
@import "../style/style.scss";
|
||||
</style>
|
||||
@@ -104,3 +105,5 @@
|
||||
<slot></slot>
|
||||
|
||||
<Transition />
|
||||
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />
|
||||
|
||||
|
||||
@@ -6,21 +6,26 @@
|
||||
let preloaded
|
||||
currentPhotos.subscribe(store => preloaded = store ? store : undefined)
|
||||
|
||||
|
||||
// Preload data
|
||||
export async function preload (page, session) {
|
||||
// Load the photos if not loaded
|
||||
if (!preloaded) {
|
||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=id,name,slug,date,image.*,location.*,location.country.*,created_on,modified_on&filter[location.slug][rlike]=%${page.params.location}%`)
|
||||
// Fields
|
||||
const fields = [
|
||||
'id', 'name', 'slug', 'date', 'image.private_hash',
|
||||
'location.id', 'location.name', 'location.slug', 'location.country.name', 'location.country.slug'
|
||||
]
|
||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=${fields.join()}&filter[location.slug][rlike]=%${page.params.place}%`)
|
||||
const photos = await req.json()
|
||||
return {
|
||||
photos: photos.data
|
||||
if (req.ok) {
|
||||
return { photos: photos.data }
|
||||
}
|
||||
}
|
||||
// Use the store otherwise
|
||||
else return {
|
||||
photos: preloaded
|
||||
}
|
||||
this.error(404, 'Not found')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,33 +39,33 @@
|
||||
pageReady,
|
||||
pageTransition
|
||||
} from '../../../../utils/store'
|
||||
import { getThumbnail } from '../../../../utils/functions'
|
||||
const { page } = stores()
|
||||
const dispatch = createEventDispatcher()
|
||||
import { getThumbnail, analyticsUpdate } from '../../../../utils/functions'
|
||||
|
||||
// Components
|
||||
import IconGlobe from '../../../../atoms/IconGlobe'
|
||||
import IconCross from '../../../../atoms/IconCross'
|
||||
import Carousel from '../../../../organisms/Carousel'
|
||||
import Fullscreen from '../../../../organisms/Fullscreen'
|
||||
import SocialMetas from '../../../../utils/SocialMetas'
|
||||
|
||||
// Animations
|
||||
import { animateIn } from '../../../../animations/viewer'
|
||||
pageTransition.onAnimationEnd = animateIn
|
||||
|
||||
|
||||
// Props
|
||||
export let photos
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
const dispatch = createEventDispatcher()
|
||||
let windowWidth
|
||||
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
||||
|
||||
// Update store current location
|
||||
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.location))
|
||||
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
|
||||
if (!$currentPhotos) currentPhotos.set(photos)
|
||||
|
||||
// The photo has changed from the carousel
|
||||
// Photo has changed from the Carousel component
|
||||
const photoChanged = event => {
|
||||
currentPhoto = event.detail.currentPhoto
|
||||
|
||||
@@ -69,6 +74,7 @@
|
||||
const windowPathname = window.location.pathname
|
||||
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
|
||||
history.pushState('', document.title, newUrl)
|
||||
analyticsUpdate(newUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +83,7 @@
|
||||
// On init, send an event to the Carousel component with the photoSlug to set currentIndex and then change the photo
|
||||
|
||||
// Pop event from browser (prev/next)
|
||||
const changedUrl = event => {
|
||||
dispatch('changedUrl', {
|
||||
currentPhoto: currentPhoto
|
||||
})
|
||||
}
|
||||
const changedUrl = event => dispatch('changedUrl', { currentPhoto: currentPhoto })
|
||||
|
||||
|
||||
/*
|
||||
@@ -91,25 +93,21 @@
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
|
||||
/*
|
||||
!!! TODO:
|
||||
- Change the title with the current photo name and update Metas (with window location url)
|
||||
*/
|
||||
|
||||
dispatch('changeUrl', {
|
||||
page: $page
|
||||
})
|
||||
dispatch('changeUrl', { page: $page })
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- TODO:
|
||||
- Change the title with the current photo name and update Metas (with window location url)
|
||||
-->
|
||||
<title>{$site.seo_name} – Photos of {$currentLocation.name}, {$currentLocation.country.name}</title>
|
||||
<meta name="description" content="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}">
|
||||
<SocialMetas
|
||||
url="https://{$page.host}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
|
||||
title="{$site.seo_name} - Beautiful homes of {$currentLocation.name}, {$currentLocation.country.name}"
|
||||
description="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}"
|
||||
image={getThumbnail(currentPhoto.image.private_hash, 1200, 630)}
|
||||
url="//{$page.host.split(':3000')[0]}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
@@ -119,14 +117,14 @@
|
||||
<div class="viewer__top">
|
||||
<p class="tip">Tap for fullscreen</p>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="viewer__buttons">
|
||||
<a href="/choose" class="button-control button-control--dashed" aria-label="Change the location" rel="prefetch">
|
||||
<IconGlobe color="#fff" width={windowWidth >= 768 ? 22 : 18} />
|
||||
<svg>
|
||||
<circle cx="50%" cy="50%" r="{windowWidth >= 768 ? 32 : 24}px"></circle>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--pink dir-bottom" aria-label="Close">
|
||||
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--pink dir-bottom" aria-label="Back to photos" rel="prefetch">
|
||||
<IconCross color="#fff" width="18" class="icon" />
|
||||
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
||||
</a>
|
||||
@@ -134,9 +132,11 @@
|
||||
</div>
|
||||
|
||||
<Carousel
|
||||
viewer="true"
|
||||
photos={photos}
|
||||
viewer={true}
|
||||
init={$page}
|
||||
on:photoChange={photoChanged}
|
||||
/>
|
||||
|
||||
<Fullscreen />
|
||||
</section>
|
||||
@@ -68,6 +68,15 @@
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Effects
|
||||
*/
|
||||
&--shadow {
|
||||
box-shadow: 0 0 10px rgba(#000, 0.4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Directions
|
||||
*/
|
||||
|
||||
83
src/style/organisms/_fullscreen.scss
Normal file
83
src/style/organisms/_fullscreen.scss
Normal file
@@ -0,0 +1,83 @@
|
||||
.fullscreen {
|
||||
position: fixed;
|
||||
z-index: 200;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
// Photo
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
opacity: 0;
|
||||
transform: scale(1.1);
|
||||
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||
will-change: transform, opacity;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
z-index: 200;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// Open
|
||||
&.is-open {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
&__close {
|
||||
position: fixed;
|
||||
z-index: 201;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transform: scale(1.1) translateY(24px);
|
||||
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||
will-change: transform, opacity;
|
||||
|
||||
// Visible state
|
||||
&.is-visible {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Loading
|
||||
&__loading {
|
||||
position: absolute;
|
||||
z-index: 202;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
transform-origin: 50% 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-color: $color-primary;
|
||||
border-radius: 100%;
|
||||
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||
will-change: transform, opacity;
|
||||
|
||||
// Hidden state
|
||||
&.is-hidden {
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,17 +44,17 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.buttons {
|
||||
display: flex;
|
||||
// Buttons
|
||||
&__buttons {
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
margin-left: 16px;
|
||||
a {
|
||||
margin-left: 16px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,10 +86,8 @@
|
||||
right: 0;
|
||||
|
||||
// Specific box shadow for images
|
||||
&__images {
|
||||
&--photo {
|
||||
box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4);
|
||||
}
|
||||
&__photo {
|
||||
box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// Organisms
|
||||
@import "organisms/carousel";
|
||||
@import "organisms/photos";
|
||||
@import "organisms/fullscreen";
|
||||
@import "organisms/locations";
|
||||
@import "organisms/pagination";
|
||||
@import "organisms/footer";
|
||||
|
||||
27
src/utils/AnalyticsTracker.svelte
Normal file
27
src/utils/AnalyticsTracker.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script>
|
||||
import { analyticsUpdate } from '../utils/functions'
|
||||
|
||||
// Props
|
||||
export let stores
|
||||
export let id
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
|
||||
// Init Google Analytics
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dataLayer = window.dataLayer || []
|
||||
window.gtag = function gtag() {
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
window.gtag('js', new Date())
|
||||
window.gtag('config', id)
|
||||
}
|
||||
|
||||
// Push new page to GA
|
||||
$: analyticsUpdate($page.path, id)
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={id}"></script>
|
||||
</svelte:head>
|
||||
@@ -175,3 +175,15 @@ export const parallaxAnime = (element, anime) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Google Analytics send page
|
||||
*/
|
||||
export const analyticsUpdate = (page, id = process.env.CONFIG.GA_TRACKER_ID) => {
|
||||
if (typeof gtag !== 'undefined') {
|
||||
window.gtag('config', id, {
|
||||
page_path: page
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ export const dev = process.env.NODE_ENV === 'development'
|
||||
/* ==========================================================================
|
||||
Site related
|
||||
========================================================================== */
|
||||
const apiEndpoint = dev ? 'http://api.housesof.localhost/how' : 'https://api.housesof.world/_'
|
||||
const apiAccessToken = 'NJk0urljsdSvApUDzWxGgoO6'
|
||||
const apiEndpoint = dev ? process.env.CONFIG.API_URL_DEV : process.env.CONFIG.API_URL_PROD
|
||||
export const apiEndpoints = {
|
||||
gql: `${apiEndpoint}/gql?access_token=${apiAccessToken}`,
|
||||
gql: `${apiEndpoint}/gql?access_token=${process.env.CONFIG.API_TOKEN}`,
|
||||
rest: apiEndpoint
|
||||
}
|
||||
|
||||
@@ -30,6 +29,7 @@ export let pageReady = writable(false)
|
||||
export const pageTransition = {
|
||||
onAnimationEnd () {}
|
||||
}
|
||||
export let fullscreen = writable()
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 860 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 270 KiB |
Reference in New Issue
Block a user