/* The booking page inside the app, and nothing else.

   Three jobs, and they are here rather than in member-theme.css for three
   different reasons.

   1. The member chrome this page now carries in its own markup — the header
      and the tab bar — has to be INVISIBLE to a guest. member-theme.css
      cannot say so: test/member-theme.test.js requires every selector in
      that sheet to begin body.linen or body.member, and the rule that hides
      the chrome is by definition about a body that carries neither. It is a
      rule about this one document anyway, which is what this sheet is for.

   2. The safe-area lifts below are the GUEST's, inside the app. Member mode
      already pays for its own: --tabs-reserve carries the home indicator
      through --tabs, and member-theme.css lifts the summary bar off that one
      number in one place, on purpose — a second rule in that sheet setting
      `bottom` on .summary-bar is exactly what its tests forbid, and rightly:
      two offsets for one bar is how the pill ended up painted over the
      Confirm button. A guest has no tab bar and no reserve, so their lift is
      a different number for a different situation, and it is written here
      with `:not(.member)` on it so it can never argue with the member's.

   3. `color-scheme:dark` at the foot of this file. The dark SKIN used to
      live down there too, scoped body.linen.native, back when dark was one
      runtime's problem; it now follows the browser for everybody and reaches
      /my/ as well, which never loads this sheet, so all of it moved to
      member-theme.css. What stayed is the one line that sheet cannot write:
      `color-scheme` belongs to the ROOT element, and every selector in
      member-theme.css must begin `body.` — see the note over the block that
      is left.

   This sheet is the old app-shell.css from the iOS repo, brought back onto
   the Worker. It was deleted when the app stopped bundling a snapshot of the
   site and started loading the live host — at which point nothing was
   injecting it any more, and the booking page has been drawing its header
   under the status-bar clock ever since. */

/* ---- no crossfade between the two documents ----

   There was a cross-document view transition here, and my.css carried its
   other half. It made the stutter it was written to cure: a cross-document
   transition captures the NEW document at its first render opportunity —
   before app.js has run — so the crossfade ran into a booking page whose
   header read "HELLO" with nobody's name on it and had no tab bar at all,
   and both then popped in. Three visible steps per tap.

   The cure is that there is nothing left to build: the header and the bar
   are in this document's markup now, and the greeting is written by the
   inline classic script that follows the header, out of the Home the
   member's own page kept. The prefetch link app.js adds stays — it is what
   makes the navigation itself quick. */

/* ---- the chrome a guest must never see ----

   The header and the tab bar are in this document's markup for everybody,
   because a member's copy of them has to exist before anything runs. What
   makes them a MEMBER's is the class — set by the inline head script from
   the flag, before the first paint, and taken off again by leaveMemberMode
   when /api/me says the visitor holding that link has no session.

   So the class is the whole switch, and this is the only rule that reads it
   the other way round. It is one line and it is load-bearing: without it
   every stranger who opens the booking page gets a header greeting nobody
   and a bar into four pages they cannot sign in to. */
body:not(.member) .top,
body:not(.member) .tabs { display:none; }

/* ---- and the invitation a shell must never show ----

   The three Google doors on this page (see guest-sign-in.js) and the App
   Store link in the footer are for somebody reading this in a BROWSER.

   Inside the Capacitor app, Google refuses an embedded WebView outright —
   "disallowed_useragent" — so the redirect this page's button makes would
   take a member to a Google page that says no and leave them there; the app
   signs people in through the phone's own system sheet instead, on the
   member's card, and that is the only door that works in there. Inside the
   native shell there is a sign-in of its own for the same reason. And
   neither is a place to offer somebody an app they are already holding.

   The classes land on <html> and on <body> before the first paint — the two
   head scripts in index.html — so this is a rule the FIRST frame obeys, and
   nothing flashes on before the module has run. guest-sign-in.js then takes
   the elements out of the document altogether, so nothing can reveal them
   later either. Both halves, because a rule cannot answer a `hidden = false`
   and a module cannot beat the first paint.

   Here rather than in member-theme.css for that sheet's own reason: every
   selector in it must begin `body.linen` or `body.member`, and these begin
   with neither. The member's page carries the footer link too and loads no
   copy of this sheet — its rule is at the foot of the skin's guest-invite
   block, where `body.member.app` is a root that sheet allows. */
body.native .guest-invite,
body.app .guest-invite,
body.native .app-store-link,
body.app .app-store-link { display:none; }

/* ---- scrolling, inside the app ----

   styles.css puts `overflow-x:hidden` on <body>, which on iOS can make the
   BODY the scrolling box instead of the viewport — and a scrolling box that
   is not the viewport does not get the platform's momentum or its rubber
   band, which is precisely the "doesn't feel natural" the owner reported.
   `clip` is the same visual result without creating a scroll container, and
   <html> already uses it two lines below in that sheet.

   `scroll-behavior:smooth` is the other half: it makes every programmatic
   scroll animate, including the jump to the top of a tab, so a member
   flicking between tabs watches the page glide rather than arrive. The
   member's own page asks for `behavior:"instant"` in route(); this switches
   off the sheet-level default for everything else.

   styles.css itself may not change — test/member-theme.test.js byte-compares
   it against main, because the skin is an overlay and the file underneath is
   what makes unlinking it safe. So both are restated here, for the app only.

   Not scoped under body: the properties that matter are the ROOT element's,
   and `html` is where a sheet has to say so. The app class is on <html> and
   on <body> both — see the inline head script. */
html.app, body.app {
  overflow-x: clip;
  scroll-behavior: auto;
  /* No rubber band past the top of a page that has no pull-to-refresh
     behind it. Inside a WebView that bounce is the shell announcing itself. */
  overscroll-behavior-x: none; overscroll-behavior-y: auto; /* the native bounce stays: without it a flick stops dead at the edge and reads as too fast */
}

/* ---- the status bar ----

   The page carries `viewport-fit=cover` now, so the WebView lays out under
   the clock and the home indicator and `env(safe-area-inset-*)` is finally a
   real number rather than zero. Everything pinned to an edge has to pay for
   it.

   The member's header already did: member-theme.css pads body.member .top by
   the top inset, and has since it was written — it was simply being handed a
   zero. That is the whole of "the Greenjacket logo overlaps with the time".

   What was never covered is the GUEST's marketing top bar, which is fixed at
   inset-top 0. Three rules, because styles.css sets its padding in three
   places and .topbar.scrolled would otherwise outrank a bare .topbar: each
   keeps its own original top padding and adds the notch. */
body.linen.app .topbar { padding-top: calc(16px + env(safe-area-inset-top)); }
body.linen.app .topbar.scrolled { padding-top: calc(12px + env(safe-area-inset-top)); }

@media (max-width: 900px) {
  body.linen.app .topbar { padding-top: calc(14px + env(safe-area-inset-top)); }
  body.linen.app .topbar.scrolled { padding-top: calc(12px + env(safe-area-inset-top)); }
}

/* And sideways, which is the half of `cover` that is easy to forget. The app
   allows landscape on both device families, and in landscape the notch is on
   an EDGE: the top inset goes to zero and a left or right one appears in its
   place. The top bar is fixed across that edge and the column runs full-bleed
   against it, so both pay. Each keeps its own original number — styles.css
   sets the bar's side padding to 32px, and 18px under 900. */
body.linen.app .topbar {
  padding-left: calc(32px + env(safe-area-inset-left));
  padding-right: calc(32px + env(safe-area-inset-right));
}
body.linen.app #booking-main {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

@media (max-width: 900px) {
  body.linen.app .topbar {
    padding-left: calc(18px + env(safe-area-inset-left));
    padding-right: calc(18px + env(safe-area-inset-right));
  }
}

/* And the bottom, for a guest only.

   styles.css already rests the summary bar at `10px + env(safe-area-inset-
   bottom)` on a phone, so that half has been right all along and was simply
   being handed a zero; adding viewport-fit=cover is the whole fix, and
   restating the number here would only be a second copy of it to keep. Above
   900px it rests at a flat 18px, which no phone reaches and an iPad does.

   What DOES break the moment the inset becomes real is where the bar hides.
   styles.css parks it with translate(-50%, 130%) — a percentage of the bar's
   own height, about 104px against a bar ~80px tall — which cleared the edge
   while the bar rested 10px above it and does not clear it once the home
   indicator is added to that rest. So the fall is restated in absolute
   terms, exactly the way member-theme.css restates it for the member: the
   bar's own height, plus the offset it now rests at, plus 40px so the shadow
   goes with it. A percentage cannot express that sum.

   `:not(.member)` on every one of them. Member mode pays for the bottom of
   the window out of --tabs-reserve, in one place, and two rules lifting one
   bar by two different numbers is the bug that arithmetic exists to stop. */
body.linen.app:not(.member) .summary-bar:not(.show) {
  transform: translate(-50%, calc(100% + 18px + env(safe-area-inset-bottom) + 40px));
}
body.linen.app:not(.member) .booking-progress {
  bottom: calc(24px + env(safe-area-inset-bottom));
}

@media (max-width: 900px) {
  /* The phone's resting offset is styles.css's 10px, so the phone's fall is
     paid back off the same 10. */
  body.linen.app:not(.member) .summary-bar:not(.show) {
    transform: translate(-50%, calc(100% + 10px + env(safe-area-inset-bottom) + 40px));
  }
}

/* ---- the confirm bar stops floating, inside the native shell ----

   The owner, on a screenshot of the Book tab: "the confirm booking is not
   good UI. Maybe just leave it at the bottom so it doesn't float?"

   He is right, and the reason is particular to the shell. On the web the
   pill is a thing the page hands you when you have picked enough to book,
   and it can float because the page under it is tall and the pill is small
   against it. Inside the app the same pill is a slab across a 375pt window
   that appears the moment a bay is tapped — which is BEFORE the time board
   is on screen — so the member picks a start time with the thing they are
   heading towards already covering the bottom of the board and the Start
   time field under it. A control that hides the choice it is waiting for.

   In flow it stops being a claim on the window. The markup already puts
   #summary-bar last inside the form, immediately after 03 Details, so
   `position:static` alone lands it exactly where it belongs: the end of the
   form, after the last thing you fill in, in the same card it has always
   been. Nothing else about it changes — the border, the radius, the linen
   and dark grounds, the confirm pill are all still the skin's.

   `.show` stops meaning anything here, and that is deliberate: app.js adds
   and removes the class as the picks come and go, and every one of those was
   a slide. Both the shown and the hidden transform are neutralised, so the
   bar is simply always there at the foot of the form. `transition:none`
   because a static box with nothing to move has nothing to ease.

   Only in the shell. `native` is the user agent and nothing else, so the
   public site and the Capacitor app keep the floating pill exactly as it is,
   and `:not(.member)` keeps this the GUEST's copy — the member's is the same
   decision written once in member-theme.css, off their own column width. */
body.linen.native:not(.member) .summary-bar {
  position:static;
  transform:none;
  left:auto;
  bottom:auto;
  /* The form column, and not the window: .act is max-width var(--shell) with
     40px of padding inside it, so the bar's box is that content box. */
  width:min(100% - 80px, calc(var(--shell) - 80px));
  margin:18px auto 0;
  transition:none;
  z-index:auto;
  flex-wrap:wrap;
}
body.linen.native:not(.member) .summary-bar:not(.show) { transform:none; }

/* The three picks come back. styles.css hides .summary-slots under 900px
   because a floating pill on a phone has room for the button and nothing
   else; a card at the end of the form has a whole line, and a recap of what
   you are about to confirm is the most useful thing that can be in it. It
   wraps, and the button takes the line under it. */
body.linen.native:not(.member) .summary-bar .summary-slots {
  display:flex; flex-wrap:wrap; gap:10px 20px;
}
body.linen.native:not(.member) .summary-bar > button {
  flex-basis:100%; flex-grow:1;
}

/* And the room that was being kept for a bar that no longer floats. 03
   Details reserved 150px under itself and the page another 110 under that,
   both of them the height of the pill plus its rest; in flow the bar is
   part of that stack and pays for its own space with its margin. What is
   left at the bottom is the home indicator, plus air. */
body.linen.native:not(.member) .act-details { padding-bottom:12px; }
body.linen.native:not(.member) #booking-main {
  padding-bottom:calc(24px + env(safe-area-inset-bottom));
}

@media (max-width: 900px) {
  /* The phone's column is .act's 20px of padding, at both edges. */
  body.linen.native:not(.member) .summary-bar {
    width:calc(100% - 40px);
    margin:16px auto 0;
    padding:12px 14px;
    gap:12px;
  }
}

/* ==================================================================
   THE ROOT ELEMENT, IN THE DARK

   The dark skin used to live here in full, under `body.linen.native` —
   because it began as the answer to one complaint about one runtime: the
   Book tab was the single white screen in a dark app, and a white screen at
   11pm in a bay is not a style question.

   The owner then asked for the rest of it: "make it follow the browser's
   settings." A skin that follows the browser has to reach BOTH of this
   Worker's pages, and /my/ has never loaded this sheet. So the whole of it
   moved to member-theme.css, which both pages load, scoped `body.linen` for
   the surfaces they share and `body.member` for the chrome only the member
   has. The palette, the per-selector rules and the contrast table went with
   it; the comment over that block is the one to read.

   What cannot move is this line. `color-scheme` is the ROOT element's
   property — it is what makes the phone's own date picker, the bay <select>,
   the scrollbars and the canvas behind the page come up dark rather than a
   white sheet sliding over a dark one — and every selector in
   member-theme.css must begin `body.`, by that sheet's own test and for a
   good reason. So it is said here, on the one document this sheet is for,
   and my.css says it for the member's page.

   Unscoped, where it used to carry `.native`: the page is dark for every
   visitor now, not only for the shell, and the root has to know before any
   class lands on <body> — which is exactly why the native shell wanted it
   on <html> in the first place.
   ================================================================== */

@media (prefers-color-scheme: dark) {
  html { color-scheme:dark; }
}
