/* =====================================================================
   Homepage — Pickleta
   The hero is the product's actual thesis: a court seen from above with
   a live queue beside it. Structure carries meaning, not decoration.
   ===================================================================== */

/* Full-bleed and dark. The old hero sat on the page background, so the page
   opened on the same colour it ended on and nothing announced the top. */
.hero {
  position: relative;
  padding: 52px 0 56px;
  background: var(--brand);
  color: var(--on-brand);
  overflow: hidden;
}
/* A wash of the action colour bleeding in from the right, so the hero is not
   a flat rectangle. Sized in vmax so it scales with the viewport rather than
   turning into a hard circle on a phone.

   rgb(190,211,30) is --action. It was still rgb(189,241,28) after the
   rebrand -- the old lime, which is brighter and more acid -- so the one
   warm note in the hero was the only thing on the page not wearing the
   new palette. */
.hero::after {
  content: "";
  position: absolute;
  top: -30%;
  right: -10%;
  width: 60vmax;
  height: 60vmax;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(190,211,30,.16) 0%, rgba(190,211,30,0) 70%);
  pointer-events: none;
}

/* ---- the ball crossing the hero floor -------------------------------- */
/* z-index 2, with the copy. At 0 it sat under both the photograph and the
   scrim that darkens it, which is to say it rolled along invisibly. */
.hero-floor {
  position: absolute;
  left: 0; right: 0; bottom: 6px;
  height: 90px;
  pointer-events: none;
  z-index: 2;
}
.hero-ball {
  position: absolute;
  bottom: 0; left: 0;
  width: 54px; height: 54px;
  animation: ball-travel 13s linear infinite;
}
.hero-hop, .hero-spin { display: block; width: 100%; height: 100%; }
/* Sized in pixels, not percent. app.css caps every image at max-width:100%,
   and a percentage width inside a parent that is itself sizing from its
   content has nothing to resolve against -- the mark collapsed to nothing. */
.hero-ball .ps-ball { display: block; width: 54px; height: 54px; max-width: none; }
/* bottom centre, so the squash flattens the ball against the floor rather
   than shrinking it towards its middle. */
.hero-hop {
  transform-origin: 50% 100%;
  /* One crossing, one arrival: the hop shares the travel's 13s so the
     bounces land in the same places on every pass. On its own shorter
     loop the two drift against each other and the ball touches down
     somewhere different each time round. The easing lives in the
     keyframes, one function per leg, so this stays linear. */
  animation: ball-hop 13s linear infinite;
}
/* A little under one turn per bounce, which is what a ball this size does
   crossing a screen this wide. Exactness is not the point; a ball that
   slides without turning is what looks wrong. */
/* A 54px ball is about 170px around, and it crosses roughly 1600px, so a
   turn every 1.4s is close to the distance it actually covers. Rolling
   that does not match the travel reads as a ball skidding. */
.hero-spin { animation: ball-spin 1.4s linear infinite; }
.hero-ball .ps-ball { opacity: .95; }

@keyframes ball-travel {
  from { transform: translateX(-90px); }
  to   { transform: translateX(calc(100vw + 90px)); }
}
/* The stops are bunched near the floor and spread near the top: that is
   where the time goes when something falls, and evenly-spaced stops give
   the hovering, weightless bounce of a screensaver. */
/* Drops in, bounces four times with the height dying away, then runs flat
   along the floor for the rest of the crossing -- which is the whole point:
   it should arrive bouncing and leave rolling.

   Each keyframe carries the easing for the leg that FOLLOWS it: ease-in
   while gravity pulls it down, ease-out while it climbs and slows. A
   single ease-in-out across the lot makes it float like a balloon.

   The squash is only on the contact frames, and only horizontal-wide /
   vertical-short, against the transform-origin at the foot of the ball. */
@keyframes ball-hop {
  0%   { transform: translateY(-104px) scale(1);         animation-timing-function: ease-in; }
  6%   { transform: translateY(0)      scale(1.18, .82); animation-timing-function: ease-out; }
  15%  { transform: translateY(-78px)  scale(1);         animation-timing-function: ease-in; }
  24%  { transform: translateY(0)      scale(1.14, .86); animation-timing-function: ease-out; }
  32%  { transform: translateY(-46px)  scale(1);         animation-timing-function: ease-in; }
  39%  { transform: translateY(0)      scale(1.10, .90); animation-timing-function: ease-out; }
  45%  { transform: translateY(-22px)  scale(1);         animation-timing-function: ease-in; }
  50%  { transform: translateY(0)      scale(1.06, .94); animation-timing-function: ease-out; }
  54%  { transform: translateY(-8px)   scale(1);         animation-timing-function: ease-in; }
  57%  { transform: translateY(0)      scale(1.03, .97); animation-timing-function: ease-out; }
  59%, 100% { transform: translateY(0) scale(1); }
}
@keyframes ball-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Scenery is the first thing to go when somebody has asked for less of it. */
@media (prefers-reduced-motion: reduce) {
  .hero-floor { display: none; }
}
@media (min-width: 860px) { .hero { padding: 84px 0 88px; } }

.hero-grid { display: grid; gap: 40px; grid-template-columns: 1fr; align-items: center; }
@media (min-width: 860px) { .hero-grid { grid-template-columns: 1.05fr .95fr; gap: 56px; } }

.hero h1 {
  font-size: clamp(38px, 8.5vw, 68px);
  line-height: .98;
  letter-spacing: -.035em;
  margin-bottom: 18px;
}
/* Over the scrim, which is at 1. A pseudo-element and a child DIV at the
   same z-index are ordered by the DOM, and the photo DIV comes second -- so
   the photograph painted straight over the scrim that exists to make this
   text readable. */
.hero-grid > * { position: relative; z-index: 2; }

.hero h1 { color: var(--on-brand); }
/* The pull-quote takes the action colour. On the dark hero the bright orange
   is readable (6.6:1) where it would not be on the light page. */
.hero h1 em {
  font-style: normal;
  color: var(--action);
}
.hero-eyebrow {
  display: inline-block;
  margin-bottom: 14px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--action-on-dark);
}
.hero-lead {
  font-size: clamp(16.5px, 2.2vw, 19px);
  color: var(--on-brand);
  opacity: .82;
  max-width: 46ch;
  margin-bottom: 26px;
}

/* ---- the code field, which is the hero's real call to action --------- */
/* One control: a field and its button share a single rounded shell, so it
   reads as one thing to do rather than two things to choose between. */
.hero-join {
  display: flex;
  /* stretch, so the field is as tall as the button beside it. At
     min-height:46 against a 54px btn-lg the input stopped short of the
     shell it sits in, and the part of the control that looked clickable
     was larger than the part that actually took a click. */
  align-items: stretch;
  gap: 8px;
  padding: 7px;
  margin-bottom: 14px;
  max-width: 460px;
  background: rgba(255,255,255,.09);
  /* A field has an edge. At 1px/.18 the shell read as decoration around
     the button rather than as the thing you type in. */
  border: 1.5px solid rgba(255,255,255,.24);
  border-radius: var(--r-full);
  transition: background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.hero-join-input {
  flex: 1;
  min-width: 0;
  padding: 0 22px;
  background: transparent;
  border: 0;
  color: var(--on-brand);
  font-family: var(--mono);
  font-size: 16px;            /* 16px keeps iOS from zooming on focus */
  letter-spacing: .22em;
  text-transform: uppercase;
}
.hero-join-input::placeholder {
  color: var(--on-brand);
  /* .5 measured 4.41:1 on the hero navy, under the 4.5 floor — a prompt
     nobody can read is a field with no label. .62 is 5.99:1 and still
     clearly lighter than typed text. */
  opacity: .62;
  letter-spacing: .04em;
  text-transform: none;
  font-family: var(--body);
}
/* The ring belongs to the shell, not the input: the two are one control,
   and a ring around the inner box would draw a rounded rectangle inside a
   pill. Killing the input's own outline is only safe because this
   replaces it with something at least as loud. */
.hero-join-input:focus,
.hero-join-input:focus-visible { outline: none !important; }
.hero-join:focus-within {
  background: rgba(255,255,255,.14);
  border-color: var(--action);
  box-shadow: 0 0 0 4px rgba(190, 211, 30, .25);
}
.hero-join .btn { border-radius: var(--r-full); flex: none; }

.hero-note { font-size: 13.5px; color: var(--on-brand); opacity: .7; }
.hero-note a { color: var(--action-on-dark); font-weight: 700; }

/* ---- the court diagram: this IS the product, drawn ------------------ */
/* The stage the demo stands on. Without it the card takes the whole width
   of the wrap -- measured at 1145px, which reads as a banner rather than
   as a screen. Held to a laptop-ish measure and centred, it looks like the
   thing it is a picture of, and it sits directly under the three steps it
   illustrates. */
.demo-stage {
  max-width: 720px;
  margin: var(--s-6) auto 0;
}

/* This card spent its life on the navy hero, where a white translucent
   fill and a white hairline made it read as a lit screen. It moved to a
   light section and those same rules left it invisible: the fill was
   white-on-white, the border was white-on-white, and the head text came
   out at 1.08:1 against the page. Measured, not guessed. So it is built
   for the ground it actually stands on now -- a white screen, a real
   hairline, and ink dark enough to read. */
.court-demo {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 18px;
  box-shadow: var(--shadow-lift);
  color: var(--text);
}
.court-demo-head {
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px;
}
.court-demo-head .row { gap: 7px; }

.mini-court {
  position: relative;
  background: var(--court-surface);
  border-radius: var(--r-md);
  padding: 16px 12px;
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
/* the net */
.mini-court::before {
  content: ""; position: absolute; top: 12px; bottom: 12px; left: 50%;
  border-left: 2px dashed var(--court-line); transform: translateX(-1px);
}
/* the kitchen, either side of the net */
.mini-court::after {
  content: ""; position: absolute; top: 12px; bottom: 12px;
  left: calc(50% - 42px); width: 84px;
  border-left: 1px solid var(--court-line); border-right: 1px solid var(--court-line);
  opacity: .6;
}
.mini-side { position: relative; z-index: 1; text-align: center; color: var(--text); }
.mini-names { font-size: 13.5px; font-weight: 600; line-height: 1.35; min-height: 38px; }
/* Doto is a dot-matrix face, and its legibility runs the opposite way to
   the intuition: dropping to 700 to "unclog" the dots separates them into
   specks and a 9 stops reading as a 9 at all. Rendering the digits side by
   side at 700/46, 900/58 and 900/76 settled it — the dots need to be fat
   enough to touch, and more pixels help rather than hurt. Hence the
   heaviest weight, and a size nearer the board's than the old one.
   tabular-nums so 7 and 11 occupy the same width and the score does not
   shift sideways when somebody reaches double figures. */
.mini-score {
  font-family: var(--dot); font-weight: 900; font-size: 64px; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: .02em; margin-top: 4px;
}

/* Inset against the card rather than the same white as it, or the list
   has no edges at all now that the card behind it is white. */
.demo-queue {
  margin-top: var(--s-3); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden;
}
.demo-queue-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 13px; border-bottom: 1px solid var(--border);
  font-size: 14px; color: var(--text);
}
.demo-queue-row:last-child { border-bottom: none; }
.demo-queue-row.up { background: var(--accent-soft); }
.demo-pos { font-family: var(--mono); font-size: 12.5px; color: var(--text-faint); width: 18px; }
.demo-queue-row.up .demo-pos { color: var(--accent); }
.demo-name { flex: 1; font-weight: 600; }
.demo-meta { font-family: var(--mono); font-size: 11.5px; color: var(--text-faint); }

/* Wide enough, and the court and the queue sit beside each other the way
   they do on the real board on a laptop. Narrower, they stack -- the court
   cannot lose any more width before the two scores start colliding. */
@media (min-width: 680px) {
  .court-demo {
    display: grid; grid-template-columns: 1fr 1fr;
    column-gap: var(--s-4); align-items: start;
  }
  .court-demo-head { grid-column: 1 / -1; }
  .demo-queue { margin-top: 0; }
}

/* ---- sections -------------------------------------------------------- */
/* --sec is the beat between one section and the next, and it is the only
   place that number lives. Sections used to pick their own, which is how
   a page ends up reading as a list of unrelated blocks. */
.section { padding: var(--sec) 0; }
.section-head { max-width: 60ch; margin-bottom: var(--s-5); }
.section-head h2 { font-size: clamp(26px, 4.4vw, 36px); margin: 8px 0 12px; }
.section-head p { color: var(--text-muted); font-size: 16.5px; }

/* Numbered because it genuinely is a sequence: three steps, in order. */
.steps { display: grid; gap: 18px; grid-template-columns: 1fr; counter-reset: step; }
@media (min-width: 760px) { .steps { grid-template-columns: repeat(3, 1fr); } }
.step { padding: 22px; border-radius: var(--r-lg); background: var(--surface); border: 1px solid var(--border); }
.steps { list-style: none; padding: 0; margin: 0; }
.step-no {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  margin-bottom: 14px;
  border-radius: var(--r-full);
  background: var(--action-soft);
  color: var(--action-ink);
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 500;
}
.step h3 { font-size: 18.5px; margin-bottom: 7px; }
.step p { color: var(--text-muted); font-size: 14.5px; }

.features { display: grid; gap: 16px; grid-template-columns: 1fr; }
@media (min-width: 700px) { .features { grid-template-columns: 1fr 1fr; } }
.feature { display: flex; gap: 14px; padding: 20px; border-radius: var(--r-lg); background: var(--surface); border: 1px solid var(--border); }
.feature-ico { flex: none; width: 38px; height: 38px; border-radius: var(--r-sm); background: var(--action-soft); color: var(--action-ink); display: grid; place-items: center; }
.feature h3 { font-size: 16.5px; margin-bottom: 5px; }
.feature p { font-size: 14px; color: var(--text-muted); }

/* ---- closing band ---------------------------------------------------- */
.band {
  background: var(--brand); color: var(--on-brand);
  border-radius: var(--r-xl); padding: var(--s-6) var(--s-5); text-align: center;
}
.band h2 { font-size: clamp(25px, 4.5vw, 34px); margin-bottom: 10px; color: var(--on-brand); }
.band p { color: var(--on-brand); opacity: .8; max-width: 48ch; margin: 0 auto 22px; }
.band .btn-ghost { color: var(--on-brand); border-color: color-mix(in srgb, var(--on-brand) 40%, transparent); }
.band .btn-ghost:hover { color: var(--on-brand); border-color: var(--on-brand); }

/* ---- join-by-code strip ---------------------------------------------- */
.codebar { display: flex; gap: 10px; flex-wrap: wrap; }
.codebar .input {
  font-family: var(--mono); text-transform: uppercase;
  letter-spacing: .28em; font-size: 18px; text-align: center;
  flex: 1; min-width: 160px;
}


/* =====================================================================
   Live activity — the section that shows the product is actually in use.
   Modelled on the reference's venue cards: status first, then the name,
   then the few figures somebody decides on.
   ===================================================================== */

/* A tinted section breaks the page into parts. Without it the homepage is
   one long column of white and the eye has nowhere to rest. */
.section-tint { background: var(--bg-alt); }
.section-live { padding-bottom: 20px; }

.play-grid {
  display: grid;
  gap: 16px;
  grid-template-columns: 1fr;
}
@media (min-width: 700px)  { .play-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .play-grid { grid-template-columns: repeat(3, 1fr); } }

.play-card {
  display: flex;
  flex-direction: column;
  padding: 18px;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  color: var(--text);
  text-decoration: none;
  transition: transform .16s ease, box-shadow .16s ease;
}
.play-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }

.play-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

/* Waiting is the quiet state and playing is the loud one, so only one of
   them gets a colour. A badge on every card would signal nothing. */
.play-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.play-status .livedot { opacity: .35; }
.play-status.is-playing { color: var(--action-ink); }
.play-status.is-playing .livedot { opacity: 1; }

.play-flag {
  flex: none;
  padding: 3px 10px;
  border-radius: var(--r-full);
  background: var(--action-soft);
  color: var(--action-ink);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.play-name { font-size: 18.5px; margin-bottom: 5px; }
.play-where {
  font-size: 13.5px;
  color: var(--text-muted);
  margin-bottom: 16px;
  overflow-wrap: anywhere;
}

/* A definition list, because that is what it is: three labelled figures. */
.play-meta {
  display: flex;
  gap: 22px;
  margin: 0 0 2px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.play-meta div { min-width: 0; }
.play-meta dt {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 3px;
}
.play-meta dd {
  margin: 0;
  font-family: var(--mono);
  font-size: 15px;
  color: var(--text);
}

.play-when { margin-top: 12px; font-size: 12.5px; color: var(--text-faint); }

/* Signed out: the count is honest about there being something here without
   publishing where it is. */
.live-locked { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

.band-cta { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
/* A ghost button on the dark band has to borrow the band's ink, not the
   page's, or it renders as dark text on a dark ground. */
.ui-refresh .btn-ghost.btn-on-dark {
  color: var(--on-brand);
  border-color: rgba(255,255,255,.4);
}
.ui-refresh .btn-ghost.btn-on-dark:hover {
  background: rgba(255,255,255,.12);
  border-color: var(--on-brand);
  color: var(--on-brand);
}

.ui-refresh .band .eyebrow { color: var(--action-on-dark); display: block; margin-bottom: 10px; }

@media (prefers-reduced-motion: reduce) {
  .play-card { transition: none; }
  .play-card:hover { transform: none; }
}

/* =====================================================================
   Sign-in, registration and join.
   These were a narrow column on the page ground, which made them read as
   an afterthought hanging under the header. Centring them on a tinted
   ground turns them into a destination — and on a phone, where the form
   is all there is, it removes the wasted band at the top.
   ===================================================================== */
.section-auth {
  display: flex;
  align-items: center;
  min-height: calc(100vh - 220px);
  padding: 44px 0 64px;
  background: var(--bg-alt);
}
.section-auth .wrap-narrow { width: 100%; }
.section-auth h1 { font-size: clamp(28px, 6vw, 38px); margin-top: 8px; }
/* The form is the page here, so it gets the deeper shadow rather than the
   flat one every other card uses. */
.section-auth .card { box-shadow: var(--shadow-lift); }

/* =====================================================================
   Find an open play — the public listing.
   The filter bar sits in the dark head rather than above the results,
   because on a nationwide list choosing where you are is the first
   thing you do, not an afterthought applied to what you were shown.
   ===================================================================== */
.find-head {
  padding: 40px 0 34px;
  background: var(--brand);
  color: var(--on-brand);
}
.find-head h1 {
  color: var(--on-brand);
  font-size: clamp(28px, 5vw, 42px);
  margin-bottom: 10px;
}
.find-head .find-lead {
  color: var(--on-brand);
  opacity: .82;
  max-width: 56ch;
  margin-bottom: 22px;
}

.find-form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: stretch;
}
.find-field { flex: 1 1 190px; min-width: 0; }
/* The free-text box earns more room than the two selects. */
.find-field-grow { flex: 2 1 240px; }
.find-form .btn { flex: none; }

/* The controls sit on the dark head, so they cannot use the page's field
   styling — that is a light box with a light border on a dark ground. */
.find-head .find-form .input {
  background: rgba(255,255,255,.12);
  border-color: rgba(255,255,255,.22);
  color: var(--on-brand);
  min-height: 46px;
}
.find-head .find-form .input::placeholder { color: var(--on-brand); opacity: .55; }
.find-head .find-form .input:focus {
  border-color: var(--action-on-dark);
  background: rgba(255,255,255,.18);
  outline: none;
  box-shadow: none;
}
/* A native select drops its options on the browser's own surface, so the
   option text has to stay legible there rather than inheriting this one. */
.find-head .find-form select.input option { color: #0E1917; background: #FFFFFF; }

/* The city and region, above the venue line on a listing card. */
.play-place {
  font-weight: 700;
  color: var(--action-ink);
}
.live-more { text-align: center; margin-top: 22px; }
/* A plain nav link beside the buttons in the site header. */
.ui-refresh .site-head .site-link {
  color: var(--on-brand);
  font-weight: 700;
  font-size: 14.5px;
  padding: 0 6px;
}
.ui-refresh .site-head .site-link:hover { color: var(--action-on-dark); }


/* =====================================================================
   Pricing.
   Three cards, one of them raised. The raised one is a recommendation,
   not a trick: it is genuinely cheaper past fifteen sessions, and the
   card says so rather than just looking louder.
   ===================================================================== */
.plan-grid {
  display: grid;
  gap: 18px;
  grid-template-columns: 1fr;
  /* align-items: start; */
  margin-bottom: 64px;
}
/* Four cards now: free, and the three bundles. Explicit steps rather than
   auto-fit, because auto-fit lands on three columns at tablet widths and
   strands the fourth on a row of its own. */
@media (min-width: 640px)  { .plan-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .plan-grid { grid-template-columns: repeat(4, 1fr); } }

.plan-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 26px 24px;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
}
/* Lifted rather than merely outlined: on a phone the three cards stack and
   a border alone stops reading as "this one". */
.plan-card.is-best {
  box-shadow: var(--shadow-lift);
  border: 1.5px solid var(--action-dark);
}

.plan-flag {
  position: absolute;
  top: -11px;
  left: 24px;
  padding: 3px 12px;
  border-radius: var(--r-full);
  background: var(--action);
  color: var(--on-action);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
}

.plan-name {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 10px;
}
.plan-price { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.plan-price strong {
  font-family: var(--display);
  font-size: 38px;
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1;
}
.plan-price span { font-size: 13.5px; color: var(--text-faint); }

.plan-line { font-size: 14.5px; color: var(--text-muted); margin-bottom: 20px; }

.plan-has { list-style: none; padding: 0; margin: 0 0 24px; display: grid; gap: 10px; }
.plan-has li {
  position: relative;
  padding-left: 26px;
  font-size: 14.5px;
  line-height: 1.45;
}
/* A drawn tick rather than an emoji: it inherits the text colour, scales with
   the type, and reads the same on every platform. */
.plan-has li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 6px;
  width: 11px;
  height: 6px;
  border-left: 2px solid var(--action-ink);
  border-bottom: 2px solid var(--action-ink);
  transform: rotate(-45deg);
}

/* The button sits at the bottom whatever the card above it says, so the
   three line up across the row. */
.plan-card .btn { margin-top: auto; }
.plan-note { margin-top: 12px; font-size: 12.5px; color: var(--text-faint); text-align: center; }

.plan-faq { max-width: 660px; margin: 0 auto; }
.plan-faq h2 { font-size: clamp(22px, 3.5vw, 30px); margin-bottom: 20px; text-align: center; }
.plan-faq .calc { margin-bottom: 12px; }
/* Live open plays, said in the hero rather than only in a section further
   down. It is the one number on this page that is true right now, and a
   claim about a live product is worth more above the fold than below it.
   Hidden entirely when there are none: an empty boast is worse than
   silence. */
.hero-live {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 700; text-decoration: none;
  color: var(--on-brand);
  border-bottom: 1.5px solid var(--action);
  padding-bottom: 1px;
}
.hero-live:hover { color: var(--action-on-dark); }

/* The one paragraph on the pricing page that is about mechanics rather than
   value. Set apart from the pitch above it so it reads as a fact, not a
   claim. */
.paynote {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(255,255,255,.14);
  font-size: 15px;
}
.paynote strong { color: var(--action-on-dark); }

/* ---- the photographic hero ------------------------------------------- */
/* The court sits behind everything in the hero. Scoped to .has-photo so the
   plain navy hero is still what every other page gets. */
.hero-photo { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.hero-photo img {
  display: block; width: 100%; height: 100%;
  max-width: none;              /* app.css caps every image at 100% */
  object-fit: cover;
  /* The delivered image is already cropped so the figure sits about two
     thirds across -- doing it in the file rather than here because at this
     aspect cover scales by WIDTH, shows the whole frame, and object-position
     X has no effect at all. Only Y steers: enough to keep his head in and
     his feet on the court. */
  object-position: 50% 46%;
}

/* The scrim, which is the whole reason the copy is still readable. Two
   gradients: one across, to hold the left column on solid brand, and one up
   from the floor, because the court is pale down there and the note line
   sits on it. Written as explicit rgba rather than color-mix: this is the
   one rule the text's contrast depends on, and it must not silently
   degrade to transparent anywhere.

   These were rgba(33,51,62) -- #21333E, the brand colour from before the
   Pickleta rebrand. The hero itself is painted var(--brand) = #013E83, so
   the scrim was washing the photograph in a slate-teal that belonged to no
   other surface on the site: the one hero with a photograph read as a
   different blue from every hero without one.

   It is NOT a straight substitution. The new navy is LIGHTER than the old
   one -- relative luminance .0509 against .0304 -- so reusing these stops
   with #013E83 would hand back the contrast they were measured to win. So
   the ramp now starts on --brand-deep #012C5E (L .0262, darker than the
   colour it replaces) where the copy sits, and opens into --brand #013E83
   as it crosses toward the figure. Deeper where it has to carry text,
   truer to the palette where it has to carry the photograph. */
.hero.has-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  /* The two gradients MULTIPLY where they overlap: .40 across and .45 down is
     two thirds opaque, not four tenths, which is how the first attempt buried
     the character it was meant to frame. The vertical one now only seats the
     hero against the section below; the horizontal one does the real work of
     holding the copy, and lets go by the time it reaches the figure. */
  background:
    linear-gradient(180deg, rgba(1,44,94,.34) 0%, rgba(1,44,94,0) 24%,
                            rgba(1,44,94,0) 66%, rgba(1,44,94,.58) 100%),
    /* Measured, not guessed. The previous stops left the lightest pixel behind
       the copy at rgb(103,116,122): white passed at 4.82:1 but the lime links
       came out 3.62:1 and the lead text 3.45:1, both under the 4.5 floor. The
       plateau now holds to 48% -- past the right edge of the copy column --
       and then drops fast so the figure is still lit. */
    linear-gradient(90deg, rgba(1,44,94,.97) 0%, rgba(1,44,94,.95) 38%,
                           rgba(1,44,94,.90) 48%, rgba(1,62,131,.34) 62%,
                           rgba(1,62,131,.10) 100%);
  pointer-events: none;
}

/* On a phone the copy runs the full width, so a side wash cannot work and a
   flat heavy one buried the photograph: at .86 across the whole frame the
   figure was a faint smudge behind the words, which is a 1400px photo doing
   the job of a dark rectangle.
   
   So the two stop competing for the same space. The hero becomes a column —
   words on solid brand, then the photograph beneath them as a band of its own,
   where the figure is fully visible and nothing has to be read over him. */
@media (max-width: 859px) {
  .hero.has-photo {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 30px 0 0;
  }

  /* Out of the background and into the flow, below the copy. */
  .hero.has-photo .hero-photo {
    position: relative;
    order: 2;
    width: 100%;
    height: 210px;
    margin-top: 26px;
  }
  .hero.has-photo .hero-grid { order: 1; }

  .hero-photo img { object-position: 62% 42%; }

  /* Only a seam now: a little brand at the top of the band so the photograph
     grows out of the copy instead of starting at a hard edge. */
  .hero.has-photo::before {
    top: auto;
    bottom: 0;
    height: 210px;
    background: linear-gradient(180deg, rgba(1,44,94,.92) 0%, rgba(1,44,94,0) 38%);
  }

  .hero.has-photo .hero-copy { max-width: none; }
}

@media (max-width: 420px) {
  .hero.has-photo .hero-photo { height: 180px; }
  .hero.has-photo::before { height: 180px; }
}


/* One column on the photo hero. The second column used to be the demo card;
   it moved out so the figure in the photograph has somewhere to stand, which
   is the whole point of using a photograph. The copy is held to a readable
   measure rather than stretched across the frame. */
.hero.has-photo .hero-grid { grid-template-columns: 1fr; gap: 0; }
@media (min-width: 860px) {
  .hero.has-photo .hero-grid { grid-template-columns: 1fr; gap: 0; }
  .hero.has-photo .hero-copy { max-width: 46%; }
  /* The extra floor is the ball's lane. At 76px the copy ran to within
     20px of where the ball rolls and it crossed the "open plays running
     right now" line -- measured, not guessed. */
  .hero.has-photo { padding: 72px 0 112px; }
}
@media (min-width: 1200px) {
  .hero.has-photo .hero-copy { max-width: 620px; }
}
/* Over a photograph the dimmed body copy is doing real work, not decoration:
   .82 measured 3.45:1 against the brightest part of the court behind it. */
.hero.has-photo .hero-lead { opacity: .94; }
.hero.has-photo .hero-note { opacity: .88; }
/* ---- court owners, before listings exist ------------------------------ */
.venue-ask { display: grid; gap: 26px; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) {
  .venue-ask { grid-template-columns: 1.05fr .95fr; gap: 48px; }
}
.venue-ask-copy h2 { font-size: clamp(24px, 3.4vw, 32px); line-height: 1.12; margin: 10px 0 12px; }
.venue-ask-copy p { color: var(--text-muted); max-width: 46ch; }
.venue-ask-form {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 22px; box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 14px;
}
.venue-ask-form .grid-2 { gap: 14px; }

/* One card left on the pricing page: the bundles are bought in the dashboard
   now. The grid above goes to four columns at 1080px, which left the single
   free card sitting quarter-width against the left edge. Centred, and held to
   a width a card actually reads at. */
.plan-grid-one { grid-template-columns: 1fr !important; max-width: 420px; margin-inline: auto; }

/* =====================================================================
   PRICING
   =====================================================================
   The page sells one thing now -- the free start -- and a single card
   adrift in the middle of a wide page does not look like a decision,
   it looks like a leftover. So the offer moves INTO the dark band and
   takes the half of it that was empty navy, and the band stops being a
   title bar and becomes the page.
   ===================================================================== */

.price-head { padding: 48px 0 56px; }

.price-hero { display: grid; gap: 28px; grid-template-columns: 1fr; align-items: center; }
@media (min-width: 900px) {
  /* Copy a little wider than the offer: the sentence has to breathe, the
     card only has to be tappable. */
  .price-hero { grid-template-columns: 1.15fr .85fr; gap: 48px; }
  .price-head { padding: 64px 0 72px; }
}

.price-say h1 { margin-bottom: 14px; }
.price-say .find-lead { margin: 0; }

/* The payment note is an aside, not a second lead. Ruled off so it reads as
   a footnote to the promise above rather than competing with it. */
.price-say .paynote {
  margin-top: 20px; padding-top: 18px;
  border-top: 1px solid rgba(255,255,255,.16);
  font-size: 14.5px; line-height: 1.55; color: var(--on-brand); opacity: .88;
  max-width: 54ch;
}
.price-say .paynote strong { color: var(--accent-on-dark, #B8F14A); opacity: 1; }

/* ---- the offer card ------------------------------------------------- */
.price-offer {
  background: var(--surface);
  color: var(--text);
  border-radius: var(--r-xl);
  padding: 26px 24px 22px;
  box-shadow: var(--shadow-lift);
}

.price-offer-top { margin-bottom: 18px; }

.price-tag {
  display: inline-block;
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; font-weight: 700;
  color: var(--accent); background: var(--accent-soft);
  padding: 5px 10px; border-radius: 999px;
}

/* The number is the headline. Two free open plays is the whole pitch, so it
   gets display type and the words sit beside it rather than under it. */
.price-big { display: flex; align-items: center; gap: 12px; margin: 14px 0 0; }
.price-big strong {
  font-family: var(--display); font-size: 62px; font-weight: 800;
  line-height: .9; letter-spacing: -.04em; color: var(--text);
}
.price-big span { font-size: 15px; line-height: 1.3; color: var(--text-muted); }

.price-offer .plan-has { margin-bottom: 20px; }

/* What it costs afterwards, said quietly and immediately under the button --
   the one place somebody actually asks it. */
.price-after {
  margin: 14px 0 0; text-align: center;
  font-size: 13.5px; color: var(--text-muted);
}
.price-after strong { color: var(--text); }

/* ---- what you get --------------------------------------------------- */
@media (min-width: 1000px) {
  .price-features { grid-template-columns: repeat(2, 1fr); }
}

/* The features grid and the FAQ share one section, so nothing separated
   them: "Questions people ask" landed hard against the last card and read
   as a caption for it. */
.price-features { margin-bottom: 64px; }
@media (min-width: 900px) { .price-features { margin-bottom: 84px; } }
