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
*/
...(!dev && {
serviceworker: {
input: sapperConfig.serviceworker.input(),
output: sapperConfig.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': false,
...replaceOptions
}),
aliases,
glslify(),
commonjs(),
!dev && terser()
],
onwarn,
}
})
// ...(!dev && {
// serviceworker: {
// input: sapperConfig.serviceworker.input(),
// output: sapperConfig.serviceworker.output(),
// plugins: [
// resolve(),
// replace({
// 'process.browser': false,
// ...replaceOptions
// }),
// aliases,
// glslify(),
// commonjs(),
// !dev && terser()
// ],
// 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>
import { dateOlderThan } from 'utils/functions'
// Components
import Badge from 'atoms/Badge'
// Props
export let location
// 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>
<div class="location" role="listitem">
<a href="/location/{country.slug}/{slug}" rel="prefetch" sapper-noscroll>
<img src={country.flag.full_url} alt="Flag of {country.name}">
<div class="anim-mask mask-city">
<h3 class="location__city">{name}</h3>
</div>
<div class="anim-mask mask-country">
<p class="location__country style-caps">{country.name}</p>
</div>
{#if dateOlderThan(last_updated, timeLimit)}
<Badge size="small" text="new" />
{/if}
</a>
</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
import Button from 'atoms/Button'
import Location from 'molecules/Location'
import Newsletter from 'organisms/Newsletter'
// Animations
import { animateIn } from 'animations/Locations'
@@ -58,12 +59,11 @@
<div class="browse__locations" id="locations_list" role="list" bind:this={list}>
{#each filteredLocations as location (location.id)}
<div animate:flip="{{ duration: transitionDuration }}"
in:receive="{{ key: location.id }}"
out:send="{{ key: location.id }}"
>
<div animate:flip="{{ duration: transitionDuration }}" in:receive="{{ key: location.id }}" out:send="{{ key: location.id }}">
<Location {location} />
</div>
{/each}
</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 { site, currentLocation } from 'utils/store'
// Components
import Newsletter from 'molecules/Newsletter'
import Newsletter from 'organisms/Newsletter'
// Props
export let photos
export let paginatedPhotos
@@ -58,12 +58,9 @@
{:else if $currentLocation}
<div class="pagination__message">
<h3>That's all folks!</h3>
<Newsletter
small={true}
title="That's all folks!"
brightness="light"
title={false}
/>
</div>
{/if}

View File

@@ -1,6 +1,6 @@
<script context="module">
export async function preload (page, session) {
const req = await this.fetch(apiEndpoints.gql, {
await this.fetch(apiEndpoints.gql, {
method: 'post',
headers: {
'Content-Type': 'application/json',
@@ -56,16 +56,58 @@
illu_mobile { full_url }
}
}
photos (filter: { status_eq: "published" }) {
data {
created_on
location { id }
}
}
}`})
})
const result = await req.json()
if (req.ok) {
// Set data into store
site.set(result.data.site.data[0])
continents.set(result.data.continents.data)
countries.set(result.data.countries.data)
locations.set(result.data.locations.data)
}
.then(res => res.json())
.then(res => {
const { data } = res
// Manipulate countries data
data.countries.data.forEach(country => {
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>
@@ -112,30 +154,6 @@
'/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>
<style lang="scss" global>

View File

@@ -9,7 +9,7 @@
import TitleSite from 'atoms/TitleSite'
import LinkTranslate from 'atoms/LinkTranslate'
import InteractiveGlobe from 'molecules/InteractiveGlobe'
import Newsletter from 'molecules/Newsletter'
import NewsletterForm from 'molecules/NewsletterForm'
import Footer from 'organisms/Footer'
import SocialMetas from 'utils/SocialMetas'
// Animations
@@ -53,7 +53,15 @@
<TitleSite />
</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>
{#if process.browser}

View File

@@ -1,7 +1,9 @@
// Colors
$color-primary: #3C0576;
$color-primary-dark: #36046A;
$color-primary-darker: #2D0458;
$color-secondary: #FF6C89;
$color-secondary-light: #FFB3C2;
$color-secondary-bright: #FF0536;
$color-text: #333;
$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;
z-index: 3;
margin-top: 72px;
margin-bottom: 72px;
}
}

View File

@@ -36,8 +36,9 @@
/*
** Light version
** Variants
*/
// Light version
&--light {
// Text input
.input__text {
@@ -47,44 +48,3 @@
}
}
}
// 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);
}
}
.mask-city {
height: 40px;
margin: 24px 0 16px;
@include breakpoint (md) {
height: 64px;
margin-bottom: 24px;
}
// Masks
.mask-city {
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,8 @@
@import "atoms/link";
@import "atoms/switcher";
@import "atoms/counter";
@import "atoms/badge";
@import "atoms/inputs";
// Molecules
@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
*/