jun-26-updates #4
@@ -26,30 +26,35 @@ const slides = images.length > 0 ? images : [{ src: '/images/banner.png', alt: '
|
|||||||
{slides.length > 1 && (
|
{slides.length > 1 && (
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
const root = document.querySelector('[data-banner-rotator]');
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
if (!root || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
|
||||||
|
|
||||||
const slides = Array.from(root.querySelectorAll('[data-banner-slide]'));
|
const roots = Array.from(document.querySelectorAll('[data-banner-rotator]:not([data-banner-rotator-ready])'));
|
||||||
if (slides.length < 2) return;
|
|
||||||
|
|
||||||
if (root.getAttribute('data-randomize-after-first') === 'true') {
|
roots.forEach((root) => {
|
||||||
const firstSlide = slides[0];
|
root.setAttribute('data-banner-rotator-ready', 'true');
|
||||||
const restSlides = slides.slice(1);
|
|
||||||
|
|
||||||
for (let index = restSlides.length - 1; index > 0; index -= 1) {
|
const slides = Array.from(root.querySelectorAll('[data-banner-slide]'));
|
||||||
const swapIndex = Math.floor(Math.random() * (index + 1));
|
if (slides.length < 2) return;
|
||||||
[restSlides[index], restSlides[swapIndex]] = [restSlides[swapIndex], restSlides[index]];
|
|
||||||
|
if (root.getAttribute('data-randomize-after-first') === 'true') {
|
||||||
|
const firstSlide = slides[0];
|
||||||
|
const restSlides = slides.slice(1);
|
||||||
|
|
||||||
|
for (let index = restSlides.length - 1; index > 0; index -= 1) {
|
||||||
|
const swapIndex = Math.floor(Math.random() * (index + 1));
|
||||||
|
[restSlides[index], restSlides[swapIndex]] = [restSlides[swapIndex], restSlides[index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
slides.splice(0, slides.length, firstSlide, ...restSlides);
|
||||||
}
|
}
|
||||||
|
|
||||||
slides.splice(0, slides.length, firstSlide, ...restSlides);
|
let currentIndex = 0;
|
||||||
}
|
window.setInterval(() => {
|
||||||
|
slides[currentIndex].classList.remove('active');
|
||||||
let currentIndex = 0;
|
currentIndex = (currentIndex + 1) % slides.length;
|
||||||
window.setInterval(() => {
|
slides[currentIndex].classList.add('active');
|
||||||
slides[currentIndex].classList.remove('active');
|
}, 3500);
|
||||||
currentIndex = (currentIndex + 1) % slides.length;
|
});
|
||||||
slides[currentIndex].classList.add('active');
|
|
||||||
}, 3500);
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ const contactRequestEndpoint = `${pprApiBase}/contact-requests/public`;
|
|||||||
<select id="contact-enquiry-type" name="enquiry_type" required>
|
<select id="contact-enquiry-type" name="enquiry_type" required>
|
||||||
<option value="">Select enquiry type</option>
|
<option value="">Select enquiry type</option>
|
||||||
<option value="general">General enquiry</option>
|
<option value="general">General enquiry</option>
|
||||||
|
<option value="pilot">Pilot or visiting aircraft / Fuel</option>
|
||||||
<option value="aviation_business">Aviation business / basing</option>
|
<option value="aviation_business">Aviation business / basing</option>
|
||||||
<option value="pilot">Pilot or visiting aircraft</option>
|
|
||||||
<option value="events">Events and visits</option>
|
<option value="events">Events and visits</option>
|
||||||
<option value="community">Community or local resident</option>
|
<option value="community">Community or local resident</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import SectionHeading from './SectionHeading.astro';
|
|||||||
import type { FuelPrice } from '../lib/fallback-data';
|
import type { FuelPrice } from '../lib/fallback-data';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
contactHref?: string;
|
||||||
fuelPrices: FuelPrice[];
|
fuelPrices: FuelPrice[];
|
||||||
|
moreInfoHref?: string;
|
||||||
|
sectionId?: string;
|
||||||
|
serviceNotes?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const { fuelPrices } = Astro.props as Props;
|
const { contactHref, fuelPrices, moreInfoHref, sectionId, serviceNotes = [] } = Astro.props as Props;
|
||||||
|
|
||||||
function formatFuelPrice(value: unknown): string {
|
function formatFuelPrice(value: unknown): string {
|
||||||
const numeric = Number(value);
|
const numeric = Number(value);
|
||||||
@@ -18,8 +22,16 @@ function formatVatLabel(value: unknown): string {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<section>
|
<section id={sectionId}>
|
||||||
<SectionHeading title="Fuel at Swansea" />
|
<SectionHeading title="Fuel at Swansea" />
|
||||||
|
{serviceNotes.length > 0 && (
|
||||||
|
<div class="fuel-service-notes">
|
||||||
|
{serviceNotes.map((note) => <p>{note}</p>)}
|
||||||
|
{contactHref && (
|
||||||
|
<a class="button primary fuel-contact-link" href={contactHref}>Contact Us</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="cards-grid fuel-cards-grid">
|
<div class="cards-grid fuel-cards-grid">
|
||||||
{fuelPrices.map((fuel) => (
|
{fuelPrices.map((fuel) => (
|
||||||
<article class="card fuel-card">
|
<article class="card fuel-card">
|
||||||
@@ -37,6 +49,14 @@ function formatVatLabel(value: unknown): string {
|
|||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{moreInfoHref && (
|
||||||
|
<div class="fuel-more-info">
|
||||||
|
<a class="button primary fuel-info-link" href={moreInfoHref}>
|
||||||
|
More fuel information
|
||||||
|
<span aria-hidden="true">-></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -44,6 +64,56 @@ function formatVatLabel(value: unknown): string {
|
|||||||
gap: 1.1rem;
|
gap: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fuel-service-notes {
|
||||||
|
margin: 0 0 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-service-notes p {
|
||||||
|
margin: 0 0 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-service-notes p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-contact-link {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-more-info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-info-link {
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding-inline: 1.15rem;
|
||||||
|
box-shadow: 0 12px 24px rgba(11, 79, 122, 0.18);
|
||||||
|
transition:
|
||||||
|
background 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-info-link span {
|
||||||
|
font-weight: 800;
|
||||||
|
transition: transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-info-link:hover,
|
||||||
|
.fuel-info-link:focus-visible {
|
||||||
|
color: white;
|
||||||
|
background: var(--brand-2);
|
||||||
|
box-shadow: 0 14px 28px rgba(11, 79, 122, 0.24);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-info-link:hover span,
|
||||||
|
.fuel-info-link:focus-visible span {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
.fuel-card {
|
.fuel-card {
|
||||||
border: 1px solid rgba(11, 79, 122, 0.18);
|
border: 1px solid rgba(11, 79, 122, 0.18);
|
||||||
background:
|
background:
|
||||||
@@ -104,6 +174,14 @@ function formatVatLabel(value: unknown): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
|
.fuel-more-info {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuel-info-link {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.fuel-row {
|
.fuel-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
fallbackEvents,
|
fallbackEvents,
|
||||||
fallbackFuelPrices,
|
fallbackFuelPrices,
|
||||||
fallbackHomepageBannerImages,
|
fallbackHomepageBannerImages,
|
||||||
|
fallbackHomepageVolunteerImages,
|
||||||
fallbackNews,
|
fallbackNews,
|
||||||
fallbackNotices,
|
fallbackNotices,
|
||||||
type ContactItem,
|
type ContactItem,
|
||||||
@@ -45,6 +46,7 @@ const directusAssetUrlTemplate =
|
|||||||
const directusToken = process.env.DIRECTUS_TOKEN ?? process.env.DIRECTUS_ADMIN_TOKEN;
|
const directusToken = process.env.DIRECTUS_TOKEN ?? process.env.DIRECTUS_ADMIN_TOKEN;
|
||||||
const directusDebug = ['1', 'true', 'yes', 'on'].includes((process.env.DIRECTUS_DEBUG ?? '').toLowerCase());
|
const directusDebug = ['1', 'true', 'yes', 'on'].includes((process.env.DIRECTUS_DEBUG ?? '').toLowerCase());
|
||||||
const homepageBannerFolder = process.env.DIRECTUS_HOMEPAGE_BANNER_FOLDER ?? 'homepage-banners';
|
const homepageBannerFolder = process.env.DIRECTUS_HOMEPAGE_BANNER_FOLDER ?? 'homepage-banners';
|
||||||
|
const homepageVolunteersFolder = process.env.DIRECTUS_HOMEPAGE_VOLUNTEERS_FOLDER ?? 'homepage-volunteers';
|
||||||
const cafePageFolder = process.env.DIRECTUS_CAFE_PAGE_FOLDER ?? 'cafe-page';
|
const cafePageFolder = process.env.DIRECTUS_CAFE_PAGE_FOLDER ?? 'cafe-page';
|
||||||
|
|
||||||
type DirectusFolder = {
|
type DirectusFolder = {
|
||||||
@@ -293,6 +295,10 @@ export const getHomepageBannerImages = () =>
|
|||||||
firstTag: 'first',
|
firstTag: 'first',
|
||||||
shuffleRest: true,
|
shuffleRest: true,
|
||||||
});
|
});
|
||||||
|
export const getHomepageVolunteerImages = () =>
|
||||||
|
getImagesFromFolder(homepageVolunteersFolder, fallbackHomepageVolunteerImages, {
|
||||||
|
shuffleRest: true,
|
||||||
|
});
|
||||||
export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages);
|
export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages);
|
||||||
|
|
||||||
function stripHtml(value = ''): string {
|
function stripHtml(value = ''): string {
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ export const fallbackCafePageImages: HomepageBannerImage[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const fallbackHomepageVolunteerImages: HomepageBannerImage[] = [
|
||||||
|
{
|
||||||
|
src: '/images/cessna.jpg',
|
||||||
|
alt: 'A Cessna aircraft on the apron at Swansea Airport',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const fallbackEvents: EventItem[] = [
|
export const fallbackEvents: EventItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Airfield open day',
|
title: 'Airfield open day',
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ export const site = {
|
|||||||
},
|
},
|
||||||
{ label: 'Events', href: '/events/' },
|
{ label: 'Events', href: '/events/' },
|
||||||
{ label: 'News', href: '/news/' },
|
{ label: 'News', href: '/news/' },
|
||||||
{ label: 'Documents', href: '/documents/' },
|
// Keep the documents page available, but hidden from the menu until needed.
|
||||||
|
// { label: 'Documents', href: '/documents/' },
|
||||||
{ label: 'Contact', href: '/contact/' },
|
{ label: 'Contact', href: '/contact/' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import EventsList from '../components/EventsList.astro';
|
|||||||
import FacebookWidget from '../components/FacebookWidget.astro';
|
import FacebookWidget from '../components/FacebookWidget.astro';
|
||||||
import NewsFeed from '../components/NewsFeed.astro';
|
import NewsFeed from '../components/NewsFeed.astro';
|
||||||
import { getUpcomingEvents } from '../lib/events';
|
import { getUpcomingEvents } from '../lib/events';
|
||||||
import { getEvents, getFuelPrices, getHomepageBannerImages, getNews } from '../lib/directus';
|
import { getEvents, getFuelPrices, getHomepageBannerImages, getHomepageVolunteerImages, getNews } from '../lib/directus';
|
||||||
|
|
||||||
const [fuelPrices, events, news, bannerImages] = await Promise.all([
|
const [fuelPrices, events, news, bannerImages, volunteerImages] = await Promise.all([
|
||||||
getFuelPrices(),
|
getFuelPrices(),
|
||||||
getEvents(),
|
getEvents(),
|
||||||
getNews(),
|
getNews(),
|
||||||
getHomepageBannerImages(),
|
getHomepageBannerImages(),
|
||||||
|
getHomepageVolunteerImages(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const upcomingEvents = getUpcomingEvents(events);
|
const upcomingEvents = getUpcomingEvents(events);
|
||||||
@@ -74,7 +75,7 @@ const businessPromos = [
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="container stack">
|
<div class="container stack">
|
||||||
<FuelPricesWidget fuelPrices={fuelPrices} />
|
<FuelPricesWidget fuelPrices={fuelPrices} moreInfoHref="/pilot-info/#fuel" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="container business-promo">
|
<section class="container business-promo">
|
||||||
@@ -140,7 +141,7 @@ const businessPromos = [
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<figure class="story-image">
|
<figure class="story-image">
|
||||||
<img src="/images/cessna.jpg" alt="A Cessna aircraft on the apron at Swansea Airport" loading="lazy" />
|
<BannerRotator images={volunteerImages} />
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ const runwayRows = site.runwayFacts
|
|||||||
|
|
||||||
const otherFacts = site.runwayFacts.filter((fact) => !fact.match(runwayPattern));
|
const otherFacts = site.runwayFacts.filter((fact) => !fact.match(runwayPattern));
|
||||||
const fuelPrices = await getFuelPrices();
|
const fuelPrices = await getFuelPrices();
|
||||||
|
const fuelServiceNotes = [
|
||||||
|
'Attended refuelling is available. Please advise on PPR, or after landing, if fuel is required.',
|
||||||
|
'JET A1 is available gravity fed or pressurised. Rotors-running and out-of-hours fuel may be available by prior arrangement.',
|
||||||
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title="Pilot Info" description="Essential information for pilots planning a visit to Swansea Airport.">
|
<BaseLayout title="Pilot Info" description="Essential information for pilots planning a visit to Swansea Airport.">
|
||||||
@@ -128,7 +132,7 @@ When the Tower is unavailable, this will be NOTAMed and blind calls to Swansea T
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<FuelPricesWidget fuelPrices={fuelPrices} />
|
<FuelPricesWidget contactHref="/contact/" fuelPrices={fuelPrices} sectionId="fuel" serviceNotes={fuelServiceNotes} />
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -406,12 +406,18 @@ section {
|
|||||||
.story-image img {
|
.story-image img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 4 / 3;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.story-image .banner-rotator {
|
||||||
|
min-height: 0;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.business-promo {
|
.business-promo {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user