feat(page): match live contact page layout

This commit is contained in:
2026-07-02 15:13:52 -07:00
parent 8c9c581cc0
commit 77b83f5d21
7 changed files with 230 additions and 16 deletions
+15 -1
View File
@@ -44,6 +44,9 @@ Implemented fidelity work includes:
- Restored the consultation form section's injected background image. - Restored the consultation form section's injected background image.
- Added a dedicated live-derived team card grid rather than presenting the - Added a dedicated live-derived team card grid rather than presenting the
extracted team content as a generic article. extracted team content as a generic article.
- Rebuilt the contact page around the live two-column content/form section and
full-width Leaflet map while retaining local clickable obfuscated phone
links.
- Rebuilt library indexes and blog-post presentation around the live sidebar, - Rebuilt library indexes and blog-post presentation around the live sidebar,
article list, byline, featured-image, and counter patterns. article list, byline, featured-image, and counter patterns.
- Converted FAQ-bearing posts to MDX and recreated their live-style accordions - Converted FAQ-bearing posts to MDX and recreated their live-style accordions
@@ -77,6 +80,12 @@ backend and spam-protection TODO comments. Submission is intentionally
disabled. Appointment and enrollment calls to action route to the static disabled. Appointment and enrollment calls to action route to the static
consultation page rather than retaining the production Jotform backend. consultation page rather than retaining the production Jotform backend.
## External Dependencies
- The contact page map intentionally matches the live Leaflet/OpenStreetMap
implementation and loads Leaflet from `unpkg.com` plus map tiles from
`tile.openstreetmap.org`.
## Multilingual ## Multilingual
- Spanish has the full live-derived homepage, translated service pages, - Spanish has the full live-derived homepage, translated service pages,
@@ -128,10 +137,15 @@ unavailable.
- `npm run generate:sitemap`: passed; generated 97 URLs. - `npm run generate:sitemap`: passed; generated 97 URLs.
- `npm run generate:redirects`: passed. - `npm run generate:redirects`: passed.
- All migration `.mjs` tools and the sandbox DNS helper pass `node --check`. - All migration `.mjs` tools and the sandbox DNS helper pass `node --check`.
- The Astro compiler parsed all 33 `.astro` files successfully. - The Astro compiler parsed all 49 `.astro` files successfully.
- `npm run build`: blocked before compilation because this autonomous sandbox - `npm run build`: blocked before compilation because this autonomous sandbox
cannot resolve `localhost`, causing `getaddrinfo EAI_AGAIN localhost`. cannot resolve `localhost`, causing `getaddrinfo EAI_AGAIN localhost`.
- `npm run build:sandbox`: passed; generated 97 static pages. - `npm run build:sandbox`: passed; generated 97 static pages.
- Live contact-page structure was inspected in browser at a 1280px desktop
viewport; local generated HTML was checked for the contact layout, Leaflet
assets, map container, and obfuscated clickable phone script. Local browser
rendering was blocked because the Playwright browser cannot reach shell
loopback servers and blocks `file:` URLs in this environment.
- Nix shell verification and `npm audit` retrieval were blocked by sandbox - Nix shell verification and `npm audit` retrieval were blocked by sandbox
proxy/cache network resets. proxy/cache network resets.
+84
View File
@@ -0,0 +1,84 @@
---
import Button from './Button.astro';
import EmailObfuscation from './EmailObfuscation.astro';
interface Props {
title?: string;
}
const { title = 'Contact AIA' } = Astro.props;
---
<form class="contact-form-card" method="post" action="">
<!-- TODO: Wire this form to Netlify Forms, a custom API endpoint, or another backend service. -->
<!-- TODO: Add spam protection before production launch. -->
<h3 class="contact-form-card__title">{title}</h3>
<div class="contact-form-card__fields">
<label>Parent or guardian name<input name="name" autocomplete="name" /></label>
<label>Email<input type="email" name="email" autocomplete="email" /></label>
<label>Phone<input type="tel" name="phone" autocomplete="tel" /></label>
<label>Service of interest<select name="service"><option>ABA Therapy</option><option>Autism Evaluation</option><option>Learner Social Club</option><option>Other</option></select></label>
<label>How can we help?<textarea name="message" rows="5"></textarea></label>
<Button type="submit" disabled aria-describedby="contact-form-note" class="contact-form-card__submit">Submit</Button>
<p id="contact-form-note" class="contact-form-card__note">Online submission is not yet connected. Please call <EmailObfuscation phone="(480) 687-7099" hrefValue="+14806877099" obfuscatedText="9907-786 (084)" />.</p>
</div>
</form>
<style>
.contact-form-card {
background: white;
border-radius: 4px;
box-shadow: 0 4px 4px 0 rgb(81 81 81 / 4%), 0 4px 16px 0 rgb(81 81 81 / 8%);
margin: 0 auto;
max-width: 600px;
overflow: hidden;
}
.contact-form-card__title {
border-bottom: 1px solid #f1f1f1;
margin: 0;
padding: 1.2rem 1.5rem;
text-align: center;
}
.contact-form-card__fields {
display: grid;
gap: 1rem;
padding: 1.5rem;
}
.contact-form-card label {
display: grid;
font-weight: 700;
gap: .4rem;
}
.contact-form-card input,
.contact-form-card select,
.contact-form-card textarea {
background: #fcf9f5;
border: 1px solid var(--color-border);
border-radius: 4px;
color: var(--color-primary);
font: inherit;
padding: .8rem;
width: 100%;
}
.contact-form-card textarea {
resize: vertical;
}
.contact-form-card :global(.contact-form-card__submit) {
justify-self: center;
max-width: 375px;
width: 100%;
}
.contact-form-card__note {
color: var(--color-muted);
font-size: .9rem;
margin: 0;
text-align: center;
}
</style>
+111
View File
@@ -0,0 +1,111 @@
---
import ContactForm from '../ContactForm.astro';
interface Props {
title: string;
}
const { title } = Astro.props;
---
<section class="contact-page">
<div class="container contact-page__grid">
<div class="contact-page__copy">
<slot />
</div>
<ContactForm title={title} />
</div>
</section>
<section class="contact-map-section" aria-label="Map showing Arizona Institute for Autism in Scottsdale">
<div id="aia-contact-map" class="contact-map"></div>
</section>
<script is:inline src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script is:inline>
(() => {
const mapElement = document.getElementById('aia-contact-map');
if (!mapElement || !window.L) return;
const map = window.L.map(mapElement).setView([33.63, -111.88756], 12);
const tiles = window.L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19
});
tiles.on('tileload', (event) => {
if (event.tile && !event.tile.alt) {
event.tile.alt = 'OpenStreetMap tile showing area around Arizona Institute for Autism in Scottsdale, AZ';
}
});
tiles.addTo(map);
window.L.marker([33.617746, -111.88756]).addTo(map).bindPopup(`
<span style="display:block;text-align:center">
<b>Arizona Institute for Autism</b><br>
<img alt="Arizona Institute for Autism" src="/assets/images/aia-logo.svg" width="94">
</span>
`).openPopup();
})();
</script>
<style>
.contact-page {
background: white;
padding-block: 100px;
}
.contact-page__grid {
align-items: start;
display: grid;
gap: clamp(2rem, 5vw, 4rem);
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.contact-page__copy {
color: var(--color-primary);
text-align: left;
}
.contact-page__copy :global(h2) {
margin: 0 0 25px;
}
.contact-page__copy :global(p) {
margin: 0 0 25px;
}
.contact-page__copy :global(.content-button) {
margin-bottom: 40px;
}
.contact-map-section {
background: white;
margin: 0;
padding: 0;
}
.contact-map {
height: 360px;
width: 100%;
}
@media (max-width: 992px) {
.contact-page {
padding-block: 50px;
}
}
@media (max-width: 767px) {
.contact-page__grid {
grid-template-columns: 1fr;
}
.contact-page__copy :global(.content-button) {
display: table;
margin-inline: auto;
}
}
</style>
+7 -6
View File
@@ -8,23 +8,24 @@ translationKey: "contact"
draft: false draft: false
--- ---
import EmailObfuscation from '../../../components/EmailObfuscation.astro'; import EmailObfuscation from '../../../components/EmailObfuscation.astro';
import Button from '../../../components/Button.astro';
## Schedule a Tour ## Schedule a Tour
Reach out to one of our expert Client Advocates to see if ABA-integrated therapy is right for your learner. Reach out to one of our expert Client Advocates to see if ABA-integrated therapy is right for your learner.
Schedule Tour <Button href="/tour">Schedule Tour</Button>
## Reach Out ## Reach Out
If you have questions about any of our services and programs, feel free to use the contact form on this page. You may also call or email using the following information. If you have questions about any of our services and programs, feel free to use the contact form on this page. You may also call or email using the following information.
General Inquiries: <EmailObfuscation phone="(480) 687-7099" hrefValue="+14806877099" obfuscatedText="9907-786 (084)" /> **General Inquiries:** <EmailObfuscation phone="(480) 687-7099" hrefValue="+14806877099" obfuscatedText="9907-786 (084)" />
Billing Inquiries: <EmailObfuscation phone="(206) 807-4924" hrefValue="+12068074924" obfuscatedText="9240-807 (206)" /> **Billing Inquiries:** <EmailObfuscation phone="(602) 708-0429" hrefValue="+16027080429" obfuscatedText="9240-807 (206)" />
Email: <EmailObfuscation email="info@azinstitute4autism.com" obfuscatedText="moc.msitua4etutitsniza@ofni" /> **Email:** <EmailObfuscation email="info@azinstitute4autism.com" obfuscatedText="moc.msitua4etutitsniza@ofni" />
Address: 8901 E. Raintree Drive, St #160 Scottsdale, AZ 85260 **Address:** 8901 E. Raintree Drive, St #160 Scottsdale, AZ 85260
Hours: Monday - Friday, 8am - 6pm MST **Hours:** Monday - Friday, 8am - 6pm MST
+7 -8
View File
@@ -8,25 +8,24 @@ translationKey: "contact"
draft: false draft: false
--- ---
import EmailObfuscation from '../../../components/EmailObfuscation.astro'; import EmailObfuscation from '../../../components/EmailObfuscation.astro';
import Button from '../../../components/Button.astro';
## Programar una visita ## Programar una visita
Comuníquese con uno de nuestros defensores de clientes expertos para ver si la terapia integrada con ABA es adecuada para su alumno. Comuníquese con uno de nuestros defensores de clientes expertos para ver si la terapia integrada con ABA es adecuada para su alumno.
Programar visita <Button href="/tour">Programar visita</Button>
## Comunícate ## Comunícate
Si tiene preguntas sobre alguno de nuestros servicios y programas, no dude en utilizar el formulario de contacto de esta página. También puede llamar o enviar un correo electrónico utilizando la siguiente información. Si tiene preguntas sobre alguno de nuestros servicios y programas, no dude en utilizar el formulario de contacto de esta página. También puede llamar o enviar un correo electrónico utilizando la siguiente información.
Consultas generales: <EmailObfuscation phone="(480) 687-7099" hrefValue="+14806877099" obfuscatedText="9907-786 (084)" /> **Consultas generales:** <EmailObfuscation phone="(480) 687-7099" hrefValue="+14806877099" obfuscatedText="9907-786 (084)" />
Consultas de facturación: <EmailObfuscation phone="(206) 807-4924" hrefValue="+12068074924" obfuscatedText="9240-807 (206)" /> **Consultas de facturación:** <EmailObfuscation phone="(602) 708-0429" hrefValue="+16027080429" obfuscatedText="9240-807 (206)" />
Correo electrónico: <EmailObfuscation email="info@azinstitute4autism.com" obfuscatedText="moc.msitua4etutitsniza@ofni" /> **Correo electrónico:** <EmailObfuscation email="info@azinstitute4autism.com" obfuscatedText="moc.msitua4etutitsniza@ofni" />
Dirección: 8901 E. Raintree Drive, St #160 Scottsdale, AZ 85260 **Dirección:** 8901 E. Raintree Drive, St #160 Scottsdale, AZ 85260
Horas: Lunes - Viernes, 8am - 6pm MST **Horas:** Lunes - Viernes, 8am - 6pm MST
### Contactar a AIA
+1
View File
@@ -31,6 +31,7 @@ const organizationSchema = {
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" href="/assets/images/favicon.svg" /> <link rel="icon" href="/assets/images/favicon.svg" />
<Seo title={title} description={description} canonical={canonical} image={image} lang={lang} type={type} /> <Seo title={title} description={description} canonical={canonical} image={image} lang={lang} type={type} />
<slot name="head" />
<script type="application/ld+json" set:html={JSON.stringify(organizationSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(organizationSchema)} />
</head> </head>
<body> <body>
+5 -1
View File
@@ -5,11 +5,12 @@ import TeamPage from '../components/pages/TeamPage.astro';
import AboutPage from '../components/pages/AboutPage.astro'; import AboutPage from '../components/pages/AboutPage.astro';
import ServicesPage from '../components/pages/ServicesPage.astro'; import ServicesPage from '../components/pages/ServicesPage.astro';
import CareersPage from '../components/pages/CareersPage.astro'; import CareersPage from '../components/pages/CareersPage.astro';
import ContactPage from '../components/pages/ContactPage.astro';
import FaqPage from '../components/pages/FaqPage.astro'; import FaqPage from '../components/pages/FaqPage.astro';
import PageHero from '../components/PageHero.astro'; import PageHero from '../components/PageHero.astro';
import { pageHeroImages } from '../data/page-visuals'; import { pageHeroImages } from '../data/page-visuals';
const { entry } = Astro.props; const { entry } = Astro.props;
const showForm = ['client-consultation', 'contact', 'schedule-consultation', 'referrals'].includes(entry.data.slug); const showForm = ['client-consultation', 'schedule-consultation', 'referrals'].includes(entry.data.slug);
const serviceTitles: Record<string, Record<string, string>> = { const serviceTitles: Record<string, Record<string, string>> = {
en: { en: {
'aba-therapy': 'Behavioral', 'aba-therapy': 'Behavioral',
@@ -53,6 +54,7 @@ const isService = Boolean(serviceTitles[entry.data.lang]?.[entry.data.slug]);
const heroImage = pageHeroImages[entry.data.slug]; const heroImage = pageHeroImages[entry.data.slug];
--- ---
<BaseLayout title={entry.data.title} description={entry.data.description} canonical={entry.data.canonical} image={entry.data.featuredImage || heroImage} lang={entry.data.lang}> <BaseLayout title={entry.data.title} description={entry.data.description} canonical={entry.data.canonical} image={entry.data.featuredImage || heroImage} lang={entry.data.lang}>
{entry.data.slug === 'contact' && <Fragment slot="head"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" /></Fragment>}
<PageHero title={bannerTitle} subtitle={entry.data.slug === 'faqs' ? 'FAQs' : undefined} image={heroImage} constrain /> <PageHero title={bannerTitle} subtitle={entry.data.slug === 'faqs' ? 'FAQs' : undefined} image={heroImage} constrain />
{entry.data.slug === 'team' && entry.data.lang === 'en' {entry.data.slug === 'team' && entry.data.lang === 'en'
? <TeamPage /> ? <TeamPage />
@@ -64,6 +66,8 @@ const heroImage = pageHeroImages[entry.data.slug];
? <CareersPage /> ? <CareersPage />
: entry.data.slug === 'faqs' && entry.data.lang === 'en' : entry.data.slug === 'faqs' && entry.data.lang === 'en'
? <FaqPage><slot /></FaqPage> ? <FaqPage><slot /></FaqPage>
: entry.data.slug === 'contact'
? <ContactPage title={bannerTitle}><slot /></ContactPage>
: <article class:list={['prose', 'source-page', 'container', { 'service-page': isService }]}><slot /></article>} : <article class:list={['prose', 'source-page', 'container', { 'service-page': isService }]}><slot /></article>}
{showForm && <FormShell title={bannerTitle} showBackground={entry.data.slug === 'client-consultation'} />} {showForm && <FormShell title={bannerTitle} showBackground={entry.data.slug === 'client-consultation'} />}
</BaseLayout> </BaseLayout>