Create custom /contact page with details and appointment CTAs

- Refactor AboutController to PageController to handle multiple static pages:
  - /about
  - /services/{slug} (diagnostic, sports, pre-post, neuro)
  - /contact (new)

- New template riverside-pt-contact.html.twig:
  - Contact details (address, phone, email)
  - Office hours
  - 'Send us a message' section directing to booking tool
  - Multiple 'Make an Appointment' links back to /home#book-an-appointment

- Updated riverside_pt.routing.yml with riverside_pt.contact route
- Registered 'riverside_pt_contact' theme in riverside_pt.module
- Updated riverside_pt.install to skip legacy node creation for 'Contact' (and previously About/Services) to avoid alias conflicts
- Minor updates to home template, header handling, libraries (scroll support), and other controllers for consistency with page flows and email/booking features

All details pages (/about, /services/*, /contact) now include clear links back to 'Make an Appointment'.
This commit is contained in:
Philip Peterson 2026-06-03 23:55:02 -07:00
parent 95a0a3e004
commit 797e580cc0
14 changed files with 454 additions and 1946 deletions

File diff suppressed because one or more lines are too long

View file

@ -9,11 +9,6 @@
background-color: theme('colors.pt-blue.400');
scroll-behavior: smooth;
}
@media (min-width: theme('screens.sm')) {
[id] {
scroll-margin-top: 110px; /* fixed header (78px) + breathing room */
}
}
/* Neutralise any theme container constraints */
.page-wrapper {
max-width: none;

View file

@ -1,13 +1,38 @@
// Add data-scroll-to="#target-id" to any link to smooth-scroll instead of navigate.
(function () {
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('click', function (e) {
var link = e.target.closest('[data-scroll-to]');
import zenscroll from "https://esm.sh/zenscroll@4.0.2";
var FIXED_BUFFER = 91; // breathing room below fixed header when menu is closed
var MOBILE_BUFFER = 0; // no offset needed when header is in-flow (scrolls away)
// Returns the effective scroll offset to clear the header.
// When the hamburger is open, offsetHeight already includes the expanded nav,
// so no extra buffer is needed. When closed, add FIXED_BUFFER for breathing room.
function headerOffset() {
var header = document.querySelector(".rpt-header");
if (!header) return MOBILE_BUFFER;
var pos = window.getComputedStyle(header).position;
if (pos !== "fixed" && pos !== "sticky") return MOBILE_BUFFER;
var nav = document.getElementById("rpt-main-nav");
var menuOpen = nav && nav.classList.contains("is-open");
return header.offsetHeight + (menuOpen ? 0 : FIXED_BUFFER);
}
// On load: if the URL has a hash (e.g. /home#pt-services from another page),
// re-scroll with the correct header offset instead of the browser's native jump.
document.addEventListener("DOMContentLoaded", function () {
if (!window.location.hash) return;
var target = document.querySelector(window.location.hash);
if (!target) return;
// Defer until layout is stable, then position correctly.
requestAnimationFrame(function () {
zenscroll.to(target, 0, headerOffset());
});
});
document.addEventListener("click", function (e) {
var link = e.target.closest("[data-scroll-to]");
if (!link) return;
var target = document.querySelector(link.dataset.scrollTo);
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: 'smooth' });
zenscroll.to(target, 400, headerOffset());
});
});
})();

View file

@ -203,7 +203,19 @@ function _riverside_pt_build_navigation(): void {
$link->delete();
}
foreach (['Services', 'About', 'FAQ', 'Contact'] as $title) {
// Clean up legacy nodes for pages we now serve via custom controllers/routes
foreach (['About', 'Contact'] as $title) {
$nodes = $em->getStorage('node')->loadByProperties(['title' => $title, 'type' => 'page']);
foreach ($nodes as $node) {
$node->delete();
}
$aliases = $em->getStorage('path_alias')->loadByProperties(['alias' => '/' . strtolower($title)]);
foreach ($aliases as $alias) {
$alias->delete();
}
}
foreach (['FAQ'] as $title) {
if ($em->getStorage('node')->loadByProperties(['title' => $title, 'type' => 'page'])) {
continue;
}
@ -218,12 +230,12 @@ function _riverside_pt_build_navigation(): void {
$defs = [
['title' => 'Home', 'uri' => 'route:<front>', 'weight' => 0, 'class' => NULL],
['title' => 'Services', 'uri' => 'internal:/services', 'weight' => 1, 'class' => NULL],
['title' => 'Services', 'uri' => 'internal:/home#pt-services', 'weight' => 1, 'class' => NULL],
['title' => 'About', 'uri' => 'internal:/about', 'weight' => 2, 'class' => NULL],
['title' => 'FAQ', 'uri' => 'internal:/faq', 'weight' => 3, 'class' => NULL],
['title' => 'FAQ', 'uri' => 'internal:/home#pt-faq', 'weight' => 3, 'class' => NULL],
['title' => 'Contact', 'uri' => 'internal:/contact', 'weight' => 4, 'class' => 'nav-cta nav-cta--primary'],
['title' => 'Book An Appointment', 'uri' => 'internal:/home', 'weight' => 5, 'class' => 'nav-cta nav-cta--primary'],
['title' => 'Book An Appointment', 'uri' => 'internal:/home#book-an-appointment','weight' => 5, 'class' => 'nav-cta nav-cta--primary'],
];
foreach ($defs as $def) {

View file

@ -4,7 +4,7 @@ app:
css/app.css: {}
js:
js/nav.js: {}
js/scroll.js: {}
js/scroll.js: { attributes: { type: module } }
js/phone-format.js: {}
js/components/rpt-toggle.js: { attributes: { type: module } }
js/components/rpt-carousel.js: { attributes: { type: module } }

View file

@ -25,6 +25,22 @@ function riverside_pt_theme(): array {
'riverside_pt_home' => [
'variables' => [],
],
'riverside_pt_about' => [
'variables' => [],
],
'riverside_pt_service' => [
'variables' => [
'slug' => NULL,
'title' => NULL,
'description' => NULL,
'long_description' => NULL,
'what_to_expect' => NULL,
'benefits' => [],
],
],
'riverside_pt_contact' => [
'variables' => [],
],
];
}
@ -75,10 +91,15 @@ function riverside_pt_preprocess_riverside_pt_header(array &$variables): void {
}
function riverside_pt_mail(string $key, array &$message, array $params): void {
if ($key !== 'booking_request') {
return;
}
$service_map = [
'diagnostic' => 'Diagnostic Assessment',
'sports' => 'Sports Rehabilitation',
'surgical' => 'Surgery Rehabilitation',
'neuro' => 'Neurological Therapy',
];
$service_label = $service_map[$params['service'] ?? ''] ?? 'Appointment';
if ($key === 'booking_request') {
$start = new \DateTime($params['start']);
$end = new \DateTime($params['end']);
@ -86,6 +107,7 @@ function riverside_pt_mail(string $key, array &$message, array $params): void {
$lines = [
'Name: ' . ($params['first_name'] ?? '') . ' ' . ($params['last_name'] ?? ''),
'Email: ' . ($params['email'] ?? ''),
'Service: ' . $service_label,
'Phone: ' . ($params['phone'] ?? ''),
'Slot: ' . $start->format('l, F j, Y') . ', ' . $start->format('g:i A') . '' . $end->format('g:i A'),
];
@ -95,4 +117,38 @@ function riverside_pt_mail(string $key, array &$message, array $params): void {
}
$message['body'][] = implode("\n", $lines);
return;
}
if ($key === 'booking_confirmation') {
$start = new \DateTime($params['start']);
$end = new \DateTime($params['end']);
$first = $params['first_name'] ?? 'Patient';
$message['subject'] = 'Your appointment is confirmed — ' . $start->format('M j, Y g:i A');
$lines = [
'Dear ' . $first . ',',
'',
'Your appointment is *confirmed* for:',
$start->format('l, F j, Y') . ', ' . $start->format('g:i A') . '' . $end->format('g:i A') . ' PST',
'',
'Service: ' . $service_label,
];
$full_name = trim(($params['first_name'] ?? '') . ' ' . ($params['last_name'] ?? ''));
if ($full_name) {
$lines[] = 'Name: ' . $full_name;
}
if (!empty($params['phone'])) {
$lines[] = 'Phone: ' . $params['phone'];
}
$lines[] = '';
$lines[] = 'We look forward to seeing you at Riverside Physical Therapy.';
$lines[] = 'If you need to cancel or reschedule, please contact us as soon as possible.';
$message['body'][] = implode("\n", $lines);
return;
}
}

View file

@ -31,3 +31,44 @@ riverside_pt.schedule_events:
options:
_auth:
- cookie
riverside_pt.about:
path: '/about'
defaults:
_controller: '\Drupal\riverside_pt\Controller\PageController::page'
_title: 'About'
requirements:
_permission: 'access content'
riverside_pt.services:
path: '/services'
defaults:
_controller: '\Drupal\riverside_pt\Controller\HomeController::redirectToAnchor'
destination: '/home#pt-services'
requirements:
_access: 'TRUE'
riverside_pt.faq:
path: '/faq'
defaults:
_controller: '\Drupal\riverside_pt\Controller\HomeController::redirectToAnchor'
destination: '/home#pt-faq'
requirements:
_access: 'TRUE'
riverside_pt.service:
path: '/services/{slug}'
defaults:
_controller: '\Drupal\riverside_pt\Controller\PageController::service'
_title: 'Service'
requirements:
_permission: 'access content'
slug: 'diagnostic-assessment|sports-rehabilitation|pre-post-surgical-rehab|neurological-therapy'
riverside_pt.contact:
path: '/contact'
defaults:
_controller: '\Drupal\riverside_pt\Controller\PageController::contact'
_title: 'Contact'
requirements:
_permission: 'access content'

View file

@ -4,9 +4,15 @@ namespace Drupal\riverside_pt\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
class HomeController extends ControllerBase {
public function redirectToAnchor(Request $request): RedirectResponse {
return new RedirectResponse($request->attributes->get('destination'), 301);
}
public function page(): array {
$holidays = $this->config('riverside_pt.settings')->get('holidays') ?? [];
$holidayMap = [];

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\riverside_pt\Controller;
use Drupal\Core\Controller\ControllerBase;
class PageController extends ControllerBase {
public function page(): array {
return [
'#theme' => 'riverside_pt_about',
'#cache' => ['max-age' => 0],
];
}
public function contact(): array {
return [
'#theme' => 'riverside_pt_contact',
'#cache' => ['max-age' => 0],
];
}
public function service($slug): array {
$services = $this->getServices();
$service = $services[$slug] ?? NULL;
if (!$service) {
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
}
return [
'#theme' => 'riverside_pt_service',
'#slug' => $slug,
'#title' => $service['title'],
'#description' => $service['description'],
'#long_description' => $service['long_description'],
'#what_to_expect' => $service['what_to_expect'],
'#benefits' => $service['benefits'],
'#cache' => ['max-age' => 0],
];
}
private function getServices(): array {
return [
'diagnostic-assessment' => [
'title' => 'Diagnostic Assessment',
'description' => 'Your recovery starts with clarity. We perform a thorough evaluation of your condition, movement, and goals to create a precise, personalized treatment plan from day one.',
'long_description' => '<p>Our comprehensive diagnostic assessment is the foundation of effective physical therapy. During this 60-minute session, our expert therapists conduct a detailed evaluation including:</p>
<ul>
<li>Medical history review</li>
<li>Physical examination of movement patterns</li>
<li>Strength and flexibility testing</li>
<li>Postural and gait analysis</li>
<li>Specialized orthopedic tests</li>
</ul>
<p>This allows us to identify the root cause of your pain or limitation, not just the symptoms.</p>',
'what_to_expect' => '<p>You will be asked to perform various movements and exercises while we observe and measure. We may use hands-on techniques to assess joint mobility and soft tissue. Wear comfortable clothing that allows easy movement and access to the area being evaluated. The goal is to gather enough information to design a targeted treatment plan that addresses your specific needs and goals.</p>',
'benefits' => [
'Accurate identification of the source of your pain or dysfunction',
'Personalized treatment plan tailored to your body and lifestyle',
'Clear understanding of your condition and recovery timeline',
'Baseline measurements to track progress objectively',
'Prevention of future injuries through early detection of imbalances',
],
],
'sports-rehabilitation' => [
'title' => 'Sports Rehabilitation',
'description' => 'We help athletes recover from injury and return to peak performance with targeted, sport-specific programs built around your body and your goals.',
'long_description' => '<p>Whether you\'re a weekend warrior or a competitive athlete, our sports rehabilitation program is designed to get you back in the game safely and stronger than before. We combine evidence-based techniques with sport-specific training to address the unique demands of your activity.</p>
<p>Our therapists have experience working with athletes from a variety of sports including running, cycling, soccer, basketball, tennis, golf, and more.</p>',
'what_to_expect' => '<p>Treatment sessions focus on restoring mobility, strength, power, and coordination specific to your sport. We incorporate functional movements, plyometrics, agility drills, and sport-specific simulations. You will receive a customized home exercise program and guidance on return-to-sport criteria and injury prevention strategies.</p>',
'benefits' => [
'Faster, safer return to your sport or activity',
'Sport-specific strengthening and conditioning',
'Improved performance and biomechanics',
'Reduced risk of re-injury',
'Education on proper warm-up, recovery, and training principles',
],
],
'pre-post-surgical-rehab' => [
'title' => 'Pre/Post-Surgical Rehab',
'description' => 'Expert care before and after surgery to reduce recovery time, minimize complications, and restore full strength and function.',
'long_description' => '<p>Surgery is often just one step in the recovery journey. Our pre- and post-surgical rehabilitation programs are designed to optimize outcomes and get you back to your normal activities as quickly and safely as possible.</p>
<p>Pre-hab (pre-surgery rehab) can significantly improve post-op results by strengthening supporting muscles and improving range of motion before the procedure.</p>',
'what_to_expect' => '<p>Pre-surgery: We focus on maximizing strength, flexibility, and cardiovascular health to prepare your body for surgery and the demands of recovery. Post-surgery: We follow evidence-based protocols specific to your procedure (joint replacements, ACL reconstruction, rotator cuff repair, spinal surgery, etc.), progressing you through phases of healing while monitoring for any complications.</p>',
'benefits' => [
'Shorter hospital stays and faster initial recovery',
'Reduced post-operative pain and swelling',
'Restored range of motion and strength more quickly',
'Lower risk of complications such as blood clots or stiffness',
'Better long-term functional outcomes',
],
],
'neurological-therapy' => [
'title' => 'Neurological Therapy',
'description' => 'Specialized therapy for nervous system conditions — helping you rebuild strength, coordination, and independence at every stage of recovery.',
'long_description' => '<p>Neurological conditions such as stroke, Parkinson\'s disease, multiple sclerosis, traumatic brain injury, or spinal cord injury can significantly impact mobility, balance, and daily function. Our neurological physical therapy program uses specialized techniques to help you regain as much independence as possible.</p>
<p>We work closely with neurologists, occupational therapists, and other healthcare providers to create a coordinated care plan.</p>',
'what_to_expect' => '<p>Sessions may include balance and gait training, functional electrical stimulation, task-specific training, manual therapy, and exercises to improve strength, coordination, and proprioception. We also focus on fall prevention strategies and adaptive techniques to help you safely perform daily activities.</p>',
'benefits' => [
'Improved balance, coordination, and walking ability',
'Increased strength and endurance',
'Greater independence in daily living activities',
'Reduced fall risk',
'Better quality of life and confidence',
],
],
];
}
}

View file

@ -69,6 +69,18 @@ class ScheduleController extends ControllerBase {
$to = $this->configFactory->get('riverside_pt.settings')->get('notification_email');
$lang = \Drupal::languageManager()->getDefaultLanguage()->getId();
// Send confirmation to the user
$this->mailManager->mail('riverside_pt', 'booking_confirmation', $email, $lang, [
'first_name' => $firstName,
'last_name' => $lastName,
'email' => $email,
'phone' => $phone,
'comments' => $comments,
'start' => $start,
'end' => $end,
'service' => $service,
]);
$sent = $this->mailManager->mail('riverside_pt', 'booking_request', $to, $lang, [
'first_name' => $firstName,
'last_name' => $lastName,
@ -77,6 +89,7 @@ class ScheduleController extends ControllerBase {
'comments' => $comments,
'start' => $start,
'end' => $end,
'service' => $service,
]);
$this->tempStore->delete('booking_slot');

View file

@ -0,0 +1,57 @@
<div class="bg-gradient-to-b from-pt-sage-400 to-pt-blue-400 pt-px">
<section class="relative md:mt-[78px] min-h-[320px] 2xl:flex">
<div class="absolute inset-0 flex sm:pt-8 sm:pb-8 2xl:static 2xl:inset-auto 2xl:w-[62%] 2xl:flex-none 2xl:p-0">
<div class="hidden sm:block sm:basis-[8%] sm:grow-[2] 2xl:hidden"></div>
<img src="/modules/custom/riverside_pt/images/neck.jpg" alt="Our clinic" class="basis-full sm:basis-[58%] sm:grow 2xl:basis-full 2xl:flex-none min-w-0 h-full object-cover object-[center_0%]" />
<div class="hidden sm:block sm:basis-[34%] sm:grow-[2] 2xl:hidden"></div>
</div>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/30 to-transparent sm:hidden"></div>
<div class="relative flex min-h-[320px] pt-0 pb-4 2xl:flex-1 2xl:bg-pt-blue-400 2xl:min-h-0 2xl:pb-0">
<div class="hidden sm:block sm:basis-[50%] sm:grow-[2] 2xl:hidden"></div>
<div class="basis-full sm:basis-[40%] sm:grow flex flex-col justify-end sm:justify-center px-6 sm:px-0 sm:pb-0 gap-[1vw] 2xl:basis-full 2xl:grow-0 2xl:pl-16 2xl:pr-12 2xl:justify-center">
<h1 class="mt-0 mb-[1vw] text-[clamp(1.5rem,3.5vw,3.25rem)] font-serif font-normal text-white leading-none [text-shadow:-56.21px_2.55px_10.22px_rgb(0_0_0/10%)]">
About Riverside Physical Therapy
</h1>
<p class="text-white/80 leading-tight text-[clamp(1rem,2vw,1.5vw)]">Helping you restore strength and reclaim your life since 2011.</p>
</div>
<div class="hidden sm:block sm:basis-[10%] sm:grow-[2] 2xl:hidden"></div>
</div>
</section>
</div>
<section class="py-16 px-6 bg-white">
<div class="max-w-[1040px] mx-auto">
<h2 class="text-[2.25rem] font-serif font-normal text-gray-900 leading-tight mb-8 text-center">Our Story</h2>
<div class="max-w-none text-gray-700 space-y-4 text-[15px] leading-relaxed">
<p>Riverside Physical Therapy was founded with a simple belief: every patient deserves a thorough evaluation and a truly personalized plan. What started as a small clinic has grown into a trusted partner for hundreds of patients across the region.</p>
<p>Our team of licensed physical therapists brings together decades of experience in orthopedic, sports, post-surgical, and neurological rehabilitation. We stay current with the latest evidence-based techniques so you get care that actually works.</p>
</div>
</div>
</section>
<section class="bg-pt-blue-100 py-16 px-6">
<div class="max-w-[1040px] mx-auto">
<h2 class="text-[2.25rem] font-serif font-normal text-gray-900 leading-tight mb-8 text-center">What Sets Us Apart</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="bg-white p-6 rounded-xl border border-pt-blue-200">
<h3 class="text-xl font-normal mb-3">One-on-one care</h3>
<p class="text-gray-600">You work with the same therapist throughout your treatment. No hand-offs, no assembly line.</p>
</div>
<div class="bg-white p-6 rounded-xl border border-pt-blue-200">
<h3 class="text-xl font-normal mb-3">Root cause focus</h3>
<p class="text-gray-600">We don't just chase symptoms. We find and treat the underlying movement problems.</p>
</div>
<div class="bg-white p-6 rounded-xl border border-pt-blue-200">
<h3 class="text-xl font-normal mb-3">Real results</h3>
<p class="text-gray-600">Our patients consistently report faster recovery times and lasting improvements.</p>
</div>
</div>
</div>
</section>
<section class="py-16 px-6 bg-white">
<div class="max-w-[700px] mx-auto text-center">
<h2 class="text-[2.25rem] font-serif font-normal text-gray-900 leading-tight mb-6">Ready to start your recovery?</h2>
<a href="/home#book-an-appointment" class="inline-block px-[4em] py-[1em] bg-pt-blue-500 text-white text-sm font-medium no-underline transition-colors border-2 border-pt-blue-500 hover:bg-pt-blue-600 hover:border-pt-blue-600">Make an Appointment</a>
</div>
</section>

View file

@ -0,0 +1,52 @@
<div class="max-w-[1040px] mx-auto px-6 py-16">
<h1 class="text-[clamp(2.5rem,5vw,4rem)] font-serif font-light text-gray-900 mb-4">Contact Us</h1>
<p class="text-xl text-gray-600 mb-12 max-w-2xl">
We'd love to hear from you. Reach out with questions about our services, insurance, or to schedule a visit.
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<div>
<h2 class="text-2xl font-normal mb-6 text-gray-900">Our Location</h2>
<div class="text-gray-700 space-y-1 text-[15px] leading-relaxed">
<p><strong>Riverside Physical Therapy</strong></p>
<p>123 Riverside Drive, Suite 200</p>
<p>Riverside, CA 92501</p>
</div>
<h2 class="text-2xl font-normal mt-10 mb-6 text-gray-900">Get in Touch</h2>
<div class="space-y-2 text-[15px]">
<p><strong>Phone:</strong> <a href="tel:9515550123" class="text-pt-blue-500 hover:underline">(951) 555-0123</a></p>
<p><strong>Email:</strong> <a href="mailto:hello@riversidept.com" class="text-pt-blue-500 hover:underline">hello@riversidept.com</a></p>
</div>
<h2 class="text-2xl font-normal mt-10 mb-6 text-gray-900">Office Hours</h2>
<div class="text-gray-700 text-[15px] leading-relaxed">
<p>Monday Friday: 7:00 AM 7:00 PM</p>
<p>Saturday: 8:00 AM 12:00 PM</p>
<p>Sunday: Closed</p>
</div>
</div>
<div>
<h2 class="text-2xl font-normal mb-6 text-gray-900">Send Us a Message</h2>
<p class="text-gray-600 mb-6 text-[15px]">
For appointment requests, please use our online booking tool — it's the fastest way to get scheduled.
</p>
<div class="bg-pt-blue-50 border border-pt-blue-200 p-8 rounded-2xl">
<p class="mb-4">Ready to get started?</p>
<a href="/home#book-an-appointment"
class="inline-block w-full text-center px-6 py-3 bg-pt-blue-500 text-white text-sm font-medium no-underline rounded-xl hover:bg-pt-blue-600 transition-colors">
Make an Appointment
</a>
<p class="text-xs text-gray-500 mt-3 text-center">
Or call us at (951) 555-0123 during business hours.
</p>
</div>
<p class="mt-8 text-sm text-gray-500">
We typically respond to emails within 1 business day. For urgent matters, please call.
</p>
</div>
</div>
</div>

View file

@ -34,8 +34,7 @@
class="w-full sm:w-auto text-center max-sm:text-sm sm:text-[clamp(0.25rem,1vw,1.25vw)] px-[4em] py-[1em] bg-pt-blue-500 text-white font-medium no-underline transition-colors border-2 border-pt-blue-500 hover:bg-pt-blue-600 hover:border-pt-blue-600"
>Book An Appointment</a>
<a
href="/services"
data-scroll-to="#pt-services"
href="#pt-services"
class="hidden sm:inline-block text-[clamp(0.25rem,1vw,1.25vw)] px-[4em] py-[1em] bg-pt-blue-400 text-white font-medium no-underline transition-colors border-2 border-white hover:bg-pt-sage-500"
>View Our Services</a>
</div>
@ -57,7 +56,7 @@
<div class="flex flex-col gap-4 p-6 flex-1">
<h3 class="text-2xl font-normal text-gray-900">Diagnostic Assessment</h3>
<p class="text-[15px] text-gray-600 leading-relaxed flex-1">Your recovery starts with clarity. We perform a thorough evaluation of your condition, movement, and goals to create a precise, personalized treatment plan from day one.</p>
<a href="/services" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
<a href="/services/diagnostic-assessment" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
</div>
</div>
<div class="flex flex-col border border-pt-blue-200 bg-white overflow-hidden">
@ -65,7 +64,7 @@
<div class="flex flex-col gap-4 p-6 flex-1">
<h3 class="text-2xl font-normal text-gray-900">Sports Rehabilitation</h3>
<p class="text-[15px] text-gray-600 leading-relaxed flex-1">We help athletes recover from injury and return to peak performance with targeted, sport-specific programs built around your body and your goals.</p>
<a href="/services" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
<a href="/services/sports-rehabilitation" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
</div>
</div>
<div class="flex flex-col border border-pt-blue-200 bg-white overflow-hidden">
@ -73,7 +72,7 @@
<div class="flex flex-col gap-4 p-6 flex-1">
<h3 class="text-2xl font-normal text-gray-900">Pre/Post-Surgical Rehab</h3>
<p class="text-[15px] text-gray-600 leading-relaxed flex-1">Expert care before and after surgery to reduce recovery time, minimize complications, and restore full strength and function.</p>
<a href="/services" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
<a href="/services/pre-post-surgical-rehab" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
</div>
</div>
<div class="flex flex-col border border-pt-blue-200 bg-white overflow-hidden">
@ -81,7 +80,7 @@
<div class="flex flex-col gap-4 p-6 flex-1">
<h3 class="text-2xl font-normal text-gray-900">Neurological Therapy</h3>
<p class="text-[15px] text-gray-600 leading-relaxed flex-1">Specialized therapy for nervous system conditions — helping you rebuild strength, coordination, and independence at every stage of recovery.</p>
<a href="/services" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
<a href="/services/neurological-therapy" class="inline-flex items-center gap-3 px-5 py-3 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600">More Info &rarr;</a>
</div>
</div>
</div>

View file

@ -0,0 +1,42 @@
<div class="max-w-[1040px] mx-auto px-6 py-12">
<div class="mb-8">
<a href="/home#book-an-appointment" class="inline-flex items-center text-pt-blue-500 hover:text-pt-blue-600 text-sm font-medium">
← Make an Appointment
</a>
</div>
<h1 class="text-[2.5rem] font-serif font-normal text-gray-900 leading-tight mb-4">{{ title }}</h1>
<p class="text-xl text-gray-600 mb-10">{{ description }}</p>
<div class="max-w-none mb-12 text-gray-700 text-[15px] leading-relaxed space-y-4">
{{ long_description|raw }}
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 mb-12">
<div>
<h2 class="text-2xl font-normal mb-4">What to Expect</h2>
<div class="text-gray-700 text-[15px] leading-relaxed">
{{ what_to_expect|raw }}
</div>
</div>
<div>
<h2 class="text-2xl font-normal mb-4">Key Benefits</h2>
<ul class="space-y-3 text-gray-700">
{% for benefit in benefits %}
<li class="flex items-start gap-3">
<span class="mt-1.5 block w-2 h-2 bg-pt-blue-500 rounded-full flex-shrink-0"></span>
<span>{{ benefit }}</span>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="pt-8 border-t border-pt-blue-200 text-center">
<p class="mb-6 text-lg">Ready to get started with {{ title }}?</p>
<a href="/home#book-an-appointment" class="inline-flex items-center gap-3 px-8 py-4 bg-pt-blue-500 text-white text-[15px] font-medium no-underline hover:bg-pt-blue-600 rounded-xl">
Make an Appointment →
</a>
<p class="mt-3 text-sm text-gray-500">or call us to discuss your needs</p>
</div>
</div>