Tidy up metadata, headings, forms, and small accessibility fixes
Build and Deploy to Web Server / deploy (push) Successful in 4m34s

This commit is contained in:
encie22 committed 2026-10-15 10:55:24 +10:30
1 parent ff54fe8848
commit 1cf8ddc940
18 files changed
+107 -59

No files matched your search

+38 -16
View File
@@ -1,7 +1,11 @@
--- ---
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
const ID = `contact-form-${Math.random().toString(36)}`; interface Props {
id?: string;
}
const { id: ID = "contact-form" } = Astro.props;
--- ---
<form <form
@@ -9,60 +13,78 @@ const ID = `contact-form-${Math.random().toString(36)}`;
id={ID} id={ID}
> >
<div> <div>
<label for="name" class="block font-bold">Name</label> <label for={`${ID}-name`} class="block font-bold">Name</label>
<input <input
type="text" type="text"
id="name" id={`${ID}-name`}
name="name" name="name"
required required
autocomplete="name"
maxlength="100"
class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600" class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600"
/> />
</div> </div>
<div> <div>
<label for="email" class="block font-bold">Your email</label> <label for={`${ID}-email`} class="block font-bold">Your email</label>
<input <input
type="email" type="email"
id="email" id={`${ID}-email`}
name="email" name="email"
required required
autocomplete="email"
maxlength="254"
class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600" class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600"
/> />
</div> </div>
<div> <div>
<label for="subject" class="block font-bold">Subject</label> <label for={`${ID}-subject`} class="block font-bold">Subject</label>
<input <input
type="text" type="text"
id="subject" id={`${ID}-subject`}
name="subject" name="subject"
required
maxlength="150"
class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600" class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600"
/> />
</div> </div>
<div> <div>
<label for="message" class="block font-bold">Message</label> <label for={`${ID}-message`} class="block font-bold">Message</label>
<textarea <textarea
id="message" id={`${ID}-message`}
name="message" name="message"
rows="4" rows="4"
required
maxlength="5000"
class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600"></textarea> class="mt-2 block w-full rounded p-2 shadow dark:bg-gray-600"></textarea>
</div> </div>
<button <button
id="submit" id={`${ID}-submit`}
type="submit" type="submit"
class="bg-primary text-md font-header hover:text-primary repeat hover:ring-primary mx-auto inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow transition hover:bg-white hover:ring-2" class="bg-primary text-md font-header hover:text-primary hover:ring-primary mx-auto inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow transition hover:bg-white hover:ring-2"
><Icon ><Icon
name="mdi:loading" name="mdi:loading"
id="loading-icon" id={`${ID}-loading-icon`}
class="mr-4 hidden animate-spin" class="mr-4 hidden animate-spin"
/><span id="response-message" class="inline">Send message</span></button /><span
id={`${ID}-response-message`}
class="inline"
role="status"
aria-live="polite">Send message</span
></button
> >
</form> </form>
<script define:vars={{ ID }}> <script define:vars={{ ID }}>
function init() { function init() {
const form = document.getElementById(ID); const form = document.getElementById(ID);
const responseMsg = form.querySelector("#response-message"); if (!form) return;
const button = form.querySelector("#submit"); const responseMsg = form.querySelector(
const loadingIcon = form.querySelector("#loading-icon"); `#${CSS.escape(ID + "-response-message")}`
);
const button = form.querySelector(`#${CSS.escape(ID + "-submit")}`);
const loadingIcon = form.querySelector(
`#${CSS.escape(ID + "-loading-icon")}`
);
form.addEventListener("submit", async function (event) { form.addEventListener("submit", async function (event) {
event.preventDefault(); event.preventDefault();
+5 -2
View File
@@ -16,16 +16,19 @@ const imagesArray = await Promise.all(
<div class="absolute -z-50 h-full w-full bg-black"></div> <div class="absolute -z-50 h-full w-full bg-black"></div>
<img <img
src={imagesArray[0].default.src} src={imagesArray[0].default.src}
alt=""
class:list={[ class:list={[
"object-no-repeat absolute -z-50 h-full w-full object-cover object-center transition duration-2000", "absolute -z-50 h-full w-full object-cover object-center transition duration-2000",
transitionStyle transitionStyle
]} ]}
id="hero-image-1" id="hero-image-1"
/> />
<img <img
src={imagesArray[1].default.src} src={imagesArray[1].default.src}
alt=""
aria-hidden="true"
class:list={[ class:list={[
"object-no-repeat absolute -z-50 h-full w-full object-cover object-center transition duration-2000", "absolute -z-50 h-full w-full object-cover object-center transition duration-2000",
transitionStyle transitionStyle
]} ]}
id="hero-image-2" id="hero-image-2"
+19 -5
View File
@@ -62,7 +62,13 @@ const { navbarDisplay = "normal" } = Astro.props as Props;
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
<div class="md:hidden"> <div class="md:hidden">
<button id="menu-toggle" class="text-2xl focus:outline-none"> <button
id="menu-toggle"
class="text-2xl focus:outline-none"
aria-label="Toggle navigation menu"
aria-expanded="false"
aria-controls="mobile-menu"
>
☰ ☰
</button> </button>
</div> </div>
@@ -100,10 +106,7 @@ const { navbarDisplay = "normal" } = Astro.props as Props;
<!-- Extra spacing for non-transparent navbar --> <!-- Extra spacing for non-transparent navbar -->
<div <div
class:list={[ class:list={["h-12", navbarDisplay === "transparent" ? "hidden" : "block"]}
"y-0 r-0 h-12",
navbarDisplay === "transparent" ? "hidden" : "block"
]}
> >
</div> </div>
@@ -141,12 +144,23 @@ const { navbarDisplay = "normal" } = Astro.props as Props;
if (nav) { if (nav) {
const visible = nav.getAttribute("data-mobile-menu") === "visible"; const visible = nav.getAttribute("data-mobile-menu") === "visible";
nav.setAttribute("data-mobile-menu", visible ? "hidden" : "visible"); nav.setAttribute("data-mobile-menu", visible ? "hidden" : "visible");
toggle.setAttribute("aria-expanded", visible ? "false" : "true");
} }
checkScroll(); checkScroll();
}); });
} }
document.addEventListener("keydown", (e) => {
if (
e.key === "Escape" &&
nav?.getAttribute("data-mobile-menu") === "visible"
) {
nav.setAttribute("data-mobile-menu", "hidden");
toggle?.setAttribute("aria-expanded", "false");
}
});
checkScroll(); checkScroll();
}); });
+6 -2
View File
@@ -399,7 +399,10 @@ const playerArtworkOpacity = 0.4;
this.UI.progress.bar.addEventListener("click", function (event) { this.UI.progress.bar.addEventListener("click", function (event) {
event.stopPropagation(); event.stopPropagation();
window.player.seek(event.clientX / window.innerWidth); const rect = this.getBoundingClientRect();
const per =
rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0;
window.player.seek(Math.min(Math.max(per, 0), 1));
}); });
// Tap the circle to expand (not the play button). // Tap the circle to expand (not the play button).
@@ -578,8 +581,9 @@ const playerArtworkOpacity = 0.4;
*/ */
pause() { pause() {
// Get the Howl we want to manipulate. // Get the Howl we want to manipulate.
var sound = this.playlist[this.index].howl; var sound = this.playlist[this.index]?.howl;
if (!sound) return;
sound.pause(); sound.pause();
this.syncPlayPauseUI(false); this.syncPlayPauseUI(false);
+11 -8
View File
@@ -16,7 +16,7 @@ import { Schema } from "astro-seo-schema";
<!-- SEO --> <!-- SEO -->
<link rel="canonical" href={url} /> <link rel="canonical" href={url} />
<meta name="robots" content="all" /> <meta name="robots" content="index, follow" />
<Schema <Schema
item={{ item={{
"@context": "https://schema.org", "@context": "https://schema.org",
@@ -86,8 +86,11 @@ import { Schema } from "astro-seo-schema";
/> />
<meta property="og:image:alt" content={image?.alt || site.image.alt} /> <meta property="og:image:alt" content={image?.alt || site.image.alt} />
<meta property="og:image:type" content="image/jpeg" /> <meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:width" content={image?.width.toString() || "1920"} /> <meta property="og:image:width" content={image?.width?.toString() || "1920"} />
<meta property="og:image:height" content={image?.height.toString() || "1080"} /> <meta
property="og:image:height"
content={image?.height?.toString() || "1080"}
/>
<!-- Twitter --> <!-- Twitter -->
<meta name="twitter:site" content="@nathancummins.au" /> <meta name="twitter:site" content="@nathancummins.au" />
@@ -100,8 +103,8 @@ import { Schema } from "astro-seo-schema";
? image.externalURL ? image.externalURL
: site.image.externalURL} : site.image.externalURL}
/> />
<meta name="twitter:image:alt" content={title} /> <meta name="twitter:image:alt" content={image?.alt || site.image.alt} />
<meta name="twitter:domain" content={import.meta.env.SITE} /> <meta
name="twitter:domain"
<!-- Theming --> content={import.meta.env.SITE ?? "www.nathancummins.com.au"}
<meta name="theme-color" content="#ff2d00" /> />
+1 -1
View File
@@ -7,7 +7,7 @@ interface Props {
} }
const { const {
Tag = "h1", Tag = "h2",
lineColour = "border-primary", lineColour = "border-primary",
lineColourDark = "dark:text-primary", lineColourDark = "dark:text-primary",
class: className class: className
+2 -2
View File
@@ -26,13 +26,13 @@ const linkIsExternal: boolean =
target={href?.toString().startsWith("http") ? "_blank" : undefined} target={href?.toString().startsWith("http") ? "_blank" : undefined}
rel={linkIsExternal ? "noopener noreferrer" : undefined} rel={linkIsExternal ? "noopener noreferrer" : undefined}
{...attrs} {...attrs}
><span class="group-hover:text-primary-100"><slot /></span>{ ><span class="group-hover:text-primary-600"><slot /></span>{
includeExternalLinkIcon && linkIsExternal && ( includeExternalLinkIcon && linkIsExternal && (
<sup class="ml-0.5 inline-block"> <sup class="ml-0.5 inline-block">
<Icon <Icon
name="mdi:external-link" name="mdi:external-link"
class:list={[ class:list={[
"text-primary group-hover:text-primary-100 m-0 inline-block" "text-primary group-hover:text-primary-600 m-0 inline-block"
]} ]}
/> />
</sup> </sup>
+5 -3
View File
@@ -2,9 +2,11 @@
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
--- ---
<span <button
class="theme:switcher flex justify-center text-center align-middle text-2xl" type="button"
class="theme:switcher mx-auto flex w-full justify-center text-center align-middle text-2xl"
onclick="toggleTheme()" onclick="toggleTheme()"
aria-label="Toggle colour theme"
> >
<span class="theme:light-mode"> <span class="theme:light-mode">
<Icon name="fa7-solid:sun" /> <Icon name="fa7-solid:sun" />
@@ -12,7 +14,7 @@ import { Icon } from "astro-icon/components";
<span class="theme:dark-mode"> <span class="theme:dark-mode">
<Icon name="fa7-solid:moon" /> <Icon name="fa7-solid:moon" />
</span> </span>
</span> </button>
<script is:inline> <script is:inline>
function updateThemeIconAndAriaLabel( function updateThemeIconAndAriaLabel(
+1 -1
View File
@@ -8,6 +8,6 @@ export default {
image: { image: {
externalURL: `${import.meta.env.SITE}/nathan-cummins-composer-orchestrator-conductor.jpg`, externalURL: `${import.meta.env.SITE}/nathan-cummins-composer-orchestrator-conductor.jpg`,
src: "nathan-cummins-composer-orchestrator-conductor.jpg", src: "nathan-cummins-composer-orchestrator-conductor.jpg",
alt: "Nathan Cummins" alt: "Picture of Nathan Cummins, Adelaide composer, orchestrator, and conductor"
} as SiteImage } as SiteImage
}; };
+1
View File
@@ -14,6 +14,7 @@ interface Window {
} }
interface ImportMetaEnv { interface ImportMetaEnv {
readonly SITE?: string;
readonly EMAIL_USER: string; readonly EMAIL_USER: string;
readonly EMAIL_PASS: string; readonly EMAIL_PASS: string;
readonly EMAIL_PORT: number; readonly EMAIL_PORT: number;
+5 -4
View File
@@ -17,7 +17,7 @@ const allProjectHeros = import.meta.glob<{ default: ImageMetadata }>(
); );
const allImages = import.meta.glob<{ default: ImageMetadata }>( const allImages = import.meta.glob<{ default: ImageMetadata }>(
"/**/*.{jpeg,jpg,png,gif}", "/src/assets/**/*.{jpeg,jpg,png,gif}",
{ {
eager: true eager: true
} }
@@ -82,11 +82,12 @@ export function getAllProjectImages(project: Project): ImageMetadata[] {
} }
export function shuffleArray<T>(array: Array<T>): Array<T> { export function shuffleArray<T>(array: Array<T>): Array<T> {
for (let i = array.length - 1; i > 0; i--) { const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; [copy[i], copy[j]] = [copy[j], copy[i]];
} }
return array; return copy;
} }
export async function convertEagerImagesImportGlobToArray( export async function convertEagerImagesImportGlobToArray(
+5 -6
View File
@@ -17,27 +17,26 @@ const heroImagesArray = await convertEagerImagesImportGlobToArray(heroImages);
images={heroImagesArray} images={heroImagesArray}
class="h-full w-full" class="h-full w-full"
foreground={true} foreground={true}
foreground={true}
foregroundOpacity="opacity-80" foregroundOpacity="opacity-80"
/> />
</div> </div>
<div <div
class="absolute inset-0 flex h-full w-full flex-col justify-center px-8 py-36 text-center" class="absolute inset-0 flex h-full w-full flex-col justify-center px-8 py-36 text-center"
> >
<h1 <p
class="font-header pb-8 text-5xl font-medium text-white uppercase text-shadow-lg/75 md:text-7xl lg:text-9xl" class="font-header pb-8 text-5xl font-medium text-white uppercase text-shadow-lg/75 md:text-7xl lg:text-9xl"
> >
Oops! Oops!
</h1> </p>
<h2 <h1
class="font-header text-2xl font-medium text-gray-300 uppercase text-shadow-lg/75 md:text-2xl lg:text-4xl" class="font-header text-2xl font-medium text-gray-300 uppercase text-shadow-lg/75 md:text-2xl lg:text-4xl"
> >
404 - Page not found 404 - Page not found
</h2> </h1>
<a <a
href="/" href="/"
class="bg-primary text-md font-header hover:text-primary repeat hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2" class="bg-primary text-md font-header hover:text-primary hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2"
> >
Home Home
</a> </a>
+1 -1
View File
@@ -22,7 +22,7 @@ import { Image } from "astro:assets";
class="mx-auto mb-8 size-125 rounded-full object-cover shadow-lg/75 transition ease-in-out md:hover:scale-150" class="mx-auto mb-8 size-125 rounded-full object-cover shadow-lg/75 transition ease-in-out md:hover:scale-150"
height={750} height={750}
/> />
<SectionTitle class="py-8">Dr Nathan Cummins</SectionTitle> <SectionTitle Tag="h1" class="py-8">Dr Nathan Cummins</SectionTitle>
<H2>{site.tagline}</H2> <H2>{site.tagline}</H2>
<Paragraph <Paragraph
>Nathan Cummins is a composer, orchestrator, conductor, and sound >Nathan Cummins is a composer, orchestrator, conductor, and sound
+1 -1
View File
@@ -11,7 +11,7 @@ import { Icon } from "astro-icon/components";
<div class="w-full"> <div class="w-full">
<section id="contact" class="bg-primary dark:bg-gray-800"> <section id="contact" class="bg-primary dark:bg-gray-800">
<div class="mx-auto max-w-4xl px-8 py-16 text-center"> <div class="mx-auto max-w-4xl px-8 py-16 text-center">
<SectionTitle class="text-white" lineColour="text-white" <SectionTitle Tag="h1" class="text-white" lineColour="text-white"
>Let's get in touch!</SectionTitle >Let's get in touch!</SectionTitle
> >
<ContactForm /> <ContactForm />
+4 -4
View File
@@ -78,7 +78,7 @@ const tracks = (
<a <a
href="/contact/" href="/contact/"
class="bg-primary text-md font-header hover:text-primary repeat hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2" class="bg-primary text-md font-header hover:text-primary hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2"
> >
Get in touch! Get in touch!
</a> </a>
@@ -98,7 +98,7 @@ const tracks = (
<span class="block w-full text-center" <span class="block w-full text-center"
><a ><a
href="/about/" href="/about/"
class="bg-primary text-md font-header hover:text-primary repeat hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2" class="bg-primary text-md font-header hover:text-primary hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2"
> >
Find out more Find out more
</a></span </a></span
@@ -121,7 +121,7 @@ const tracks = (
><div class="flex h-full w-full items-center"> ><div class="flex h-full w-full items-center">
<a <a
href="/contact/" href="/contact/"
class="text-md font-header repeat text-primary hover:bg-primary mx-auto inline-block rounded bg-white px-6 py-3 font-light uppercase drop-shadow-lg/75 transition hover:text-white hover:ring-2 hover:ring-white" class="text-md font-header text-primary hover:bg-primary mx-auto inline-block rounded bg-white px-6 py-3 font-light uppercase drop-shadow-lg/75 transition hover:text-white hover:ring-2 hover:ring-white"
> >
Get in touch! Get in touch!
</a> </a>
@@ -150,7 +150,7 @@ const tracks = (
</div> </div>
<a <a
href="/projects/" href="/projects/"
class="bg-primary text-md font-header hover:text-primary repeat hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2" class="bg-primary text-md font-header hover:text-primary hover:ring-primary mx-auto mt-8 inline-block rounded px-6 py-3 font-light text-white uppercase drop-shadow-lg/75 transition hover:bg-white hover:ring-2"
> >
Explore more projects Explore more projects
</a> </a>
+1 -1
View File
@@ -20,7 +20,7 @@ const types = [...new Set(projects.map((project) => project.data.type))];
<div class="w-full"> <div class="w-full">
<section id="projects" class="bg-white dark:bg-gray-950"> <section id="projects" class="bg-white dark:bg-gray-950">
<div class="mx-auto max-w-4xl px-8 py-16 text-center"> <div class="mx-auto max-w-4xl px-8 py-16 text-center">
<SectionTitle>Recent Projects</SectionTitle> <SectionTitle Tag="h1">Recent Projects</SectionTitle>
<div id="filter-tokens" class="my-8 space-y-2 space-x-2"> <div id="filter-tokens" class="my-8 space-y-2 space-x-2">
<Token <Token
class="filter-token !text-primary ring-primary bg-white ring-2 hover:cursor-pointer" class="filter-token !text-primary ring-primary bg-white ring-2 hover:cursor-pointer"
+1 -1
View File
@@ -11,7 +11,7 @@ import MainLayout from "@layouts/MainLayout.astro";
<div class="w-full bg-white py-16 dark:bg-gray-950"> <div class="w-full bg-white py-16 dark:bg-gray-950">
<section id="intro"> <section id="intro">
<div class="mx-auto max-w-4xl px-8 text-left"> <div class="mx-auto max-w-4xl px-8 text-left">
<SectionTitle class="pb-8">Sheet Music</SectionTitle> <SectionTitle Tag="h1" class="pb-8">Sheet Music</SectionTitle>
<Paragraph <Paragraph
>Sheet music for many of my pieces and orchestrations is available for >Sheet music for many of my pieces and orchestrations is available for
purchase or rent. If you are interested in sheet music for performance purchase or rent. If you are interested in sheet music for performance
-1
View File
@@ -1,5 +1,4 @@
@import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap");
@import "../../public/fonts/inter/inter.css"; @import "../../public/fonts/inter/inter.css";
@import "tailwindcss"; @import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));