| @ -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> | |||
| <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> | |||
| <a href="https://nuxtjs.org">External Link to another page</a> | |||
| </main> | |||
| </template> | |||
| <script> | |||
| export default {} | |||
| </script> | |||
| <style scoped> | |||
| html { | |||
| background-color: black; | |||
| } | |||
| .empty-page { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| </style> | |||