/* ============================================================================
   MOTION — the interaction system
   ============================================================================
   One vocabulary, used everywhere. The point is not that things move; it is
   that the same intent always produces the same response, so the site feels
   like one object rather than a collection of effects.

   Four rules the whole file obeys:

   1. MOTION CARRIES HIERARCHY. How far a thing travels says how important it
      is. A primary button lifts 2px, a secondary 1px, a text link does not
      move at all — it changes weight. Nothing destructive ever moves or
      springs: there is no delight in cancelling a booking.

   2. EVERY ELEMENT HAS FOUR STATES. Rest, intent (hover or keyboard focus),
      engaged (the press itself), resolved (what happened). Intent and focus
      produce the SAME visual result, so a keyboard gets what a mouse gets.

   3. A PRESS COMPRESSES. Never grows. Pressing a real button pushes it in,
      and 90ms of compression is the difference between a page that responds
      and one that merely changes.

   4. HOVER IS A PROGRESSIVE ENHANCEMENT, NOT A LAYER OF MEANING. Every hover
      rule sits behind (hover: hover) and (pointer: fine). Nothing is ever
      only discoverable by hovering, because most of this site's visitors are
      on a phone.

   Not here, deliberately: custom cursors, page-transition overlays, scroll
   hijacking, parallax on content, character-by-character text reveals. Each
   one either slows the visitor down or moves in a way they did not ask for,
   and this is a site whose job is to sell a Saturday morning.

   The Back Office is deliberately excluded. admin.html is a tool somebody
   uses at a market with one hand; it wants speed, not polish.
   ========================================================================= */

:root {
  /* ---- Duration. Five steps, and five is enough. ---- */
  --t-instant: 90ms;    /* the press — acknowledgement, not animation      */
  --t-quick:   160ms;   /* hover, focus, colour, small state changes       */
  --t-base:    240ms;   /* the default: lifts, shadows, reveals in place   */
  --t-slow:    420ms;   /* things that travel a real distance              */
  --t-enter:   560ms;   /* content arriving on screen for the first time   */

  /* ---- Easing. Named for what they are for, not what they are. ---- */
  --e-out:    cubic-bezier(.22, .61, .36, 1);    /* arriving — decelerate  */
  --e-in-out: cubic-bezier(.65, .05, .36, 1);    /* moving between states  */
  --e-exit:   cubic-bezier(.55, .06, .68, .19);  /* leaving — accelerate   */
  --e-spring: cubic-bezier(.34, 1.42, .64, 1);   /* one overshoot. Confirm
                                                    only — never on hover  */

  /* ---- Displacement. The hierarchy, as numbers. ---- */
  --lift-1: 1px;   /* secondary                                           */
  --lift-2: 2px;   /* primary                                             */
  --lift-3: 3px;   /* a whole card                                        */
  --press:  .985;  /* the compression every button shares                 */

  /* ---- Elevation. Shadow is depth, and depth is focus. ---- */
  --sh-rest:  0 1px 2px rgba(42, 33, 26, .05);
  --sh-hover: 0 6px 18px -6px rgba(42, 33, 26, .16), 0 2px 5px rgba(42, 33, 26, .06);
  --sh-card:  0 14px 40px -18px rgba(42, 33, 26, .30), 0 3px 8px rgba(42, 33, 26, .05);
  --ring:     0 0 0 3px rgba(168, 99, 92, .34);   /* focus, in the blush   */
}

/* Our own press feedback replaces the platform's grey flash, or a tap shows
   two different acknowledgements of the same finger. */
.btn, .nav-links a, .cal-item, .event-card, .service-card, .m-photo,
button, [role="button"] { -webkit-tap-highlight-color: transparent; }

/* ---------------------------------------------------------------------------
   FOCUS — one ring, everywhere, and it is never removed
   ---------------------------------------------------------------------------
   :focus-visible rather than :focus, so a mouse press does not leave a ring
   behind but a Tab key always does. The ring is drawn with box-shadow, not
   outline-offset plus border, because a shadow cannot shift layout — a focus
   state that nudges the page is a focus state people turn off. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 3px;
  transition: outline-offset var(--t-quick) var(--e-out);
}
/* On a control that already has a shape, use the ring instead of an outline
   so it hugs the corners it actually has. */
.btn:focus-visible,
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

/* ---------------------------------------------------------------------------
   BUTTONS — three tiers, and the tier is legible from the motion alone
   ------------------------------------------------------------------------ */
.btn {
  transition:
    transform   var(--t-quick) var(--e-out),
    box-shadow  var(--t-base)  var(--e-out),
    background  var(--t-quick) var(--e-in-out),
    color       var(--t-quick) var(--e-in-out),
    border-color var(--t-quick) var(--e-in-out);
  will-change: transform;
}

/* The arrow is the affordance: it says this takes you somewhere, and it
   leans that way before you commit. */
.btn-arrow::after {
  display: inline-block;
  transition: transform var(--t-base) var(--e-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover, .btn-blush:hover { transform: translateY(calc(var(--lift-2) * -1)); box-shadow: var(--sh-hover); }
  .btn-ghost:hover   { transform: translateY(calc(var(--lift-1) * -1)); }
  .btn-arrow:hover::after { transform: translateX(3px); }
}
/* Keyboard gets the same answer as the mouse. This is the whole of rule 2. */
.btn-primary:focus-visible, .btn-blush:focus-visible { transform: translateY(calc(var(--lift-2) * -1)); box-shadow: var(--sh-hover), var(--ring); }
.btn-arrow:focus-visible::after { transform: translateX(3px); }

/* The press. Fastest thing in the system, and it overrides every hover above
   — because the last thing you felt should be the thing you just did. */
.btn:active {
  transform: translateY(1px) scale(var(--press));
  box-shadow: var(--sh-rest);
  transition-duration: var(--t-instant);
}

/* Busy. The label is replaced, never removed, and the width is frozen, so
   nothing on the page moves while somebody waits to find out if they paid. */
.btn[data-busy] {
  pointer-events: none;
  position: relative;
  color: transparent !important;
  transform: none !important;
}
.btn[data-busy]::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--cream);
  animation: btn-spin 620ms linear infinite;
}
.btn-ghost[data-busy]::before { color: var(--ink); }
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* Disabled says "not yet", not "broken": it keeps its shape and loses its
   response. No motion at all, so the absence is the message. */
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .48;
  cursor: not-allowed;
  transform: none !important;
  box-shadow: none !important;
}

/* ---------------------------------------------------------------------------
   MAGNETIC — the one cursor effect, kept on a short lead
   ---------------------------------------------------------------------------
   motion.js sets --mx/--my only within 90px of the button, only on a fine
   pointer, and only up to 5px. It composes with the hover lift rather than
   fighting it, because the translate happens on an inner span.

   Applied to exactly two things: the hero's primary call to action and the
   Book button. A page where everything is magnetic is a page where nothing
   is, and the effect stops meaning "this is the thing to press". */
.magnetic { position: relative; display: inline-block; }
.magnetic > .mag-inner {
  display: inline-block;
  transform: translate(var(--mx, 0px), var(--my, 0px));
  transition: transform var(--t-base) var(--e-out);
}
.magnetic[data-near] > .mag-inner { transition-duration: 60ms; }

/* ---------------------------------------------------------------------------
   NAVIGATION
   ---------------------------------------------------------------------------
   The underline grows from the left rather than fading in, because a wipe has
   a direction and a fade does not — direction reads as intent. The current
   page keeps its underline permanently: that is state, not hover, and the two
   must never look the same. */
.nav-links a {
  position: relative;
  transition: color var(--t-quick) var(--e-in-out);
}
.nav-links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-base) var(--e-out);
}
@media (hover: hover) and (pointer: fine) {
  .nav-links a:hover::after { transform: scaleX(1); }
}
.nav-links a:focus-visible::after { transform: scaleX(1); }
.nav-links a[aria-current="page"]::after {
  transform: scaleX(1);
  background: var(--blush);
}

/* The bar earns a hairline and a lift once you have left the top of the page.
   Binary at 40px, not a continuous function of scroll position: a header that
   changes constantly as you move is a header you keep looking at. */
.nav {
  transition: box-shadow var(--t-base) var(--e-out),
              background var(--t-base) var(--e-out);
}
.nav[data-scrolled] {
  background: color-mix(in srgb, var(--bg) 97%, transparent);
  box-shadow: 0 10px 26px -18px rgba(42, 33, 26, .38);
}

/* ---------------------------------------------------------------------------
   CARDS — depth without wobble
   ---------------------------------------------------------------------------
   The card lifts and its shadow deepens; the photograph inside it scales a
   little within a frame that does not move. That reads as depth, and unlike a
   card that tilts toward the cursor it does not move in a way the visitor did
   not ask for. Tilt was tried and cut: it looks expensive in a showreel and
   feels unsteady when you are trying to read a date. */
.cal-item, .event-card, .featured-card, .service-card {
  transition: transform var(--t-base) var(--e-out),
              box-shadow var(--t-base) var(--e-out),
              border-color var(--t-base) var(--e-in-out);
}
.event-card, .featured-card, .cal-item { overflow: clip; }
.event-card img, .featured-card img, .cal-item img, .service-card img {
  transition: transform var(--t-slow) var(--e-out);
  will-change: transform;
}

@media (hover: hover) and (pointer: fine) {
  .cal-item:hover, .event-card:hover, .service-card:hover {
    transform: translateY(calc(var(--lift-3) * -1));
    box-shadow: var(--sh-card);
  }
  .event-card:hover img, .featured-card:hover img, .cal-item:hover img { transform: scale(1.035); }
}
.cal-item:focus-within, .event-card:focus-within {
  transform: translateY(calc(var(--lift-3) * -1));
  box-shadow: var(--sh-card);
}
/* A card is a big target; its press should be felt across the whole surface. */
.cal-item:active, .event-card:active, .service-card:active {
  transform: translateY(0) scale(.995);
  box-shadow: var(--sh-hover);
  transition-duration: var(--t-instant);
}

/* Photographs in the gallery: the same idea, quieter, because they are the
   content rather than a route to it. */
@media (hover: hover) and (pointer: fine) {
  .m-photo img { transition: transform var(--t-slow) var(--e-out); }
  .m-photo:hover img { transform: scale(1.03); }
}

/* ---------------------------------------------------------------------------
   TEXT LINKS — weight, not movement
   ------------------------------------------------------------------------ */
.prose a:not(.btn), .footer-bottom a, .legal-list a {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--t-base) var(--e-out),
              color var(--t-quick) var(--e-in-out);
}
@media (hover: hover) and (pointer: fine) {
  .prose a:not(.btn):hover, .footer-bottom a:hover, .legal-list a:hover { background-size: 100% 1px; }
}
.prose a:not(.btn):focus-visible, .footer-bottom a:focus-visible { background-size: 100% 1px; }

/* ---------------------------------------------------------------------------
   CONTENT REVEALS
   ---------------------------------------------------------------------------
   Transform only, never opacity — a renderer that freezes CSS animations at
   their first frame would otherwise leave the page permanently blank, and
   that rule predates this file. Everything here respects it.

   The stagger is capped at five. Beyond that the last card is waiting on an
   animation nobody is watching any more, which is the moment a reveal stops
   being elegant and starts being a wait. */
.reveal.in { animation: reveal-rise var(--t-enter) var(--e-out) both; }
.stagger > * { animation-delay: 0ms; }
.stagger > *:nth-child(2) { animation-delay: 60ms; }
.stagger > *:nth-child(3) { animation-delay: 120ms; }
.stagger > *:nth-child(4) { animation-delay: 180ms; }
.stagger > *:nth-child(n+5) { animation-delay: 240ms; }

/* ---------------------------------------------------------------------------
   THE STICKY BOOK BAR — an entrance, and deliberately nothing more
   ---------------------------------------------------------------------------
   This bar is display:none above 880px and always on screen below it: it is
   the mobile Book Now button, and on a site that sells Saturday mornings it
   is the most valuable pixel there is.

   The obvious "premium" move is to hide it until the hero has scrolled away.
   That was built, and then cut. Whether the main booking button is visible on
   arrival is a decision about conversion, not about polish, and it is not an
   interaction designer's to make quietly. So it keeps appearing exactly when
   it always has; it just arrives instead of snapping in.

   The hidden start is scoped to html.mo, which motion.js sets as its first
   act. If this file loads and the script does not — blocked, errored, an old
   browser — the bar is simply visible, as it is today. An interaction layer
   must never be the reason a call to action disappears. */
html.mo .sticky-book {
  transform: translateY(115%);
  transition: transform var(--t-slow) var(--e-out);
}
html.mo .sticky-book[data-in] { transform: none; }

/* ---------------------------------------------------------------------------
   FORMS — the money path, so restraint is the feature
   ---------------------------------------------------------------------------
   Validation appears on blur or on submit, never while somebody is still
   typing: telling a person their email is wrong while they are halfway
   through writing it is the single rudest thing a form does. A field that is
   correct says nothing at all — silence is the reward, and a column of green
   ticks is noise that makes the one real error harder to find. */
.field input, .field select, .field textarea {
  transition: border-color var(--t-quick) var(--e-in-out),
              box-shadow   var(--t-quick) var(--e-out),
              background   var(--t-quick) var(--e-in-out);
}
.field label {
  transition: color var(--t-quick) var(--e-in-out);
}
.field:focus-within label { color: var(--ink); }

.field[data-invalid] input,
.field[data-invalid] select,
.field[data-invalid] textarea { border-color: var(--red, #a3453a); }
.field[data-invalid] label { color: var(--red, #a3453a); }

/* The message opens rather than appearing — a height that animates tells you
   where it came from, and nothing below it jumps. */
.field .field-msg {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--t-base) var(--e-out),
              opacity var(--t-quick) var(--e-out);
  opacity: 0;
}
.field .field-msg > span { overflow: hidden; }
.field[data-invalid] .field-msg { grid-template-rows: 1fr; opacity: 1; }

/* The one spring in the system, spent on the one moment that deserves it. */
.form-done { animation: confirm-in var(--t-slow) var(--e-spring) both; }
@keyframes confirm-in {
  from { transform: scale(.92); }
  to   { transform: none; }
}

/* ---------------------------------------------------------------------------
   TOUCH — not a fallback, a different instrument
   ---------------------------------------------------------------------------
   With no hover there is no way to telegraph, so the press itself has to do
   all the work — and it is given a little longer than on desktop so it is
   still visible when a finger is covering the thing it happened to. */
@media (hover: none), (pointer: coarse) {
  .btn:active { transition-duration: 120ms; transform: translateY(1px) scale(.975); }
  .cal-item:active, .event-card:active, .service-card:active {
    transform: scale(.985);
    transition-duration: 120ms;
  }
  /* Nothing is magnetic on a touchscreen: there is no cursor to attract. */
  .magnetic > .mag-inner { transform: none !important; }
}

/* ---------------------------------------------------------------------------
   REDUCED MOTION — a different answer, not a switched-off one
   ---------------------------------------------------------------------------
   Everything that travels stops travelling. Colour, shadow and the focus ring
   keep their transitions, because they are not vestibular triggers and they
   are carrying the actual information. Turning those off too would be the lazy
   reading of the preference: the visitor asked for less movement, not for a
   page that stops answering them. */
@media (prefers-reduced-motion: reduce) {
  .reveal.in, .form-done { animation: none !important; }
  .btn:hover, .btn:focus-visible,
  .btn:active,
  .cal-item:hover, .event-card:hover, .service-card:hover,
  .cal-item:active, .event-card:active, .service-card:active,
  .cal-item:focus-within, .event-card:focus-within { transform: none !important; }
  .event-card:hover img, .featured-card:hover img, .cal-item:hover img,
  .m-photo:hover img { transform: none !important; }
  .btn-arrow:hover::after, .btn-arrow:focus-visible::after { transform: none !important; }
  .magnetic > .mag-inner { transform: none !important; }
  .sticky-book { transition: none; }
  .btn[data-busy]::before { animation-duration: 1.6s; }
  /* Kept on purpose: shadow, colour, the underline wipe and the focus ring. */
}
