| @ -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> | |||||