@ -0,0 +1,96 @@ | |||||
html, | |||||
body, | |||||
#__nuxt, | |||||
#__layout, | |||||
.page-container, | |||||
main { | |||||
height: 100%; | |||||
} | |||||
html { | |||||
font-size: min(3vmin, 18px); | |||||
} | |||||
header { | |||||
position: fixed; | |||||
top: 0; | |||||
width: 100vw; | |||||
z-index: 403; | |||||
display: grid; | |||||
grid-template-columns: 1fr 1fr 1fr; | |||||
padding: 1rem 1rem 0.75rem 1rem; | |||||
pointer-events: none; | |||||
} | |||||
.white-bkg header { | |||||
background-color: white; | |||||
} | |||||
header button { | |||||
pointer-events: all; | |||||
} | |||||
.header__col:nth-child(2) { | |||||
display: flex; | |||||
justify-content: center; | |||||
align-items: flex-start; | |||||
} | |||||
.header__col:nth-child(3) { | |||||
text-align: right; | |||||
} | |||||
main { | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
.nav { | |||||
flex: 0 0 auto; | |||||
background: white; | |||||
display: flex; | |||||
flex-direction: column; | |||||
justify-content: flex-end; | |||||
} | |||||
:root { | |||||
--m-s: 0.1s; | |||||
--h-s: 0.3s; | |||||
--d: calc(var(--h-s) - var(--m-s)); | |||||
} | |||||
.nav--hide { | |||||
transition: margin-top var(--m-s) linear var(--h-s), max-height var(--h-s) linear 0s; | |||||
margin-top: 0; | |||||
max-height: 0; | |||||
} | |||||
.nav--show { | |||||
margin-top: 3.5rem; | |||||
max-height: 250px; | |||||
transition: margin-top var(--m-s) linear 0s, max-height var(--h-s) linear var(--m-s); | |||||
} | |||||
.btn--nav-open { | |||||
padding: 1rem; | |||||
display: block; | |||||
} | |||||
.flyout-container { | |||||
z-index: 1001; | |||||
position: fixed; | |||||
bottom: 0; | |||||
display: flex; | |||||
justify-content: center; | |||||
width: 100vw; | |||||
transition: all 0.3s; | |||||
pointer-events: none; | |||||
} | |||||
.flyout-container.flyout-container--hide { | |||||
transform: translateY(200px); | |||||
} | |||||
.flyout-container.flyout-container--show { | |||||
transform: translateY(0); | |||||
} |
@ -0,0 +1,163 @@ | |||||
<template> | |||||
<div class="chosen-wrapper"> | |||||
<div class="chosen-cols"> | |||||
<div class="chosen-col"> | |||||
<label for="" class="chosen-text"> | |||||
{{ selectedOrig.municipality }} | |||||
</label> | |||||
<h1 class="chosen-text"> | |||||
{{ selectedOrig.iata }} | |||||
</h1> | |||||
<span v-if="departure" class="date-badge"> | |||||
{{ departure }} {{ (departureTime ? '@' + departureTime : '') }} | |||||
</span> | |||||
</div> | |||||
<div class="chosen-col"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-arrow-right-circle" | |||||
width="100" | |||||
height="100" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="1.5" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<path d="M18 15l3 -3l-3 -3" /> | |||||
<circle cx="5" cy="12" r="2" /> | |||||
<path d="M7 12h14" /> | |||||
</svg> | |||||
</div> | |||||
<div class="chosen-col"> | |||||
<label for="" class="chosen-text"> | |||||
{{ selectedDest.municipality }} | |||||
</label> | |||||
<h1 class="chosen-text"> | |||||
{{ selectedDest.iata }} | |||||
</h1> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: { | |||||
selectedOrig: { | |||||
type: [Object], | |||||
default () { | |||||
return {} | |||||
} | |||||
}, | |||||
selectedDest: { | |||||
type: [Object], | |||||
default () { | |||||
return {} | |||||
} | |||||
}, | |||||
departure: { | |||||
type: [String], | |||||
default () { | |||||
return '' | |||||
} | |||||
}, | |||||
departureTime: { | |||||
type: [String], | |||||
default () { | |||||
return '' | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
.chosen-wrapper { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
} | |||||
.chosen-cols { | |||||
display: flex; | |||||
justify-content: center; | |||||
background-color: #003c79; | |||||
border-radius: 1rem; | |||||
padding: 0.5rem 2rem; | |||||
margin-bottom: 2rem; | |||||
position: relative; | |||||
max-width: 450px; | |||||
--knockout-size: 0.75rem; | |||||
} | |||||
.chosen-col:nth-of-type(2) { | |||||
display: flex; | |||||
flex-direction: column; | |||||
justify-content: center; | |||||
align-items: center; | |||||
} | |||||
.chosen-col:nth-of-type(2) svg { | |||||
margin-top: -0.9rem; | |||||
width: 3.5rem; | |||||
height: 3.5rem; | |||||
} | |||||
.chosen-col:nth-of-type(2)::before { | |||||
content: ""; | |||||
height: var(--knockout-size); | |||||
width: var(--knockout-size); | |||||
background-color: white; | |||||
border-radius: 0 0 999px 999px; | |||||
display: block; | |||||
position: absolute; | |||||
top: 0; | |||||
} | |||||
.chosen-col:nth-of-type(2)::after { | |||||
content: ""; | |||||
height: var(--knockout-size); | |||||
width: var(--knockout-size); | |||||
background-color: white; | |||||
border-radius: 999px 999px 0 0; | |||||
display: block; | |||||
position: absolute; | |||||
bottom: 0; | |||||
} | |||||
.flyout .chosen-col:nth-of-type(2)::before, | |||||
.flyout .chosen-col:nth-of-type(2)::after { | |||||
background-color: #007fff; | |||||
} | |||||
label.chosen-text { | |||||
color: #007fff; | |||||
line-height: 0.8; | |||||
font-size: 0.8rem; | |||||
font-weight: 400; | |||||
} | |||||
.header__col:nth-child(3) label.chosen-text { | |||||
text-align: right; | |||||
} | |||||
h1.chosen-text { | |||||
font-size: 3rem; | |||||
line-height: 0.8; | |||||
font-weight: 500; | |||||
color: #eee; | |||||
} | |||||
.date-badge { | |||||
background: white; | |||||
border-radius: 999px; | |||||
padding: 0 0.5em; | |||||
line-height: 1; | |||||
display: inline-block; | |||||
font-size: 0.8rem; | |||||
} | |||||
</style> |
@ -0,0 +1,30 @@ | |||||
<template> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-x" | |||||
width="60" | |||||
height="60" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#bbbbbb" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<line x1="18" y1="6" x2="6" y2="18" /> | |||||
<line x1="6" y1="6" x2="18" y2="18" /> | |||||
</svg> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
svg { | |||||
max-height: 3.2rem; | |||||
} | |||||
</style> |
@ -0,0 +1,29 @@ | |||||
<template> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-chevron-down" | |||||
width="60" | |||||
height="60" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#bbbbbb" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<polyline points="6 9 12 15 18 9" fill="none" /> | |||||
</svg> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
svg { | |||||
max-height: 3.2rem; | |||||
} | |||||
</style> |
@ -0,0 +1,93 @@ | |||||
<template> | |||||
<header> | |||||
<div class="header__col"> | |||||
<Logo /> | |||||
<div v-if="!selectedOrig.iata" class="logo-blurb"> | |||||
from one of <strong>{{ airportsOrig.length }}</strong> | |||||
<span style="color: white;">local</span> airports | |||||
</div> | |||||
<div | |||||
v-if=" | |||||
selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1 | |||||
" | |||||
class="logo-blurb" | |||||
> | |||||
to one of <strong>{{ airportsDest.length }}</strong> | |||||
{{ getCompliment }} destinations | |||||
</div> | |||||
</div> | |||||
<div class="header__col"> | |||||
<div v-show="!isPickerVisible"> | |||||
<button class="btn btn--primary" @click="showPicker"> | |||||
tap the map or search 🔎 | |||||
</button> | |||||
</div> | |||||
<div v-show="isPickerVisible"> | |||||
<button class="btn btn--primary" @click="showPicker"> | |||||
hide search <svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-arrow-bar-to-up" | |||||
width="24" | |||||
height="24" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="3" | |||||
stroke="#ffffff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<line x1="12" y1="10" x2="12" y2="20" /> | |||||
<line x1="12" y1="10" x2="16" y2="14" /> | |||||
<line x1="12" y1="10" x2="8" y2="14" /> | |||||
<line x1="4" y1="4" x2="20" y2="4" /> | |||||
</svg> | |||||
</button> | |||||
</div> | |||||
</div> | |||||
<div class="header__col" /> | |||||
</header> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: { | |||||
selectedOrig: { | |||||
type: [Object], | |||||
default () { | |||||
return {} | |||||
} | |||||
}, | |||||
selectedDest: { | |||||
type: [Object], | |||||
default () { | |||||
return {} | |||||
} | |||||
}, | |||||
airportsOrig: { | |||||
type: [Array], | |||||
default () { | |||||
return [] | |||||
} | |||||
}, | |||||
airportsDest: { | |||||
type: [Array], | |||||
default () { | |||||
return [] | |||||
} | |||||
}, | |||||
isPickerVisible: { | |||||
type: [Boolean], | |||||
default () { | |||||
return null | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
.asdf { | |||||
color: red; | |||||
} | |||||
</style> |
@ -0,0 +1,592 @@ | |||||
<template> | |||||
<div> | |||||
<button class="btn btn--primary sidebar-button" @click="showSidebar"> | |||||
<svg | |||||
v-if="!isSidebarVisible" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-menu" | |||||
width="24" | |||||
height="24" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="3" | |||||
stroke="#ffffff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<line x1="4" y1="8" x2="20" y2="8" /> | |||||
<line x1="4" y1="16" x2="20" y2="16" /> | |||||
</svg> | |||||
<svg | |||||
v-if="isSidebarVisible" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-arrow-bar-to-right" | |||||
width="24" | |||||
height="24" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="3" | |||||
stroke="#ffffff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<line x1="14" y1="12" x2="4" y2="12" /> | |||||
<line x1="14" y1="12" x2="10" y2="16" /> | |||||
<line x1="14" y1="12" x2="10" y2="8" /> | |||||
<line x1="20" y1="4" x2="20" y2="20" /> | |||||
</svg> | |||||
</button> | |||||
<aside | |||||
:class="[ | |||||
'sidebar-container', | |||||
{ 'sidebar-container--hide': !isSidebarVisible }, | |||||
{ 'sidebar-container--show': isSidebarVisible }, | |||||
]" | |||||
> | |||||
<div class="sidebar"> | |||||
<div class="sidebar-content"> | |||||
<h1 class="h1--about"> | |||||
What is FlyLocal Alaska? | |||||
</h1> | |||||
<div class="img-divider"> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F3D4.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F4C5.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F601.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
</div> | |||||
<p class="text--about"> | |||||
<strong>FlyLocal-Alaska</strong> is an easy way to view the | |||||
schedules for your favorite Alaskan airlines - all in one place. | |||||
</p> | |||||
<h1 class="h1--about"> | |||||
Why FlyLocal in Alaska? | |||||
</h1> | |||||
<div class="img-divider"> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F6AB.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F697.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F6E3.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
</div> | |||||
<p class="text--about"> | |||||
<strong>80% of Alaskan communities are not connected to the road | |||||
system.</strong> | |||||
Therefore, they rely heavily on local airlines for transportation. | |||||
</p> | |||||
<div class="img-divider"> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F6A3.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F3D5.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F43B.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
</div> | |||||
<p class="text--about"> | |||||
<strong>See new places.</strong> There are ~400 public airports in | |||||
Alaska, but only 20 (or 5%) with service from major US carriers. | |||||
</p> | |||||
<div class="img-divider"> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F6E9.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F681.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
<img | |||||
class="img--about" | |||||
src="https://openmoji.org/data/black/svg/1F6F8.svg" | |||||
alt="" | |||||
srcset="" | |||||
> | |||||
</div> | |||||
<p class="text--about"> | |||||
Enjoy a completely <strong>unique flying experience</strong>. Have | |||||
you ever landed on a lake in a seaplane? Now is your chance to check | |||||
it off the bucket-list. | |||||
</p> | |||||
<h1 class="h1--about"> | |||||
Up-to-date Travel Info | |||||
</h1> | |||||
<ul class="about-carriers list-lrg"> | |||||
<li> | |||||
<a href="https://covid19.alaska.gov/travelers/"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
:src="require(`@/assets/images/orgs/alaska-travel-info.png`)" | |||||
alt="Alaska Travel Info" | |||||
> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
<h1 class="h1--about"> | |||||
Explore Alaska on these great local airlines | |||||
</h1> | |||||
<ul class="about-carriers"> | |||||
<li v-for="carrier in carriers" :key="carrier.id"> | |||||
<a :href="carrier.fields.BookingLink" target="_blank"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
:src="require(`@/assets/images/carriers/${carrier.fields.Slug}.png`)" | |||||
:alt="carrier.fields.Name" | |||||
> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
<h1 class="h1--about"> | |||||
Proud Member of | |||||
</h1> | |||||
<ul class="about-carriers list-med"> | |||||
<li> | |||||
<a href="https://alaskaaircarriers.org"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
:src="require(`@/assets/images/orgs/aaca.png`)" | |||||
alt="Alaska Air Carriers Association" | |||||
> | |||||
</a> | |||||
</li> | |||||
<li> | |||||
<a href="https://alaskatia.org"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
:src="require(`@/assets/images/orgs/alaska-tia.png`)" | |||||
alt="Alaska Travel Industry Association" | |||||
> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
<h1 class="h1--about"> | |||||
Featured on | |||||
</h1> | |||||
<ul class="about-carriers list-med"> | |||||
<li> | |||||
<a href="https://www.1millioncups.com/anchorage/presentations/flylocal-alaska-35304"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
:src="require(`@/assets/images/orgs/1-million-cups.png`)" | |||||
alt="1 Million Cups" | |||||
> | |||||
</a> | |||||
</li> | |||||
<li> | |||||
<a href="https://www.travelalaska.com/offers/FlyLocal/Plane-Statewide.aspx"> | |||||
<img | |||||
class="img--carrier-logo" | |||||
style="background: black; border-radius: 0.5rem; padding-left: 0.4rem;" | |||||
:src="require(`@/assets/images/orgs/travel-alaska.png`)" | |||||
alt="Travel Alaska" | |||||
> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
<div class="company-footer"> | |||||
⎨ FlyLocal v5.0.2 𐄙 ©2021 FlyLocal LLC ⎬ | |||||
</div> | |||||
</div> | |||||
<ul class="contact"> | |||||
<li class="contact-item"> | |||||
<a href="https://twitter.com/fly_local"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-brand-twitter" | |||||
width="40" | |||||
height="40" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<path | |||||
d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" | |||||
/> | |||||
</svg> | |||||
<span>@fly_local</span> | |||||
</a> | |||||
</li> | |||||
<li class="contact-item"> | |||||
<a href="https://facebook.com/fly_local"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-brand-facebook" | |||||
width="40" | |||||
height="40" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<path | |||||
d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3" | |||||
/> | |||||
</svg> | |||||
<span>@fly_local</span> | |||||
</a> | |||||
</li> | |||||
<li class="contact-item"> | |||||
<a href="https://instagram.com/fly_local"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-brand-instagram" | |||||
width="40" | |||||
height="40" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<rect x="4" y="4" width="16" height="16" rx="4" /> | |||||
<circle cx="12" cy="12" r="3" /> | |||||
<line x1="16.5" y1="7.5" x2="16.5" y2="7.501" /> | |||||
</svg> | |||||
<span>@fly_local</span> | |||||
</a> | |||||
</li> | |||||
<li class="contact-item"> | |||||
<a href="mailto:team@iflylocal.com"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-user" | |||||
width="40" | |||||
height="40" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<circle cx="12" cy="7" r="4" /> | |||||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /> | |||||
</svg> | |||||
<span>contact us</span> | |||||
</a> | |||||
</li> | |||||
<li class="contact-item"> | |||||
<a href="mailto:partners@iflylocal.com"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-briefcase" | |||||
width="40" | |||||
height="40" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="2" | |||||
stroke="#007fff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<rect x="3" y="7" width="18" height="13" rx="2" /> | |||||
<path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2" /> | |||||
<line x1="12" y1="12" x2="12" y2="12.01" /> | |||||
<path d="M3 13a20 20 0 0 0 18 0" /> | |||||
</svg> | |||||
<span>partner with us</span> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</aside> | |||||
<div | |||||
:class="[ | |||||
'sidebar-blockui', | |||||
{ 'sidebar-blockui--show': isSidebarVisible }, | |||||
]" | |||||
/> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data: () => ({ | |||||
isSidebarVisible: false, | |||||
carriers: [] | |||||
}), | |||||
async fetch () { | |||||
const filterFormula = 'AND(NOT(Status="Inactive"),FIND("Alaska",Region)>0)' | |||||
const sortFormula = '&sort[0][field]=Name&sort[0][direction]=asc' | |||||
let offset | |||||
let data = [] | |||||
while (true) { | |||||
const offsetParam = offset ? `&offset=${offset}` : '' | |||||
const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Carriers_Alaska?filterByFormula=${filterFormula}${sortFormula}${offsetParam}`, { | |||||
method: 'GET', | |||||
headers: { | |||||
'Content-Type': 'application/x-www-form-urlencoded', | |||||
Authorization: 'Bearer keyJ2ht64ZSN57AG1' | |||||
} | |||||
}) | |||||
const json = await res.json() | |||||
offset = await json.offset | |||||
data = [...data, ...await json.records] | |||||
// await console.log(data.length) | |||||
if (await !offset) { break } // Were done let's stop this thing | |||||
} | |||||
this.carriers = [...data] | |||||
}, | |||||
methods: { | |||||
showSidebar () { | |||||
this.isSidebarVisible = !this.isSidebarVisible | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
:root { | |||||
--sidebar-width: 50ch; | |||||
} | |||||
.sidebar-container { | |||||
z-index: 1002; | |||||
position: fixed; | |||||
top: 0; | |||||
right: 0; | |||||
display: flex; | |||||
justify-content: flex-start; | |||||
height: 100vh; | |||||
transition: all 0.3s; | |||||
pointer-events: none; | |||||
} | |||||
.sidebar-container.sidebar-container--hide { | |||||
transform: translateX(calc(var(--sidebar-width) + 6rem)); | |||||
} | |||||
.sidebar-container.sidebar-container--show { | |||||
transform: translateX(0); | |||||
} | |||||
.sidebar { | |||||
background: white; | |||||
padding: 3rem 0 3rem 3rem; | |||||
border-radius: 3rem 0 0 3rem; | |||||
pointer-events: all; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: flex-end; | |||||
} | |||||
.sidebar-content { | |||||
overflow: auto; | |||||
max-width: var(--sidebar-width); | |||||
height: calc(100vh - 11rem); | |||||
padding-right: 3rem; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
} | |||||
.img--about { | |||||
height: min(3rem, 60px); | |||||
display: inline-block; | |||||
} | |||||
div.img-divider { | |||||
display: flex; | |||||
justify-content: space-around; | |||||
padding: 0 25%; | |||||
} | |||||
.img--carrier-logo { | |||||
max-width: 6rem; | |||||
max-height: 6rem; | |||||
} | |||||
.list-med .img--carrier-logo { | |||||
max-width: 9rem; | |||||
max-height: 9rem; | |||||
} | |||||
.list-lrg .img--carrier-logo { | |||||
max-width: 15rem; | |||||
max-height: 15rem; | |||||
} | |||||
h1.h1--about { | |||||
font-weight: 200; | |||||
text-transform: uppercase; | |||||
color: #007fff; | |||||
font-size: min(2.7rem, 50px); | |||||
line-height: 1; | |||||
text-align: center; | |||||
margin-bottom: 1rem; | |||||
} | |||||
.text--about { | |||||
margin-top: 1rem; | |||||
margin-bottom: 1rem; | |||||
max-width: 40ch; | |||||
font-size: min(1rem, 18px); | |||||
} | |||||
.text--about:nth-of-type(1), | |||||
.text--about:nth-of-type(4) { | |||||
margin-bottom: 5rem; | |||||
} | |||||
.about-carriers { | |||||
display: flex; | |||||
flex-direction: row; | |||||
flex-wrap: wrap; | |||||
gap: 2rem; | |||||
align-items: center; | |||||
justify-content: space-around; | |||||
margin-bottom: 5rem; | |||||
} | |||||
.about-carriers li { | |||||
filter: saturate(0%); | |||||
transition: 0.3s all; | |||||
position: relative; | |||||
} | |||||
/* .about-carriers li::after { | |||||
position: absolute; | |||||
top: 0; | |||||
right: 0; | |||||
bottom: 0; | |||||
left: 0; | |||||
background-color: red; | |||||
content: ''; | |||||
} */ | |||||
.about-carriers li:hover { | |||||
filter: saturate(100%); | |||||
transform: scale(1.05); | |||||
} | |||||
.sidebar-button { | |||||
position: fixed; | |||||
top: 1rem; | |||||
right: 1rem; | |||||
z-index: 1003; | |||||
} | |||||
.sidebar-blockui { | |||||
position: fixed; | |||||
top: 0; | |||||
right: 0; | |||||
bottom: 0; | |||||
left: 0; | |||||
opacity: 0; | |||||
filter: blur(0.5); | |||||
background-color: #007fff; | |||||
transition: all 0.3s; | |||||
pointer-events: none; | |||||
z-index: 1001; | |||||
} | |||||
.sidebar-blockui.sidebar-blockui--show { | |||||
opacity: 0.8; | |||||
pointer-events: all; | |||||
} | |||||
ul.contact { | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: center; | |||||
justify-content: center; | |||||
gap: 1rem; | |||||
flex-wrap: wrap; | |||||
margin: 3rem 3rem 0 0; | |||||
max-width: var(--sidebar-width); | |||||
} | |||||
li.contact-item a span { | |||||
color: #007fff; | |||||
transition: all 0.3s; | |||||
} | |||||
ul.contact li.contact-item a { | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: center; | |||||
transition: all 0.3s; | |||||
} | |||||
ul.contact li.contact-item a:hover span { | |||||
color: #00ca00; | |||||
} | |||||
ul.contact li.contact-item a svg { | |||||
max-height: 2rem; | |||||
max-width: 2rem; | |||||
stroke-width: 1; | |||||
transition: all 0.3s; | |||||
} | |||||
ul.contact li.contact-item a:hover svg { | |||||
stroke: #00ca00; | |||||
} | |||||
.company-footer { | |||||
font-family: monospace; | |||||
font-size: 0.8rem; | |||||
text-transform: uppercase; | |||||
color: gray; | |||||
} | |||||
</style> |
@ -0,0 +1,18 @@ | |||||
<template> | |||||
<div style="height: 100%;"> | |||||
<Nuxt /> | |||||
<TheSideBar /> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
} | |||||
</script> | |||||
<style> | |||||
.asdf { | |||||
color: red; | |||||
} | |||||
</style> |
@ -0,0 +1,15 @@ | |||||
export default function (req, res, next) { | |||||
const redirects = [ | |||||
{ | |||||
from: '/', | |||||
to: '/go' | |||||
} | |||||
] | |||||
const redirect = redirects.find(r => r.from === req.url) | |||||
if (redirect) { | |||||
res.writeHead(301, { Location: redirect.to }) | |||||
res.end() | |||||
} else { | |||||
next() | |||||
} | |||||
} |
@ -0,0 +1,226 @@ | |||||
<template> | |||||
<div class="white-bkg"> | |||||
<header> | |||||
<div class="header__col"> | |||||
<button class="btn btn--primary" @click="goBack"> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="icon icon-tabler icon-tabler-chevrons-left" | |||||
width="24" | |||||
height="24" | |||||
viewBox="0 0 24 24" | |||||
stroke-width="3" | |||||
stroke="#ffffff" | |||||
fill="none" | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
> | |||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |||||
<polyline points="11 7 6 12 11 17" /> | |||||
<polyline points="17 7 12 12 17 17" /> | |||||
</svg> back | |||||
</button> | |||||
</div> | |||||
<div class="header__col"> | |||||
<Logo /> | |||||
</div> | |||||
<div class="header__col" /> | |||||
</header> | |||||
<main class="main-with-header"> | |||||
<ChosenFlights | |||||
:selected-orig="selectedOrig" | |||||
:selected-dest="selectedDest" | |||||
style="margin: 0 1rem;" | |||||
/> | |||||
<!-- <pre> | |||||
{{ schedules }} | |||||
</pre> --> | |||||
<DatePicker | |||||
:starting-date="startingDate" | |||||
:selected-dows="selectedDows" | |||||
/> | |||||
</main> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data () { | |||||
return { | |||||
schedules: [], | |||||
startingDate: new Date(), | |||||
selectedSchedule: {}, | |||||
selectedDays: [], | |||||
selectedOrig: {}, | |||||
selectedDest: {} | |||||
} | |||||
}, | |||||
async fetch () { | |||||
// console.log(this.$route.params.o) | |||||
const today = new Date().toLocaleDateString('en-CA') | |||||
// function addDays (date, days) { | |||||
// const result = new Date(date) | |||||
// result.setDate(result.getDate() + days) | |||||
// return result | |||||
// } | |||||
// const tomorrow = addDays(new Date(), 1).toLocaleDateString('en-CA') | |||||
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}",Effective_End>"${today}")` | |||||
const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc' | |||||
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, { | |||||
method: 'GET', | |||||
headers: { | |||||
'Content-Type': 'application/x-www-form-urlencoded', | |||||
Authorization: 'Bearer keyJ2ht64ZSN57AG1' | |||||
} | |||||
}) | |||||
const data = await response.json() | |||||
// console.log(data) | |||||
this.schedules = [...data.records] | |||||
}, | |||||
computed: { | |||||
selectedDows () { | |||||
const mappedDows = this.schedules.map((schedule) => { | |||||
// console.log(schedule.fields) | |||||
return { | |||||
Flight_Number: schedule.fields.Flight_Number, | |||||
DowsList: this.daysList(schedule.fields.Frequency_Convert), | |||||
Effective_Start: schedule.fields.Effective_Start, | |||||
Effective_End: schedule.fields.Effective_End, | |||||
DepartureTimeFormatted: schedule.fields.DepartureTimeFormatted | |||||
} | |||||
}) | |||||
return mappedDows | |||||
} | |||||
}, | |||||
created () { | |||||
// console.log(this.$route.path) | |||||
if (this.$route && this.$route.params && this.$route.params.o) { | |||||
this.fetchSingleAirport(this.$route.params.o, true) | |||||
} | |||||
if (this.$route && this.$route.params && this.$route.params.d) { | |||||
this.fetchSingleAirport(this.$route.params.d, false) | |||||
} | |||||
}, | |||||
methods: { | |||||
daysList (days) { | |||||
const list = days.split(', ').map((day) => { | |||||
const name = day.toLowerCase().slice(0, -1) | |||||
let num | |||||
switch (name) { | |||||
case 'su': | |||||
num = 0 | |||||
break | |||||
case 'mo': | |||||
num = 1 | |||||
break | |||||
case 'tu': | |||||
num = 2 | |||||
break | |||||
case 'we': | |||||
num = 3 | |||||
break | |||||
case 'th': | |||||
num = 4 | |||||
break | |||||
case 'fr': | |||||
num = 5 | |||||
break | |||||
case 'sa': | |||||
num = 6 | |||||
break | |||||
default: | |||||
break | |||||
} | |||||
return { | |||||
name, | |||||
num | |||||
} | |||||
}) | |||||
return list.sort(function (a, b) { | |||||
return a.num - b.num | |||||
}) | |||||
}, | |||||
goBack () { | |||||
this.$router.go(-1) | |||||
}, | |||||
book (schedule) { | |||||
if (schedule.fields.IsExpedia[0] === 'true') { | |||||
this.startingDate = new Date() | |||||
this.selectedSchedule = schedule.fields | |||||
this.selectedDays = this.daysList(schedule.fields.Frequency_Convert) | |||||
} else { | |||||
window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0]) | |||||
} | |||||
}, | |||||
clearSelectedSchedule () { | |||||
console.log('clear') | |||||
this.startingDate = '' | |||||
this.selectedSchedule = {} | |||||
}, | |||||
async fetchSingleAirport (iata, isOrig) { | |||||
const airportFetchFilterFormula = | |||||
isOrig | |||||
? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")` | |||||
: `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")` | |||||
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}`, { | |||||
method: 'GET', | |||||
headers: { | |||||
'Content-Type': 'application/x-www-form-urlencoded', | |||||
Authorization: 'Bearer keyJ2ht64ZSN57AG1' | |||||
} | |||||
}) | |||||
const mapData = await response.json() | |||||
const thisAirport = { | |||||
iata: mapData.records[0].fields.Airport_IATA, | |||||
lat: mapData.records[0].fields.Latitude_Deg, | |||||
long: mapData.records[0].fields.Longitude_Deg, | |||||
icon: mapData.records[0].fields.Icon_Url, | |||||
name: mapData.records[0].fields.Airport_Name, | |||||
municipality: mapData.records[0].fields.Municipality, | |||||
type: mapData.records[0].fields.Type, | |||||
search: mapData.records[0].fields.Search_Field | |||||
} | |||||
switch (isOrig) { | |||||
case true: | |||||
this.selectedOrig = { ...thisAirport } | |||||
break | |||||
case false: | |||||
this.selectedDest = { ...thisAirport } | |||||
break | |||||
default: | |||||
break | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss"> | |||||
.main-with-header { | |||||
margin-top: clamp(20px + 1.75rem, 4rem + 1.75rem, 50px + 1.75rem); | |||||
} | |||||
</style> |
@ -0,0 +1,323 @@ | |||||
<template> | |||||
<main> | |||||
<div> | |||||
<button class="btn btn--primary" style="margin-left: 2rem; margin-top: 2rem;" @click="goBack"> | |||||
👈 back | |||||
</button> | |||||
<h1 style="text-align: center; font-size: 2.5rem;"> | |||||
{{ selectedOrig.municipality }} 🛩 {{ selectedDest.municipality }} | |||||
</h1> | |||||
</div> | |||||
<ul class="schedule-grid"> | |||||
<li | |||||
v-for="schedule in schedules" | |||||
:key="schedule.fields.Record_ID" | |||||
class="grid__row" | |||||
> | |||||
<pre> | |||||
{{ schedule.fields.Flight_Number }} | |||||
</pre> | |||||
<div class="grid__field"> | |||||
<label> Local Airline </label> | |||||
<div class="field__content"> | |||||
{{ schedule.fields.Carrier_Name }} | |||||
</div> | |||||
</div> | |||||
<div class="grid__field"> | |||||
<label> Days of the Week </label> | |||||
<div class="field__content"> | |||||
<div class="dow-grid"> | |||||
<div | |||||
v-for="day in daysList(schedule.fields.Frequency_Convert)" | |||||
:key="day.name" | |||||
:class="['dow-grid__day', 'day--' + day.name]" | |||||
> | |||||
<span> {{ day.name }} </span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="grid__field"> | |||||
<label> Departing </label> | |||||
<div class="field__content"> | |||||
{{ schedule.fields.Departure_TimeL }} | |||||
</div> | |||||
</div> | |||||
<div class="grid__field"> | |||||
<label> Arriving </label> | |||||
<div class="field__content"> | |||||
{{ schedule.fields.Arrival_TimeL }} | |||||
</div> | |||||
</div> | |||||
<div class="grid__field"> | |||||
<label> Available Until </label> | |||||
<div class="field__content"> | |||||
{{ schedule.fields.Effective_End }} | |||||
</div> | |||||
</div> | |||||
<div class="grid__field"> | |||||
<div class="field__content"> | |||||
{{ schedule.fields.Price_USD }} | |||||
</div> | |||||
<button class="btn btn--primary" @click="book(schedule)"> | |||||
{{ schedule.fields.IsExpedia[0] === 'true' ? 'book it!' : 'see flights' }} | |||||
</button> | |||||
</div> | |||||
</li> | |||||
</ul> | |||||
</main> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data () { | |||||
return { | |||||
schedules: [], | |||||
startingDate: '', | |||||
selectedSchedule: {}, | |||||
selectedDays: [], | |||||
selectedOrig: {}, | |||||
selectedDest: {} | |||||
} | |||||
}, | |||||
async fetch () { | |||||
// console.log(this.$route.params.o) | |||||
const today = new Date().toLocaleDateString('en-CA') | |||||
// function addDays (date, days) { | |||||
// const result = new Date(date) | |||||
// result.setDate(result.getDate() + days) | |||||
// return result | |||||
// } | |||||
// const tomorrow = addDays(new Date(), 1).toLocaleDateString('en-CA') | |||||
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}",Effective_End>"${today}",SEARCH("${new Date(this.$route.params.departure).getDay()}",Frequency)>0)` | |||||
// console.log(scheduleFetchFilterFormula) | |||||
const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc' | |||||
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, { | |||||
method: 'GET', | |||||
headers: { | |||||
'Content-Type': 'application/x-www-form-urlencoded', | |||||
Authorization: 'Bearer keyJ2ht64ZSN57AG1' | |||||
} | |||||
}) | |||||
const data = await response.json() | |||||
// console.log(data) | |||||
this.schedules = [...data.records] | |||||
}, | |||||
computed: { | |||||
}, | |||||
created () { | |||||
// console.log(this.$route.path) | |||||
if (this.$route && this.$route.params && this.$route.params.o) { | |||||
this.fetchSingleAirport(this.$route.params.o, true) | |||||
} | |||||
if (this.$route && this.$route.params && this.$route.params.d) { | |||||
this.fetchSingleAirport(this.$route.params.d, false) | |||||
} | |||||
}, | |||||
methods: { | |||||
daysList (days) { | |||||
const list = days.split(', ').map((day) => { | |||||
const name = day.toLowerCase().slice(0, -1) | |||||
let num | |||||
switch (name) { | |||||
case 'su': | |||||
num = 0 | |||||
break | |||||
case 'mo': | |||||
num = 1 | |||||
break | |||||
case 'tu': | |||||
num = 2 | |||||
break | |||||
case 'we': | |||||
num = 3 | |||||
break | |||||
case 'th': | |||||
num = 4 | |||||
break | |||||
case 'fr': | |||||
num = 5 | |||||
break | |||||
case 'sa': | |||||
num = 6 | |||||
break | |||||
default: | |||||
break | |||||
} | |||||
return { | |||||
name, | |||||
num | |||||
} | |||||
}) | |||||
return list.sort(function (a, b) { | |||||
return a.num - b.num | |||||
}) | |||||
}, | |||||
goBack () { | |||||
this.$router.go(-1) | |||||
}, | |||||
book (schedule) { | |||||
if (schedule.fields.IsExpedia[0] === 'true') { | |||||
this.startingDate = new Date() | |||||
this.selectedSchedule = schedule.fields | |||||
this.selectedDays = this.daysList(schedule.fields.Frequency_Convert) | |||||
} else { | |||||
window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0]) | |||||
} | |||||
}, | |||||
clearSelectedSchedule () { | |||||
console.log('clear') | |||||
this.startingDate = '' | |||||
this.selectedSchedule = {} | |||||
}, | |||||
async fetchSingleAirport (iata, isOrig) { | |||||
const airportFetchFilterFormula = | |||||
isOrig | |||||
? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")` | |||||
: `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")` | |||||
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}`, { | |||||
method: 'GET', | |||||
headers: { | |||||
'Content-Type': 'application/x-www-form-urlencoded', | |||||
Authorization: 'Bearer keyJ2ht64ZSN57AG1' | |||||
} | |||||
}) | |||||
const mapData = await response.json() | |||||
const thisAirport = { | |||||
iata: mapData.records[0].fields.Airport_IATA, | |||||
lat: mapData.records[0].fields.Latitude_Deg, | |||||
long: mapData.records[0].fields.Longitude_Deg, | |||||
icon: mapData.records[0].fields.Icon_Url, | |||||
name: mapData.records[0].fields.Airport_Name, | |||||
municipality: mapData.records[0].fields.Municipality, | |||||
type: mapData.records[0].fields.Type, | |||||
search: mapData.records[0].fields.Search_Field | |||||
} | |||||
switch (isOrig) { | |||||
case true: | |||||
this.selectedOrig = { ...thisAirport } | |||||
break | |||||
case false: | |||||
this.selectedDest = { ...thisAirport } | |||||
break | |||||
default: | |||||
break | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss"> | |||||
.schedule-grid { | |||||
display: flex; | |||||
flex-direction: column; | |||||
gap: 1rem; | |||||
margin: 2rem; | |||||
} | |||||
.grid__row { | |||||
display: grid; | |||||
grid-template-columns: 2fr 2fr 1fr 1fr 2fr 1fr; | |||||
grid-template-rows: auto; | |||||
gap: 1rem; | |||||
padding: 1rem; | |||||
} | |||||
@media only screen and (max-width: 768px) { | |||||
.grid__row { | |||||
display: grid; | |||||
grid-template-columns: 1fr 1fr; | |||||
grid-template-rows: auto; | |||||
} | |||||
} | |||||
.grid__row:nth-child(2n) { | |||||
background: #eee; | |||||
} | |||||
// .grid__field { | |||||
// } | |||||
label { | |||||
font-size: 0.75rem; | |||||
font-weight: 700; | |||||
color: gray; | |||||
display: block; | |||||
} | |||||
.field__content { | |||||
font-size: 1.5rem; | |||||
} | |||||
.dow-grid { | |||||
width: 200px; | |||||
display: grid; | |||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; | |||||
gap: 0.5rem; | |||||
} | |||||
.dow-grid__day { | |||||
font-size: 0.8rem; | |||||
border: 1px solid #ccc !important; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
border-radius: 3px !important; | |||||
text-transform: capitalize; | |||||
} | |||||
.day--su { | |||||
grid-column: 1 / span 1; | |||||
} | |||||
.day--mo { | |||||
grid-column: 2 / span 1; | |||||
} | |||||
.day--tu { | |||||
grid-column: 3 / span 1; | |||||
} | |||||
.day--we { | |||||
grid-column: 4 / span 1; | |||||
} | |||||
.day--th { | |||||
grid-column: 5 / span 1; | |||||
} | |||||
.day--fr { | |||||
grid-column: 6 / span 1; | |||||
} | |||||
.day--sa { | |||||
grid-column: 7 / span 1; | |||||
} | |||||
</style> |
@ -0,0 +1,92 @@ | |||||
<template> | |||||
<main class="special"> | |||||
<div class="section"> | |||||
<NuxtLink to="/go" class="btn btn--primary"> | |||||
go to the site | |||||
</NuxtLink> | |||||
<br><br> | |||||
<h1 style="font-size: 2rem;"> | |||||
"what is this site, really?" | |||||
</h1> | |||||
<br> | |||||
<pre style="white-space: pre-wrap;"> | |||||
Last week, I was mulling over FlyLocal.io's speed issues. | |||||
As an exercise, I made a list of all the things that the FlyLocal website does. | |||||
- [1] get data from a server three times | |||||
- [2] display data on a map, twice | |||||
- [3] display data on a page, once | |||||
I let it ruminate that, by design, all the heavy lifting, all the processing, is already being done by Airtable. | |||||
From a technical perspective, the site is really just a *viewer* of our product, and our product is Airtable data. | |||||
*** | |||||
Then I thought about all the previous iterations of the site. | |||||
It occurred to me that all the fancy stuff I'd done in Bubble v1 and pre-Bubble was for a business model that no longer existed. | |||||
There was nothing fancy about this *new* model. No forms, no calculations. Get data, display data. | |||||
And it occurred me that all the time-consuming stuff I'd done with Bubble v2 over the last year was really about me learning Bubble and figuring out how to get it to do the stuff that I already knew that websites could do: Get data, display data. | |||||
And really, there was nothing all that time-consuming about getting data and displaying data, especially in 2021. | |||||
*** | |||||
So... I decided to take a couple days and make a website. And I'm pleasantly surprised with how quickly it all came together. | |||||
Hand-coded from the ground up on a modern, popular web-dev framework, it's lean, snappy, mobile-first, it has an elegant backend, and it checks all of my boxes. | |||||
It still needs some css TLC, but all the major dev is complete. | |||||
</pre> | |||||
<NuxtLink to="/go" class="btn btn--primary"> | |||||
v5, for your consideration | |||||
</NuxtLink> | |||||
</div> | |||||
</main> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
mounted () { | |||||
const body = document.querySelector('body') | |||||
body.classList.add('special') | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
main.special { | |||||
color: #888 !important; | |||||
display: flex; | |||||
align-items: center; | |||||
padding: 3rem; | |||||
background: #262626; | |||||
overflow-y: scroll; | |||||
} | |||||
.section { | |||||
max-width: 60ch; | |||||
border-radius: 2rem; | |||||
border: 1px solid #888; | |||||
background: rgba(255, 255, 255, 0.05); | |||||
padding: 3rem; | |||||
} | |||||
.btn { | |||||
border-radius: 999px; | |||||
padding: 0.4em 0.8em; | |||||
font-size: 1rem; | |||||
white-space: nowrap; | |||||
transition: 0.2s all; | |||||
} | |||||
.btn.btn--primary { | |||||
background: #007fff; | |||||
color: #fff; | |||||
} | |||||
</style> |
@ -1,13 +1,25 @@ | |||||
<template> | <template> | ||||
<main> | |||||
<h1>Home page</h1> | |||||
<NuxtLink to="/go"> | |||||
Go! | |||||
<main class="empty-page white-bkg"> | |||||
<NuxtLogo /> | |||||
<NuxtLink to="/go" class="btn btn--primary"> | |||||
go to the site | |||||
</NuxtLink> | </NuxtLink> | ||||
<a href="https://nuxtjs.org">External Link to another page</a> | |||||
</main> | </main> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
export default {} | export default {} | ||||
</script> | </script> | ||||
<style scoped> | |||||
html { | |||||
background-color: black; | |||||
} | |||||
.empty-page { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
justify-content: center; | |||||
} | |||||
</style> |