/* Incredoball public homepage — online court/activity booking.
   Same brand tokens as public/style.css (the admin app) for visual
   continuity, but this stylesheet is otherwise independent: this page
   has its own layout concerns (a public marketing/booking site, not a
   dense till UI) and its own audience (a customer's phone, not a
   fixed-size POS terminal). See
   C:\Users\alun\Downloads\ui-design-and-engines-research-2026.md's Part 6
   (responsive design) for the patterns applied throughout — content-
   driven breakpoints, clamp() fluid type/spacing, 44px+ touch targets,
   :focus-visible, prefers-reduced-motion, dvh units, safe-area-inset. */

:root {
  --ink: #1b2a41;
  --paper: #eef1f4;
  --panel: #ffffff;
  --line: #d8dce1;
  --accent: #0e7c66;
  --accent-dark: #0a5c4d;
  --accent-soft: #e4f2ee;
  --muted: #6b7686;
  --yellow: #e8b930;
  --yellow-soft: #fbf1d9;
  --red: #d64545;
  --red-soft: #fbdede;
  --font: -apple-system, "Segoe UI", "Inter", Roboto, Helvetica, Arial, sans-serif;
  --mono: "Consolas", "SF Mono", "Menlo", monospace;
  --radius: 10px;
  --header-h: 60px;
}

* {
  box-sizing: border-box;
}

html {
  /* Fluid base size: never smaller than mobile-legible, never larger
     than desktop-comfortable — every rem-based size on the page scales
     with it instead of needing its own clamp(). */
  font-size: clamp(15px, 0.9vw + 12px, 17px);
}

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font);
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

/* Respect the notch/home-indicator on an iPhone in standalone/full-bleed
   contexts — costs nothing on a device without one (env() resolves to 0). */
body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

img {
  max-width: 100%;
}

a {
  color: var(--accent-dark);
}

button, input, select {
  font-family: var(--font);
}

/* Every interactive control gets a visible keyboard-focus ring — never
   suppressed with outline:none. Skipped for a mouse/touch click
   (:focus-visible only fires for keyboard/AT focus), so this adds
   accessibility without adding visual noise for the common case. */
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2.5px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  height: var(--header-h);
  padding: 0 clamp(14px, 3vw, 32px);
  padding-top: env(safe-area-inset-top);
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.site-header .logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  text-decoration: none;
  color: var(--ink);
}
.site-header .logo img {
  width: 30px;
  height: 30px;
  border-radius: 8px;
}
/* "Incredo" + "ball" live inside one shared <span> (a single flex item
   of .logo) specifically so flexbox's own gap never lands between
   them — gap applies between siblings, and text directly inside a
   flex container gets wrapped in its own anonymous item, so the old
   markup (a bare "Incredo" text node next to a <span>ball</span>) had
   .logo's icon-to-wordmark gap silently applying a second time, right
   in the middle of one word ("Incredo ball" instead of "Incredoball").
   <i> here is a plain color hook, not italic emphasis. */
.site-header .logo i {
  font-style: normal;
  color: var(--accent);
}
.header-spacer {
  flex: 1;
}
.header-auth {
  display: flex;
  align-items: center;
  gap: 8px;
}
.header-auth .who {
  font-size: 0.85rem;
  color: var(--muted);
  display: none;
}
@media (min-width: 480px) {
  .header-auth .who {
    display: inline;
  }
}

/* ---------- buttons ---------- */
.btn {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px; /* touch-target floor */
  transition: background-color 0.15s, border-color 0.15s, transform 0.1s;
}
.btn:hover {
  border-color: var(--accent);
}
.btn:active {
  transform: translateY(1px);
}
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
}
.btn-primary:disabled,
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}
.btn-ghost {
  background: transparent;
}
.btn-danger {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
}
.btn-block {
  width: 100%;
}
.btn-sm {
  min-height: 38px;
  padding: 6px 12px;
  font-size: 0.82rem;
}

/* ---------- hero ---------- */
/* Real venue photo (from the Incredoball Facebook page) as a background,
   not a stock image — a customer landing here should recognize the
   actual place. Dark gradient overlay (not a flat tint) keeps the text
   readable without fully hiding the photo, matching the accent color so
   it reads as branded rather than a generic dark scrim. */
.hero {
  position: relative;
  padding: clamp(48px, 10vw, 96px) clamp(14px, 3vw, 32px);
  text-align: center;
  /* hero-cover.png is a wide, ~2.7:1 panoramic photo, but this box's
     own shape is driven by its text content, not the image — on a
     narrow/tall screen the box ends up far taller-than-wide relative
     to the photo, and background-size:cover (which crops whichever
     dimension overflows) was cutting off both the player on one edge
     and the "INCREDOBALL SPORTS" watermark baked into the photo on
     the other, same class of bug as the slideshow's cropping above.
     The gradient layer stays "cover" (it has no intrinsic shape to
     protect and needs to fully darken the box either way); the photo
     layer is "contain" so it's always shown in full, letterboxed
     against the solid background-color below rather than the
     transparent gap contain would otherwise leave around it. */
  background-color: var(--ink);
  background-image: linear-gradient(180deg, rgba(10, 20, 30, 0.72), rgba(10, 46, 38, 0.82)), url('images/hero-cover.png');
  background-size:
    cover,
    contain;
  background-position: center;
  background-repeat: no-repeat, no-repeat;
  color: #fff;
}
.hero h1 {
  margin: 0 0 8px;
  font-size: clamp(1.8rem, 4.5vw, 2.8rem);
  letter-spacing: -0.02em;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
.hero p {
  margin: 0 auto;
  max-width: 46em;
  color: rgba(255, 255, 255, 0.88);
  font-size: clamp(0.9rem, 1.2vw, 1.05rem);
  line-height: 1.55;
}

/* ---------- photo slideshow — real venue/community photos ---------- */
.slideshow {
  position: relative;
  max-width: 1080px;
  margin: 0 auto clamp(20px, 4vw, 32px);
  padding: 0 clamp(12px, 3vw, 28px);
}
.slideshow-viewport {
  overflow: hidden;
  border-radius: var(--radius);
}
.slideshow-track {
  display: flex;
}
.slide {
  flex: 0 0 100%;
  margin: 0;
  transition: transform 0.5s ease;
}
.slide a {
  display: block;
  line-height: 0;
}
.slide img {
  width: 100%;
  /* Taller than a typical landscape crop band on purpose: 6 of the 7
     venue photos are portrait phone shots (aspect ratio ~0.7-0.8, not
     the ~1.3-1.8 a "cover" band this short assumes) — object-fit:cover
     at the old shorter height cropped away most of each portrait
     photo's top and bottom, visible on every slide but the first once
     the clip/transform bug above stopped hiding slides 2-7. contain
     shows every photo in full regardless of its own orientation,
     letterboxed on whichever sides don't match this box's ratio — the
     background color fills that letterbox space instead of leaving it
     default white, so it reads as an intentional frame rather than a
     rendering gap. */
  height: clamp(280px, 46vw, 480px);
  object-fit: contain;
  background: var(--ink);
  display: block;
}
.slideshow-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(15, 23, 32, 0.55);
  color: #fff;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.slideshow-nav:hover {
  background: rgba(15, 23, 32, 0.8);
}
.slideshow-nav.prev {
  left: calc(clamp(12px, 3vw, 28px) + 8px);
}
.slideshow-nav.next {
  right: calc(clamp(12px, 3vw, 28px) + 8px);
}
.slideshow-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}
.slideshow-dots button {
  width: 9px;
  height: 9px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: var(--line);
  cursor: pointer;
}
.slideshow-dots button.active {
  background: var(--accent);
  width: 22px;
  border-radius: 5px;
}
/* prefers-reduced-motion turns off the sliding transition (auto-advance
   itself is disabled in JS — see initSlideshow()), matching the same
   rule already applied globally near the top of this file, restated
   here since transform-based carousels are exactly the kind of motion
   that rule exists for. */
@media (prefers-reduced-motion: reduce) {
  .slide {
    transition: none;
  }
}

/* ---------- main content shell ---------- */
.page {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 clamp(12px, 3vw, 28px) clamp(40px, 6vw, 64px);
}

.closed-banner {
  display: none;
  background: var(--yellow-soft);
  border: 1px solid var(--yellow);
  color: #6b4f0a;
  border-radius: var(--radius);
  padding: 12px 16px;
  font-size: 0.87rem;
  line-height: 1.5;
  margin-bottom: 18px;
}
.closed-banner.show {
  display: block;
}

/* ---------- unit tabs ---------- */
.unit-tabs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.unit-tabs button {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
  min-height: 40px;
}
.unit-tabs button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* ---------- date toolbar ---------- */
.date-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.date-toolbar input[type="date"] {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 9px 10px;
  font-size: 0.88rem;
  min-height: 44px;
}
.date-nav-btn {
  width: 44px;
  padding: 0;
}
.legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 10px;
}
.legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.legend i {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: inline-block;
}

/* ---------- availability grid ----------
   Wide content scrolls inside its OWN container, never the page body —
   this table can easily be wider than a phone screen (17 hourly
   columns), and letting IT scroll horizontally (not <body>) is what
   keeps the header/date toolbar/legend pinned in place while browsing. */
.avail-scroll {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.avail-table {
  border-collapse: collapse;
  width: 100%;
  min-width: 760px;
  font-size: 0.82rem;
}
.avail-table th,
.avail-table td {
  border: 1px solid var(--line);
  text-align: center;
  padding: 0;
}
.avail-table th {
  background: var(--paper);
  color: var(--muted);
  font-weight: 600;
  padding: 8px 4px;
  position: sticky;
  top: 0;
}
.avail-table td.item-name {
  text-align: left;
  padding: 10px 12px;
  font-weight: 700;
  background: var(--paper);
  position: sticky;
  left: 0;
  white-space: nowrap;
}
.slot-cell {
  width: 44px;
  height: 44px; /* touch-target floor applies to grid cells too — this is the primary booking action on the whole page */
  cursor: pointer;
}
.slot-cell.open {
  background: var(--accent-soft);
}
.slot-cell.open:hover {
  background: var(--accent);
}
.slot-cell.booked {
  background: var(--red-soft);
  cursor: not-allowed;
}
.slot-cell.past {
  background: var(--paper);
  cursor: not-allowed;
  opacity: 0.5;
}
.slot-cell.selected {
  background: var(--accent-dark);
  outline: 2px solid var(--accent-dark);
  outline-offset: -2px;
}
/* One header row per business unit inside the single combined table —
   Court Rentals and Recreation Areas share one view for a customer (see
   loadAvailability() in app.js), so this is what tells the two apart
   without a separate tab-switcher. */
.avail-table tr.unit-section td {
  text-align: left;
  background: var(--accent-soft);
  color: var(--accent-dark);
  font-weight: 800;
  padding: 8px 12px;
}

/* ---------- modals ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 32, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
  padding-top: calc(16px + env(safe-area-inset-top));
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 50;
}
.modal-overlay.show {
  display: flex;
}
.modal-box {
  background: var(--panel);
  border-radius: var(--radius);
  padding: clamp(18px, 3vw, 26px);
  width: 100%;
  max-width: 480px;
  max-height: min(88dvh, 720px);
  overflow-y: auto;
}
.modal-box h2 {
  margin: 0 0 4px;
  font-size: 1.2rem;
}
.modal-box .sub {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0 0 16px;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}
.modal-close-x {
  position: absolute;
  top: 14px;
  right: 14px;
  background: none;
  border: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--muted);
  width: 44px;
  height: 44px;
}
.modal-box {
  position: relative;
}

.field {
  margin-bottom: 14px;
}
.field label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  margin-bottom: 5px;
  color: var(--ink);
}
.field input,
.field select {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 0.92rem;
  min-height: 44px;
}
.field .hint {
  font-size: 0.76rem;
  color: var(--muted);
  margin-top: 4px;
}
.field-error {
  color: var(--red);
  font-size: 0.8rem;
  margin-top: 4px;
  display: none;
}
.field-error.show {
  display: block;
}

.auth-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 16px;
  background: var(--paper);
  border-radius: 8px;
  padding: 3px;
}
.auth-tabs button {
  flex: 1;
  border: none;
  background: transparent;
  padding: 9px;
  border-radius: 6px;
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
  min-height: 40px;
}
.auth-tabs button.active {
  background: var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* ---------- booking flow ---------- */
.hold-timer {
  text-align: center;
  background: var(--accent-soft);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 16px;
}
.hold-timer .clock {
  font-family: var(--mono);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--accent-dark);
}
.hold-timer .label {
  font-size: 0.78rem;
  color: var(--muted);
}
.hold-timer.low .clock {
  color: var(--red);
}

.booking-summary {
  background: var(--paper);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 16px;
  font-size: 0.88rem;
}
.booking-summary .row {
  display: flex;
  justify-content: space-between;
  padding: 3px 0;
}
.booking-summary .total {
  font-weight: 800;
  font-size: 1.05rem;
  border-top: 1px solid var(--line);
  margin-top: 6px;
  padding-top: 8px;
}

.pay-methods {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}
.pay-methods button {
  flex: 1;
  border: 1.5px solid var(--line);
  background: var(--panel);
  border-radius: 8px;
  padding: 12px 8px;
  font-weight: 700;
  font-size: 0.86rem;
  cursor: pointer;
  min-height: 48px;
}
.pay-methods button.active {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.qr-box {
  text-align: center;
  margin-bottom: 16px;
}
.qr-box img {
  width: min(220px, 60vw);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px;
  background: #fff;
}

.photo-preview {
  margin-top: 8px;
}
.photo-preview img {
  width: 100px;
  height: 100px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--line);
}

.success-box {
  text-align: center;
  padding: 8px 0;
}
.success-box .icon {
  font-size: 2.6rem;
  margin-bottom: 8px;
}

.duration-picker {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}
.duration-picker button {
  flex: 1;
  border: 1.5px solid var(--line);
  background: var(--panel);
  border-radius: 8px;
  padding: 10px 4px;
  font-weight: 700;
  cursor: pointer;
  min-height: 44px;
}
.duration-picker button.active {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.duration-picker button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.toast {
  position: fixed;
  bottom: calc(20px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: #fff;
  padding: 11px 18px;
  border-radius: 999px;
  font-size: 0.85rem;
  z-index: 100;
  max-width: min(90vw, 420px);
  text-align: center;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
}
.toast.error {
  background: var(--red);
}

.skeleton-msg {
  color: var(--muted);
  font-size: 0.85rem;
  padding: 20px;
  text-align: center;
}

footer.site-footer {
  text-align: center;
  color: var(--muted);
  font-size: 0.78rem;
  padding: 24px 16px calc(24px + env(safe-area-inset-bottom));
}

/* ---------- container query: booking summary tightens on a narrow modal ----------
   The modal itself (not the viewport) determines whether the row layout
   still has room — relevant if this same modal markup is ever reused in
   a narrower embed. */
.modal-box {
  container-type: inline-size;
}
@container (max-width: 360px) {
  .pay-methods {
    flex-direction: column;
  }
}
