/* The member's own area of the booking site.

   Same faces and the same palette as public/styles.css, deliberately: this
   is the club a guest just booked with, seen from the inside, not a second
   product with its own look. The tokens are copied rather than imported
   because styles.css is a 2,000-line cinematic front page and this is a
   phone app — loading the whole of it here would bring a hero, a timeline
   and a summary bar with it.

   Nothing loads from a CDN. Every face is served from /fonts/ by this same
   Worker, and every icon is inline SVG in the HTML. */

/* The club's script face, the one the booking page's hero and ticket sign
   off in. It was not here because this page had nothing written by hand on
   it; the member's own greeting and their next session are the two places it
   now says the name and the welcome the way the club says them aloud. */
@font-face { font-family:"Hilogen"; src:url("/fonts/Hilogen-Regular.otf") format("opentype"); font-weight:400; font-display:swap; }
@font-face { font-family:"Hilogen Sans"; src:url("/fonts/Hilogen-Sans.otf") format("opentype"); font-weight:400 900; font-display:swap; }
@font-face { font-family:"HK Grotesk"; src:url("/fonts/HKGrotesk-Regular.otf") format("opentype"); font-weight:400; font-display:swap; }
@font-face { font-family:"HK Grotesk"; src:url("/fonts/HKGrotesk-Medium.otf") format("opentype"); font-weight:500; font-display:swap; }
@font-face { font-family:"HK Grotesk"; src:url("/fonts/HKGrotesk-SemiBold.otf") format("opentype"); font-weight:600; font-display:swap; }
@font-face { font-family:"HK Grotesk"; src:url("/fonts/HKGrotesk-Bold.otf") format("opentype"); font-weight:700; font-display:swap; }

:root {
  --reserve:#253c1d; --reserve-900:#141f0e;
  /* The lighter brand green the owner chose (2026-09-13) for every dark
     surface: the hero runs from it down to reserve, the ticket top and the
     primary button wear it flat. */
  --fairway:#4e6342; --fairway-300:#8ba07c;
  --linen:#ecead5; --linen-050:#faf9f2; --linen-100:#f3f1e5; --linen-400:#c3bfa2;
  --ember:#4e6c88; --ember-400:#a9c2d1; --ember-300:#cddee8;
  --danger:#e08563;
  /* Good news that is not a price: what a bigger package saves per hour. On
     a green page the club's own green is not a colour, so the dark sheet's
     success voice is the slate's light tint; the daylight skin re-tones it
     to a real green in member-theme.css. */
  --ok:#cddee8;
  --muted:rgba(236,234,213,.72);
  --line:rgba(236,234,213,.16);
  --script:"Hilogen","Brush Script MT",cursive;
  --display:"Hilogen Sans","HK Grotesk",sans-serif;
  --sans:"HK Grotesk",ui-sans-serif,system-ui,sans-serif;
  --ease:cubic-bezier(.16,1,.3,1);
  /* The bar is 62px of controls; the rest is the phone's own home
     indicator, which would otherwise sit on top of the Profile tab. */
  --tabs:calc(62px + env(safe-area-inset-bottom));
}

/* ---- the root element, in the dark ----

   The skin follows the browser now (member-theme.css, the block at the foot
   of it), and `color-scheme` is the one thing that sheet cannot say: it is
   the ROOT's property, and every selector in there has to begin `body.`.
   Without it the phone's own <select> menus and this page's scrollbars come
   up as white sheets sliding over a night page. The booking page says the
   same line in book-shell.css, for its own document. */
@media (prefers-color-scheme: dark) {
  html { color-scheme:dark; }
}

* { box-sizing:border-box; }
[hidden] { display:none !important; }

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

   There was a cross-document view transition here, and it made the stutter
   it was written to cure. A frame analysis of a 60fps recording says why: a
   cross-document transition captures the NEW document at its first render
   opportunity — before this page's module has run — so the crossfade ran
   from the finished page the member was reading INTO "Loading your page…",
   and Home then popped in on top of it. Three visible steps per tap.

   What a tab switch actually needs is to land on a whole page in one frame,
   and that is now the job of the inline classic script at the foot of this
   document (and its twin on the booking page): the greeting, the hero and
   the chrome are on screen before the first paint, out of the Home this
   browser already kept. The prefetch links stay — they are what makes the
   navigation itself quick. There is nothing left to crossfade.

   Deliberately NOT replaced by a same-document animation on arrival: the
   whole point is that the page is simply there. */

/* ---- inside the app ----

   The viewport's own scrolling, which is decided on <html> and so cannot be
   said in the body-scoped skin. The booking page has the same two lines in
   book-shell.css, for the same reason and against the same symptom: "the
   scrolling doesn't feel natural."

   `clip` rather than `hidden` because on iOS `overflow:hidden` on a scroll
   axis can make the element itself the scrolling box instead of the
   viewport, and a scrolling box that is not the viewport gets none of the
   platform's momentum or its rubber band. `scroll-behavior:auto` because
   smooth makes every programmatic scroll animate, including route()'s jump
   to the top of a tab — a member flicking between Wallet and Home was
   watching the page glide back rather than simply being there. */
html.app, body.app {
  overflow-x: clip;
  scroll-behavior: auto;
  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 */
}

/* Sideways, the column pays for the notch too. In landscape the top inset
   goes to zero and a left or right one takes its place, and this page's one
   column of cards runs full-bleed against that edge. The header and the tab
   bar are member-theme.css's on both of the member's pages, so their side
   insets are said once, there. */
body.app .shell {
  padding-left: calc(16px + env(safe-area-inset-left));
  padding-right: calc(16px + env(safe-area-inset-right));
}

body {
  min-width:320px; margin:0;
  font-family:var(--sans); line-height:1.5;
  color:var(--linen);
  background:
    radial-gradient(1100px 560px at 50% -180px, rgba(139,160,124,.30), transparent 70%),
    radial-gradient(800px 520px at 100% 30%, rgba(20,31,14,.42), transparent 70%),
    var(--fairway);
  -webkit-font-smoothing:antialiased;
}

a { color:var(--ember-300); }
button, select { font:inherit; }
:focus-visible { outline:2px solid var(--ember-400); outline-offset:3px; border-radius:12px; }

.sr-only {
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.booting { padding:80px 24px; text-align:center; color:var(--muted); }

/* ---- signed out ---- */

.signed-out { display:grid; place-items:center; min-height:100dvh; padding:24px; }
.signed-out .card { width:min(420px,100%); display:grid; gap:14px; text-align:center; }
.signed-out h1 { margin:0; font-family:var(--display); font-size:26px; font-weight:900; line-height:1.15; }
.signed-out p { margin:0; color:var(--muted); font-size:15px; }
.signed-out .mark { width:180px; margin:0 auto 4px; }
/* He waves at somebody who is not signed in yet, which is the friendliest
   thing on the page and the only thing on it that is not a decision. */
.signed-out .bear { width:120px; margin:0 auto; animation:bear-bob 4s ease-in-out infinite alternate; }
.signed-out h1 em {
  font-family:var(--script); font-style:normal; font-weight:400; font-size:1.12em;
  color:var(--accent, #cddee8);
}
.quiet { font-size:13.5px; color:var(--muted); text-decoration:none; }
.quiet:hover { color:var(--linen); }

/* ---- the doors, on the card ----

   The shapes are apps/auth/public/auth.css's — a 48px box on a hairline, a
   pill for the one thing to press, the quiet choices as text with a 44px
   target under them, an "or" rule between Google and the rest. The COLOURS
   are not: that page is linen with its own palette, and this is a white card
   on the club's linen wearing member-theme.css's tokens, so every rule here
   reads --ink, --muted, --accent and --primary and the card stays one thing.

   Everything is prefixed `.signed-out` rather than left at one class: the
   skin loads after this sheet and its `body.linen .card p` and
   `body.linen .status` would otherwise have the last word on type that
   belongs to the form. */

.signed-out .doors { display:grid; text-align:left; --door-line:rgba(28,30,23,.18); }
/* The panels swap by class, the page's own sections by [hidden]. Both are
   here because these are two different mechanisms and this is the only
   place the first one is used — and this one has to win outright, the way
   [hidden] does: every control on the card is a .btn with a display of its
   own, and "Use email instead" stayed on screen through the code step
   until this said !important. */
.signed-out .hidden { display:none !important; }

.signed-out .doors label {
  display:block; margin-bottom:7px;
  font-size:13px; font-weight:600; letter-spacing:.01em;
  color:var(--ink,#1c1e17);
}

.signed-out .doors input {
  width:100%; min-height:48px; padding:12px 14px; margin-bottom:14px;
  border:1px solid var(--door-line); border-radius:12px;
  background:var(--card,#fff); color:var(--ink,#1c1e17);
  /* 16px exactly: anything smaller makes iOS Safari zoom the page on focus,
     and on a card this narrow that reads as the layout breaking. */
  font:400 16px/1.4 var(--sans);
}
.signed-out .doors input::placeholder { color:var(--muted,#5d6156); opacity:.7; }
.signed-out .doors input:focus-visible { border-color:var(--accent,#45607a); }


/* One shape for every control on the card, then three faces of it. */
.signed-out .doors .btn {
  width:100%; min-height:52px;
  display:flex; align-items:center; justify-content:center; gap:10px;
  padding:14px 20px;
  border:1px solid transparent; border-radius:26px;
  font-family:var(--sans); font-size:14px; font-weight:800;
  letter-spacing:.1em; text-transform:uppercase;
  text-align:center; text-decoration:none;
  cursor:pointer;
}
.signed-out .doors .btn[disabled] { opacity:.5; cursor:progress; }

/* The page's one bold button, in the page's own green. */
.signed-out .doors .btn-primary { background:var(--primary,#253c1d); color:var(--primary-ink,#ecead5); }
.signed-out .doors .btn-primary:hover:not([disabled]) { background:#1d3116; }

/* Google's own button is white with a hairline and the G mark, and its
   label is Google's sentence rather than the club's, so it is the one
   control here that is not set in small caps. */
.signed-out .doors .btn-google {
  background:var(--card,#fff); color:#1f1f1f; border-color:var(--door-line);
  font-size:14.5px; font-weight:600; letter-spacing:0; text-transform:none;
}
.signed-out .doors .btn-google:hover { border-color:var(--muted,#5d6156); }
.signed-out .doors .btn-google svg { flex:none; width:18px; height:18px; }

/* Apple's button, which is Apple's and not the club's: black, their mark,
   their sentence, and the same 52px pill the rest of the column stands in so
   the two social doors read as a pair. Their Human Interface Guidelines fix
   the wording and the logo; what is left to us is only that it sits on this
   card at the same width as everything else. Drawn in the iOS app alone —
   sign-in.js removes it everywhere else. */
.signed-out .doors .btn-apple {
  background:#000; color:#fff; border-color:#000;
  font-size:14.5px; font-weight:600; letter-spacing:0; text-transform:none;
  margin-top:10px;
}
.signed-out .doors .btn-apple:hover { background:#1a1a1a; }
.signed-out .doors .btn-apple svg { flex:none; width:17px; height:17px; margin-top:-2px; }

/* The secondary choices are text, the way `.quiet` is everywhere else on
   this page — three pills in a column would shout as loudly as the one
   thing to press. The padding keeps a 44px target under a 13.5px word. */
.signed-out .doors .btn-quiet {
  width:auto; min-height:44px; margin:2px auto 0; padding:11px 10px;
  background:none; border:0;
  color:var(--muted,#5d6156);
  font-size:13.5px; font-weight:400; letter-spacing:0; text-transform:none;
}
.signed-out .doors .btn-quiet:hover { color:var(--ink,#1c1e17); text-decoration:underline; }
.signed-out .doors .btn-primary + .btn-quiet { margin-top:8px; }

.signed-out .doors .rule {
  display:flex; align-items:center; gap:12px;
  margin:18px 0;
  color:var(--muted,#5d6156); font-size:12.5px;
}
.signed-out .doors .rule::before, .signed-out .doors .rule::after {
  content:""; flex:1; height:1px; background:var(--door-line);
}

.signed-out .doors .sent-to { margin:-6px 0 16px; color:var(--muted,#5d6156); font-size:13.5px; }

/* say() writes here. It holds its line's worth of room whether or not there
   is anything in it, so an answer never shoves the button under a thumb. */
.signed-out .doors .status { margin:14px 0 0; min-height:1.3em; font-size:13.5px; color:var(--muted,#5d6156); }
.signed-out .doors .status.error { color:var(--danger,#b5502f); }

/* The day the auth Worker cannot be reached from this origin at all. One
   line, under everything, and only then. */
.signed-out .doors .auth-fallback { margin:10px 0 0; text-align:center; font-size:13px; }

/* ---- shell ---- */

.top {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:calc(14px + env(safe-area-inset-top)) 20px 10px;
}
.top .mark { height:22px; }
/* The greeting used to stand here. It is the hero card's headline now — a
   name is too big a thing to say in 15px beside a wordmark — so the header
   keeps the club's mark and one quiet word for where the member is. The
   `.hello` rule stays for the booking page, whose member mode builds the
   same header out of app.js and has no hero of its own. */
.hello { margin:0; font-family:var(--display); font-size:15px; font-weight:900; color:var(--ember-300); }
.top .members {
  margin:0;
  font-size:11px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  color:var(--muted);
}

/* ---- the hero ----

   The booking page's hero, in a phone column: a dark card on the linen, the
   eyebrow behind its short slate rule, the club's cinema face for the
   welcome and the script face for the member's own name at the tilt the
   front page gives it. Cream type never meets the linen — it is all inside
   the card — and the four colours are the ones the skin's hero note already
   measured on this exact gradient: eyebrow 8.7, headline 10.6, the name 6.5,
   the lede 6.4. */
.my-hero {
  padding:26px 22px 24px;
  border-radius:var(--radius, 22px);
  background:linear-gradient(165deg, var(--fairway) 0%, var(--reserve) 100%);
  box-shadow:var(--card-shadow, 0 18px 44px rgba(13,20,9,.16));
  overflow:hidden;
}
.my-hero-eyebrow {
  display:flex; align-items:center; gap:12px;
  margin:0 0 14px;
  font-size:11.5px; font-weight:700; letter-spacing:.24em; text-transform:uppercase;
  color:var(--ember-300);
}
.my-hero-eyebrow::before { content:""; width:34px; height:2px; border-radius:2px; background:var(--ember); }
.my-hero-hello {
  margin:0;
  font-family:var(--display); font-size:clamp(30px, 9vw, 40px); font-weight:900; line-height:1;
  color:var(--linen-100);
}
/* On its own line, in the script face, tilted the way the front page tilts
   "your time." — and left-origin, so a long name leans rather than drifts. */
.my-hero-name {
  display:block; margin-top:2px;
  font-family:var(--script); font-style:normal; font-weight:400; font-size:1.1em; line-height:1.1;
  color:var(--ember-300);
  transform:rotate(-2.5deg); transform-origin:left center;
  padding:0 .1em .12em;
}
.my-hero-lede { margin:14px 0 0; font-size:15px; color:rgba(236,234,213,.88); }
/* A hero with a ticket under it has no lede — the ticket says the facility,
   the day and the hour, and the card would only be saying them again — so
   the bottom of it closes up rather than holding the room the line had. */
.my-hero.compact { padding-bottom:20px; }

.shell {
  width:min(720px,100%); margin:0 auto;
  /* --tabs-reserve, not --tabs: the bar is flush to the bottom edge on a
     phone and a floating pill on a laptop, and the reserve is the one number
     that knows which. Off --tabs alone this column ended its last card
     eighteen pixels under a pill it was supposed to clear. The name is
     member-theme.css's — see the note there — and this page always loads it. */
  padding:6px 16px calc(var(--tabs-reserve) + 28px);
}
.view { display:grid; gap:14px; }

/* A view arrives rather than appearing: its own direct children fade and
   rise, 28ms apart, and the sixth is the last one to wait — everything below
   it comes in with the sixth. my.js takes the class off and puts it back so
   switching tabs replays it, EXCEPT inside the app, where it plays once per
   document and never again (see arrive() and route()).

   It was .5s at 45ms, which is a seventh of a second of stagger before the
   fourth card and about half a second before the eighth. At a phone's
   distance that is not a flourish, it is a page loading in front of you. */
.view.arrive > * { animation:rise .32s var(--ease) both; animation-delay:calc(var(--i, 0) * 28ms); }
.view.arrive > *:nth-child(2) { --i:1; }
.view.arrive > *:nth-child(3) { --i:2; }
.view.arrive > *:nth-child(4) { --i:3; }
.view.arrive > *:nth-child(5) { --i:4; }
.view.arrive > *:nth-child(n+6) { --i:5; }
@keyframes rise { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }

/* Home, drawn from the last answer this browser kept, while the real one is
   still in the air. The figures on screen are a member's own and were true a
   moment ago; this is the page saying so rather than pretending otherwise. */
#tiles.updating::after {
  content:"Updating\2026";
  display:block; margin-top:10px;
  text-align:center; font-size:11.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--linen-400);
}

/* ---- waiting ----

   "Loading…" was a grey sentence where a list was about to be. It is a bar
   with the light moving across it now — the booking page's own skeleton —
   and the word itself stays in the markup, unseen, because a screen reader
   has nothing to read off a gradient. */
.loading { display:grid; gap:10px; }
.shimmer {
  display:block; height:76px;
  border:1px solid var(--hairline, var(--line)); border-radius:14px;
  background:linear-gradient(90deg, rgba(28,30,23,.03), rgba(28,30,23,.09), rgba(28,30,23,.03));
  background-size:200% 100%;
  animation:shimmer 1.15s ease-in-out infinite;
}
@keyframes shimmer { from { background-position:100% 0; } to { background-position:-100% 0; } }

/* The hero's short slate rule, in front of every section heading — the one
   piece of the front page's grammar that survives being shrunk to a phone
   column. (The outlined act numeral does not: it is a full-bleed flourish,
   and at this width it is a smudge behind the words.) */
.section-title {
  display:inline-flex; align-items:center; gap:10px;
  margin:16px 0 -2px;
  font-size:11.5px; font-weight:800; letter-spacing:.18em; text-transform:uppercase;
  color:var(--linen-400);
}
.section-title::before { content:""; flex:none; width:22px; height:2px; border-radius:2px; background:var(--accent, #4e6c88); }

/* ---- cards ---- */

.card {
  padding:18px 20px;
  border:1px solid var(--line); border-radius:22px;
  background:linear-gradient(180deg, rgba(37,60,29,.72), rgba(20,31,14,.72));
  box-shadow:0 18px 44px rgba(13,20,9,.16);
}
.card + .card { margin-top:12px; }
.card h3 { margin:0 0 6px; font-family:var(--display); font-size:20px; font-weight:900; }
.card p { margin:0; color:var(--muted); font-size:14.5px; }
.card .eyebrow {
  margin:0 0 8px;
  font-size:11px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ember-300);
}
.card.accent { border-color:rgba(169,194,209,.42); }
.card.warn { border-color:rgba(224,133,99,.5); }

.empty { margin:0; padding:16px 2px; color:var(--muted); font-size:14.5px; }

/* ---- the bear ----

   The club's line-art bear, at the size the booking page gives him, above
   the words that were already the empty state. He is dark green on nothing,
   so he reads on the linen and on a white card alike — and never goes on the
   hero, which is the one dark ground on this page. He breathes: 3px over
   four seconds, which should be noticed only by somebody looking for it. */
.bear-empty {
  display:grid; justify-items:center; gap:10px;
  padding:36px 20px; text-align:center; color:var(--muted);
}
.bear-empty img { width:110px; animation:bear-bob 4s ease-in-out infinite alternate; }
.bear-empty .empty { padding:0; }
/* The same bear, where the state is a card rather than a line. */
.card-bear {
  display:block; width:110px; margin:0 auto 12px;
  animation:bear-bob 4s ease-in-out infinite alternate;
}
@keyframes bear-bob { from { transform:translateY(0); } to { transform:translateY(-3px); } }

/* ---- next session ---- */

.next-when { margin:2px 0 0; font-size:15px; color:var(--linen); }
.next-when b { font-weight:700; }

/* ---- the next session, as a ticket ----

   The booking site's confirmation, drawn again at phone width: a reserve
   green top with the lockup on it, a perforation with two notches cut out of
   the PAGE colour, and the white body that holds what a member reads. The
   ticket is one element with the skin's radius at both ends; the middle is
   square, or the notches would be cut out of a corner. */
.my-ticket { position:relative; animation:ticket-in .45s var(--ease) both; }
.my-ticket-top {
  padding:24px 22px 26px; text-align:center;
  border-radius:var(--radius, 22px) var(--radius, 22px) 0 0;
  background:var(--fairway); color:var(--linen);
}
.my-ticket-lockup { display:block; height:40px; margin:0 auto 16px; }
.my-ticket-eyebrow {
  margin:0 0 8px;
  font-size:11px; font-weight:800; letter-spacing:.24em; text-transform:uppercase;
  color:var(--ember-300);
}
.my-ticket-headline {
  margin:0;
  font-family:var(--display); font-size:clamp(24px, 7vw, 30px); font-weight:900; line-height:1.02;
  color:var(--linen-100);
}
.my-ticket-headline em {
  font-family:var(--script); font-style:normal; font-weight:400; font-size:1.1em;
  color:var(--ember-400);
}
/* Height nothing, a dashed rule, and the two notches the page shows through.
   The notch colour is the ground the ticket lies on — see the same trick in
   member-theme.css, where the booking page's own perforation is re-cut for
   daylight. */
.my-ticket-perf {
  position:relative; height:0;
  border-top:2px dashed rgba(20,31,14,.35);
  background:var(--linen-050);
}
.my-ticket-perf::before, .my-ticket-perf::after {
  content:""; position:absolute; top:-13px; width:26px; height:26px;
  border-radius:50%; background:var(--paper, #ecead5);
}
.my-ticket-perf::before { left:-13px; }
.my-ticket-perf::after { right:-13px; }
.my-ticket-body {
  padding:20px 20px 22px;
  border-radius:0 0 var(--radius, 22px) var(--radius, 22px);
  background:var(--linen-050); color:var(--ink, #1c1e17);
}

.my-summary { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.my-summary .summary-item {
  padding:12px 14px;
  border:1px solid var(--hairline, rgba(28,30,23,.08)); border-radius:13px;
  background:var(--card, #fff);
}
.my-summary .summary-item.wide { grid-column:span 2; }
.summary-label {
  display:block; margin-bottom:3px;
  font-size:10px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  color:var(--fairway);
}
.summary-value { display:block; font-weight:800; color:var(--reserve); overflow-wrap:break-word; }
/* The one string a member reads out at a counter, in the club's own face and
   on a line of its own. It may not wrap — it is one code — so at 22px, the
   size the 640px guest ticket gives it, an 18-character code ran out of a
   375px phone and off the side of the card. The size is measured off the
   column instead, and 22px is where it stops growing. */
.summary-item.code-cell .summary-value {
  font-family:var(--display); font-size:clamp(14px, 4.6vw, 22px); font-weight:900; letter-spacing:.06em;
}
/* The chip and the bank rows are the skin's own, unchanged — they are on a
   white ground here exactly as they are inside a card. */
.my-ticket-body .chip { margin-top:12px; }

/* ---- buttons ---- */

.primary {
  display:block; padding:15px 18px;
  border:0; border-radius:16px;
  background:var(--ember); color:var(--linen-050);
  font-size:14px; font-weight:800; letter-spacing:.12em; text-transform:uppercase; text-align:center;
  text-decoration:none;
  transition:background .2s, transform .15s var(--ease);
}
.primary:hover { background:#3f607d; color:var(--linen-050); }
.primary:active { transform:scale(.985); }
.book { margin:2px 0; }

.cta {
  display:inline-block; margin-top:10px;
  color:var(--ember-300); font-size:13px; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
  text-decoration:none;
}
.cta:hover { color:var(--linen); }

/* ---- tiles ---- */

.tiles { display:grid; gap:10px; grid-template-columns:repeat(3,1fr); }
.tile {
  padding:14px 12px; text-align:center;
  border:1px solid var(--line); border-radius:18px;
  background:rgba(20,31,14,.42);
}
.tile b { display:block; font-family:var(--display); font-size:26px; font-weight:900; line-height:1.1; }
.tile span { display:block; margin-top:4px; font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--linen-400); }
.allowance { margin:10px 0 0; color:var(--muted); font-size:13.5px; text-align:center; }
/* The month's bar fills from nothing when the card renders — the skin draws
   the bar itself; this is only how long it takes to get there. */
.allowance-bar i { transition:width .7s var(--ease); }

/* ---- lists ---- */

.list { display:grid; gap:10px; }
.row {
  display:flex; align-items:baseline; justify-content:space-between; gap:12px;
  padding:14px 16px;
  border:1px solid var(--line); border-radius:16px;
  background:rgba(20,31,14,.34);
}
.row .what { min-width:0; }
.row .what b { display:block; font-size:15px; font-weight:700; }
.row .what span { display:block; color:var(--muted); font-size:13px; }
.row .when { flex:none; color:var(--linen-400); font-size:12.5px; text-align:right; }

.chip {
  display:inline-block; margin-top:6px; padding:3px 9px;
  border-radius:999px; border:1px solid var(--line);
  font-size:11px; font-weight:800; letter-spacing:.08em; text-transform:uppercase;
  color:var(--linen-400);
}
.chip.paid { color:var(--ember-300); border-color:rgba(169,194,209,.42); }
.chip.due { color:var(--danger); border-color:rgba(224,133,99,.5); }
.chip.dead { color:var(--muted); }
.due-by { display:block; margin-top:5px; color:var(--danger); font-size:12.5px; }

/* The bank rows on an unpaid booking — the same ticket the email carries. */
.pay { margin-top:12px; padding-top:12px; border-top:1px solid var(--line); display:grid; gap:6px; }
.pay-row { display:flex; align-items:baseline; justify-content:space-between; gap:14px; font-size:13.5px; }
.pay-row span { color:var(--muted); }
.pay-row b { font-weight:700; }
.pay-number { font-variant-numeric:tabular-nums; letter-spacing:.06em; }

/* ---- wallet ---- */

/* The balance wears the club's crest, the way a member's card would: the linen
   emblem at 120px behind the number, at .55 on white, which on this ground
   is a watermark rather than a stamp. It is decoration with no words in it,
   so it is a background and never an <img>. */
.balance { position:relative; overflow:hidden; text-align:center; padding:26px 20px; }
.balance::before {
  content:""; position:absolute; z-index:0; top:50%; right:10px;
  width:120px; height:120px; transform:translateY(-50%);
  background:url("/assets/emblem-linen.png") center/contain no-repeat;
  opacity:.55; pointer-events:none;
}
.balance > * { position:relative; z-index:1; }
.balance b { display:block; font-family:var(--display); font-size:46px; font-weight:900; line-height:1; }
/* The tiles' own slate underline, under the one number this page is about. */
.balance b::after {
  content:""; display:block; width:22px; height:2px; margin:10px auto 0;
  border-radius:2px; background:var(--accent, #4e6c88);
}
.balance span { display:block; margin-top:8px; color:var(--linen-400); font-size:12px; font-weight:800; letter-spacing:.16em; text-transform:uppercase; }
.balance .exp { margin-top:10px; color:var(--muted); font-size:13.5px; letter-spacing:0; text-transform:none; font-weight:400; }

/* ---- buy hours ----

   Six packages used to be six lines: "Private Room 10 Hours ₱16,990.00",
   twice over, and nothing on the card said which of them was worth buying.
   They are two shelves of three, so the card says so: a segmented control
   picks the shelf, three tiers stand under it, and each one carries the
   number the list never showed — what an hour of it costs.

   Nothing here decides anything. planFamilies does the grouping and the
   arithmetic; this is the shape of it. */

.buy .seg {
  display:flex; gap:2px; margin-bottom:14px; padding:3px;
  background:rgba(13,20,9,.42); border-radius:15px;
}
.buy .seg-btn {
  flex:1; min-width:0; padding:9px 6px;
  border:0; border-radius:12px; background:none; color:var(--muted);
  font:inherit; font-size:14px; text-align:center; cursor:pointer;
  transition:background .18s var(--ease), color .18s;
}
.buy .seg-btn.on { background:rgba(236,234,213,.13); color:var(--linen); font-weight:700; }

.tiers { display:grid; gap:10px; }

/* The whole row is the target: on a phone a thumb lands on the line, not on
   the circle at the start of it. */
.plan-tier {
  position:relative; display:flex; align-items:center; gap:10px;
  width:100%; padding:15px 14px; text-align:left;
  border:1px solid var(--line); border-radius:18px;
  background:rgba(20,31,14,.42); color:inherit; font:inherit; cursor:pointer;
  transition:box-shadow .18s var(--ease), background .18s;
}
/* The ring is an inset shadow and not a second pixel of border: a row that
   grows when it is picked pushes the two under it down the screen. */
.plan-tier.picked { box-shadow:inset 0 0 0 2px var(--ember-300); }
.plan-tier:hover { background:rgba(20,31,14,.58); }

.tier-mark {
  flex:none; width:20px; height:20px; border-radius:50%;
  border:1.5px solid var(--linen-400);
  transition:border .18s var(--ease);
}
.picked .tier-mark { border:6px solid var(--ember-300); }

.tier-what { flex:1; min-width:0; }
.tier-what b { display:block; font-size:17px; font-weight:700; }
/* The rate is the line this whole card exists to say, and at 375px the
   longest of them — "Good for 6 months · ₱1,499 per hour" — is four pixels
   inside the column. It is allowed to wrap on a narrower phone rather than
   being cut: a truncated price is a lie about a price. */
.tier-what small { display:block; margin-top:3px; color:var(--muted); font-size:12px; }

.tier-price { flex:none; text-align:right; }
.tier-price b { display:block; font-size:17px; font-weight:700; font-variant-numeric:tabular-nums; }
/* What the bigger package saves per hour, which is the whole argument for
   buying one. Its own colour, because it is good news and not a price. */
.tier-price i { display:block; margin-top:3px; color:var(--ok); font-style:normal; font-size:12px; font-weight:700; }

/* The club's recommendation, on the edge of the row it recommends. */
.tier-badge {
  position:absolute; top:-9px; left:14px;
  padding:3px 9px; border-radius:999px;
  background:var(--ember-300); color:var(--reserve-900);
  font-size:10.5px; font-weight:800; letter-spacing:.08em; text-transform:uppercase;
}

/* One bold button under the three, and the sentence that was always here. */
.buy .primary { width:100%; margin-top:16px; }
.buy .note { text-align:center; }
/* The card holding the packages is a wrapper and not a surface: the tiers
   carry their own. The skin says the same thing again, harder, because the
   dark half re-dresses every .card — see member-theme.css. */
.card.buy { padding:0; border:0; background:none; box-shadow:none; }

/* ---- the ticket a package is bought against ---- */

.ticket .pay { margin-top:14px; }
/* The file input is never shown raw — its own button says "No file chosen"
   in the browser's words and its own face. The label is the control. */
.proof { display:block; margin-top:16px; cursor:pointer; }
.proof > span {
  display:block; padding:14px 18px;
  border:1px dashed var(--linen-400); border-radius:16px;
  color:var(--linen); font-size:14px; font-weight:700; text-align:center;
}
.proof:hover > span { border-color:var(--ember-400); color:var(--ember-300); }
.proof input { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.proof:focus-within > span { outline:2px solid var(--ember-400); outline-offset:3px; }

.ticket-actions { display:grid; grid-template-columns:auto 1fr; gap:10px; margin-top:16px; align-items:center; }
.ghost {
  padding:15px 18px; border:1px solid var(--line); border-radius:16px;
  background:none; color:var(--muted);
  font-size:13px; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
  cursor:pointer;
}
.ghost:hover { color:var(--linen); border-color:var(--linen-400); }
/* Disabled has to LOOK disabled: a Send that is merely inert is a member
   tapping it four times and concluding the club's app is broken. */
.primary:disabled { background:rgba(78,108,136,.34); color:var(--muted); cursor:not-allowed; }
.primary:disabled:active { transform:none; }
button.primary { width:100%; border:0; cursor:pointer; }

.error { display:block; margin-top:12px; color:var(--danger); font-size:13.5px; }
.error:empty { display:none; }

/* A top-up the counter is still looking at, and one it turned down. */
.card.pending { border-color:rgba(169,194,209,.42); }
.card.rejected { border-color:rgba(224,133,99,.5); }
.card .reason { margin-top:8px; color:var(--danger); font-size:13.5px; }

/* ---- profile ---- */

.facts { display:grid; gap:10px; }
.fact { display:flex; align-items:baseline; justify-content:space-between; gap:14px; }
.fact span { color:var(--linen-400); font-size:12px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; }
.fact b { font-weight:600; font-size:15px; text-align:right; overflow-wrap:anywhere; }
.ok { color:var(--ember-300); }

.pref { display:grid; gap:8px; }
.pref span { color:var(--linen-400); font-size:12px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; }
/* 16px or iOS zooms the page on focus and never zooms back — the same
   threshold the staff pages carry a note about. */
select { width:100%; padding:12px 13px; font-size:16px; color:var(--linen-050); background:rgba(13,20,9,.46); border:1px solid var(--line); border-radius:12px; }
.note { margin:10px 0 0; color:var(--muted); font-size:13px; }

.row-link {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:13px 2px; color:var(--linen); text-decoration:none; font-weight:600;
}
/* Sign out DOES something rather than going somewhere, so it is a button
   now. It still has to read as the row above it. */
button.row-link { width:100%; background:none; border:0; font:inherit; font-weight:600; text-align:left; cursor:pointer; }
.row-link + .row-link { border-top:1px solid var(--line); }
.row-link span { color:var(--linen-400); }
.version { margin:18px 0 0; text-align:center; color:var(--linen-400); font-size:12px; }

/* ---- the tab bar ---- */

.tabs {
  position:fixed; inset:auto 0 0 0; z-index:20;
  display:grid; grid-template-columns:repeat(4,1fr);
  padding-bottom:env(safe-area-inset-bottom);
  background:rgba(20,31,14,.92);
  backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
  border-top:1px solid var(--line);
}
.tab {
  display:grid; gap:3px; justify-items:center; align-content:center;
  min-height:62px; padding:8px 4px;
  color:var(--muted); text-decoration:none;
}
.tab svg { width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
.tab span { font-size:10.5px; font-weight:700; letter-spacing:.06em; }
.tab[aria-current="page"] { color:var(--ember-300); }
/* The tab you are on leans in a little. Nothing slides: on a four-tab rail a
   moving indicator is a thing to watch instead of a thing to read. */
.tab svg { transition:transform .2s var(--ease); }
.tab span, .tab { transition:color .2s; }
.tab[aria-current="page"] svg { transform:scale(1.08); }

@media (min-width:760px) {
  /* On a laptop the bar stops being a thumb rail and becomes a centred
     strip, so it does not stretch a metre wide across the bottom. */
  .tabs { left:50%; right:auto; bottom:18px; transform:translateX(-50%); width:min(520px,92%); border:1px solid var(--line); border-radius:22px; }
}

/* ---- motion ----

   The ticket arrives the way the booking page's does: the same keyframes
   under the same name, copied rather than imported because the two sheets
   never load together — my.css is this page's only structure and
   test/my-page-wiring.test.js forbids pulling styles.css in beside it. */
@keyframes ticket-in {
  from { opacity:0; transform:translateY(44px) rotate(-1deg); }
  to { opacity:1; transform:none; }
}

/* Nobody who has asked for less movement gets any of it. One block, and it
   switches off animation as well as transition: an animation is the half
   `transition:none` never reached. */
@media (prefers-reduced-motion:reduce) {
  * { animation:none !important; transition:none !important; }
}

/* The error line report() writes inside the app. Red on white so it is
   read as what it is; never shown on the web, which has a console. */
.diag { margin:0 0 8px; padding:10px 12px; border-radius:12px; background:#fff; color:var(--danger,#b5502f); font-size:12.5px; word-break:break-word; }
