Merge branch 'dev'
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2020-05-06 23:06:54 +02:00
23 changed files with 374 additions and 199 deletions

View File

@@ -127,22 +127,22 @@ export default {
/* /*
** Service worker ** Service worker
*/ */
...(!dev && { // ...(!dev && {
serviceworker: { // serviceworker: {
input: sapperConfig.serviceworker.input(), // input: sapperConfig.serviceworker.input(),
output: sapperConfig.serviceworker.output(), // output: sapperConfig.serviceworker.output(),
plugins: [ // plugins: [
resolve(), // resolve(),
replace({ // replace({
'process.browser': false, // 'process.browser': false,
...replaceOptions // ...replaceOptions
}), // }),
aliases, // aliases,
glslify(), // glslify(),
commonjs(), // commonjs(),
!dev && terser() // !dev && terser()
], // ],
onwarn, // onwarn,
} // }
}) // })
} }

9
src/atoms/Badge.svelte Normal file
View File

@@ -0,0 +1,9 @@
<script>
// Props
export let text
export let size = 'small'
</script>
<div class="badge badge--{size}">
<span>{text}</span>
</div>

View File

@@ -1,19 +1,30 @@
<script> <script>
import { dateOlderThan } from 'utils/functions'
// Components
import Badge from 'atoms/Badge'
// Props // Props
export let location export let location
// Variables // Variables
const { name, slug, country } = location const { name, slug, country, last_updated } = location
const timeLimit = 7 * 24 * 60 * 60 * 1000 // d*h*m*s*ms = 1 week
</script> </script>
<div class="location" role="listitem"> <div class="location" role="listitem">
<a href="/location/{country.slug}/{slug}" rel="prefetch" sapper-noscroll> <a href="/location/{country.slug}/{slug}" rel="prefetch" sapper-noscroll>
<img src={country.flag.full_url} alt="Flag of {country.name}"> <img src={country.flag.full_url} alt="Flag of {country.name}">
<div class="anim-mask mask-city"> <div class="anim-mask mask-city">
<h3 class="location__city">{name}</h3> <h3 class="location__city">{name}</h3>
</div> </div>
<div class="anim-mask mask-country"> <div class="anim-mask mask-country">
<p class="location__country style-caps">{country.name}</p> <p class="location__country style-caps">{country.name}</p>
</div> </div>
{#if dateOlderThan(last_updated, timeLimit)}
<Badge size="small" text="new" />
{/if}
</a> </a>
</div> </div>

View File

@@ -1,46 +0,0 @@
<script>
import { site } from 'utils/store'
// Components
import IconArrow from 'atoms/IconArrow'
// Props
export let title = true
export let small = false
export let brightness = 'dark'
</script>
<div class="newsletter" class:newsletter--small={small} class:newsletter--light={brightness === 'light'}>
<div class="newsletter__text style-description" class:style-description--small={small} class:style-description--dark={brightness === 'light'} class:page__part={!small}>
<p>{$site.newsletter_text}</p>
</div>
<form method="POST" action={$site.newsletter_url} target="_blank" id="sib-form" class="form"
class:form--light={brightness === 'light'}
class:page__part={!small}
>
{#if title}
<h2 class="style-location">
<label for="EMAIL">{$site.newsletter_subtitle}</label>
</h2>
{/if}
<div class="newsletter__input form__group form__inputgroup">
<input type="email" id="EMAIL" name="EMAIL" value="" placeholder="Your email address..." class="input__text" required>
<button type="submit" form="sib-form" class="button-control dir-right"
class:button-control--pink={brightness === 'light'}
class:button-control--lightpink={brightness === 'dark'}
>
<IconArrow direction="right" color="#fff" class="icon" width="12" />
<IconArrow direction="right" color="#fff" class="icon" width="12" hidden="true" />
</button>
</div>
<div class="newsletter__notice">
<p class="style-notice">No spam, promised!</p>
</div>
<input type="text" name="email_address_check" value="" style="display: none;">
<input type="hidden" name="locale" value="en">
<input type="hidden" name="html_type" value="simple">
</form>
</div>

View File

@@ -0,0 +1,41 @@
<script>
import { site } from 'utils/store'
// Components
import IconArrow from 'atoms/IconArrow'
// Props
export let title = false
export let small = false
export let brightness = 'dark'
export let align = 'center'
</script>
<form method="POST" action={$site.newsletter_url} target="_blank" id="sib-form" class="form"
class:form--light={brightness === 'light'}
class:page__part={!small}
>
{#if title}
<h2 class="style-location">
<label for="SUB_EMAIL">{$site.newsletter_subtitle}</label>
</h2>
{/if}
<div class="newsletter__input form__group form__inputgroup">
<input type="email" id="SUB_EMAIL" name="EMAIL" value="" placeholder="Your email address..." class="input__text" required>
<button type="submit" form="sib-form" class="button-control dir-right"
class:button-control--pink={brightness === 'light'}
class:button-control--lightpink={brightness === 'dark'}
>
<IconArrow direction="right" color="#fff" class="icon" width="12" />
<IconArrow direction="right" color="#fff" class="icon" width="12" hidden="true" />
</button>
</div>
<div class="newsletter__notice" class:is-aligned--right={align === 'right'}>
<p class="style-notice">No spam, we promise!</p>
</div>
<input type="text" name="email_address_check" value="" style="display: none;">
<input type="hidden" name="locale" value="en">
<input type="hidden" name="html_type" value="simple">
</form>

View File

@@ -7,6 +7,7 @@
// Components // Components
import Button from 'atoms/Button' import Button from 'atoms/Button'
import Location from 'molecules/Location' import Location from 'molecules/Location'
import Newsletter from 'organisms/Newsletter'
// Animations // Animations
import { animateIn } from 'animations/Locations' import { animateIn } from 'animations/Locations'
@@ -58,12 +59,11 @@
<div class="browse__locations" id="locations_list" role="list" bind:this={list}> <div class="browse__locations" id="locations_list" role="list" bind:this={list}>
{#each filteredLocations as location (location.id)} {#each filteredLocations as location (location.id)}
<div animate:flip="{{ duration: transitionDuration }}" <div animate:flip="{{ duration: transitionDuration }}" in:receive="{{ key: location.id }}" out:send="{{ key: location.id }}">
in:receive="{{ key: location.id }}"
out:send="{{ key: location.id }}"
>
<Location {location} /> <Location {location} />
</div> </div>
{/each} {/each}
</div> </div>
</div> </div>
<Newsletter />

View File

@@ -0,0 +1,22 @@
<script>
import { site } from 'utils/store'
// Components
import NewsletterForm from 'molecules/NewsletterForm'
// Props
export let brightness = undefined
export let title = 'Keep Updated'
</script>
<div class="newsletter" class:newsletter--light={brightness === 'light'}>
<div class="wrapper">
<div class="newsletter__text style-description style-description--small" class:style-description--dark={brightness === 'light'}>
<h2 class="style-location">
<label for="SUB_EMAIL">{title}</label>
</h2>
<p>{$site.newsletter_text}</p>
</div>
<NewsletterForm align="right" {brightness} />
</div>
</div>

View File

@@ -2,7 +2,7 @@
import { onMount, createEventDispatcher } from 'svelte' import { onMount, createEventDispatcher } from 'svelte'
import { site, currentLocation } from 'utils/store' import { site, currentLocation } from 'utils/store'
// Components // Components
import Newsletter from 'molecules/Newsletter' import Newsletter from 'organisms/Newsletter'
// Props // Props
export let photos export let photos
export let paginatedPhotos export let paginatedPhotos
@@ -58,12 +58,9 @@
{:else if $currentLocation} {:else if $currentLocation}
<div class="pagination__message"> <div class="pagination__message">
<h3>That's all folks!</h3>
<Newsletter <Newsletter
small={true} title="That's all folks!"
brightness="light" brightness="light"
title={false}
/> />
</div> </div>
{/if} {/if}

View File

@@ -1,6 +1,6 @@
<script context="module"> <script context="module">
export async function preload (page, session) { export async function preload (page, session) {
const req = await this.fetch(apiEndpoints.gql, { await this.fetch(apiEndpoints.gql, {
method: 'post', method: 'post',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -56,16 +56,58 @@
illu_mobile { full_url } illu_mobile { full_url }
} }
} }
photos (filter: { status_eq: "published" }) {
data {
created_on
location { id }
}
}
}`}) }`})
}) })
const result = await req.json() .then(res => res.json())
if (req.ok) { .then(res => {
// Set data into store const { data } = res
site.set(result.data.site.data[0])
continents.set(result.data.continents.data) // Manipulate countries data
countries.set(result.data.countries.data) data.countries.data.forEach(country => {
locations.set(result.data.locations.data) const matchingContinent = data.continents.data.find(continent => continent.id === country.continent.id)
} // Replace continent with request data
country.continent = matchingContinent
// Add countries to each continents
matchingContinent.countries = []
matchingContinent.countries.push(country)
})
// Replace each location's country with request data
data.locations.data.forEach(location => {
location.country = data.countries.data.find(country => country.id === location.country.id)
})
// Filter and keep only the latest photo for each location
// https://stackoverflow.com/questions/61636965/
const latestPhotos = Object.values(data.photos.data.reduce((photos, photo) => {
if (photos.hasOwnProperty(photo.location.id)) {
if (new Date(photos[photo.location.id].created_on) < new Date(photo.created_on)) {
photos[photo.location.id] = photo
}
} else {
photos[photo.location.id] = photo
}
return photos
}, {}))
// Add last updated date to each location
data.locations.data.forEach(location => {
const latestPhoto = latestPhotos.find(photo => photo.location.id === location.id)
location.last_updated = latestPhoto.created_on
})
// Set data into store
site.set(data.site.data[0])
continents.set(data.continents.data)
countries.set(data.countries.data)
locations.set(data.locations.data)
})
} }
</script> </script>
@@ -112,30 +154,6 @@
'/fonts/M-Light.woff2', '/fonts/M-Light.woff2',
] ]
} }
/*
** Manipulate data
*/
if (process.browser) {
if ($countries) {
$countries.forEach(country => {
const matchingContinent = $continents.find(continent => continent.id === country.continent.id)
// Replace continent with request data
country.continent = matchingContinent
// Add countries to each continents
matchingContinent.countries = []
matchingContinent.countries.push(country)
})
}
if ($locations) {
// Replace each location's country with request data
$locations.forEach(location => {
location.country = $countries.find(country => country.id === location.country.id)
})
}
}
</script> </script>
<style lang="scss" global> <style lang="scss" global>
@@ -157,4 +175,4 @@
<Transition /> <Transition />
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} /> <AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />

View File

@@ -9,7 +9,7 @@
import TitleSite from 'atoms/TitleSite' import TitleSite from 'atoms/TitleSite'
import LinkTranslate from 'atoms/LinkTranslate' import LinkTranslate from 'atoms/LinkTranslate'
import InteractiveGlobe from 'molecules/InteractiveGlobe' import InteractiveGlobe from 'molecules/InteractiveGlobe'
import Newsletter from 'molecules/Newsletter' import NewsletterForm from 'molecules/NewsletterForm'
import Footer from 'organisms/Footer' import Footer from 'organisms/Footer'
import SocialMetas from 'utils/SocialMetas' import SocialMetas from 'utils/SocialMetas'
// Animations // Animations
@@ -53,7 +53,15 @@
<TitleSite /> <TitleSite />
</div> </div>
<Newsletter /> <div class="newsletter newsletter--column">
<div class="newsletter__text style-description page__part">
<p>{$site.newsletter_text}</p>
</div>
<div class="page__part">
<NewsletterForm title={true} />
</div>
</div>
</div> </div>
{#if process.browser} {#if process.browser}

View File

@@ -1,7 +1,9 @@
// Colors // Colors
$color-primary: #3C0576; $color-primary: #3C0576;
$color-primary-dark: #36046A;
$color-primary-darker: #2D0458; $color-primary-darker: #2D0458;
$color-secondary: #FF6C89; $color-secondary: #FF6C89;
$color-secondary-light: #FFB3C2;
$color-secondary-bright: #FF0536; $color-secondary-bright: #FF0536;
$color-text: #333; $color-text: #333;
$color-tertiary: #FFE0C5; $color-tertiary: #FFE0C5;

View File

@@ -0,0 +1,23 @@
/*
** Badge
*/
.badge {
display: inline-flex;
justify-content: center;
text-align: center;
color: $color-primary-darker;
font-family: $font-sans-sb;
text-transform: uppercase;
letter-spacing: 1px;
background-color: $color-secondary-light;
box-shadow: 0 0 0 4px rgba($color-tertiary, 0.15);
border-radius: 50vh;
// Small size
&--small {
height: 14px;
padding: 0 4px;
font-size: rem(7px);
line-height: 15px;
}
}

View File

@@ -0,0 +1,40 @@
// Text input
.input__text {
position: relative;
display: block;
width: 100%;
height: 48px;
padding: 0 24px;
color: #fff;
font-size: rem(16px);
font-family: $font-sans-light;
border: 2px solid rgba($color-secondary, 0.6);
border-radius: 50vh;
transition: border 300ms $ease-quart;
background: none;
outline: none;
@include breakpoint (sm) {
font-size: rem(18px);
height: 64px;
padding: 0 32px;
}
// States
&::placeholder {
color: #fff;
opacity: 0.75;
transition: all 300ms $ease-quart;
}
&:focus {
@extend %input__text--active;
}
}
%input__text--active {
border-color: $color-secondary;
&::placeholder {
opacity: 1;
}
}

View File

@@ -21,6 +21,5 @@
position: relative; position: relative;
z-index: 3; z-index: 3;
margin-top: 72px; margin-top: 72px;
margin-bottom: 72px;
} }
} }

View File

@@ -36,8 +36,9 @@
/* /*
** Light version ** Variants
*/ */
// Light version
&--light { &--light {
// Text input // Text input
.input__text { .input__text {
@@ -46,45 +47,4 @@
} }
} }
} }
}
// Text input
.input__text {
position: relative;
display: block;
width: 100%;
height: 48px;
padding: 0 24px;
color: #fff;
font-size: rem(16px);
font-family: $font-sans-light;
border: 2px solid rgba($color-secondary, 0.6);
border-radius: 50vh;
transition: border 300ms $ease-quart;
background: none;
outline: none;
@include breakpoint (sm) {
font-size: rem(18px);
height: 64px;
padding: 0 32px;
}
// States
&::placeholder {
color: #fff;
opacity: 0.75;
transition: all 300ms $ease-quart;
}
&:focus {
@extend %input__text--active;
}
}
%input__text--active {
border-color: $color-secondary;
&::placeholder {
opacity: 1;
}
} }

View File

@@ -35,18 +35,29 @@
font-size: rem(48px); font-size: rem(48px);
} }
} }
.mask-city {
height: 40px;
margin: 24px 0 16px;
@include breakpoint (md) { // Masks
height: 64px; .mask-city {
margin-bottom: 24px; height: 40px;
} margin: 16px 0 12px;
@include breakpoint (md) {
height: 64px;
margin-top: 24px;
} }
.mask-country { }
height: 16px; .mask-country {
height: 16px;
}
// Badge
.badge {
margin-top: 16px;
@include breakpoint (sm) {
margin-top: 24px;
} }
}
// Hover // Hover
&:hover { &:hover {

View File

@@ -1,58 +1,109 @@
// Newsletter // Newsletter
.newsletter { .newsletter {
max-width: 360px; background-color: $color-primary-dark;
margin: 0 auto; padding: 56px 0;
text-align: center;
@include breakpoint (sm) { @include breakpoint (sm) {
max-width: 444px; padding: 96px 0;
}
.wrapper {
@include breakpoint (md) {
display: flex;
justify-content: space-between;
}
} }
// Title
h2 { h2 {
margin-bottom: 40px;
color: $color-secondary; color: $color-secondary;
} }
// Text // Text
&__text { &__text {
margin-bottom: 72px; max-width: 400px;
margin: 0 auto 32px;
@include breakpoint (sm) { @include breakpoint (sm) {
margin-bottom: 96px; margin-bottom: 40px;
}
@include breakpoint (md) {
margin-left: 0;
margin-right: 48px;
margin-bottom: 0;
text-align: left;
}
h2 {
margin-bottom: 16px;
@include breakpoint (md) {
text-align: left;
}
} }
} }
// Form // Form
.form { .form {
min-width: 270px;
max-width: 320px;
margin: 0 auto;
} @include breakpoint (md) {
min-width: 320px;
max-width: 440px;
margin: 48px 0 0;
}
// Notice // Title (if existing)
&__notice { h2 {
margin-top: 32px; margin-bottom: 24px;
}
/*
** Small version
*/
&--small {
.newsletter__text {
max-width: 344px;
margin-bottom: 32px;
@include breakpoint (sm) { @include breakpoint (sm) {
margin-bottom: 48px; margin-bottom: 40px;
} }
} }
} }
// Notice
&__notice {
margin-top: 16px;
text-align: center;
}
/* /*
** Light version ** Variants
*/ */
// Centered column mode
&--column {
max-width: 360px;
margin: 0 auto;
padding: 0;
background: none;
@include breakpoint (sm) {
max-width: 444px;
}
// Text
.newsletter__text {
max-width: 440px;
margin: 0 0 48px;
text-align: center;
@include breakpoint (sm) {
margin-bottom: 72px;
}
@include breakpoint (md) {
margin-bottom: 96px;
}
}
}
// Light version
&--light { &--light {
background: none;
.newsletter__notice { .newsletter__notice {
p { p {
color: rgba($color-text, 0.5); color: rgba($color-text, 0.5);

View File

@@ -61,6 +61,10 @@
li { li {
margin-right: 20px; margin-right: 20px;
&:last-child {
margin-right: 0;
}
} }
a { a {
display: block; display: block;

View File

@@ -1,10 +1,10 @@
.browse { .browse {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
margin-bottom: 56px;
@include breakpoint (sm) { @include breakpoint (sm) {
margin-top: 120px; margin-top: 120px;
margin-bottom: 52px;
} }
// Description // Description
@@ -50,7 +50,7 @@
grid-row-gap: pxVW(96); grid-row-gap: pxVW(96);
min-height: 200px; min-height: 200px;
margin-top: 112px; margin-top: 112px;
margin-bottom: pxVW(120); margin-bottom: pxVW(72);
} }
@include breakpoint (xl) { @include breakpoint (xl) {
grid-column-gap: 96px; grid-column-gap: 96px;
@@ -58,7 +58,7 @@
max-width: 1024px; max-width: 1024px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 184px; margin-bottom: 72px;
// To apply when having 4 locations: // To apply when having 4 locations:
// grid-template-columns: repeat(4, 1fr); // grid-template-columns: repeat(4, 1fr);

View File

@@ -99,16 +99,23 @@
// Message // Message
&__message { &__message {
h3 { // Newsletter
font-family: $font-serif; .newsletter {
font-size: rem(32px); margin: 0;
color: $color-secondary; padding: 0;
margin-bottom: 16px;
@include breakpoint (sm) { @include breakpoint (sm) {
margin-bottom: 24px; padding: 24px 0 0;
font-size: rem(40px); }
// Title
h2 {
font-size: rem(32px);
@include breakpoint (md) {
font-size: rem(40px);
}
} }
} }
} }
} }

View File

@@ -74,6 +74,10 @@
@include breakpoint (sm) { @include breakpoint (sm) {
padding-bottom: 120px; padding-bottom: 120px;
&:last-child {
padding-bottom: 0;
}
} }
// Even photos // Even photos
@@ -129,14 +133,16 @@
margin-top: 24px; margin-top: 24px;
@include breakpoint (sm) { @include breakpoint (sm) {
margin-top: -184px; margin-top: -64px;
padding-top: pxVW(224); padding-top: pxVW(240);
padding-bottom: pxVW(120);
} }
@include breakpoint (md) { @include breakpoint (md) {
padding-top: pxVW(200); padding-top: pxVW(200);
} }
@include breakpoint (xl) { @include breakpoint (xl) {
padding-top: 200px; padding-top: 200px;
padding-bottom: 120px;
} }
} }
} }

View File

@@ -22,6 +22,8 @@
@import "atoms/link"; @import "atoms/link";
@import "atoms/switcher"; @import "atoms/switcher";
@import "atoms/counter"; @import "atoms/counter";
@import "atoms/badge";
@import "atoms/inputs";
// Molecules // Molecules
@import "molecules/location"; @import "molecules/location";

View File

@@ -185,6 +185,16 @@ export const relativeTime = (originDate, limit = 0) => {
} }
/*
** Check if date is older than
*/
export const dateOlderThan = (originDate, limit) => {
const date = new Date(originDate)
const diff = Number(new Date()) - date
return diff < limit
}
/* /*
** Controls Anime.js parallax ** Controls Anime.js parallax
*/ */