Compare commits
19 Commits
002ba4047d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 09323fbd91 | |||
| 6f59e7fdbc | |||
| 7e7fcbc458 | |||
| a3aa683190 | |||
| 9d7d7a8b6d | |||
| 8fc5c1fa29 | |||
| 4f79b4dd3c | |||
| 352fa24e6d | |||
| fabd8becc5 | |||
| cfd0e54f07 | |||
| c9410cb114 | |||
| befe3e6ba3 | |||
| 63522f545a | |||
| 85020d2dae | |||
| 6a9daeab0d | |||
| 730eb7758a | |||
| c43e4acc32 | |||
| 4a4279e91f | |||
| d5d643fbcb |
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
@@ -62,8 +62,8 @@ const frzGeoJsonEndpoint = `${pprApiBase}/drone-requests/frz`;
|
||||
</div>
|
||||
|
||||
<div class="drone-field">
|
||||
<label for="max-elevation">Maximum Elevation, feet AMSL <span aria-hidden="true">*</span></label>
|
||||
<input type="number" id="max-elevation" name="maximum_elevation_ft_amsl" min="0" step="1" inputmode="numeric" required />
|
||||
<label for="max-elevation">Max height above ground in feet <span aria-hidden="true">*</span></label>
|
||||
<input type="number" id="max-elevation" name="maximum_elevation_ft_agl" min="0" step="1" inputmode="numeric" required />
|
||||
</div>
|
||||
|
||||
<div class="drone-field drone-full">
|
||||
@@ -407,7 +407,7 @@ const frzGeoJsonEndpoint = `${pprApiBase}/drone-requests/frz`;
|
||||
if (!fieldValue) return;
|
||||
const normalizedValue = key === 'operator_id' || key === 'flyer_id' ? fieldValue.toUpperCase() : fieldValue;
|
||||
|
||||
if (key === 'maximum_elevation_ft_amsl') {
|
||||
if (key === 'maximum_elevation_ft_agl') {
|
||||
data[key] = Number.parseInt(normalizedValue, 10);
|
||||
} else if (key === 'location_latitude' || key === 'location_longitude') {
|
||||
data[key] = Number.parseFloat(normalizedValue);
|
||||
|
||||
@@ -65,7 +65,7 @@ function formatVatLabel(value: unknown): string {
|
||||
}
|
||||
|
||||
.fuel-service-notes {
|
||||
margin: 0 0 1.1rem;
|
||||
margin: 0 0 1.9rem;
|
||||
}
|
||||
|
||||
.fuel-service-notes p {
|
||||
@@ -77,7 +77,10 @@ function formatVatLabel(value: unknown): string {
|
||||
}
|
||||
|
||||
.fuel-contact-link {
|
||||
margin-top: 0.35rem;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin-top: 0.55rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.fuel-more-info {
|
||||
|
||||
@@ -26,7 +26,7 @@ const { news, title = 'Latest news', description = 'Fresh updates, operational c
|
||||
) : (
|
||||
<article class="card">
|
||||
<h3>No news items</h3>
|
||||
<p>News articles will be generated from Directus at build time.</p>
|
||||
<p>Stay tuned for updates as they are published!</p>
|
||||
</article>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
const webcamBase = (import.meta.env.PUBLIC_WEATHER_BASE ?? 'https://wx.swansea-airport.wales').replace(/\/$/, '');
|
||||
const webcamImage = `${webcamBase}/webcam/apron.jpg`;
|
||||
---
|
||||
|
||||
<section class="webcam-shell" aria-labelledby="webcam-heading">
|
||||
<div class="webcam-head">
|
||||
<div>
|
||||
<p class="eyebrow">Live apron webcam</p>
|
||||
<h1 id="webcam-heading" class="section-title">Swansea Airport apron</h1>
|
||||
<p class="section-copy">
|
||||
View from the tower facing south west.<br>The peak in the middle of the view is Cefn Bryn, 5500 metres distant.</p>
|
||||
</div>
|
||||
<div class="webcam-status" aria-live="polite">
|
||||
<span class="status-dot waiting" id="webcam-dot"></span>
|
||||
<span id="webcam-status">Loading</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<figure class="webcam-frame">
|
||||
<img
|
||||
id="webcam-image"
|
||||
src={webcamImage}
|
||||
alt="Live webcam view of Swansea Airport apron"
|
||||
decoding="async"
|
||||
fetchpriority="high"
|
||||
/>
|
||||
<figcaption>
|
||||
<span id="webcam-updated">Waiting for latest image</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<script define:vars={{ webcamImage }}>
|
||||
(() => {
|
||||
const REFRESH_MS = 30000;
|
||||
const image = document.getElementById('webcam-image');
|
||||
const status = document.getElementById('webcam-status');
|
||||
const dot = document.getElementById('webcam-dot');
|
||||
const updated = document.getElementById('webcam-updated');
|
||||
|
||||
function setStatus(text, className) {
|
||||
status.textContent = text;
|
||||
dot.className = `status-dot ${className}`;
|
||||
}
|
||||
|
||||
function refreshImage() {
|
||||
const next = new URL(webcamImage);
|
||||
next.searchParams.set('t', Date.now().toString());
|
||||
image.src = next.toString();
|
||||
}
|
||||
|
||||
image.addEventListener('load', () => {
|
||||
const time = new Intl.DateTimeFormat('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
}).format(new Date());
|
||||
|
||||
setStatus('Live', 'ok');
|
||||
updated.textContent = `Last refreshed ${time}`;
|
||||
});
|
||||
|
||||
image.addEventListener('error', () => {
|
||||
setStatus('Image unavailable', 'error');
|
||||
updated.textContent = 'The webcam image could not be loaded.';
|
||||
});
|
||||
|
||||
refreshImage();
|
||||
window.setInterval(refreshImage, REFRESH_MS);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.webcam-shell {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem 0 2.5rem;
|
||||
}
|
||||
|
||||
.webcam-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.webcam-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
flex: 0 0 auto;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
color: var(--muted);
|
||||
font-size: 0.84rem;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 10px 22px rgba(16, 34, 51, 0.06);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 0.62rem;
|
||||
height: 0.62rem;
|
||||
border-radius: 999px;
|
||||
background: var(--muted);
|
||||
box-shadow: 0 0 0 0.25rem rgba(81, 100, 117, 0.14);
|
||||
}
|
||||
|
||||
.status-dot.ok {
|
||||
background: #13834f;
|
||||
box-shadow: 0 0 0 0.25rem rgba(19, 131, 79, 0.15);
|
||||
}
|
||||
|
||||
.status-dot.waiting {
|
||||
background: var(--warning);
|
||||
box-shadow: 0 0 0 0.25rem rgba(187, 104, 0, 0.15);
|
||||
}
|
||||
|
||||
.status-dot.error {
|
||||
background: var(--critical);
|
||||
box-shadow: 0 0 0 0.25rem rgba(161, 31, 58, 0.14);
|
||||
}
|
||||
|
||||
.webcam-frame {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.webcam-frame img {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
background: rgba(16, 34, 51, 0.08);
|
||||
}
|
||||
|
||||
.webcam-frame figcaption {
|
||||
display: block;
|
||||
padding: 0.8rem 1rem;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.webcam-head {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.webcam-status {
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
fallbackDocuments,
|
||||
fallbackEvents,
|
||||
fallbackFuelPrices,
|
||||
fallbackGiftShopImages,
|
||||
fallbackHomepageBannerImages,
|
||||
fallbackHomepageVolunteerImages,
|
||||
fallbackNews,
|
||||
@@ -48,6 +49,7 @@ const directusDebug = ['1', 'true', 'yes', 'on'].includes((process.env.DIRECTUS_
|
||||
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 giftShopFolder = process.env.DIRECTUS_GIFT_SHOP_FOLDER ?? 'gift-shop';
|
||||
|
||||
type DirectusFolder = {
|
||||
id: string;
|
||||
@@ -300,6 +302,7 @@ export const getHomepageVolunteerImages = () =>
|
||||
shuffleRest: true,
|
||||
});
|
||||
export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages);
|
||||
export const getGiftShopImages = () => getImagesFromFolder(giftShopFolder, fallbackGiftShopImages);
|
||||
|
||||
function stripHtml(value = ''): string {
|
||||
return value
|
||||
|
||||
@@ -100,6 +100,13 @@ export const fallbackCafePageImages: HomepageBannerImage[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackGiftShopImages: HomepageBannerImage[] = [
|
||||
{
|
||||
src: '/images/camain.jpg',
|
||||
alt: 'Chocks Away Gift Shop at Swansea Airport',
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackHomepageVolunteerImages: HomepageBannerImage[] = [
|
||||
{
|
||||
src: '/images/cessna.jpg',
|
||||
|
||||
+3
-1
@@ -17,18 +17,20 @@ export const site = {
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Pilot Info', href: '/pilot-info/' },
|
||||
{ label: 'Weather', href: '/weather/' },
|
||||
{ label: 'Webcam', href: '/webcam/' },
|
||||
{
|
||||
label: 'About',
|
||||
href: '/about/',
|
||||
children: [
|
||||
{ label: 'Cafe', href: '/about/cafe/' },
|
||||
{ label: 'Gift Shop', href: '/about/gift-shop/' },
|
||||
{ label: 'History', href: '/about/history/' },
|
||||
{ label: 'Drones', href: '/about/drones/' },
|
||||
{ label: 'Fees and Charges', href: '/about/fees-and-charges/' },
|
||||
{ label: 'Noise', href: '/about/noise/' },
|
||||
{ label: 'Volunteering', href: '/about/volunteering/' },
|
||||
],
|
||||
},
|
||||
{ label: 'Drones', href: '/about/drones/' },
|
||||
{ label: 'Events', href: '/events/' },
|
||||
{ label: 'News', href: '/news/' },
|
||||
// Keep the documents page available, but hidden from the menu until needed.
|
||||
|
||||
@@ -21,11 +21,21 @@ const cafeImages = await getCafePageImages();
|
||||
Fairwood Common and the Gower, the cafe offers a comfortable place to pause and enjoy the
|
||||
airport atmosphere.
|
||||
</p>
|
||||
<div class="opening-times">
|
||||
<p>Open 7 days a week.</p>
|
||||
<div class="opening-list">
|
||||
<p><strong>Monday & Tuesday</strong><span>Snacks and drinks served 10am - 2pm</span></p>
|
||||
<p><strong>Wednesday - Sunday</strong><span>Open 9am - 3pm, full menu served 9.30am - 2.30pm</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
On Thursday to Sunday evenings, the cafe transforms into Thai Bach Express, serving Thai
|
||||
food for delivery and dining in.
|
||||
<a href="https://thaibach.co.uk/" target="_blank" rel="noopener noreferrer">Visit Thai Bach</a>.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<div class="social-follow">
|
||||
<div class="cta-row">
|
||||
<a class="button primary social-button" href="https://www.facebook.com/SwanseaAirportCafe" target="_blank" rel="noopener noreferrer">
|
||||
@@ -62,6 +72,40 @@ const cafeImages = await getCafePageImages();
|
||||
min-height: clamp(16rem, 42vw, 30rem);
|
||||
}
|
||||
|
||||
.opening-times {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
margin: 0.2rem 0;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.opening-times h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.opening-times p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.opening-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.opening-list p {
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.opening-list span {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.social-follow {
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
@@ -93,4 +137,5 @@ const cafeImages = await getCafePageImages();
|
||||
line-height: 1;
|
||||
transform: translateY(-0.02em);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -46,6 +46,11 @@ const additionalCharges = [
|
||||
['Runway closure', '£50', 'At management discretion following incident or accident.'],
|
||||
['Drones', '£25', 'Commercial drones need 2 days notice before flight and a permit.'],
|
||||
];
|
||||
|
||||
const gaHeaders = ['Type', 'Landing fee', 'Daytime parking', 'Overnight parking outside', 'Overnight parking hangar'];
|
||||
const touchAndGoHeaders = ['Type', 'Single', 'Unlimited'];
|
||||
const businessHeaders = ['MTOW', 'Landing fee', 'Daytime parking', 'Overnight parking'];
|
||||
const additionalHeaders = ['Charge', 'Price', 'Notes'];
|
||||
---
|
||||
|
||||
<BaseLayout title="Fees and Charges" description="Swansea Airport landing, parking, handling, and related charges.">
|
||||
@@ -60,17 +65,13 @@ const additionalCharges = [
|
||||
<table class="fee-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Landing fee</th>
|
||||
<th scope="col">Daytime parking</th>
|
||||
<th scope="col">Overnight parking outside</th>
|
||||
<th scope="col">Overnight parking hangar</th>
|
||||
{gaHeaders.map((header) => <th scope="col">{header}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{gaCharges.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell) => <td>{cell}</td>)}
|
||||
{row.map((cell, index) => <td data-label={gaHeaders[index]}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -84,15 +85,13 @@ const additionalCharges = [
|
||||
<table class="fee-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Single</th>
|
||||
<th scope="col">Unlimited</th>
|
||||
{touchAndGoHeaders.map((header) => <th scope="col">{header}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{touchAndGoCharges.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell) => <td>{cell}</td>)}
|
||||
{row.map((cell, index) => <td data-label={touchAndGoHeaders[index]}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -119,16 +118,13 @@ const additionalCharges = [
|
||||
<table class="fee-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">MTOW</th>
|
||||
<th scope="col">Landing fee</th>
|
||||
<th scope="col">Daytime parking</th>
|
||||
<th scope="col">Overnight parking</th>
|
||||
{businessHeaders.map((header) => <th scope="col">{header}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{businessCharges.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell) => <td>{cell}</td>)}
|
||||
{row.map((cell, index) => <td data-label={businessHeaders[index]}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -149,15 +145,13 @@ const additionalCharges = [
|
||||
<table class="fee-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Charge</th>
|
||||
<th scope="col">Price</th>
|
||||
<th scope="col">Notes</th>
|
||||
{additionalHeaders.map((header) => <th scope="col">{header}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{additionalCharges.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell) => <td>{cell}</td>)}
|
||||
{row.map((cell, index) => <td data-label={additionalHeaders[index]}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -236,4 +230,101 @@ const additionalCharges = [
|
||||
.fee-section ul {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.fees-page {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.fee-section {
|
||||
padding-block: 0.35rem;
|
||||
}
|
||||
|
||||
.fee-table-wrap {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.fee-table,
|
||||
.fee-table tbody,
|
||||
.fee-table tr,
|
||||
.fee-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fee-table {
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.fee-table-wrap.compact .fee-table {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fee-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fee-table tbody {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.fee-table tr {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 10px 22px rgba(16, 34, 51, 0.07);
|
||||
}
|
||||
|
||||
.fee-table tbody tr:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
|
||||
.fee-table td {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(8rem, 0.9fr) minmax(0, 1.1fr);
|
||||
gap: 0.75rem;
|
||||
padding: 0.68rem 0.85rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.fee-table td:first-child {
|
||||
display: block;
|
||||
padding: 0.85rem;
|
||||
background: linear-gradient(180deg, rgba(11, 79, 122, 0.12), rgba(29, 118, 184, 0.07));
|
||||
color: var(--text);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.fee-table td:first-child::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.fee-table td:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.fee-table td::before {
|
||||
content: attr(data-label);
|
||||
color: var(--brand);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.25;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.fee-table td {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
import BannerRotator from '../../components/BannerRotator.astro';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getGiftShopImages } from '../../lib/directus';
|
||||
|
||||
const giftShopImages = await getGiftShopImages();
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Gift Shop"
|
||||
description="Discover Chocks Away Gift Shop at Swansea Airport, with aviation memorabilia, RAF-themed gifts, Welsh souvenirs, toys, clothing, and accessories."
|
||||
>
|
||||
<section class="container prose gift-shop-page">
|
||||
<figure class="gift-shop-hero">
|
||||
<img src="/images/camain.jpg" alt="Chocks Away Gift Shop at Swansea Airport" />
|
||||
</figure>
|
||||
|
||||
<div class="gift-shop-copy">
|
||||
<h1 class="section-title">Gift Shop</h1>
|
||||
|
||||
<p>
|
||||
Discover Chocks Away Gift Shop at Swansea Airport, a small shop packed with unique gifts
|
||||
and aviation charm. Inspired by the airport's rich WWII heritage, you will find aviation
|
||||
memorabilia, RAF-themed gifts, Welsh souvenirs, children's toys, clothing, accessories,
|
||||
and much more.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
From Spitfires to dragons, cufflinks to caps, whether you are flying in, visiting the
|
||||
airport, or simply looking for the best gift or souvenir, there is something for every
|
||||
aviation lover.
|
||||
</p>
|
||||
|
||||
<div class="social-follow">
|
||||
<div class="cta-row">
|
||||
<a class="button primary social-button" href="https://www.facebook.com/profile.php?id=61590636748267" target="_blank" rel="noopener noreferrer">
|
||||
<span class="facebook-mark" aria-hidden="true">f</span>
|
||||
Chocks Away Gift Shop on Facebook
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gift-shop-gallery" aria-label="Gift shop photo gallery">
|
||||
<BannerRotator images={giftShopImages} />
|
||||
</div>
|
||||
<p class="gift-shop-disclaimer">
|
||||
Chocks Away Gift Shop is an independent business based at Swansea Airport and is not
|
||||
affiliated with airport management.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.gift-shop-page {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.gift-shop-hero {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
aspect-ratio: 21 / 9;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.gift-shop-hero img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
}
|
||||
|
||||
.gift-shop-copy {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.gift-shop-copy p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gift-shop-gallery {
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.gift-shop-gallery :global(.banner-rotator) {
|
||||
min-height: clamp(16rem, 42vw, 30rem);
|
||||
}
|
||||
|
||||
.gift-shop-disclaimer {
|
||||
margin-top: 0.35rem !important;
|
||||
padding-top: 0.85rem;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.social-follow {
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.social-button {
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.facebook-mark {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.45rem;
|
||||
height: 1.45rem;
|
||||
border-radius: 50%;
|
||||
background: #1877f2;
|
||||
color: white;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
transform: translateY(-0.02em);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.gift-shop-hero {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,10 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
<h3><a href="/about/cafe/">Cafe</a></h3>
|
||||
<p>A friendly, dog-friendly cafe for visitors, walkers, pilots, and local families.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3><a href="/about/gift-shop/">Gift Shop</a></h3>
|
||||
<p>Aviation gifts, RAF-themed keepsakes, Welsh souvenirs, toys, clothing, and accessories.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3><a href="/about/history/">History</a></h3>
|
||||
<p>The story of Swansea Airport, formerly RAF Fairwood Common.</p>
|
||||
|
||||
@@ -27,28 +27,28 @@ const businessPromos = [
|
||||
href: 'https://www.goskydive.com/dropzone/skydive-centre-swansea/',
|
||||
logo: '/images/goskydive.png',
|
||||
alt: 'Go Skydive logo',
|
||||
description: 'Tandem skydiving and experiences from Swansea.',
|
||||
description: 'Tandem skydiving from Swansea',
|
||||
},
|
||||
{
|
||||
name: 'Gower Flight Centre',
|
||||
href: 'https://www.gowerflightcentre.co.uk/',
|
||||
logo: '/images/gowerflightcentre.jpg',
|
||||
alt: 'Gower Flight Centre logo',
|
||||
description: 'Flying lessons, aircraft hire, and pilot training.',
|
||||
description: 'Air experiences, and pilot training',
|
||||
},
|
||||
{
|
||||
name: 'Fly A Spitfire',
|
||||
href: 'https://flyaspitfire.com/',
|
||||
logo: '/images/flyaspitfire.png',
|
||||
alt: 'Fly A Spitfire logo',
|
||||
description: 'Spitfire flight experiences and aviation events.',
|
||||
description: 'Spitfire flights',
|
||||
},
|
||||
{
|
||||
name: 'AeroSuperBatics',
|
||||
href: 'https://www.aerosuperbatics.com/',
|
||||
logo: '/images/aerosuperbatics.jpg',
|
||||
alt: 'AeroSuperBatics logo',
|
||||
description: 'Wingwalking and aerobatic entertainment flights.',
|
||||
description: 'Wingwalking and aerobatic entertainment flights',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getFuelPrices } from '../lib/directus';
|
||||
import { site } from '../lib/site';
|
||||
|
||||
const runwayPattern = /^Runway\s+(\d+)\s+-\s+([^\s]+)\s+LDA\s+\(([^)]+)\)\s+Code\s+(\d+)\s+\(([^)]+)\)$/;
|
||||
const runwayHeaders = ['Runway', 'LDA', 'Surface', 'Code', 'Circuits'];
|
||||
|
||||
const runwayRows = site.runwayFacts
|
||||
.map((fact) => {
|
||||
@@ -114,11 +115,11 @@ When the Tower is unavailable, this will be NOTAMed and blind calls to Swansea T
|
||||
<tbody>
|
||||
{runwayRows.map((runway) => (
|
||||
<tr>
|
||||
<td>{runway.runway}</td>
|
||||
<td>{runway.lda}</td>
|
||||
<td>{runway.surface}</td>
|
||||
<td>{runway.code}</td>
|
||||
<td>{runway.circuits}</td>
|
||||
<td data-label={runwayHeaders[0]}>{runway.runway}</td>
|
||||
<td data-label={runwayHeaders[1]}>{runway.lda}</td>
|
||||
<td data-label={runwayHeaders[2]}>{runway.surface}</td>
|
||||
<td data-label={runwayHeaders[3]}>{runway.code}</td>
|
||||
<td data-label={runwayHeaders[4]}>{runway.circuits}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import WebcamPanel from '../components/WebcamPanel.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Webcam" description="View from the tower facing south west. The peak in the middle of the view is Cefn Bryn, 5500 metres distant.">
|
||||
<section class="container">
|
||||
<WebcamPanel />
|
||||
</section>
|
||||
</BaseLayout>
|
||||
+92
-1
@@ -971,6 +971,97 @@ section {
|
||||
}
|
||||
|
||||
.runway-facts-table {
|
||||
min-width: 38rem;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.runway-table-wrap {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.runway-facts-table,
|
||||
.runway-facts-table tbody,
|
||||
.runway-facts-table tr,
|
||||
.runway-facts-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.runway-facts-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.runway-facts-table tbody {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.runway-facts-table tr {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 10px 22px rgba(16, 34, 51, 0.07);
|
||||
}
|
||||
|
||||
.runway-facts-table tbody tr:nth-child(even),
|
||||
.runway-facts-table tbody tr:hover {
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
|
||||
.runway-facts-table td {
|
||||
display: grid;
|
||||
gap: 0.18rem;
|
||||
min-height: 3.4rem;
|
||||
padding: 0.62rem 0.7rem;
|
||||
border: 1px solid rgba(16, 34, 51, 0.08);
|
||||
border-radius: 0.7rem;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.runway-facts-table td:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
grid-column: 1 / -1;
|
||||
min-height: 0;
|
||||
padding: 0.85rem;
|
||||
border: 0;
|
||||
background: linear-gradient(180deg, rgba(11, 79, 122, 0.12), rgba(29, 118, 184, 0.07));
|
||||
color: var(--text);
|
||||
font-size: 1.08rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.runway-facts-table td:first-child::before {
|
||||
content: 'Runway';
|
||||
color: var(--brand);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.runway-facts-table td:last-child {
|
||||
border-bottom: 1px solid rgba(16, 34, 51, 0.08);
|
||||
}
|
||||
|
||||
.runway-facts-table td::before {
|
||||
content: attr(data-label);
|
||||
color: var(--brand);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user