feat(services): spa & gym page, mock selection, logo loading state

- Add /services with Spa-only and Gym-only offerings, filters, add/remove selection
- Selection panel with subtotal and mailto request; mobile sticky bar when items selected
- ShitayeLogoLoader + route loading.tsx with breathe/ring animations in globals.css
- Home: replace full services grid with promo strip linking to /services
- Nav/footer point to /services; include logo assets and map-related components

Made-with: Cursor
This commit is contained in:
“kirukib” 2026-03-25 19:54:11 +03:00
parent 0065ea5c34
commit 93f93eb087
13 changed files with 669 additions and 47 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -173,3 +173,33 @@ body {
animation: mock3d-rotate 8s ease-in-out infinite alternate;
transform-style: preserve-3d;
}
/* Branded route loader — /services and similar */
@keyframes shitaye-logo-breathe {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.045);
opacity: 0.94;
}
}
@keyframes shitaye-logo-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.animate-shitaye-logo {
animation: shitaye-logo-breathe 2.4s ease-in-out infinite;
}
.animate-shitaye-ring {
animation: shitaye-logo-ring 12s linear infinite;
}

View File

@ -4,6 +4,7 @@ import { AmenityItem } from "@/components/AmenityItem";
import { BookingSearchWidget } from "@/components/BookingSearchWidget";
import { OutletCard } from "@/components/OutletCard";
import { RoomCard } from "@/components/RoomCard";
import { GoogleMapEmbed } from "@/components/GoogleMapEmbed";
import { VirtualTourBlock } from "@/components/VirtualTourBlock";
import { roomAmenities } from "@/lib/mocks/amenities";
import { bookingStyleReviews } from "@/lib/mocks/bookingReviews";
@ -64,10 +65,11 @@ export default function HomePage() {
Guest trust
</p>
<h2 className="mt-2 font-heading text-3xl md:text-4xl">
What guests say before they book
What our guests say after their stay
</h2>
<p className="mt-2 max-w-2xl text-sm text-[var(--color-muted)]">
Real feedback from recent stays helps new visitors book directly with confidence.
Honest reviews from people whove stayed with us so you can book directly with
confidence.
</p>
</div>
<Link
@ -104,41 +106,40 @@ export default function HomePage() {
</div>
</section>
<section className="mx-auto max-w-7xl px-4 py-20 md:px-8">
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div>
<p className="text-xs font-semibold uppercase tracking-widest text-[var(--color-primary)]">
About us
</p>
<h2 className="mt-3 font-heading text-3xl text-[var(--color-text)] md:text-4xl">
Geo-convenient. Unmistakably Shitaye.
</h2>
<p className="mt-4 leading-relaxed text-[var(--color-muted)]">
Close to key places of attraction and major businesses or institutions your base
for work, culture, and rest. Begin your journey with us.
</p>
<Link href="/#rooms" className="btn-mustard mt-6 inline-flex px-6 py-3 text-sm">
View rooms
</Link>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div className="relative aspect-[4/5] overflow-hidden rounded-2xl">
<Image
src={siteConfig.lobbyImageUrl}
alt="Shitaye Suite Hotel lobby and lounge"
fill
className="object-cover"
sizes="(max-width: 640px) 100vw, 50vw"
/>
</div>
<div className="relative mt-0 aspect-[4/5] overflow-hidden rounded-2xl sm:mt-12">
<Image
src="https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=800&q=80"
alt="Luxury suite interior"
fill
className="object-cover"
/>
<section id="location" className="border-y border-[var(--color-border)] bg-[var(--color-surface-muted)] py-16 md:py-20">
<div className="mx-auto max-w-7xl px-4 md:px-8">
<div className="grid gap-10 lg:grid-cols-2 lg:items-start lg:gap-12">
<div>
<p className="text-xs font-semibold uppercase tracking-widest text-[var(--color-primary)]">
Find us
</p>
<h2 className="mt-2 font-heading text-3xl text-[var(--color-text)] md:text-4xl">
Location & directions
</h2>
<p className="mt-4 leading-relaxed text-[var(--color-muted)]">
{siteConfig.address}. Search &ldquo;Shitaye Suite Hotel&rdquo; on Google Maps or use
the map below.
</p>
<div className="mt-6 flex flex-wrap gap-3">
<a
href={siteConfig.googleMapsDirectionsUrl}
target="_blank"
rel="noopener noreferrer"
className="btn-mustard inline-flex px-6 py-3 text-sm"
>
Get directions
</a>
<a
href={siteConfig.googleMapsPlaceUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-6 py-3 text-sm font-semibold text-[var(--color-text)] transition hover:border-[var(--color-accent)] hover:text-[var(--color-accent)]"
>
Open in Google Maps
</a>
</div>
</div>
<GoogleMapEmbed />
</div>
</div>
</section>
@ -171,6 +172,29 @@ export default function HomePage() {
</div>
</section>
<section
id="services"
className="border-y border-[var(--color-border)] bg-pattern-brand-gold py-16 md:py-20"
>
<div className="mx-auto flex max-w-7xl flex-col items-start gap-6 px-4 md:flex-row md:items-center md:justify-between md:px-8">
<div className="max-w-xl">
<p className="text-xs font-semibold uppercase tracking-[0.25em] text-[var(--color-primary)]">
Spa & gym
</p>
<h2 className="mt-2 font-heading text-2xl text-[var(--color-text)] md:text-3xl">
Treatments & fitness sessions
</h2>
<p className="mt-3 text-sm leading-relaxed text-[var(--color-muted)]">
Browse our spa menu and gym add-ons build a mock selection and send a request from the
dedicated services page.
</p>
</div>
<Link href="/services" className="btn-mustard shrink-0 px-8 py-3.5 text-sm">
View spa & gym services
</Link>
</div>
</section>
<section
id="wellness"
className="border-y border-[var(--color-border)] bg-[var(--color-surface-muted)] py-24 md:py-32"
@ -294,6 +318,45 @@ export default function HomePage() {
</div>
</section>
<section id="about" className="mx-auto max-w-7xl px-4 py-20 md:px-8">
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div>
<p className="text-xs font-semibold uppercase tracking-widest text-[var(--color-primary)]">
About us
</p>
<h2 className="mt-3 font-heading text-3xl text-[var(--color-text)] md:text-4xl">
Geo-convenient. Unmistakably Shitaye.
</h2>
<p className="mt-4 leading-relaxed text-[var(--color-muted)]">
Close to key places of attraction and major businesses or institutions your base
for work, culture, and rest. Begin your journey with us.
</p>
<Link href="/#rooms" className="btn-mustard mt-6 inline-flex px-6 py-3 text-sm">
View rooms
</Link>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div className="relative aspect-[4/5] overflow-hidden rounded-2xl">
<Image
src={siteConfig.lobbyImageUrl}
alt="Shitaye Suite Hotel lobby and lounge"
fill
className="object-cover"
sizes="(max-width: 640px) 100vw, 50vw"
/>
</div>
<div className="relative mt-0 aspect-[4/5] overflow-hidden rounded-2xl sm:mt-12">
<Image
src="https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=800&q=80"
alt="Luxury suite interior"
fill
className="object-cover"
/>
</div>
</div>
</div>
</section>
<section className="border-y border-[var(--color-border)] bg-[var(--color-surface)] py-16">
<div className="mx-auto max-w-7xl px-4 md:px-8">
<h2 className="font-heading text-2xl md:text-3xl">All rooms include</h2>

View File

@ -0,0 +1,283 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { useMemo, useState } from "react";
import {
spaGymFilters,
spaGymServices,
type SpaGymFilterId,
type SpaGymService,
} from "@/lib/mocks/services";
import { siteConfig } from "@/lib/mocks/site";
function ServiceCard({
service,
selected,
onToggle,
}: {
service: SpaGymService;
selected: boolean;
onToggle: () => void;
}) {
const kindLabel = service.kind === "spa" ? "Spa" : "Gym";
return (
<article className="card-lift flex flex-col overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] shadow-sm">
<div className="relative aspect-[16/10] overflow-hidden">
<Image
src={service.image}
alt=""
fill
className="object-cover transition duration-500"
sizes="(max-width:640px) 100vw, (max-width:1024px) 50vw, 33vw"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
<span className="absolute left-3 top-3 rounded-full bg-[var(--color-surface)]/95 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-[var(--color-primary)] shadow-sm backdrop-blur-sm">
{kindLabel}
</span>
<span className="absolute bottom-3 right-3 rounded-full bg-[var(--color-primary)] px-3 py-1 text-xs font-bold text-[var(--color-on-primary)] shadow-md">
${service.priceUsd}
<span className="font-normal opacity-90"> · {service.priceNote}</span>
</span>
</div>
<div className="flex flex-1 flex-col p-5 md:p-6">
<p className="text-[11px] font-semibold uppercase tracking-[0.2em] text-[var(--color-muted)]">
{service.duration}
</p>
<h3 className="mt-2 font-heading text-lg font-semibold text-[var(--color-text)] md:text-xl">
{service.title}
</h3>
<p className="mt-2 flex-1 text-sm leading-relaxed text-[var(--color-muted)]">
{service.description}
</p>
<button
type="button"
onClick={onToggle}
aria-pressed={selected}
className={`mt-5 w-full rounded-full border-2 border-transparent px-4 py-2.5 text-sm font-semibold transition md:mt-6 ${
selected
? "bg-[var(--color-primary)] text-[var(--color-on-primary)] shadow-md"
: "bg-[var(--color-accent-soft)] text-[var(--color-primary)] ring-1 ring-[var(--color-accent)]/40 hover:bg-[var(--color-accent)]/15"
}`}
>
{selected ? "Added — tap to remove" : "Add to selection"}
</button>
</div>
</article>
);
}
function SelectionPanel({
items,
onRemove,
onClear,
}: {
items: SpaGymService[];
onRemove: (id: string) => void;
onClear: () => void;
}) {
const total = useMemo(
() => items.reduce((sum, s) => sum + s.priceUsd, 0),
[items],
);
return (
<div className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] p-5 shadow-sm md:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-[var(--color-primary)]">
Your selection
</p>
<p className="mt-1 text-xs text-[var(--color-muted)]">
Mock basket pick services to preview a request (no real payment).
</p>
{items.length === 0 ? (
<p className="mt-6 rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-muted)] px-4 py-8 text-center text-sm text-[var(--color-muted)]">
Tap &ldquo;Add to selection&rdquo; on any spa or gym service to build your list.
</p>
) : (
<ul className="mt-5 max-h-[min(320px,50vh)] space-y-3 overflow-y-auto pr-1">
{items.map((s) => (
<li
key={s.id}
className="flex items-start justify-between gap-3 rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-2.5 text-sm"
>
<div className="min-w-0">
<p className="font-semibold text-[var(--color-text)]">{s.title}</p>
<p className="text-xs text-[var(--color-muted)]">
{s.kind === "spa" ? "Spa" : "Gym"} · {s.duration}
</p>
</div>
<div className="flex shrink-0 items-center gap-2">
<span className="font-semibold text-[var(--color-primary)]">${s.priceUsd}</span>
<button
type="button"
onClick={() => onRemove(s.id)}
className="rounded-full p-1 text-[var(--color-muted)] transition hover:bg-[var(--color-border)]/50 hover:text-[var(--color-text)]"
aria-label={`Remove ${s.title}`}
>
×
</button>
</div>
</li>
))}
</ul>
)}
{items.length > 0 ? (
<div className="mt-5 border-t border-[var(--color-border)] pt-4">
<div className="flex items-center justify-between text-sm">
<span className="text-[var(--color-muted)]">Subtotal (mock)</span>
<span className="font-heading text-xl font-semibold text-[var(--color-text)]">
${total.toFixed(0)}
</span>
</div>
<div className="mt-4 flex flex-col gap-2">
<a
href={`mailto:${siteConfig.email}?subject=Spa%20%26%20Gym%20request&body=${encodeURIComponent(
`Selected services:\n${items.map((s) => `- ${s.title} ($${s.priceUsd})`).join("\n")}\n\nTotal (estimate): $${total}`,
)}`}
className="btn-mustard px-4 py-3 text-center text-sm"
>
Email request
</a>
<button
type="button"
onClick={onClear}
className="rounded-full border border-[var(--color-border)] py-2.5 text-sm font-semibold text-[var(--color-muted)] transition hover:bg-[var(--color-surface-muted)]"
>
Clear selection
</button>
</div>
</div>
) : null}
</div>
);
}
export function ServicesPageClient() {
const [filter, setFilter] = useState<SpaGymFilterId>("all");
const [selected, setSelected] = useState<Set<string>>(new Set());
const filtered = useMemo(() => {
if (filter === "all") return spaGymServices;
return spaGymServices.filter((s) => s.kind === filter);
}, [filter]);
const selectedItems = useMemo(
() => spaGymServices.filter((s) => selected.has(s.id)),
[selected],
);
function toggle(id: string) {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}
function remove(id: string) {
setSelected((prev) => {
const next = new Set(prev);
next.delete(id);
return next;
});
}
function clear() {
setSelected(new Set());
}
return (
<div className="bg-[var(--color-bg)]">
<section className="border-b border-[var(--color-border)] bg-pattern-brand-gold py-12 md:py-16">
<div className="mx-auto max-w-7xl px-4 md:px-8">
<nav className="text-xs font-medium text-[var(--color-muted)]">
<Link href="/" className="transition hover:text-[var(--color-accent)]">
Home
</Link>
<span className="mx-2 opacity-50">/</span>
<span className="text-[var(--color-text)]">Spa & gym</span>
</nav>
<h1 className="mt-4 font-heading text-3xl font-semibold tracking-tight text-[var(--color-text)] md:text-4xl lg:text-[2.35rem]">
Spa & gym services
</h1>
<p className="mt-4 max-w-2xl text-sm leading-relaxed text-[var(--color-muted)] md:text-base">
Choose treatments and gym sessions your selection is shown on the right (desktop) or
below on mobile. This is a demo flow; confirm times and pricing at the desk.
</p>
<p className="mt-2 text-xs text-[var(--color-muted)]">
Taxes and service charges may apply. Prices shown in USD (mock).
</p>
</div>
</section>
<section
className={`mx-auto max-w-7xl px-4 py-10 md:px-8 md:py-14 ${selectedItems.length > 0 ? "pb-28 lg:pb-14" : ""}`}
>
<div className="flex flex-wrap justify-center gap-2 md:justify-start md:gap-2.5">
{spaGymFilters.map((f) => {
const active = filter === f.id;
return (
<button
key={f.id}
type="button"
onClick={() => setFilter(f.id)}
className={`rounded-full border px-4 py-2 text-xs font-semibold transition md:px-5 md:text-sm ${
active
? "border-[var(--color-primary)] bg-[var(--color-primary)] text-[var(--color-on-primary)] shadow-md"
: "border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text)] hover:border-[var(--color-primary)]/40 hover:bg-[var(--color-surface-muted)]"
}`}
>
{f.label}
</button>
);
})}
</div>
<div className="mt-10 grid gap-10 lg:grid-cols-[1fr_380px] lg:items-start lg:gap-12">
<div className="grid gap-6 sm:grid-cols-2">
{filtered.map((service) => (
<ServiceCard
key={service.id}
service={service}
selected={selected.has(service.id)}
onToggle={() => toggle(service.id)}
/>
))}
</div>
<aside className="lg:sticky lg:top-28">
<SelectionPanel items={selectedItems} onRemove={remove} onClear={clear} />
<Link
href="/#wellness"
className="mt-4 block text-center text-sm font-semibold text-[var(--color-primary)] underline-offset-4 hover:underline"
>
Back to hotel wellness overview
</Link>
</aside>
</div>
{/* Mobile sticky summary bar */}
{selectedItems.length > 0 ? (
<div className="fixed bottom-0 left-0 right-0 z-30 border-t border-[var(--color-border)] bg-[var(--color-surface)]/95 p-4 shadow-[0_-8px_30px_rgba(0,0,0,0.08)] backdrop-blur-md lg:hidden">
<div className="mx-auto flex max-w-lg items-center justify-between gap-3">
<p className="text-sm text-[var(--color-muted)]">
<span className="font-semibold text-[var(--color-text)]">{selectedItems.length}</span>{" "}
selected
</p>
<a
href={`mailto:${siteConfig.email}?subject=Spa%20%26%20Gym%20request`}
className="btn-mustard shrink-0 px-5 py-2.5 text-sm"
>
Email request
</a>
</div>
</div>
) : null}
</section>
</div>
);
}

View File

@ -0,0 +1,9 @@
import { ShitayeLogoLoader } from "@/components/ShitayeLogoLoader";
export default function ServicesLoading() {
return (
<div className="flex min-h-[min(70vh,560px)] flex-col items-center justify-center bg-[var(--color-bg)] px-4 py-20">
<ShitayeLogoLoader label="Preparing spa & gym…" />
</div>
);
}

12
src/app/services/page.tsx Normal file
View File

@ -0,0 +1,12 @@
import type { Metadata } from "next";
import { ServicesPageClient } from "./ServicesPageClient";
export const metadata: Metadata = {
title: "Spa & gym services",
description:
"Browse spa treatments and gym sessions at Shitaye Suite Hotel — build a mock selection and send a request.",
};
export default function ServicesPage() {
return <ServicesPageClient />;
}

View File

@ -1,3 +1,4 @@
import Image from "next/image";
import Link from "next/link";
import { siteConfig } from "@/lib/mocks/site";
@ -10,9 +11,25 @@ export function Footer() {
<div className="mx-auto max-w-7xl px-4 py-16 md:px-8">
<div className="grid gap-12 md:grid-cols-2 lg:grid-cols-12">
<div className="lg:col-span-4">
<p className="font-heading text-2xl text-white">{siteConfig.name}</p>
<p className="mt-2 text-sm text-stone-300">{siteConfig.address}</p>
<p className="mt-4 text-sm text-stone-300">{siteConfig.city}, Ethiopia</p>
<Image
src="/images/shitaye-logo-mono.png"
alt="Shitaye Suite Hotel"
width={400}
height={333}
className="h-12 w-auto max-w-[240px] brightness-0 invert sm:h-14"
/>
<p className="mt-5 text-sm leading-relaxed text-stone-300">{siteConfig.address}</p>
<p className="mt-2 text-sm text-stone-300">{siteConfig.city}, Ethiopia</p>
<p className="mt-4">
<a
href={siteConfig.googleMapsDirectionsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm font-semibold text-[var(--color-accent)] underline-offset-4 transition hover:text-white hover:underline"
>
Directions on Google Maps
</a>
</p>
</div>
<div className="lg:col-span-3">
@ -128,6 +145,11 @@ export function Footer() {
Rooms
</Link>
</li>
<li>
<Link href="/services" className="text-stone-200 hover:text-white">
Spa & gym services
</Link>
</li>
<li>
<Link href="/#wellness" className="text-stone-200 hover:text-white">
Gym & Spa
@ -138,6 +160,11 @@ export function Footer() {
Dining
</Link>
</li>
<li>
<Link href="/#location" className="text-stone-200 hover:text-white">
Location & map
</Link>
</li>
<li>
<Link href="/meetings/serenity" className="text-stone-200 hover:text-white">
Serenity meeting room

View File

@ -0,0 +1,22 @@
import { siteConfig } from "@/lib/mocks/site";
/**
* Google Maps embed (search result for the hotel). Uses the same pattern as
* Maps Share Embed without requiring an API key.
*/
export function GoogleMapEmbed({ className = "" }: { className?: string }) {
return (
<div
className={`relative aspect-[16/10] w-full overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-muted)] shadow-sm md:aspect-[21/9] ${className}`}
>
<iframe
title="Shitaye Suite Hotel — Google Maps"
src={siteConfig.googleMapsEmbedUrl}
className="absolute inset-0 h-full w-full border-0"
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
allowFullScreen
/>
</div>
);
}

View File

@ -1,3 +1,4 @@
import Image from "next/image";
import Link from "next/link";
import { CurrencySwitcher } from "@/components/CurrencySwitcher";
import { ReviewsMenu } from "@/components/ReviewsMenu";
@ -5,9 +6,11 @@ import { siteConfig } from "@/lib/mocks/site";
const nav = [
{ href: "/#rooms", label: "Rooms" },
{ href: "/services", label: "Services" },
{ href: "/#wellness", label: "Gym & Spa" },
{ href: "/#dining", label: "Dining & venues" },
{ href: "/#meetings", label: "Meetings" },
{ href: "/#location", label: "Location" },
];
export function Header() {
@ -30,12 +33,25 @@ export function Header() {
</div>
<div className="border-b border-[var(--color-border)] bg-[var(--color-surface)]/95 backdrop-blur-md">
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-4 md:px-8">
<Link href="/" className="group min-w-0 shrink flex flex-col leading-tight">
<span className="font-nav text-lg tracking-tight text-[var(--color-primary)] sm:text-xl md:text-2xl">
{siteConfig.name}
</span>
<span className="text-[10px] font-medium uppercase tracking-[0.2em] text-[var(--color-muted)] sm:text-[11px]">
{siteConfig.city}
<Link
href="/"
className="group flex min-w-0 shrink items-center gap-2.5 leading-tight sm:gap-3"
>
<Image
src="/images/shitaye-logo.png"
alt=""
width={400}
height={390}
className="h-9 w-auto shrink-0 sm:h-10 md:h-11"
priority
/>
<span className="flex min-w-0 flex-col">
<span className="font-nav text-lg tracking-tight text-[var(--color-primary)] sm:text-xl md:text-2xl">
{siteConfig.name}
</span>
<span className="text-[10px] font-medium uppercase tracking-[0.2em] text-[var(--color-muted)] sm:text-[11px]">
{siteConfig.city}
</span>
</span>
</Link>
<nav

View File

@ -0,0 +1,44 @@
import Image from "next/image";
type Props = {
label?: string;
className?: string;
};
/**
* Full-brand loading state Shitaye mark with soft pulse + slow ring.
*/
export function ShitayeLogoLoader({ label = "Loading…", className = "" }: Props) {
return (
<div
className={`flex flex-col items-center justify-center gap-6 ${className}`}
role="status"
aria-live="polite"
aria-busy="true"
>
<div className="relative flex h-32 w-32 items-center justify-center md:h-36 md:w-36">
<div
className="absolute inset-0 rounded-full bg-[var(--color-accent)]/12"
aria-hidden
/>
<div
className="animate-shitaye-ring absolute inset-1 rounded-full border border-dashed border-[var(--color-primary)]/30"
aria-hidden
/>
<div
className="absolute inset-3 rounded-full border border-[var(--color-accent)]/25"
aria-hidden
/>
<Image
src="/images/shitaye-logo.png"
alt=""
width={400}
height={390}
className="animate-shitaye-logo relative z-10 h-[4.5rem] w-auto drop-shadow-sm md:h-[5.25rem]"
priority
/>
</div>
<p className="text-sm font-medium tracking-wide text-[var(--color-muted)]">{label}</p>
</div>
);
}

108
src/lib/mocks/services.ts Normal file
View File

@ -0,0 +1,108 @@
/**
* Bookable Spa & Gym offerings for the dedicated /services page (mock pricing).
*/
export type SpaGymKind = "spa" | "gym";
export type SpaGymService = {
id: string;
kind: SpaGymKind;
title: string;
description: string;
duration: string;
priceUsd: number;
/** Shown on card badge, e.g. "per session" */
priceNote: string;
image: string;
};
export const spaGymFilterIds = ["all", "spa", "gym"] as const;
export type SpaGymFilterId = (typeof spaGymFilterIds)[number];
export const spaGymFilters: { id: SpaGymFilterId; label: string }[] = [
{ id: "all", label: "All" },
{ id: "spa", label: "Spa" },
{ id: "gym", label: "Gym" },
];
export const spaGymServices: SpaGymService[] = [
{
id: "gym-day-pass",
kind: "gym",
title: "Fitness day pass",
description: "Full access to cardio, weights, and stretch zones for one calendar day.",
duration: "All day · 6:00 — 22:00",
priceUsd: 18,
priceNote: "per guest / day",
image: "https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=900&q=80",
},
{
id: "gym-pt",
kind: "gym",
title: "Personal training",
description: "One-on-one session tailored to your goals — form, intensity, and recovery.",
duration: "45 minutes",
priceUsd: 55,
priceNote: "per session",
image: "https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=900&q=80",
},
{
id: "gym-hiit",
kind: "gym",
title: "Small-group HIIT",
description: "High-energy class in our studio — limited spots, hotel guests priority.",
duration: "50 minutes",
priceUsd: 28,
priceNote: "per class",
image: "https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=900&q=80",
},
{
id: "spa-swedish",
kind: "spa",
title: "Signature Swedish massage",
description: "Long, flowing strokes to ease travel tension and improve circulation.",
duration: "60 minutes",
priceUsd: 85,
priceNote: "per treatment",
image: "https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=900&q=80",
},
{
id: "spa-deep",
kind: "spa",
title: "Deep tissue therapy",
description: "Targeted work for shoulders, back, and legs after long flights.",
duration: "90 minutes",
priceUsd: 125,
priceNote: "per treatment",
image: "https://images.unsplash.com/photo-1600334129128-0c9b275703e6?w=900&q=80",
},
{
id: "spa-express",
kind: "spa",
title: "Express back & neck",
description: "Focused relief when youre between meetings — clothes-on option.",
duration: "30 minutes",
priceUsd: 52,
priceNote: "per treatment",
image: "https://images.unsplash.com/photo-1519823551278-64ac92734fb1?w=900&q=80",
},
{
id: "spa-aroma",
kind: "spa",
title: "Aromatherapy ritual",
description: "Custom oil blend, warm compress, and full-body massage sequence.",
duration: "75 minutes",
priceUsd: 98,
priceNote: "per treatment",
image: "https://images.unsplash.com/photo-1540555700478-4be289fbecef?w=900&q=80",
},
{
id: "spa-couples",
kind: "spa",
title: "Couples suite ritual",
description: "Side-by-side massage in our private suite — sparkling water included.",
duration: "90 minutes",
priceUsd: 220,
priceNote: "per couple",
image: "https://images.unsplash.com/photo-1600334089648-b0d9d3028eb2?w=900&q=80",
},
];

View File

@ -3,8 +3,16 @@ export const siteConfig = {
name: "Shitaye Suite Hotel",
tagline: "The Unwinding Choice",
city: "Addis Ababa",
address:
"Prime location — geo-convenient, close to key attractions and major businesses.",
address: "Ethio China Street, Kirkos, Addis Ababa, Ethiopia",
/** Google Maps — search result embed (hotel place). */
googleMapsEmbedUrl:
"https://www.google.com/maps?q=Shitaye+Suite+Hotel+Addis+Ababa+Ethiopia&output=embed&z=16",
/** Opens Google Maps with directions to the hotel (destination preset). */
googleMapsDirectionsUrl:
"https://www.google.com/maps/dir/?api=1&destination=Shitaye+Suite+Hotel+Ethio+China+Street+Kirkos+Addis+Ababa+Ethiopia",
/** Place search — opens the hotel pin in Google Maps (not directions mode). */
googleMapsPlaceUrl:
"https://www.google.com/maps/search/?api=1&query=Shitaye+Suite+Hotel+Addis+Ababa+Ethiopia",
phones: ["+251 96 688 4400", "+251 96 688 2200", "+251 11 46 21000"],
/** Primary number shown on FAB / quick call */
primaryPhone: "+251 96 688 4400",