NobGit
public imalexnord read

Slot

Calendar-first scheduling powered by Cloudflare Workers and Google Calendar.

Languages

Repository composition by tracked source files.

TypeScript
TypeScript 79% CSS 19% SQL 2% HTML 0%
Create file Wiki Documentation
Clone
https://nobgit.com/user/imalexnord/slot.git
ssh://[email protected]:2222/user/imalexnord/slot.git

Commit

Polish booking flow and admin list

b0cb619
Alex Nord <[email protected]> 3 days ago
src/App.tsx    | 457 ++++++++++++++++++++++++++++++++++++++++-------------
 src/styles.css | 485 +++++++++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 729 insertions(+), 213 deletions(-)

Diff

diff --git a/src/App.tsx b/src/App.tsx
index 7688cf5..437c6b5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,11 +2,14 @@ import {
   ArrowLeft,
   ArrowRight,
   CalendarDays,
+  CalendarPlus,
   Check,
   ChevronLeft,
   ChevronRight,
   Clock3,
+  Copy,
   ExternalLink,
+  Inbox,
   Loader2,
   LogOut,
   MapPin,
@@ -16,7 +19,7 @@ import {
   Sparkles,
   Video,
 } from 'lucide-react';
-import { FormEvent, useEffect, useMemo, useState } from 'react';
+import { FormEvent, useEffect, useMemo, useRef, useState } from 'react';
 
 type MeetingMode = 'google_meet' | 'phone' | 'in_person' | 'decide_later';
 
@@ -34,6 +37,15 @@ type PublicConfig = {
 
 type Slot = { start: string; time: string; durations: number[] };
 
+type Confirmation = {
+  start: string;
+  end: string;
+  duration: number;
+  timezone: string;
+  meetingMode: MeetingMode;
+  meetUrl?: string | null;
+};
+
 type AppSettings = {
   title: string;
   subtitle: string;
@@ -156,6 +168,15 @@ function prettyDate(key: string, timezone: string): string {
   }).format(new Date(`${key}T12:00:00Z`));
 }
 
+function dayAriaLabel(date: Date): string {
+  return new Intl.DateTimeFormat('en-US', {
+    weekday: 'long',
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+  }).format(date);
+}
+
 function formatInstant(iso: string, timezone: string): string {
   return new Intl.DateTimeFormat('en-US', {
     timeZone: timezone,
@@ -168,6 +189,24 @@ function formatInstant(iso: string, timezone: string): string {
   }).format(new Date(iso));
 }
 
+function formatDateShort(iso: string, timezone: string): string {
+  return new Intl.DateTimeFormat('en-US', {
+    timeZone: timezone,
+    weekday: 'short',
+    month: 'short',
+    day: 'numeric',
+  }).format(new Date(iso));
+}
+
+function formatClock(iso: string, timezone: string): string {
+  return new Intl.DateTimeFormat('en-US', {
+    timeZone: timezone,
+    hour: '2-digit',
+    minute: '2-digit',
+    hourCycle: 'h23',
+  }).format(new Date(iso));
+}
+
 function monthCells(month: Date): Array<{ date: Date; current: boolean }> {
   const first = new Date(month.getFullYear(), month.getMonth(), 1, 12);
   const mondayIndex = (first.getDay() + 6) % 7;
@@ -178,13 +217,29 @@ function monthCells(month: Date): Array<{ date: Date; current: boolean }> {
   });
 }
 
-function Logo() {
-  return (
-    <a className="brand" href="/" aria-label="Slot home">
-      <span className="brand-mark">S</span>
-      <span>Slot</span>
-    </a>
-  );
+function googleCalendarUrl(title: string, conf: Confirmation): string {
+  const stamp = (iso: string) => iso.replace(/[-:]/g, '').replace(/\.\d{3}/, '');
+  const params = new URLSearchParams({
+    action: 'TEMPLATE',
+    text: title,
+    dates: `${stamp(conf.start)}/${stamp(conf.end)}`,
+  });
+  if (conf.meetUrl) params.set('details', `Google Meet: ${conf.meetUrl}`);
+  return `https://calendar.google.com/calendar/render?${params.toString()}`;
+}
+
+function useCopy(): [boolean, (value: string) => void] {
+  const [copied, setCopied] = useState(false);
+  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
+  useEffect(() => () => clearTimeout(timer.current), []);
+  function copy(value: string) {
+    navigator.clipboard?.writeText(value).then(() => {
+      setCopied(true);
+      clearTimeout(timer.current);
+      timer.current = setTimeout(() => setCopied(false), 1800);
+    });
+  }
+  return [copied, copy];
 }
 
 function PublicBooking() {
@@ -198,8 +253,9 @@ function PublicBooking() {
   const [loadingSlots, setLoadingSlots] = useState(false);
   const [booking, setBooking] = useState(false);
   const [error, setError] = useState('');
+  const [fieldErrors, setFieldErrors] = useState<{ name?: string; email?: string }>({});
   const [availabilityNotice, setAvailabilityNotice] = useState('');
-  const [confirmation, setConfirmation] = useState<{ start: string; duration: number; meetUrl?: string | null } | null>(null);
+  const [confirmation, setConfirmation] = useState<Confirmation | null>(null);
 
   useEffect(() => {
     api<PublicConfig>('/api/config')
@@ -215,6 +271,9 @@ function PublicBooking() {
   const today = config ? todayInZone(config.timezone) : dateKey(new Date());
   const maxDate = config ? dateKey(addDays(parseDateKey(today), config.bookingWindowDays)) : today;
 
+  const isSelectable = (key: string, weekday: number, current: boolean) =>
+    Boolean(config) && current && key >= today && key <= maxDate && config!.availableWeekdays.includes(weekday);
+
   async function chooseDate(key: string) {
     if (!config || key < today || key > maxDate) return;
     setSelectedDate(key);
@@ -242,23 +301,31 @@ function PublicBooking() {
     event.preventDefault();
     if (!config || !selectedSlot || !duration) return;
     const form = new FormData(event.currentTarget);
+    const name = String(form.get('name') ?? '').trim();
+    const email = String(form.get('email') ?? '').trim();
+    const nextErrors: { name?: string; email?: string } = {};
+    if (!name) nextErrors.name = 'Please add your name.';
+    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) nextErrors.email = 'Enter a valid email address.';
+    setFieldErrors(nextErrors);
+    if (Object.keys(nextErrors).length) return;
+
     setBooking(true);
     setError('');
     try {
-      const data = await api<{ booking: { start: string; duration: number; meetUrl?: string | null } }>('/api/book', {
+      const data = await api<{ booking: Confirmation }>('/api/book', {
         method: 'POST',
         body: JSON.stringify({
           start: selectedSlot.start,
           duration,
           meetingMode,
-          name: form.get('name'),
-          email: form.get('email'),
+          name,
+          email,
           phone: form.get('phone'),
           message: form.get('message'),
           website: form.get('website'),
         }),
       });
-      setConfirmation(data.booking);
+      setConfirmation({ ...data.booking, meetingMode });
     } catch (err) {
       const message = err instanceof Error ? err.message : 'Booking failed';
       setError(message);
@@ -275,7 +342,6 @@ function PublicBooking() {
   }
 
   const activeConfig = config;
-  const chosenDurations = selectedSlot?.durations ?? [];
   const bookingStep = confirmation
     ? 'confirmed'
     : !selectedSlot
@@ -299,10 +365,24 @@ function PublicBooking() {
     setSelectedSlot(null);
     setDuration(null);
     setConfirmation(null);
+    setFieldErrors({});
     setError('');
   }
 
   function renderTimesView() {
+    const groups: Array<{ label: string; items: Slot[] }> = [
+      { label: 'Morning', items: [] },
+      { label: 'Afternoon', items: [] },
+      { label: 'Evening', items: [] },
+    ];
+    for (const slot of slots) {
+      const hour = Number(slot.time.slice(0, 2));
+      const bucket = hour < 12 ? 0 : hour < 17 ? 1 : 2;
+      groups[bucket].items.push(slot);
+    }
+    const nonEmpty = groups.filter((group) => group.items.length);
+    const showLabels = slots.length > 6 && nonEmpty.length > 1;
+
     return (
       <div className="panel-view" key="times">
         <div className="selection-heading">
@@ -311,25 +391,39 @@ function PublicBooking() {
         </div>
 
         {loadingSlots ? (
-          <div className="loading-row"><Loader2 className="spin" /> Checking Google Calendar...</div>
+          <>
+            <div className="loading-row"><Loader2 className="spin" /> Checking availability…</div>
+            <div className="slot-skeleton" aria-hidden="true">
+              {Array.from({ length: 9 }, (_, i) => <span key={i} />)}
+            </div>
+          </>
         ) : availabilityNotice ? (
-          <div className="no-slots">{availabilityNotice}</div>
+          <div className="no-slots" role="status">{availabilityNotice}</div>
         ) : slots.length === 0 ? (
-          <div className="no-slots">Nothing open on this day.</div>
+          <div className="no-slots" role="status">Nothing open on this day. Try another date.</div>
         ) : (
-          <div className="time-grid time-list">
-            {slots.map((slot) => (
-              <button
-                key={slot.start}
-                className={`time-button ${selectedSlot?.start === slot.start ? 'active' : ''}`}
-                onClick={() => {
-                  setSelectedSlot(slot);
-                  setDuration(null);
-                  setConfirmation(null);
-                }}
-              >
-                {slot.time}
-              </button>
+          <div className="time-scroll">
+            {nonEmpty.map((group) => (
+              <div className="time-group" key={group.label}>
+                {showLabels && <span className="time-group-label">{group.label}</span>}
+                <div className="time-grid">
+                  {group.items.map((slot) => (
+                    <button
+                      key={slot.start}
+                      type="button"
+                      className={`time-button ${selectedSlot?.start === slot.start ? 'active' : ''}`}
+                      aria-pressed={selectedSlot?.start === slot.start}
+                      onClick={() => {
+                        setSelectedSlot(slot);
+                        setDuration(null);
+                        setConfirmation(null);
+                      }}
+                    >
+                      {slot.time}
+                    </button>
+                  ))}
+                </div>
+              </div>
             ))}
           </div>
         )}
@@ -339,6 +433,8 @@ function PublicBooking() {
 
   function renderDurationView() {
     if (!selectedSlot) return null;
+    const available = new Set(selectedSlot.durations);
+    const hasDisabled = activeConfig.durations.some((minutes) => !available.has(minutes));
     return (
       <div className="panel-view" key="duration">
         <button className="panel-back" type="button" onClick={changeTime}>
@@ -351,17 +447,27 @@ function PublicBooking() {
         <div className="duration-picker-view">
           <p className="step-label">03 · HOW LONG?</p>
           <div className="duration-grid">
-            {chosenDurations.map((minutes) => (
-              <button
-                key={minutes}
-                className={`duration-button ${duration === minutes ? 'active' : ''}`}
-                onClick={() => setDuration(minutes)}
-              >
-                <strong>{minutes}</strong>
-                <span>min</span>
-              </button>
-            ))}
+            {activeConfig.durations.map((minutes) => {
+              const enabled = available.has(minutes);
+              return (
+                <button
+                  key={minutes}
+                  type="button"
+                  className={`duration-button ${duration === minutes ? 'active' : ''}`}
+                  disabled={!enabled}
+                  aria-pressed={duration === minutes}
+                  title={enabled ? undefined : 'Runs into a later booking'}
+                  onClick={() => setDuration(minutes)}
+                >
+                  <strong>{minutes}</strong>
+                  <span>min</span>
+                </button>
+              );
+            })}
           </div>
+          {hasDisabled && (
+            <p className="duration-hint">Greyed-out lengths would overlap a later booking.</p>
+          )}
         </div>
       </div>
     );
@@ -378,23 +484,48 @@ function PublicBooking() {
           <span>{prettyDate(selectedDate, activeConfig.timezone)}</span>
           <strong>{selectedSlot.time} · {duration} min</strong>
         </div>
-        <form className="details-form compact-details" onSubmit={submitBooking}>
+        <form className="details-form compact-details" onSubmit={submitBooking} noValidate>
           <p className="step-label">04 · YOUR DETAILS</p>
           <div className="form-row">
-            <label>Name<input name="name" required autoComplete="name" placeholder="Your name" /></label>
-            <label>Email<input name="email" type="email" required autoComplete="email" placeholder="[email protected]" /></label>
+            <label>Name
+              <input
+                name="name"
+                required
+                autoComplete="name"
+                placeholder="Your name"
+                aria-invalid={fieldErrors.name ? 'true' : undefined}
+                aria-describedby={fieldErrors.name ? 'name-error' : undefined}
+                onInput={() => fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))}
+              />
+              {fieldErrors.name && <span className="field-error" id="name-error">{fieldErrors.name}</span>}
+            </label>
+            <label>Email
+              <input
+                name="email"
+                type="email"
+                required
+                autoComplete="email"
+                placeholder="[email protected]"
+                aria-invalid={fieldErrors.email ? 'true' : undefined}
+                aria-describedby={fieldErrors.email ? 'email-error' : undefined}
+                onInput={() => fieldErrors.email && setFieldErrors((e) => ({ ...e, email: undefined }))}
+              />
+              {fieldErrors.email && <span className="field-error" id="email-error">{fieldErrors.email}</span>}
+            </label>
           </div>
 
           {activeConfig.allowMeetingModes.length > 1 && (
             <div className="mode-picker">
               <span className="field-label">Meeting</span>
-              <div className="mode-options">
+              <div className="mode-options" role="radiogroup" aria-label="Meeting method">
                 {activeConfig.allowMeetingModes.map((mode) => {
                   const Icon = modeMeta[mode].icon;
                   return (
                     <button
                       key={mode}
                       type="button"
+                      role="radio"
+                      aria-checked={meetingMode === mode}
                       className={`mode-option ${meetingMode === mode ? 'active' : ''}`}
                       onClick={() => setMeetingMode(mode)}
                     >
@@ -415,7 +546,7 @@ function PublicBooking() {
           </label>
           <label className="honeypot" aria-hidden="true">Website<input name="website" tabIndex={-1} autoComplete="off" /></label>
 
-          {error && <div className="error-banner">{error}</div>}
+          {error && <div className="error-banner" role="alert">{error}</div>}
           <button className="primary-button" type="submit" disabled={booking}>
             {booking ? <><Loader2 className="spin" size={18} /> Booking...</> : <>Book this slot <ArrowRight size={18} /></>}
           </button>
@@ -426,21 +557,7 @@ function PublicBooking() {
 
   function renderConfirmationView() {
     if (!confirmation) return null;
-    return (
-      <div className="panel-view confirmation-panel" key="confirmed">
-        <span className="success-icon"><Check /></span>
-        <p className="eyebrow">YOU'RE BOOKED</p>
-        <h2>See you then.</h2>
-        <p className="confirmation-time">{formatInstant(confirmation.start, activeConfig.timezone)} · {confirmation.duration} min</p>
-        <p className="muted">A calendar invitation has been sent to your email.</p>
-        {confirmation.meetUrl && (
-          <a className="primary-button inline-button" href={confirmation.meetUrl} target="_blank" rel="noreferrer">
-            Open Google Meet <ExternalLink size={16} />
-          </a>
-        )}
-        <button className="text-button" type="button" onClick={bookAnotherTime}>Book another time</button>
-      </div>
-    );
+    return <ConfirmationView title={activeConfig.title} confirmation={confirmation} onBookAnother={bookAnotherTime} />;
   }
 
   function renderRightPanel() {
@@ -490,32 +607,35 @@ function PublicBooking() {
           </div>
         </aside>
 
-        <section className="calendar-panel">
+        <section className="calendar-panel" aria-label="Choose a date">
           <div className="calendar-toolbar">
             <div>
               <p className="step-label">01 · PICK A DAY</p>
-              <h2>{month.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}</h2>
+              <h2 aria-live="polite">{month.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}</h2>
             </div>
             <div className="month-buttons">
-              <button aria-label="Previous month" onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1))}><ChevronLeft /></button>
-              <button aria-label="Next month" onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1))}><ChevronRight /></button>
+              <button type="button" aria-label="Previous month" onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1))}><ChevronLeft /></button>
+              <button type="button" aria-label="Next month" onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1))}><ChevronRight /></button>
             </div>
           </div>
 
-          <div className="weekday-grid">
+          <div className="weekday-grid" aria-hidden="true">
             {shortDayNames.map((name) => <span key={name}>{name}</span>)}
           </div>
           <div className="calendar-grid">
             {cells.map(({ date, current }) => {
               const key = dateKey(date);
-              const disabled = !current || key < today || key > maxDate || !activeConfig.availableWeekdays.includes(date.getDay());
+              const selectable = isSelectable(key, date.getDay(), current);
               const selected = key === selectedDate;
               const isToday = key === today;
               return (
                 <button
                   key={key}
+                  type="button"
                   className={`day ${current ? '' : 'outside'} ${selected ? 'selected' : ''} ${isToday ? 'today' : ''}`}
-                  disabled={disabled}
+                  disabled={!selectable}
+                  aria-pressed={selected}
+                  aria-label={dayAriaLabel(date)}
                   onClick={() => chooseDate(key)}
                 >
                   <span>{date.getDate()}</span>
@@ -526,10 +646,10 @@ function PublicBooking() {
           </div>
         </section>
 
-        <aside className="selection-panel">{renderRightPanel()}</aside>
+        <aside className="selection-panel" aria-live="polite">{renderRightPanel()}</aside>
       </section>
 
-      {error && !(selectedSlot && duration) && <div className="floating-error">{error}</div>}
+      {error && !(selectedSlot && duration) && <div className="floating-error" role="alert">{error}</div>}
       <footer>
         <a className="powered-by" href="https://www.nobgit.com/user/imalexnord/slot" target="_blank" rel="noreferrer">Powered by Slot</a>
       </footer>
@@ -537,11 +657,97 @@ function PublicBooking() {
   );
 }
 
+function ConfirmationView({ title, confirmation, onBookAnother }: { title: string; confirmation: Confirmation; onBookAnother: () => void }) {
+  const [copied, copy] = useCopy();
+  const tz = confirmation.timezone;
+  return (
+    <div className="panel-view confirmation-panel panel-view-scroll" key="confirmed">
+      <span className="success-icon"><Check /></span>
+      <p className="eyebrow">YOU'RE BOOKED</p>
+      <h2>See you then.</h2>
+
+      <dl className="confirmation-details">
+        <div className="confirm-row"><dt>Date</dt><dd>{formatDateShort(confirmation.start, tz)}</dd></div>
+        <div className="confirm-row"><dt>Time</dt><dd>{formatClock(confirmation.start, tz)} – {formatClock(confirmation.end, tz)}</dd></div>
+        <div className="confirm-row"><dt>Length</dt><dd>{confirmation.duration} min</dd></div>
+        <div className="confirm-row"><dt>Timezone</dt><dd>{tz}</dd></div>
+        <div className="confirm-row"><dt>Meeting</dt><dd>{modeMeta[confirmation.meetingMode].label}</dd></div>
+      </dl>
+
+      <p className="muted">A calendar invitation has been sent to your email.</p>
+
+      <div className="confirm-actions">
+        <a className="primary-button" href={googleCalendarUrl(title, confirmation)} target="_blank" rel="noreferrer">
+          <CalendarPlus size={16} /> Add to calendar
+        </a>
+        {confirmation.meetUrl && (
+          <>
+            <a className="secondary-button" href={confirmation.meetUrl} target="_blank" rel="noreferrer">
+              Open Google Meet <ExternalLink size={15} />
+            </a>
+            <button
+              type="button"
+              className={`copy-button ${copied ? 'copied' : ''}`}
+              onClick={() => copy(confirmation.meetUrl!)}
+            >
+              {copied ? <><Check size={15} /> Link copied</> : <><Copy size={15} /> Copy Meet link</>}
+            </button>
+          </>
+        )}
+        <button className="text-button" type="button" onClick={onBookAnother}>Book another time</button>
+      </div>
+    </div>
+  );
+}
+
+function BookingRowItem({ booking, timezone }: { booking: BookingRow; timezone: string }) {
+  const [copied, copy] = useCopy();
+  const isPast = Date.parse(booking.end_time) < Date.now();
+  const Icon = modeMeta[booking.meeting_mode].icon;
+  return (
+    <div className={`booking-list-row ${isPast ? 'past' : ''}`}>
+      <div className="booking-avatar" aria-hidden="true">{booking.name.slice(0, 1).toUpperCase()}</div>
+      <div className="booking-person">
+        <strong>{booking.name}</strong>
+        <span>{booking.email}</span>
+      </div>
+      <div className="booking-when">
+        <strong>{formatDateShort(booking.start_time, timezone)}</strong>
+        <span>{formatClock(booking.start_time, timezone)} – {formatClock(booking.end_time, timezone)}</span>
+      </div>
+      <div className="booking-meta">
+        <span className="booking-method"><Icon size={13} /> {modeMeta[booking.meeting_mode].label}</span>
+        <span>{booking.duration_minutes} min</span>
+      </div>
+      <div className="booking-actions">
+        <span className={`status-pill ${isPast ? 'past' : 'upcoming'}`}>{isPast ? 'Past' : 'Upcoming'}</span>
+        {booking.meet_url && (
+          <>
+            <button
+              type="button"
+              className={`icon-button ${copied ? 'copied' : ''}`}
+              aria-label="Copy Meet link"
+              title="Copy Meet link"
+              onClick={() => copy(booking.meet_url!)}
+            >
+              {copied ? <Check size={15} /> : <Copy size={15} />}
+            </button>
+            <a className="icon-button" href={booking.meet_url} target="_blank" rel="noreferrer" aria-label="Open Google Meet" title="Open Google Meet">
+              <Video size={15} />
+            </a>
+          </>
+        )}
+      </div>
+    </div>
+  );
+}
+
 function Admin() {
   const [session, setSession] = useState<AdminSession | null>(null);
   const [settings, setSettings] = useState<AppSettings | null>(null);
   const [bookings, setBookings] = useState<BookingRow[]>([]);
   const [tab, setTab] = useState<'settings' | 'bookings'>('bookings');
+  const [bookingFilter, setBookingFilter] = useState<'upcoming' | 'past'>('upcoming');
   const [theme, setTheme] = useState<ThemeId>(() => {
     const stored = localStorage.getItem('meet-theme') as ThemeId | null;
     return stored && themes.some((item) => item.id === stored) ? stored : 'true-black';
@@ -611,7 +817,7 @@ function Admin() {
           <p className="eyebrow">PRIVATE</p>
           <h1>Control your availability.</h1>
           <p>Google login is only for the calendar owner. People booking you never need an account.</p>
-          {oauthError && <div className="error-banner">{oauthError}</div>}
+          {oauthError && <div className="error-banner" role="alert">{oauthError}</div>}
           <a className="primary-button inline-button" href="/auth/google/start">Continue with Google <ArrowRight size={18} /></a>
           <a className="text-button" href="/">Back to booking page</a>
         </section>
@@ -636,6 +842,12 @@ function Admin() {
     });
   }
 
+  const activeSettings = settings;
+  const now = Date.now();
+  const upcoming = bookings.filter((b) => Date.parse(b.end_time) >= now);
+  const past = bookings.filter((b) => Date.parse(b.end_time) < now);
+  const shown = bookingFilter === 'upcoming' ? upcoming : past;
+
   return (
     <main className="admin-page">
       <header className="admin-topbar">
@@ -659,8 +871,8 @@ function Admin() {
                 <div><p className="eyebrow">SETTINGS</p><h1>Keep it simple.</h1></div>
                 <button className="primary-button compact" onClick={save} disabled={saving}>{saving ? <Loader2 className="spin" /> : <Check />} Save changes</button>
               </div>
-              {notice && <div className="success-banner">{notice}</div>}
-              {error && <div className="error-banner">{error}</div>}
+              {notice && <div className="success-banner" role="status">{notice}</div>}
+              {error && <div className="error-banner" role="alert">{error}</div>}
 
               <div className="settings-grid">
                 <section className="settings-card wide-card appearance-card">
@@ -678,6 +890,7 @@ function Admin() {
                         type="button"
                         key={item.id}
                         className={`theme-option ${theme === item.id ? 'active' : ''}`}
+                        aria-pressed={theme === item.id}
                         onClick={() => setTheme(item.id)}
                       >
                         <span className={`theme-preview theme-preview-${item.id}`} aria-hidden="true">
@@ -698,16 +911,16 @@ function Admin() {
                   <div className="card-heading"><h2>Availability</h2><p>Your Google Calendar blocks busy time inside these hours.</p></div>
                   <div className="availability-list">
                     {dayNames.map((name, day) => {
-                      const interval = settings.availability[String(day)]?.[0];
+                      const interval = activeSettings.availability[String(day)]?.[0];
                       const enabled = Boolean(interval);
                       return (
                         <div className="availability-row" key={name}>
                           <label className="switch-row"><input type="checkbox" checked={enabled} onChange={(e) => setDay(day, { enabled: e.target.checked })} /><span>{name}</span></label>
                           {enabled ? (
                             <div className="time-range">
-                              <input type="time" value={interval.start} onChange={(e) => setDay(day, { start: e.target.value })} />
+                              <input type="time" aria-label={`${name} start`} value={interval.start} onChange={(e) => setDay(day, { start: e.target.value })} />
                               <span>to</span>
-                              <input type="time" value={interval.end} onChange={(e) => setDay(day, { end: e.target.value })} />
+                              <input type="time" aria-label={`${name} end`} value={interval.end} onChange={(e) => setDay(day, { end: e.target.value })} />
                             </div>
                           ) : <span className="unavailable">Unavailable</span>}
                         </div>
@@ -718,17 +931,17 @@ function Admin() {
 
                 <section className="settings-card">
                   <div className="card-heading"><h2>Page</h2><p>What visitors see first.</p></div>
-                  <label>Title<input value={settings.title} onChange={(e) => setSettings({ ...settings, title: e.target.value })} /></label>
-                  <label>Subtitle<textarea rows={3} value={settings.subtitle} onChange={(e) => setSettings({ ...settings, subtitle: e.target.value })} /></label>
+                  <label>Title<input value={activeSettings.title} onChange={(e) => setSettings({ ...activeSettings, title: e.target.value })} /></label>
+                  <label>Subtitle<textarea rows={3} value={activeSettings.subtitle} onChange={(e) => setSettings({ ...activeSettings, subtitle: e.target.value })} /></label>
                   <label>Timezone
                     <div className="timezone-control">
-                      <select value={settings.timezone} onChange={(e) => setSettings({ ...settings, timezone: e.target.value })}>
-                        {!timezoneOptions.includes(settings.timezone) && <option value={settings.timezone}>{settings.timezone}</option>}
+                      <select value={activeSettings.timezone} onChange={(e) => setSettings({ ...activeSettings, timezone: e.target.value })}>
+                        {!timezoneOptions.includes(activeSettings.timezone) && <option value={activeSettings.timezone}>{activeSettings.timezone}</option>}
                         {timezoneOptions.map((zone) => <option key={zone} value={zone}>{zone}</option>)}
                       </select>
                       <button
                         type="button"
-                        onClick={() => setSettings({ ...settings, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || settings.timezone })}
+                        onClick={() => setSettings({ ...activeSettings, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || activeSettings.timezone })}
                       >
                         Use this browser
                       </button>
@@ -739,23 +952,23 @@ function Admin() {
                 <section className="settings-card">
                   <div className="card-heading"><h2>Booking rules</h2><p>Enough control, no dashboard archaeology.</p></div>
                   <div className="two-col-fields">
-                    <label>Minimum notice<input type="number" min="0" value={settings.minimumNoticeMinutes} onChange={(e) => setSettings({ ...settings, minimumNoticeMinutes: Number(e.target.value) })} /><small>minutes</small></label>
-                    <label>Book ahead<input type="number" min="1" max="365" value={settings.bookingWindowDays} onChange={(e) => setSettings({ ...settings, bookingWindowDays: Number(e.target.value) })} /><small>days</small></label>
-                    <label>Buffer before<input type="number" min="0" value={settings.bufferBeforeMinutes} onChange={(e) => setSettings({ ...settings, bufferBeforeMinutes: Number(e.target.value) })} /><small>minutes</small></label>
-                    <label>Buffer after<input type="number" min="0" value={settings.bufferAfterMinutes} onChange={(e) => setSettings({ ...settings, bufferAfterMinutes: Number(e.target.value) })} /><small>minutes</small></label>
+                    <label>Minimum notice<input type="number" min="0" value={activeSettings.minimumNoticeMinutes} onChange={(e) => setSettings({ ...activeSettings, minimumNoticeMinutes: Number(e.target.value) })} /><small>minutes</small></label>
+                    <label>Book ahead<input type="number" min="1" max="365" value={activeSettings.bookingWindowDays} onChange={(e) => setSettings({ ...activeSettings, bookingWindowDays: Number(e.target.value) })} /><small>days</small></label>
+                    <label>Buffer before<input type="number" min="0" value={activeSettings.bufferBeforeMinutes} onChange={(e) => setSettings({ ...activeSettings, bufferBeforeMinutes: Number(e.target.value) })} /><small>minutes</small></label>
+                    <label>Buffer after<input type="number" min="0" value={activeSettings.bufferAfterMinutes} onChange={(e) => setSettings({ ...activeSettings, bufferAfterMinutes: Number(e.target.value) })} /><small>minutes</small></label>
                   </div>
                   <span className="field-label">Durations</span>
                   <div className="checkbox-chips">
                     {[15, 30, 45, 60, 90, 120].map((minutes) => (
-                      <label key={minutes} className={settings.durations.includes(minutes) ? 'active' : ''}>
+                      <label key={minutes} className={activeSettings.durations.includes(minutes) ? 'active' : ''}>
                         <input
                           type="checkbox"
-                          checked={settings.durations.includes(minutes)}
+                          checked={activeSettings.durations.includes(minutes)}
                           onChange={(e) => setSettings({
-                            ...settings,
+                            ...activeSettings,
                             durations: e.target.checked
-                              ? [...settings.durations, minutes].sort((a, b) => a - b)
-                              : settings.durations.filter((value) => value !== minutes),
+                              ? [...activeSettings.durations, minutes].sort((a, b) => a - b)
+                              : activeSettings.durations.filter((value) => value !== minutes),
                           })}
                         />
                         {minutes} min
@@ -782,53 +995,75 @@ function Admin() {
                     </div>
                   </div>
                   <div className="calendar-settings-row">
-                    <label>Create events in<input value={settings.calendarId} onChange={(e) => setSettings({ ...settings, calendarId: e.target.value })} placeholder="primary" /></label>
-                    <label>Calendars that block time<input value={settings.busyCalendarIds.join(', ')} onChange={(e) => setSettings({ ...settings, busyCalendarIds: e.target.value.split(',').map((v) => v.trim()).filter(Boolean) })} placeholder="primary" /></label>
+                    <label>Create events in<input value={activeSettings.calendarId} onChange={(e) => setSettings({ ...activeSettings, calendarId: e.target.value })} placeholder="primary" /></label>
+                    <label>Calendars that block time<input value={activeSettings.busyCalendarIds.join(', ')} onChange={(e) => setSettings({ ...activeSettings, busyCalendarIds: e.target.value.split(',').map((v) => v.trim()).filter(Boolean) })} placeholder="primary" /></label>
                   </div>
                   <label>Default meeting type
-                    <select value={settings.defaultMeetingMode} onChange={(e) => setSettings({ ...settings, defaultMeetingMode: e.target.value as MeetingMode })}>
-                      {settings.allowMeetingModes.map((mode) => <option value={mode} key={mode}>{modeMeta[mode].label}</option>)}
+                    <select value={activeSettings.defaultMeetingMode} onChange={(e) => setSettings({ ...activeSettings, defaultMeetingMode: e.target.value as MeetingMode })}>
+                      {activeSettings.allowMeetingModes.map((mode) => <option value={mode} key={mode}>{modeMeta[mode].label}</option>)}
                     </select>
                   </label>
                   <span className="field-label">Allowed meeting types</span>
                   <div className="mode-admin-grid">
                     {(Object.keys(modeMeta) as MeetingMode[]).map((mode) => {
                       const Icon = modeMeta[mode].icon;
-                      const active = settings.allowMeetingModes.includes(mode);
+                      const active = activeSettings.allowMeetingModes.includes(mode);
                       return (
                         <label key={mode} className={active ? 'active' : ''}>
-                          <input type="checkbox" checked={active} disabled={active && settings.allowMeetingModes.length === 1} onChange={(e) => setSettings({
-                            ...settings,
+                          <input type="checkbox" checked={active} disabled={active && activeSettings.allowMeetingModes.length === 1} onChange={(e) => setSettings({
+                            ...activeSettings,
                             allowMeetingModes: e.target.checked
-                              ? [...settings.allowMeetingModes, mode]
-                              : settings.allowMeetingModes.filter((value) => value !== mode),
-                            defaultMeetingMode: !e.target.checked && settings.defaultMeetingMode === mode
-                              ? (settings.allowMeetingModes.find((value) => value !== mode) ?? settings.defaultMeetingMode)
-                              : settings.defaultMeetingMode,
+                              ? [...activeSettings.allowMeetingModes, mode]
+                              : activeSettings.allowMeetingModes.filter((value) => value !== mode),
+                            defaultMeetingMode: !e.target.checked && activeSettings.defaultMeetingMode === mode
+                              ? (activeSettings.allowMeetingModes.find((value) => value !== mode) ?? activeSettings.defaultMeetingMode)
+                              : activeSettings.defaultMeetingMode,
                           })} />
                           <Icon size={18} /><span>{modeMeta[mode].label}</span>
                         </label>
                       );
                     })}
                   </div>
-                  {settings.allowMeetingModes.includes('in_person') && (
-                    <label>In-person location<input value={settings.inPersonLocation} onChange={(e) => setSettings({ ...settings, inPersonLocation: e.target.value })} placeholder="Office, café, address…" /></label>
+                  {activeSettings.allowMeetingModes.includes('in_person') && (
+                    <label>In-person location<input value={activeSettings.inPersonLocation} onChange={(e) => setSettings({ ...activeSettings, inPersonLocation: e.target.value })} placeholder="Office, café, address…" /></label>
                   )}
                 </section>
               </div>
             </>
           ) : (
             <>
-              <div className="admin-heading"><div><p className="eyebrow">BOOKINGS</p><h1>Recent meetings.</h1></div></div>
+              <div className="admin-heading"><div><p className="eyebrow">BOOKINGS</p><h1>Your meetings.</h1></div></div>
+              {error && <div className="error-banner" role="alert">{error}</div>}
+              <div className="booking-tabs" role="tablist" aria-label="Filter bookings">
+                <button
+                  role="tab"
+                  aria-selected={bookingFilter === 'upcoming'}
+                  className={`booking-tab ${bookingFilter === 'upcoming' ? 'active' : ''}`}
+                  onClick={() => setBookingFilter('upcoming')}
+                >
+                  Upcoming <span className="count">{upcoming.length}</span>
+                </button>
+                <button
+                  role="tab"
+                  aria-selected={bookingFilter === 'past'}
+                  className={`booking-tab ${bookingFilter === 'past' ? 'active' : ''}`}
+                  onClick={() => setBookingFilter('past')}
+                >
+                  Past <span className="count">{past.length}</span>
+                </button>
+              </div>
               <section className="booking-list-card">
-                {bookings.length === 0 ? <div className="empty-list">No bookings yet.</div> : bookings.map((booking) => (
-                  <div className="booking-list-row" key={booking.id}>
-                    <div className="booking-avatar">{booking.name.slice(0, 1).toUpperCase()}</div>
-                    <div className="booking-person"><strong>{booking.name}</strong><span>{booking.email}</span></div>
-                    <div className="booking-when"><strong>{formatInstant(booking.start_time, settings.timezone)}</strong><span>{booking.duration_minutes} min · {modeMeta[booking.meeting_mode].label}</span></div>
-                    {booking.meet_url ? <a href={booking.meet_url} target="_blank" rel="noreferrer"><Video size={17} /> Meet</a> : <span />}
+                {shown.length === 0 ? (
+                  <div className="empty-list">
+                    <Inbox size={26} />
+                    <strong>{bookingFilter === 'upcoming' ? 'No upcoming bookings' : 'No past bookings'}</strong>
+                    <span>{bookingFilter === 'upcoming' ? 'New bookings from your calendar page will show up here.' : 'Completed meetings will be listed here.'}</span>
                   </div>
-                ))}
+                ) : (
+                  shown.map((booking) => (
+                    <BookingRowItem key={booking.id} booking={booking} timezone={activeSettings.timezone} />
+                  ))
+                )}
               </section>
             </>
           )}
diff --git a/src/styles.css b/src/styles.css
index 2b2e73a..69a4516 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -17,6 +17,20 @@
   --success: #217a4f;
   --danger: #a13b3b;
   --shadow: 0 24px 70px rgba(18, 19, 23, 0.08);
+
+  /* Shared design tokens (theme-independent). */
+  --radius-pill: 999px;
+  --shell-radius: 24px;
+  --radius-lg: 20px;
+  --radius-md: 14px;
+  --radius-sm: 11px;
+  --radius-xs: 9px;
+  --control-h: 42px;
+  --control-h-sm: 36px;
+  /* Selected surfaces: a slightly softened off-white/off-ink that still reads high-contrast. */
+  --selected-bg: color-mix(in srgb, var(--text) 90%, var(--muted));
+  --selected-text: var(--bg);
+  --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 38%, transparent);
 }
 
 @media (prefers-color-scheme: dark) {
@@ -194,23 +208,45 @@
 * { box-sizing: border-box; }
 html, body, #root {
   width: 100%;
-  height: 100%;
+  min-height: 100%;
   margin: 0;
 }
 html { background: var(--bg); }
 body {
   min-width: 320px;
-  overflow: hidden;
+  /* Pages scroll by default (the admin can be taller than the viewport). */
+  overflow-x: hidden;
+  overflow-y: auto;
   background: var(--bg);
   color: var(--text);
 }
-button, input, textarea { font: inherit; }
+/* The public booking view is an intentional single-screen; lock it on desktop
+   only, so the scrollable admin and the stacked mobile layouts are unaffected. */
+@media (min-width: 851px) {
+  body:has(.booking-page) { overflow: hidden; height: 100%; }
+}
+button, input, textarea, select { font: inherit; }
 button, a { -webkit-tap-highlight-color: transparent; }
 button { color: inherit; }
 a { color: inherit; text-decoration: none; }
 
 ::selection { background: var(--text); color: var(--bg); }
 
+.visually-hidden {
+  position: absolute !important;
+  width: 1px; height: 1px;
+  padding: 0; margin: -1px;
+  overflow: hidden; clip: rect(0 0 0 0);
+  white-space: nowrap; border: 0;
+}
+
+/* Consistent, understated focus ring for keyboard users everywhere. */
+:where(button, a, input, textarea, select, [tabindex]):focus-visible {
+  outline: none;
+  box-shadow: var(--focus-ring);
+  border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
+}
+
 .center-screen {
   min-height: 100vh;
   display: grid;
@@ -236,7 +272,7 @@ a { color: inherit; text-decoration: none; }
   height: 29px;
   display: grid;
   place-items: center;
-  border-radius: 9px;
+  border-radius: var(--radius-xs);
   background: var(--text);
   color: var(--bg);
   font-size: 13px;
@@ -265,9 +301,9 @@ a { color: inherit; text-decoration: none; }
 .topbar {
   position: absolute;
   top: 28px;
-  left: 26px;
-  right: 26px;
-  width: calc(100% - 52px);
+  left: 30px;
+  right: 30px;
+  width: calc(100% - 60px);
   margin: 0 auto;
   justify-content: flex-end;
 }
@@ -279,37 +315,40 @@ a { color: inherit; text-decoration: none; }
   font-size: 12px;
   border: 1px solid var(--border);
   background: color-mix(in srgb, var(--surface) 78%, transparent);
-  padding: 8px 11px;
-  border-radius: 999px;
+  padding: 8px 12px;
+  border-radius: var(--radius-pill);
 }
 
 .booking-shell {
-  width: min(1180px, calc(100vw - 32px));
-  height: min(760px, calc(100dvh - 88px));
+  width: min(1200px, calc(100vw - 32px));
+  height: min(800px, calc(100dvh - 92px));
   max-width: calc(100vw - 32px);
-  max-height: calc(100dvh - 88px);
+  max-height: calc(100dvh - 92px);
   margin: 0;
   display: grid;
-  grid-template-columns: minmax(230px, .78fr) minmax(500px, 1.55fr) minmax(270px, .88fr);
+  grid-template-columns: minmax(250px, .82fr) minmax(500px, 1.5fr) minmax(300px, .92fr);
   min-height: 0;
   border: 1px solid var(--border);
-  border-radius: 26px;
+  border-radius: var(--shell-radius);
   background: var(--surface);
   overflow: hidden;
   box-shadow: var(--shadow);
+  transition: grid-template-columns 340ms cubic-bezier(.4, 0, .2, 1);
 }
 .booking-shell > * + * { border-left: 1px solid var(--border); }
 
+
 .intro-panel {
   min-height: 0;
-  padding: 48px 34px 36px;
+  padding: 52px 38px 40px;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
+  gap: 28px;
   background: linear-gradient(160deg, var(--surface) 0%, var(--surface-soft) 130%);
 }
 .eyebrow, .step-label {
-  margin: 0 0 11px;
+  margin: 0 0 12px;
   color: var(--faint);
   font-weight: 750;
   font-size: 10px;
@@ -317,14 +356,14 @@ a { color: inherit; text-decoration: none; }
 }
 .intro-panel h1, .confirmation-card h1, .admin-login-card h1, .admin-heading h1 {
   margin: 0;
-  font-size: clamp(32px, 3vw, 52px);
+  font-size: clamp(34px, 3.1vw, 56px);
   line-height: .98;
   letter-spacing: -0.055em;
   font-weight: 680;
 }
 .intro-copy {
-  max-width: 390px;
-  margin: 20px 0 0;
+  max-width: 400px;
+  margin: 22px 0 0;
   color: var(--muted);
   line-height: 1.6;
   font-size: 15px;
@@ -339,37 +378,38 @@ a { color: inherit; text-decoration: none; }
   font-size: 12px;
   line-height: 1.5;
 }
-.intro-note svg { margin-top: 2px; color: var(--text); }
+.intro-note svg { margin-top: 2px; color: var(--text); flex-shrink: 0; }
 .intro-note div { display: grid; gap: 4px; }
 .intro-note strong { color: var(--text); font-size: 12px; }
 
 .calendar-panel {
   min-height: 0;
-  padding: 38px 40px 42px;
+  padding: 42px 44px 46px;
   display: flex;
   flex-direction: column;
 }
-.calendar-toolbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 22px; margin-bottom: 34px; }
+.calendar-toolbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 22px; margin-bottom: 30px; }
 .calendar-toolbar h2, .selection-heading h2, .drawer-summary h2, .settings-card h2 {
   margin: 0;
-  font-size: 22px;
+  font-size: 23px;
   letter-spacing: -0.035em;
 }
-.month-buttons { display: flex; gap: 7px; }
+.month-buttons { display: flex; gap: 8px; }
 .month-buttons button, .admin-user button {
-  width: 36px;
-  height: 36px;
-  border-radius: 11px;
+  width: var(--control-h);
+  height: var(--control-h);
+  border-radius: var(--radius-sm);
   border: 1px solid var(--border);
   background: var(--surface);
   display: inline-grid;
   place-items: center;
   cursor: pointer;
+  transition: background 120ms ease, border-color 120ms ease;
 }
-.month-buttons button:hover { background: var(--surface-soft); }
-.month-buttons svg { width: 17px; }
+.month-buttons button:hover { background: var(--surface-soft); border-color: color-mix(in srgb, var(--text) 25%, var(--border)); }
+.month-buttons svg { width: 18px; }
 .weekday-grid, .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
-.weekday-grid { margin-bottom: 9px; }
+.weekday-grid { margin-bottom: 10px; }
 .weekday-grid span {
   text-align: center;
   color: var(--faint);
@@ -382,13 +422,13 @@ a { color: inherit; text-decoration: none; }
   flex: 1;
   min-height: 0;
   grid-template-rows: repeat(6, minmax(0, 1fr));
-  gap: 7px;
+  gap: 8px;
 }
 .day {
   min-height: 0;
   border: 1px solid transparent;
   background: transparent;
-  border-radius: 14px;
+  border-radius: var(--radius-md);
   cursor: pointer;
   display: flex;
   flex-direction: column;
@@ -396,34 +436,46 @@ a { color: inherit; text-decoration: none; }
   justify-content: center;
   gap: 3px;
   position: relative;
+  color: inherit;
   transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
 }
-.day span { font-size: 13px; font-weight: 620; }
-.day small { font-size: 8px; color: var(--faint); }
+.day span { font-size: 14px; font-weight: 620; }
+.day small {
+  font-size: 8px;
+  letter-spacing: .06em;
+  text-transform: uppercase;
+  color: var(--faint);
+  font-weight: 700;
+}
 .day:hover:not(:disabled) { background: var(--surface-soft); border-color: var(--border); transform: translateY(-1px); }
-.day.selected { background: var(--text); color: var(--bg); border-color: var(--text); }
-.day.selected small { color: color-mix(in srgb, var(--bg) 70%, transparent); }
+.day.selected { background: var(--selected-bg); color: var(--selected-text); border-color: var(--selected-bg); }
+.day.selected small { color: color-mix(in srgb, var(--selected-text) 70%, transparent); }
+/* Today: a subtle accent label rather than a competing outline. */
 .day.today:not(.selected) { border-color: var(--border); }
-.day.outside { opacity: .2; }
+.day.today:not(.selected) small { color: var(--accent); }
+.day.outside { opacity: .18; }
 .day:disabled { cursor: default; opacity: .25; }
-.day.outside:disabled { opacity: .12; }
+.day.outside:disabled { opacity: .1; }
 
 .selection-panel {
-  padding: 38px 34px;
+  padding: 42px 36px;
   background: color-mix(in srgb, var(--surface-soft) 52%, var(--surface));
   overflow: hidden;
   min-height: 0;
   display: flex;
   flex-direction: column;
+  transition: opacity 240ms ease, padding 340ms cubic-bezier(.4, 0, .2, 1), border-left-width 340ms cubic-bezier(.4, 0, .2, 1);
 }
-.selection-heading { margin-bottom: 25px; }
-.selection-heading h2 { font-size: 19px; }
+.selection-heading { margin-bottom: 24px; }
+.selection-heading h2 { font-size: 20px; }
 .panel-view {
   height: 100%;
   min-height: 0;
   display: flex;
   flex-direction: column;
+  animation: panel-in 220ms cubic-bezier(.4, 0, .2, 1);
 }
+@keyframes panel-in { from { opacity: 0; transform: translateX(8px); } }
 .panel-view-scroll {
   overflow-y: auto;
   padding-right: 4px;
@@ -436,20 +488,23 @@ a { color: inherit; text-decoration: none; }
   display: inline-flex;
   align-items: center;
   gap: 7px;
-  padding: 0 0 16px;
+  padding: 4px 6px 4px 4px;
+  margin: 0 0 14px -4px;
+  border-radius: var(--radius-xs);
   cursor: pointer;
   font-size: 12px;
   font-weight: 720;
+  transition: color 120ms ease, background 120ms ease;
 }
-.panel-back:hover { color: var(--text); }
+.panel-back:hover { color: var(--text); background: var(--surface-soft); }
 .selected-summary {
   border: 1px solid var(--border);
-  border-radius: 14px;
+  border-radius: var(--radius-md);
   background: var(--surface);
-  padding: 14px;
+  padding: 14px 15px;
   display: grid;
   gap: 5px;
-  margin-bottom: 28px;
+  margin-bottom: 26px;
 }
 .selected-summary span {
   color: var(--muted);
@@ -481,42 +536,84 @@ a { color: inherit; text-decoration: none; }
 .subtle-empty svg { opacity: .45; }
 .loading-row, .no-slots { color: var(--muted); font-size: 13px; display: flex; gap: 9px; align-items: center; padding: 18px 0; }
 .loading-row svg { width: 16px; }
-.time-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
-.time-list {
+
+/* Availability skeletons keep the panel from collapsing while loading. */
+.slot-skeleton {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+}
+.slot-skeleton span {
+  height: 40px;
+  border-radius: var(--radius-sm);
+  background: linear-gradient(100deg, var(--surface-soft) 30%, color-mix(in srgb, var(--surface-strong) 70%, var(--surface-soft)) 50%, var(--surface-soft) 70%);
+  background-size: 220% 100%;
+  animation: skeleton 1.2s ease-in-out infinite;
+}
+@keyframes skeleton { to { background-position: -220% 0; } }
+
+.time-scroll {
   min-height: 0;
   flex: 1;
   overflow-y: auto;
-  align-content: start;
   padding-right: 4px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
 }
+.time-group { display: grid; gap: 9px; }
+.time-group-label {
+  color: var(--faint);
+  font-size: 9px;
+  font-weight: 760;
+  letter-spacing: .13em;
+  text-transform: uppercase;
+}
+.time-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; align-content: start; }
 .time-button {
   border: 1px solid var(--border);
   background: var(--surface);
-  border-radius: 11px;
+  border-radius: var(--radius-sm);
   padding: 11px 8px;
   cursor: pointer;
+  color: inherit;
   font-weight: 650;
   font-size: 12px;
-  transition: 120ms ease;
+  font-variant-numeric: tabular-nums;
+  transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
 }
-.time-button:hover { border-color: color-mix(in srgb, var(--text) 45%, var(--border)); }
-.time-button.active { background: var(--text); color: var(--bg); border-color: var(--text); }
-.duration-section { border-top: 1px solid var(--border); margin-top: 28px; padding-top: 25px; }
+.time-button:hover { border-color: color-mix(in srgb, var(--text) 45%, var(--border)); background: var(--surface-soft); }
+.time-button.active { background: var(--selected-bg); color: var(--selected-text); border-color: var(--selected-bg); }
 .duration-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
 .duration-button {
   border: 1px solid var(--border);
-  border-radius: 12px;
-  padding: 12px 6px 10px;
+  border-radius: var(--radius-md);
+  padding: 13px 6px 11px;
   background: var(--surface);
   cursor: pointer;
+  color: inherit;
   display: grid;
   gap: 1px;
   text-align: center;
+  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
 }
-.duration-button strong { font-size: 16px; }
+.duration-button:hover:not(:disabled) { border-color: color-mix(in srgb, var(--text) 45%, var(--border)); transform: translateY(-1px); }
+.duration-button strong { font-size: 17px; letter-spacing: -0.02em; }
 .duration-button span { font-size: 9px; color: var(--faint); text-transform: uppercase; letter-spacing: .08em; }
-.duration-button.active { background: var(--text); color: var(--bg); border-color: var(--text); }
-.duration-button.active span { color: color-mix(in srgb, var(--bg) 65%, transparent); }
+.duration-button.active { background: var(--selected-bg); color: var(--selected-text); border-color: var(--selected-bg); }
+.duration-button.active span { color: color-mix(in srgb, var(--selected-text) 65%, transparent); }
+.duration-button:disabled {
+  cursor: not-allowed;
+  opacity: .4;
+  background: var(--surface-soft);
+}
+.duration-button:disabled strong { text-decoration: line-through; text-decoration-thickness: 1px; }
+.duration-hint {
+  margin: 12px 0 0;
+  color: var(--faint);
+  font-size: 11px;
+  line-height: 1.5;
+}
 
 .compact-details {
   gap: 11px;
@@ -539,13 +636,23 @@ input, textarea, select {
   color: var(--text);
   background: var(--surface-soft);
   border: 1px solid var(--border);
-  border-radius: 11px;
+  border-radius: var(--radius-sm);
   outline: 0;
   padding: 11px 12px;
   font-size: 13px;
   resize: vertical;
 }
 input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var(--text) 55%, var(--border)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 7%, transparent); }
+input[aria-invalid="true"], textarea[aria-invalid="true"] {
+  border-color: color-mix(in srgb, var(--danger) 60%, var(--border));
+}
+input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 15%, transparent); }
+.field-error {
+  color: var(--danger);
+  font-size: 10px;
+  font-weight: 650;
+  letter-spacing: .01em;
+}
 .optional { color: var(--faint); font-weight: 500; }
 .honeypot { position: absolute !important; left: -99999px !important; }
 .mode-picker { display: grid; gap: 8px; }
@@ -558,19 +665,24 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   text-align: left;
   border: 1px solid var(--border);
   background: var(--surface);
+  color: inherit;
   padding: 11px;
-  border-radius: 12px;
+  border-radius: var(--radius-md);
   cursor: pointer;
+  transition: border-color 120ms ease, background 120ms ease;
 }
+.mode-option:hover { border-color: color-mix(in srgb, var(--text) 35%, var(--border)); }
 .mode-option span { display: grid; gap: 2px; }
 .mode-option strong { font-size: 12px; }
 .mode-option small { color: var(--faint); font-size: 9px; line-height: 1.35; font-weight: 500; }
 .mode-option.active { border-color: var(--text); background: var(--surface-soft); }
+.mode-option svg { flex-shrink: 0; }
 
 .primary-button {
   border: 0;
-  border-radius: 12px;
-  padding: 12px 15px;
+  border-radius: var(--radius-md);
+  padding: 13px 16px;
+  min-height: var(--control-h);
   background: var(--accent);
   color: var(--accent-text);
   display: inline-flex;
@@ -580,20 +692,42 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   font-weight: 720;
   font-size: 12px;
   cursor: pointer;
+  transition: opacity 120ms ease, transform 120ms ease;
 }
 .primary-button:hover { opacity: .9; }
+.primary-button:active { transform: translateY(1px); }
 .primary-button:disabled { opacity: .5; cursor: wait; }
 .inline-button { width: fit-content; }
+.secondary-button {
+  border: 1px solid var(--border);
+  border-radius: var(--radius-md);
+  padding: 12px 15px;
+  min-height: var(--control-h);
+  background: var(--surface);
+  color: var(--text);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  font-weight: 700;
+  font-size: 12px;
+  cursor: pointer;
+  transition: border-color 120ms ease, background 120ms ease;
+}
+.secondary-button:hover { border-color: color-mix(in srgb, var(--text) 40%, var(--border)); background: var(--surface-soft); }
 .text-button {
   border: 0;
   background: transparent;
   color: var(--muted);
   font-size: 12px;
+  font-weight: 650;
   cursor: pointer;
   padding: 8px 0;
+  border-radius: var(--radius-xs);
 }
+.text-button:hover { color: var(--text); }
 .error-banner, .success-banner, .floating-error {
-  border-radius: 10px;
+  border-radius: var(--radius-xs);
   padding: 10px 12px;
   font-size: 11px;
   line-height: 1.4;
@@ -605,10 +739,12 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   left: 50%;
   bottom: 56px;
   transform: translateX(-50%);
-  width: min(1180px, calc(100vw - 32px));
+  width: min(1200px, calc(100vw - 32px));
   max-width: calc(100vw - 32px);
   margin: 0;
   z-index: 30;
+  box-shadow: var(--shadow);
+  background: color-mix(in srgb, var(--danger) 12%, var(--surface));
 }
 .booking-page footer {
   position: fixed;
@@ -646,7 +782,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   width: min(100%, 560px);
   background: var(--surface);
   border: 1px solid var(--border);
-  border-radius: 24px;
+  border-radius: var(--shell-radius);
   box-shadow: var(--shadow);
   padding: 44px;
   display: grid;
@@ -657,29 +793,72 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   height: 46px;
   display: grid;
   place-items: center;
-  border-radius: 14px;
+  border-radius: var(--radius-md);
   background: var(--surface-soft);
   margin-bottom: 6px;
 }
-.success-icon { color: var(--success); }
+.success-icon { color: var(--success); background: color-mix(in srgb, var(--success) 12%, var(--surface-soft)); }
 .confirmation-card h1, .admin-login-card h1 { font-size: 39px; }
 .confirmation-time { font-weight: 680; font-size: 14px; margin: 2px 0 0; }
 .muted, .admin-login-card p { color: var(--muted); font-size: 13px; line-height: 1.55; margin: 0; }
 .confirmation-panel {
   justify-content: center;
-  align-items: flex-start;
-  gap: 14px;
+  align-items: stretch;
+  gap: 16px;
+  text-align: left;
 }
+.confirmation-panel > .success-icon { align-self: flex-start; }
 .confirmation-panel h2 {
   margin: 0;
-  font-size: 28px;
+  font-size: 27px;
   letter-spacing: -0.04em;
 }
+/* Confirmation detail summary — the "receipt" of the booking. */
+.confirmation-details {
+  display: grid;
+  gap: 1px;
+  border: 1px solid var(--border);
+  border-radius: var(--radius-md);
+  background: var(--border);
+  overflow: hidden;
+  margin: 4px 0 2px;
+}
+.confirm-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 14px;
+  padding: 11px 14px;
+  background: var(--surface);
+  font-size: 12px;
+}
+.confirm-row dt { color: var(--muted); font-weight: 600; }
+.confirm-row dd { margin: 0; color: var(--text); font-weight: 660; text-align: right; }
+.confirm-actions { display: grid; gap: 8px; margin-top: 4px; }
+.copy-button {
+  border: 1px solid var(--border);
+  border-radius: var(--radius-md);
+  padding: 12px 15px;
+  min-height: var(--control-h);
+  background: var(--surface);
+  color: var(--text);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  font-weight: 700;
+  font-size: 12px;
+  cursor: pointer;
+  transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
+}
+.copy-button:hover { border-color: color-mix(in srgb, var(--text) 40%, var(--border)); background: var(--surface-soft); }
+.copy-button.copied { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, var(--border)); }
 
 .admin-page { min-height: 100vh; padding: 22px 26px 40px; }
 .admin-topbar { max-width: 1380px; margin-bottom: 24px; justify-content: flex-end; }
 .admin-user { display: flex; align-items: center; gap: 13px; color: var(--muted); font-size: 11px; }
-.admin-user button { width: auto; padding: 0 11px; display: inline-flex; gap: 7px; border-radius: 9px; color: var(--muted); }
+.admin-user button { width: auto; padding: 0 12px; display: inline-flex; gap: 7px; align-items: center; border-radius: var(--radius-xs); color: var(--muted); }
+.admin-user button:hover { color: var(--text); background: var(--surface-soft); }
 .admin-layout { max-width: 1380px; margin: auto; display: grid; grid-template-columns: 190px 1fr; gap: 28px; }
 .admin-nav { padding: 10px 0; display: flex; flex-direction: column; gap: 5px; position: sticky; top: 18px; height: fit-content; }
 .admin-nav .eyebrow { padding: 0 10px 8px; }
@@ -691,20 +870,21 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   align-items: center;
   gap: 9px;
   padding: 10px;
-  border-radius: 9px;
+  border-radius: var(--radius-xs);
   cursor: pointer;
   font-size: 12px;
+  font-weight: 650;
   text-align: left;
 }
 .admin-nav button.active, .admin-nav button:hover, .admin-nav a:hover { color: var(--text); background: var(--surface-strong); }
 .admin-content { min-width: 0; }
 .admin-heading { display: flex; justify-content: space-between; align-items: end; margin-bottom: 20px; gap: 20px; }
 .admin-heading h1 { font-size: 38px; }
-.primary-button.compact { padding: 10px 13px; }
+.primary-button.compact { padding: 10px 14px; }
 .settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
 .settings-card, .booking-list-card {
   border: 1px solid var(--border);
-  border-radius: 18px;
+  border-radius: var(--radius-lg);
   background: var(--surface);
   padding: 22px;
 }
@@ -732,7 +912,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
 .theme-option em {
   width: fit-content;
   border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
-  border-radius: 999px;
+  border-radius: var(--radius-pill);
   color: var(--accent);
   background: color-mix(in srgb, var(--accent) 8%, transparent);
   padding: 4px 9px;
@@ -748,7 +928,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
 .theme-option {
   min-height: 116px;
   border: 1px solid var(--border);
-  border-radius: 8px;
+  border-radius: var(--radius-md);
   background: color-mix(in srgb, var(--surface) 88%, transparent);
   color: var(--text);
   display: grid;
@@ -826,7 +1006,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
 }
 .availability-row:first-child { border-top: 0; }
 .switch-row { display: flex; align-items: center; gap: 10px; color: var(--text); font-size: 12px; }
-.switch-row input { width: 15px; height: 15px; accent-color: var(--text); }
+.switch-row input { width: 15px; height: 15px; accent-color: var(--accent); }
 .time-range { display: flex; align-items: center; gap: 8px; color: var(--faint); font-size: 10px; }
 .time-range input { width: 108px; padding: 7px 8px; font-size: 11px; }
 .unavailable { color: var(--faint); font-size: 10px; }
@@ -838,7 +1018,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
 }
 .timezone-control button {
   border: 1px solid var(--border);
-  border-radius: 11px;
+  border-radius: var(--radius-sm);
   background: var(--surface-soft);
   color: var(--text);
   padding: 0 12px;
@@ -854,7 +1034,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
 .checkbox-chips { display: flex; flex-wrap: wrap; gap: 7px; }
 .checkbox-chips label, .mode-admin-grid label {
   border: 1px solid var(--border);
-  border-radius: 10px;
+  border-radius: var(--radius-xs);
   padding: 8px 10px;
   color: var(--muted);
   display: inline-flex;
@@ -864,42 +1044,120 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
   font-size: 10px;
 }
 .checkbox-chips input, .mode-admin-grid input { display: none; }
+.checkbox-chips label:has(:focus-visible), .mode-admin-grid label:has(:focus-visible) { box-shadow: var(--focus-ring); }
 .checkbox-chips label.active, .mode-admin-grid label.active { border-color: var(--text); background: var(--surface-soft); color: var(--text); }
 
-.google-status-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 11px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-soft); }
+.google-status-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 11px 12px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-soft); }
 .connection-badge { display: inline-flex; align-items: center; gap: 8px; color: var(--muted); font-size: 10px; font-weight: 700; }
 .status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
 .connection-badge.connected .status-dot { background: var(--success); box-shadow: 0 0 0 4px color-mix(in srgb, var(--success) 10%, transparent); }
 .connection-actions { display: flex; align-items: center; gap: 10px; }
 .connection-actions a, .connection-actions button { border: 0; background: transparent; color: var(--muted); font-size: 10px; padding: 0; cursor: pointer; }
 .connection-actions a:hover, .connection-actions button:hover { color: var(--text); }
-select { width: 100%; color: var(--text); background: var(--surface-soft); border: 1px solid var(--border); border-radius: 11px; outline: 0; padding: 11px 12px; font-size: 13px; }
+select { width: 100%; color: var(--text); background: var(--surface-soft); border: 1px solid var(--border); border-radius: var(--radius-sm); outline: 0; padding: 11px 12px; font-size: 13px; }
 
 .mode-admin-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
 .mode-admin-grid label { justify-content: center; padding: 11px 8px; }
+
+/* Admin bookings */
+.booking-tabs {
+  display: inline-flex;
+  gap: 4px;
+  padding: 4px;
+  border: 1px solid var(--border);
+  border-radius: var(--radius-md);
+  background: var(--surface);
+  margin-bottom: 16px;
+}
+.booking-tab {
+  border: 0;
+  background: transparent;
+  color: var(--muted);
+  padding: 7px 14px;
+  border-radius: var(--radius-xs);
+  cursor: pointer;
+  font-size: 11px;
+  font-weight: 700;
+  display: inline-flex;
+  align-items: center;
+  gap: 7px;
+}
+.booking-tab:hover { color: var(--text); }
+.booking-tab.active { background: var(--surface-strong); color: var(--text); }
+.booking-tab .count {
+  font-size: 10px;
+  color: var(--faint);
+  font-variant-numeric: tabular-nums;
+}
+.booking-tab.active .count { color: var(--muted); }
 .booking-list-card { padding: 0; overflow: hidden; }
 .booking-list-row {
   display: grid;
-  grid-template-columns: 38px minmax(170px, 1fr) minmax(210px, 1fr) 80px;
+  grid-template-columns: 40px minmax(150px, 1.2fr) minmax(180px, 1.2fr) minmax(120px, .8fr) auto;
   align-items: center;
-  gap: 13px;
-  padding: 14px 16px;
+  gap: 14px;
+  padding: 15px 18px;
   border-top: 1px solid var(--border);
   font-size: 11px;
 }
 .booking-list-row:first-child { border-top: 0; }
-.booking-avatar { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; background: var(--surface-soft); font-weight: 750; }
-.booking-person, .booking-when { display: grid; gap: 3px; min-width: 0; }
-.booking-person strong, .booking-when strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.booking-person span, .booking-when span { color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.booking-list-row > a { display: flex; align-items: center; gap: 5px; color: var(--muted); }
-.empty-list { padding: 42px; color: var(--muted); text-align: center; font-size: 12px; }
+.booking-list-row.past { opacity: .62; }
+.booking-avatar { width: 36px; height: 36px; border-radius: var(--radius-xs); display: grid; place-items: center; background: var(--surface-soft); font-weight: 750; font-size: 13px; }
+.booking-person, .booking-when, .booking-meta { display: grid; gap: 3px; min-width: 0; }
+.booking-person strong, .booking-when strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
+.booking-person span, .booking-when span, .booking-meta span { color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.booking-meta { font-size: 10px; }
+.booking-method { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); font-weight: 650; }
+.booking-method svg { flex-shrink: 0; }
+.status-pill {
+  display: inline-flex;
+  align-items: center;
+  gap: 5px;
+  width: fit-content;
+  padding: 3px 8px;
+  border-radius: var(--radius-pill);
+  font-size: 9px;
+  font-weight: 760;
+  letter-spacing: .05em;
+  text-transform: uppercase;
+  border: 1px solid var(--border);
+  color: var(--muted);
+}
+.status-pill.upcoming { color: var(--success); border-color: color-mix(in srgb, var(--success) 40%, var(--border)); background: color-mix(in srgb, var(--success) 8%, transparent); }
+.status-pill.past { color: var(--faint); }
+.booking-actions { display: inline-flex; align-items: center; gap: 6px; justify-self: end; }
+.icon-button {
+  width: var(--control-h-sm);
+  height: var(--control-h-sm);
+  border-radius: var(--radius-xs);
+  border: 1px solid var(--border);
+  background: var(--surface);
+  color: var(--muted);
+  display: inline-grid;
+  place-items: center;
+  cursor: pointer;
+  transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
+}
+.icon-button:hover { color: var(--text); border-color: color-mix(in srgb, var(--text) 40%, var(--border)); background: var(--surface-soft); }
+.icon-button.copied { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, var(--border)); }
+.empty-list { padding: 52px 42px; color: var(--muted); text-align: center; display: grid; gap: 8px; justify-items: center; }
+.empty-list svg { color: var(--faint); }
+.empty-list strong { color: var(--text); font-size: 15px; letter-spacing: -0.02em; }
+.empty-list span { font-size: 12px; }
 
 @media (max-width: 1180px) {
-  .booking-shell { grid-template-columns: 240px minmax(460px, 1fr); }
-  .selection-panel { grid-column: 1 / -1; border-left: 0 !important; border-top: 1px solid var(--border); min-height: 260px; }
+  .booking-shell { grid-template-columns: 250px minmax(440px, 1fr); }
+  .calendar-panel { padding: 34px 34px 36px; }
+  .selection-panel {
+    grid-column: 1 / -1;
+    border-left: 0 !important;
+    border-top: 1px solid var(--border);
+    min-height: 260px;
+    opacity: 1 !important;
+  }
   .selection-panel .empty-state { min-height: 220px; }
   .time-grid { grid-template-columns: repeat(6, 1fr); }
+  .slot-skeleton { grid-template-columns: repeat(6, 1fr); }
 }
 
 @media (max-width: 850px) {
@@ -923,13 +1181,14 @@ select { width: 100%; color: var(--text); background: var(--surface-soft); borde
     height: auto;
     max-height: none;
     min-height: 0;
-    border-radius: 20px;
+    border-radius: var(--radius-lg);
   }
   .booking-shell > * + * { border-left: 0; border-top: 1px solid var(--border); }
-  .intro-panel { min-height: 290px; padding: 30px 24px; }
+  .intro-panel { min-height: 0; padding: 30px 24px; gap: 22px; }
   .calendar-panel, .selection-panel { padding: 28px 22px; }
   .day { min-height: 47px; }
   .time-grid { grid-template-columns: repeat(4, 1fr); }
+  .slot-skeleton { grid-template-columns: repeat(4, 1fr); }
   .admin-layout { grid-template-columns: 1fr; }
   .admin-nav { position: static; flex-direction: row; overflow-x: auto; padding: 0; }
   .settings-grid { grid-template-columns: 1fr; }
@@ -937,19 +1196,31 @@ select { width: 100%; color: var(--text); background: var(--surface-soft); borde
   .appearance-heading { display: grid; }
   .theme-grid { grid-template-columns: 1fr; }
   .mode-admin-grid { grid-template-columns: repeat(2, 1fr); }
-  .booking-list-row { grid-template-columns: 38px 1fr; }
-  .booking-when { grid-column: 2; }
-  .booking-list-row > a { grid-column: 2; }
+  .booking-list-row {
+    grid-template-columns: 40px 1fr auto;
+    grid-template-areas:
+      "avatar person actions"
+      "avatar when   when"
+      "avatar meta   meta";
+    row-gap: 6px;
+  }
+  .booking-avatar { grid-area: avatar; align-self: start; }
+  .booking-person { grid-area: person; }
+  .booking-when { grid-area: when; }
+  .booking-meta { grid-area: meta; }
+  .booking-actions { grid-area: actions; }
 }
 
 @media (max-width: 560px) {
   .timezone-pill { display: none; }
-  .calendar-panel { padding-left: 14px; padding-right: 14px; }
-  .calendar-grid { gap: 3px; }
-  .day { min-height: 41px; border-radius: 10px; }
-  .day span { font-size: 11px; }
+  .booking-page { padding-top: 22px; }
+  .calendar-panel { padding-left: 16px; padding-right: 16px; }
+  .calendar-grid { gap: 4px; }
+  .day { min-height: 44px; border-radius: var(--radius-sm); }
+  .day span { font-size: 13px; }
   .day small { display: none; }
   .time-grid { grid-template-columns: repeat(3, 1fr); }
+  .slot-skeleton { grid-template-columns: repeat(3, 1fr); }
   .duration-grid { grid-template-columns: repeat(4, 1fr); }
   .form-row, .mode-options, .two-col-fields, .calendar-settings-row { grid-template-columns: 1fr; }
   .timezone-control { grid-template-columns: 1fr; }
@@ -961,3 +1232,13 @@ select { width: 100%; color: var(--text); background: var(--surface-soft); borde
   .theme-preview { width: 72px; height: 60px; }
   .theme-preview span { width: 36px; height: 30px; }
 }
+
+/* Respect users who prefer reduced motion: keep state changes instant. */
+@media (prefers-reduced-motion: reduce) {
+  *, *::before, *::after {
+    animation-duration: 0.001ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: 0.001ms !important;
+    scroll-behavior: auto !important;
+  }
+}