/* ============================================================
   DETAILBYTE CORE — shared design system
   ============================================================
   Loaded LAST on every surface (admin, installer, booking wizard,
   customer portal) so its :root wins over each surface's own tokens.

   Three token vocabularies are in play across the app:
     admin/app.css  --bg --card --card2 --line --accent --accent2 --text --muted --glow
     book.php       --bg --surface --surface-2 --border --red --red-soft
     portal pages   --bg-surface-card --border-line --accent-red --text-main --text-muted
   All three are redefined below, so restyling here recolours every
   screen without touching a single inline <style> block.

   The operator's actual brand colours are emitted INLINE by
   brand_css_vars() / portal_theme_head(), which beats this stylesheet
   by specificity — so re-branding always wins over these defaults.
   ============================================================ */

:root{
  /* ---- Deep-space surfaces ---- */
  --v-bg-0:#050507;
  --v-bg-1:#0a0a0f;
  --v-bg-2:#101014;
  --v-glass:rgba(20,20,26,.72);
  --v-glass-2:rgba(26,26,33,.66);
  --v-solid:#101014;

  /* ---- Hairlines ---- */
  --v-line:rgba(255,255,255,.07);
  --v-line-strong:rgba(255,255,255,.12);
  --v-line-hot:rgba(255,42,42,.28);

  /* ---- Accent ramp ----
     Fill stays the deep brand red. Accent TEXT uses the bright tint —
     #c00000 on near-black fails legibility, #ff5c5c does not. */
  --accent:#c00000;
  --accent2:#ff2a2a;
  --accent-text:#ff5c5c;
  --accent-rim:#ff3355;
  --glow:255,42,42;
  --pglow:255,42,42;

  /* ---- Glow presets ----
     Deliberately restrained. Glow is an accent that says "this is the
     important thing", not a light source — once several elements on a
     screen glow hard, none of them read as emphasised any more. */
  --v-glow-sm:0 0 0 1px rgba(var(--glow),.12), 0 4px 14px rgba(var(--glow),.10);
  --v-glow-md:0 0 0 1px rgba(var(--glow),.16), 0 8px 22px rgba(var(--glow),.14);
  --v-glow-lg:0 0 0 1px rgba(var(--glow),.22), 0 14px 36px rgba(var(--glow),.18);
  --v-shadow:0 18px 44px rgba(0,0,0,.46);

  /* ---- Geometry / motion ---- */
  --v-r-sm:10px; --v-r-md:16px; --v-r-lg:22px; --v-r-pill:999px;
  --v-ease:cubic-bezier(.2,.7,.2,1);
  --v-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  /* Display face. app.css already defines this; repeated here so the wizard
     and portal (which never load app.css) resolve the same heading font. */
  --head-font:'Barlow Condensed',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;

  /* ---- Aliases: admin (app.css) ---- */
  --bg:var(--v-bg-1);
  --card:var(--v-bg-2);
  --card2:#15151b;
  --line:var(--v-line);

  /* ---- Aliases: booking wizard (book.php) ---- */
  --surface:var(--v-bg-2);
  --surface-2:#15151b;
  --border:var(--v-line);
  --red:var(--accent);
  --red-soft:rgba(var(--glow),.14);

  /* ---- Aliases: customer portal pages ---- */
  --bg-surface-card:var(--v-bg-2);
  --bg-surface:var(--v-bg-1);
  --border-line:var(--v-line);
  --accent-red:var(--accent);
  --text-main:#f2f2f5;
  --text-muted:#9a9aa6;
}

/* ============================================================
   AMBIENT BACKGROUND — deep space + blueprint grid + red glow
   Pure CSS: no images, no extra requests.
   ::before = grid + vignette, ::after = red glows.
   Both live here and nowhere else — a second stylesheet painting
   its own body::after would double-glow the page.
   ============================================================ */
/* The base colour lives on <html> and the ambient layers sit at z-index:-1, so
   they paint behind ALL content without any element needing to be repositioned.
   (An earlier version lifted content with `position:relative;z-index:1`, which
   silently overrode .sidebar's `position:fixed;z-index:1000` and broke the admin
   layout — never override `position` just to fix stacking.) */
html{background:var(--v-bg-1)}
/* Base typography lives here, not only in app.css.
   This file defines --head-font so headings resolve everywhere, but it never
   set a BODY font — and the customer portal, the auth screens and the
   installer load this file WITHOUT app.css. So every one of them rendered
   their body copy, their .v-eyebrow pills and their footer in the browser's
   default face while the admin panel rendered Inter, because the only
   `body{font:...}` in the codebase was app.css:5.
   Same declaration as app.css, so on admin pages (which load app.css first,
   then this) it resolves identically and nothing moves. */
body{background:transparent;font:14px/1.5 Inter,system-ui,-apple-system,Segoe UI,Roboto,sans-serif}

body::before,
body::after{content:"";position:fixed;pointer-events:none;z-index:-1}

body::before{
  inset:0;
  /* app.css also styles body::before (its old ambient glow, now replaced by
     ::after below). Explicitly reset the properties it sets, or its blur,
     70vh height and drift animation would be inherited by this layer. */
  height:auto;filter:none;animation:none;
  /* Vignette only. This used to carry a 40px blueprint grid as well; at any
     opacity high enough to see it read as graph paper behind the content,
     so it's gone. Depth comes from the vignette and the glow below. */
  background:radial-gradient(130% 100% at 50% 0%,transparent 35%,rgba(0,0,0,.50) 100%);
}

body::after{
  inset:-15% -10% auto -10%;height:70vh;
  background:
    radial-gradient(46% 58% at 18% 0%,rgba(var(--glow),.13),transparent 70%),
    radial-gradient(42% 54% at 84% 4%,rgba(var(--glow),.10),transparent 72%);
  filter:blur(12px);
  animation:vAmbient 22s ease-in-out infinite alternate;
}
@keyframes vAmbient{0%{transform:translate3d(0,0,0)}100%{transform:translate3d(0,26px,0) scale(1.05)}}

/* ============================================================
   SCROLLBARS — previously only .sidebar was styled; everything
   else fell back to the browser default.
   ============================================================ */
*{scrollbar-width:thin;scrollbar-color:rgba(var(--glow),.34) transparent}
html{scrollbar-gutter:stable}

*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,rgba(var(--glow),.42),rgba(var(--glow),.22));
  border-radius:var(--v-r-pill);
  border:2px solid transparent;
  background-clip:padding-box;
}
*::-webkit-scrollbar-thumb:hover{
  background:linear-gradient(180deg,rgba(var(--glow),.68),rgba(var(--glow),.40));
  background-clip:padding-box;
}
*::-webkit-scrollbar-corner{background:transparent}

/* ============================================================
   FOCUS — visible accent ring everywhere (accessibility)
   ============================================================ */
:focus-visible{
  outline:2px solid rgba(var(--glow),.75);
  outline-offset:2px;
  border-radius:6px;
}

/* ============================================================
   SURFACES — glass cards with a bright top hairline
   ============================================================ */
.card,.step-card,.v-panel{
  position:relative;
  background:linear-gradient(180deg,rgba(23,23,29,.82),rgba(16,16,21,.88));
  border:1px solid var(--v-line);
  border-radius:var(--v-r-lg);
  box-shadow:var(--v-shadow);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
}

/* Inner/nested panels: same material, tighter radius, no heavy shadow. */
.muted-card,.booking-hero-chip,.customer-meta-card,.health-status-card,
.timeline-group-card,.comm-column,.post-job-box,.dashboard-job-card{
  background:rgba(255,255,255,.028)!important;
  border:1px solid var(--v-line)!important;
  border-radius:var(--v-r-md)!important;
}

/* Bright hairline across the top edge — the "lit rim" from the refs.
   app.css already defines .card::before with inset:0 + an inset box-shadow;
   bottom/box-shadow/border-radius are reset here or the element would be
   over-constrained (top+bottom+height) and stretch the full card. */
.card::before,.step-card::before,.v-panel::before{
  content:"";position:absolute;left:16px;right:16px;top:0;bottom:auto;height:1px;
  border-radius:0;box-shadow:none;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.22),transparent);
  pointer-events:none;
}

.card{transition:box-shadow .22s var(--v-ease),border-color .22s var(--v-ease)}
.card:hover{
  border-color:rgba(var(--glow),.20);
  box-shadow:0 20px 46px rgba(0,0,0,.45),0 0 26px rgba(var(--glow),.10);
}
/* An auth card IS the page — there is nothing to hover-reveal, so it
   keeps one resting shadow. (.v-featured sets its own with !important.) */
.v-auth,.v-auth:hover{box-shadow:var(--v-shadow)}

/* ============================================================
   BUTTONS — ONE primary CTA, three surfaces
   Admin used .btn-primary, the wizard .wizard-btn.primary and the
   portal a flat .btn — three different reds, only one of which
   glowed. They are all the same button now; .v-cta is the canonical
   name for new markup.
   ============================================================ */
.btn-primary,.wizard-btn.primary,.v-cta{
  background:linear-gradient(135deg,var(--accent),var(--accent2))!important;
  border:1px solid rgba(var(--glow),.40)!important;
  color:#fff!important;
}
/* .v-glow is the bare utility — glow only, no colours of its own. */
.btn-primary,.wizard-btn.primary,.v-cta,.v-glow{
  box-shadow:0 6px 18px rgba(var(--glow),.22),inset 0 1px 0 rgba(255,255,255,.16);
  transition:box-shadow .22s var(--v-ease),transform .22s var(--v-ease),filter .22s var(--v-ease);
}
.btn-primary:hover,.wizard-btn.primary:hover:not(:disabled),.v-cta:hover,.v-glow:hover{
  box-shadow:0 10px 26px rgba(var(--glow),.34),inset 0 1px 0 rgba(255,255,255,.22);
  transform:translateY(-1px);
}
/* A disabled CTA must not look clickable — no glow, no lift. */
.wizard-btn.primary:disabled,.v-cta:disabled,.btn-primary:disabled{
  box-shadow:none!important;transform:none!important;
  background:rgba(255,255,255,.06)!important;
  border-color:var(--v-line)!important;
  color:#6f6f7a!important;
  opacity:1!important;
}

/* ============================================================
   PRIMITIVES — the reference design language, v- prefixed so they
   can never collide with existing class names.
   ============================================================ */

/* Eyebrow pill — bordered, accent, softly glowing */
.v-eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  padding:7px 14px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.28);
  background:rgba(var(--glow),.07);
  color:var(--accent-text);
  font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  box-shadow:inset 0 0 18px rgba(var(--glow),.05);
}

/* Accent-coloured headline fragment */
.v-accent-text{color:var(--accent-text)}

/* Short accent underline beneath a title */
.v-rule-accent{
  width:46px;height:3px;margin:10px 0 0;border:none;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--glow),.55);
}
.v-rule-accent.is-center{margin-left:auto;margin-right:auto}

/* Icon containers */
.v-icon-tile{
  display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:11px;flex:0 0 auto;
  background:rgba(var(--glow),.10);
  border:1px solid rgba(var(--glow),.22);
  color:var(--accent-text);font-size:17px;line-height:1;
}
.v-icon-ring{
  display:inline-flex;align-items:center;justify-content:center;
  width:78px;height:78px;border-radius:50%;flex:0 0 auto;
  background:radial-gradient(circle at 50% 40%,rgba(var(--glow),.16),transparent 70%);
  border:1px solid rgba(var(--glow),.30);
  color:var(--accent-text);font-size:30px;
  box-shadow:inset 0 0 26px rgba(var(--glow),.14),0 0 26px rgba(var(--glow),.14);
}

/* Check rows */
.v-check-list{display:grid;gap:10px;margin:0;padding:0;list-style:none}
.v-check-row{
  display:flex;align-items:center;gap:12px;
  padding:12px 14px;border-radius:var(--v-r-sm);
  background:rgba(255,255,255,.028);
  border:1px solid var(--v-line);
}
.v-check-row.is-ok{border-color:rgba(var(--glow),.22)}
.v-check{
  display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;border-radius:50%;flex:0 0 auto;
  background:rgba(var(--glow),.14);
  border:1px solid rgba(var(--glow),.40);
  color:var(--accent-text);font-size:12px;font-weight:800;
  box-shadow:0 0 12px rgba(var(--glow),.26);
}
.v-check-label{flex:1;min-width:0;font-size:13.5px;font-weight:600;color:#e7e7ec}

/* Featured card — accent rim, not a floodlight. The halo used to be
   0 18px 52px at 34% alpha, which lit up half the viewport. */
.v-featured{
  border-color:rgba(var(--glow),.30)!important;
  background:linear-gradient(180deg,rgba(30,14,17,.55),rgba(16,16,21,.90))!important;
  box-shadow:var(--v-glow-lg),var(--v-shadow)!important;
}
.v-featured::before{
  background:linear-gradient(90deg,transparent,rgba(var(--glow),.55),transparent)!important;
}

/* Stat tiles.
   The admin's existing stat grids are fixed at 4 columns (.profile-summary)
   or 2 (.customer-stats-grid), so any screen with a different number of
   figures had to be split across several cards to avoid ragged rows. This
   one fits whatever it's given. */
.v-stat-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:12px;
}
.v-stat{
  display:block;padding:14px 16px;border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);
  border:1px solid var(--v-line);
  text-decoration:none;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-stat-label{
  display:block;font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  color:#8f8f9a;margin-bottom:7px;
}
.v-stat-value{
  display:block;font-family:var(--head-font);
  font-size:27px;font-weight:700;line-height:1.05;color:#f4f4f5;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.v-stat-note{display:block;margin-top:6px;font-size:11.5px;color:#8f8f9a}
.v-stat.is-accent .v-stat-value{color:var(--accent-text);text-shadow:0 0 22px rgba(var(--glow),.26)}
.v-stat.is-alert{border-color:rgba(var(--glow),.34);background:rgba(var(--glow),.07)}
.v-stat.is-alert .v-stat-value{color:var(--accent-text)}
/* Only tiles that actually link anywhere should react to the cursor. */
a.v-stat:hover{border-color:rgba(var(--glow),.34);box-shadow:0 0 20px rgba(var(--glow),.12)}
/* A long date or address shouldn't be forced onto one line like a figure. */
.v-stat.is-text .v-stat-value{font-size:17px;white-space:normal;line-height:1.35}

/* Vehicle and booking-history rows on the customer page are secondary to
   the page heading, so they take a smaller title than the 24px default.
   This was four repeated inline font-size overrides. */
.customer-list-card .list-card-title{font-size:20px}

/* app.css locks .list-card-grid-meta to `repeat(4,minmax(0,1fr))`, but the
   vehicle rows (Registration + Size) and history rows (Vehicle + Price) only
   ever put TWO boxes in it. The pair was being squeezed into two of four
   narrow tracks inside an already half-width card — roughly 115px each — so
   "Registration" wrapped where "Size" didn't and the value ran into its
   neighbour. auto-fit collapses the empty tracks, so two boxes share the row
   evenly however wide the card is. */
.customer-list-card .list-card-grid-meta{
  /* min(140px,100%) rather than a bare 140px: a fixed track floor can't
     shrink below itself and would overflow the card on a narrow phone. */
  grid-template-columns:repeat(auto-fit,minmax(min(140px,100%),1fr));
}
/* app.css already sets min-width:0 and overflow-wrap:anywhere here; the extra
   overflow:hidden is the backstop for a registration with no spaces in it,
   which has nothing to wrap on and would otherwise spill past its own box. */
.customer-list-card .list-card-grid-meta .muted-card{min-width:0;overflow:hidden}
.customer-list-card .list-card-grid-meta strong{
  display:block;min-width:0;overflow-wrap:anywhere;
}
/* Keep the label on one line so the two boxes can't end up different heights. */
.customer-list-card .list-card-grid-meta .customer-meta-label{
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* ---- Settings navigation (admin/settings.php) ----
   The groups were named "General", "Booking Setup", "Integrations" and
   "Customer Programs" with no explanation, so nothing told you which one held
   what — and settings drifted into whichever sounded closest. Each group and
   each tab now states its purpose on screen, not just in a tooltip. */
.v-settings-grouphint{
  margin:10px 2px 16px;max-width:80ch;
  font-size:13px;line-height:1.6;color:#8f8f9a;
}
.v-settings-intro{
  margin:18px 0 4px;padding-left:14px;
  border-left:2px solid rgba(var(--glow),.45);
}
.v-settings-intro h2{
  margin:0 0 4px;font-family:var(--head-font);
  font-size:22px;line-height:1.15;color:#f4f4f5;
}
.v-settings-intro p{margin:0;max-width:80ch;font-size:13px;line-height:1.6;color:#8f8f9a}

/* ---- Booking form sections (admin/booking_form.php) ----
   The form was a single card holding twenty-odd fields in one grid. It is five
   numbered sections now, so you can see where you are in it. */
.bf-section{margin-bottom:16px}
.bf-section .section-head h2{display:flex;align-items:center;gap:10px}
.bf-step{
  display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;border-radius:50%;flex:0 0 auto;
  background:rgba(var(--glow),.12);
  border:1px solid rgba(var(--glow),.30);
  color:var(--accent-text);
  font-family:var(--v-mono);font-size:12px;font-weight:700;
}

/* Sticky action bar. The form runs well past one screen, so Save used to be
   stranded at the bottom of a long scroll on every edit. Carries the running
   total, which otherwise was only visible while the Price section was on
   screen. */
.bf-actions{
  position:sticky;bottom:0;z-index:30;
  display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;
  margin-top:18px;
  padding:14px 18px calc(14px + env(safe-area-inset-bottom));
  border-radius:var(--v-r-md) var(--v-r-md) 0 0;
  background:rgba(8,8,11,.92);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid var(--v-line);
  box-shadow:0 -10px 30px rgba(0,0,0,.40);
}
.bf-actions-total{font-size:13px;color:#8f8f9a}
.bf-actions-total strong{
  font-family:var(--head-font);font-size:22px;color:#f4f4f5;
  margin-left:6px;text-shadow:0 0 22px rgba(var(--glow),.26);
}
.bf-actions-buttons{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
@media (max-width:560px){
  .bf-actions{padding:12px 14px calc(12px + env(safe-area-inset-bottom))}
  .bf-actions-buttons{width:100%}
  .bf-actions-buttons .btn{flex:1;min-height:46px;justify-content:center}
}

/* ---- Delivery log summary cards ----
   These were static numbers. They are the obvious place to click when you want
   "show me the failures", so they are links now and need link styling. */
.log-stat-card{
  text-decoration:none;
  /* Was a div, so the number inherited body text. As an <a> it would pick up
     the link colour instead — state it explicitly. */
  color:#f4f4f5;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.log-stat-card .log-stat-num{color:inherit}
.log-stat-card:hover{border-color:rgba(var(--glow),.30);box-shadow:0 0 20px rgba(var(--glow),.12)}
.log-stat-card.is-active{
  border-color:rgba(var(--glow),.42)!important;
  background:rgba(var(--glow),.08)!important;
}
.log-stat-card.is-active .log-stat-num{color:var(--accent-text)}
/* Failures are the only count that should pull the eye. */
.log-stat-card.is-alert .log-stat-num{color:var(--accent-text)}

.log-empty{padding:22px 16px;font-size:13.5px;color:#8f8f9a}
.log-empty a{color:var(--accent-text)}

/* A log row behaves as a button, so it needs a focus ring like one. */
.log-row:focus-visible{outline:2px solid rgba(var(--glow),.75);outline-offset:-2px}

/* ---- Dashboard setup alerts ----
   Broken cron and a missing public site URL are invisible failures: nothing
   errors, messages just quietly never arrive. They belong on the first screen
   you look at, not only on the tab that configures them. Amber, not the brand
   red — this is "needs attention", not "danger". */
.v-setup-alerts{display:grid;gap:10px;margin:0 0 22px}
.v-setup-alert{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:14px 16px;border-radius:var(--v-r-md);
  background:rgba(224,179,65,.07);
  border:1px solid rgba(224,179,65,.28);
}
.v-setup-icon{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:32px;height:32px;border-radius:50%;
  background:rgba(224,179,65,.14);
  border:1px solid rgba(224,179,65,.40);
  color:#e0b341;font-size:15px;line-height:1;
}
.v-setup-body{flex:1 1 320px;min-width:0;display:flex;flex-direction:column;gap:3px}
.v-setup-body strong{font-size:14px;color:#f0d79a}
.v-setup-body span{font-size:12.5px;line-height:1.55;color:#a9a99f}
.v-setup-alert .btn{flex:0 0 auto;white-space:nowrap}

/* One make/model awaiting a size confirmation. Each row is its own form. */
.v-size-row{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin:0;padding:12px 0;
  border-bottom:1px solid var(--v-line);
}
.v-size-row:last-of-type{border-bottom:none}
.v-size-name{flex:1 1 200px;min-width:0;font-weight:600;color:#e7e7ec}
.v-size-count{flex:0 0 auto;font-size:12px;color:#8f8f9a}
.v-size-row select{flex:0 0 auto;min-width:110px}
.v-size-row .btn{flex:0 0 auto}

/* Pointers to settings that live on another tab but affect this one. */
.v-crosslinks{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
.v-crosslink{
  display:block;padding:14px 16px;border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);
  border:1px solid var(--v-line);
  text-decoration:none;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-crosslink:hover{border-color:rgba(var(--glow),.34);box-shadow:0 0 20px rgba(var(--glow),.12)}
.v-crosslink-title{
  display:block;margin-bottom:6px;
  font-weight:700;font-size:13.5px;color:var(--accent-text);
}
.v-crosslink-title::after{content:" \2192"}
.v-crosslink-note{display:block;font-size:12.5px;line-height:1.55;color:#8f8f9a}

/* ---- Vehicle size conflict (admin/booking_view.php) ----
   A vehicle whose saved size contradicts a size an admin has confirmed for
   that make/model. Customers choose their own size in the portal and the
   booking wizard and the price follows it, so a van saved as a medium car
   under-quotes the job silently. Amber rather than red: it is a discrepancy
   to check, not a failure. */
.v-size-conflict{
  display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;
  margin-bottom:16px;padding:16px 18px!important;
  border:1px solid rgba(240,199,120,.34)!important;
  background:linear-gradient(180deg,rgba(58,45,20,.42),rgba(16,16,21,.90))!important;
}
.v-size-conflict::before{
  background:linear-gradient(90deg,transparent,rgba(240,199,120,.55),transparent)!important;
}
.v-size-conflict-icon{font-size:19px;line-height:1.3;flex:0 0 auto;color:#f0c778}
.v-size-conflict-body{
  flex:1 1 320px;min-width:0;
  font-size:13.5px;line-height:1.6;color:#d8cdb4;
}
.v-size-conflict-body strong{color:#f7e6bf;font-weight:700}
.v-size-conflict-action{display:flex;align-items:center;gap:10px;flex-wrap:wrap}

/* Size shown on the booking's Vehicle chip — it drives the price, so it
   belongs next to the vehicle rather than only inside the edit form. */
.v-size-tag{
  display:inline-flex;align-items:center;gap:4px;
  margin-left:8px;padding:2px 8px;border-radius:var(--v-r-pill);
  background:rgba(255,255,255,.06);border:1px solid var(--v-line-strong);
  font-size:11px;font-weight:600;color:#b9b9c4;white-space:nowrap;
}
.v-size-tag.is-conflict{
  background:rgba(240,199,120,.14);border-color:rgba(240,199,120,.40);
  color:#f0c778;cursor:help;
}

/* ---- Booking hero (admin/booking_view.php) ----
   app.css lays the hero out as `minmax(0,1fr) auto`, with the action bar in
   the auto track. An `auto` track sizes to MAX-CONTENT, and the action bar is
   a non-wrapping flex row of Start job + the ETA card + On my way + Cancel +
   Reopen — roughly 950px of it. So the auto track took two thirds of the card
   and the meta column collapsed to ~340px, which forced app.css's four fixed
   chip tracks down to ~78px each: the "tiny boxes" symptom. Worse, the right
   two thirds then sat empty below the buttons.

   Stacking the hero fixes the cause rather than the symptom — the chips get
   the full card width and the actions get their own band underneath. */
.booking-hero-top{grid-template-columns:minmax(0,1fr)}

/* auto-FILL, not auto-fit: the chip count varies from five to seven (Source
   and Lifecycle only appear for a public request and a closed job). auto-fit
   collapses the unused tracks and stretches a short last row into two huge
   boxes; auto-fill keeps them, so every chip is the same width whatever the
   count and the row simply ends early. */
.booking-hero-meta{
  grid-template-columns:repeat(auto-fill,minmax(min(210px,100%),1fr));
  gap:12px;
}

/* Its own band at the foot of the hero. `position:sticky` was there to keep
   the buttons in view while scrolling, which stops meaning anything once the
   bar is no longer a tall side column. */
.booking-action-bar{
  position:static!important;
  justify-content:flex-start!important;
  align-items:flex-end;
  padding-top:18px;
  border-top:1px solid var(--v-line);
}
/* Each chip stacks label → value → any trailing badge. As a plain block the
   value ran inline against the tag next to it ("Bmw M3 Medium car"); as a
   flex column each part gets its own line without needing a wrapper element
   around the bare text node. */
.booking-hero-chip{
  display:flex;flex-direction:column;align-items:flex-start;gap:7px;
  min-width:0;padding:13px 15px!important;
  font-size:14px;font-weight:600;color:#ededf2;
  overflow-wrap:anywhere;
}
.booking-hero-chip-label{
  margin-bottom:0!important;font-weight:700;
}
.booking-hero-chip a{color:#ededf2;text-decoration:none;overflow-wrap:anywhere}
.booking-hero-chip a:hover{color:var(--accent-text)}
.booking-hero-chip .badge{margin:0}
/* No longer trails inline text, so the inline offset would just indent it. */
.booking-hero-chip .v-size-tag{margin-left:0}

/* ---- Portal invite bar (admin/customers.php) ----
   The control was a bare "Select all invitable on this page" tick box with no
   hint that "invitable" meant the customer portal, and a send button that
   stayed enabled with nothing selected. */
.v-invite-bar{
  margin-bottom:14px;
  display:flex;align-items:flex-start;justify-content:space-between;gap:18px;flex-wrap:wrap;
}
.v-invite-bar.is-empty{align-items:center}
.v-invite-copy{flex:1;min-width:min(320px,100%)}
.v-invite-check{
  display:flex;align-items:center;gap:10px;
  margin:12px 0 0;cursor:pointer;
  font-size:14px;font-weight:600;color:#e7e7ec;
}
.v-invite-check input{width:17px;height:17px;flex:0 0 auto;accent-color:var(--accent2);cursor:pointer}
.v-invite-note{
  margin:8px 0 0;max-width:64ch;
  font-size:12.5px;line-height:1.6;color:#8f8f9a;
}
.v-invite-note strong{color:var(--accent-text);font-weight:600}
.v-invite-warn{color:#e0b341}
.v-invite-send{flex:0 0 auto;align-self:center}
.v-invite-send:disabled{cursor:not-allowed}

/* Per-row pick control — keeps the customer code aligned whether or not the
   row has a tick box, so the cards don't jitter between states. */
.v-invite-pick{
  display:flex;align-items:center;gap:8px;
  margin-bottom:6px;min-height:20px;cursor:pointer;
}
.v-invite-pick.is-static{cursor:default;padding-left:25px}
.v-invite-pick input{width:17px;height:17px;flex:0 0 auto;accent-color:var(--accent2);cursor:pointer}

/* Portal account state. These were four inline style="" blobs repeated in a
   PHP function; as classes they follow the theme instead of being fixed. */
.v-portal-badge{border:1px solid currentColor}
.v-portal-badge.is-active{background:rgba(74,222,128,.12);color:#4ade80}
.v-portal-badge.is-pending{background:rgba(251,191,36,.12);color:#fbbf24}
.v-portal-badge.is-disabled{background:rgba(248,113,113,.12);color:#f87171}
.v-portal-badge.is-none{background:rgba(154,154,163,.12);color:#9a9aa3}

/* A stat tile standing in for something not done yet. */
.v-stat.is-pending{border-style:dashed}
.v-stat.is-pending .v-stat-value{color:#8f8f9a}

/* Short status line above a card's call to action. */
.v-empty-note{margin-bottom:14px;color:#8f8f9a;font-size:13px}

/* ---- Activity log ----
   The booking screen used to carry a Communication card (3 status columns)
   AND a Timeline card (3 category columns) built from the same source, so
   every message was printed twice. One list, with a connecting rail. */
.v-comm-summary{
  display:flex;flex-wrap:wrap;gap:8px;
  margin-bottom:18px;padding-bottom:16px;
  border-bottom:1px solid var(--v-line);
}
.v-comm-summary .v-chip strong{color:#f4f4f5;font-weight:700}
.v-comm-summary .v-chip.is-accent strong{color:var(--accent-text)}

.v-timeline{position:relative}
/* Rail behind the icon column — .timeline-item is a 30px + 1fr grid, so the
   line sits at half the icon width. Inset top and bottom so it starts and
   ends inside the first and last icons rather than floating past them. */
.v-timeline::before{
  content:"";position:absolute;left:15px;top:14px;bottom:14px;width:1px;
  background:linear-gradient(180deg,transparent,var(--v-line) 12%,var(--v-line) 88%,transparent);
  pointer-events:none;
}
.v-timeline .timeline-item{position:relative;z-index:1}
.v-timeline-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;
}
.v-timeline-head .small{white-space:nowrap;color:#8f8f9a}

/* Progress bar */
.v-progress{
  height:6px;border-radius:var(--v-r-pill);overflow:hidden;
  background:rgba(255,255,255,.07);
}
.v-progress-fill{
  display:block;height:100%;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent),var(--accent2));
  box-shadow:0 0 12px rgba(var(--glow),.55);
  transition:width .4s var(--v-ease);
}

/* Contact strip — label above a value that may be a mailto:/tel: link */
.v-contact-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;
}
.v-contact-value{
  display:block;font-size:14px;font-weight:600;color:#e7e7ec;
  word-break:break-word;text-decoration:none;
}
a.v-contact-value:hover{color:var(--accent-text)}

/* Small chip / pill */
.v-chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:4px 10px;border-radius:var(--v-r-pill);
  background:rgba(255,255,255,.05);
  border:1px solid var(--v-line-strong);
  font-size:11.5px;font-weight:600;color:#c9c9d2;
}
.v-chip.is-accent{
  background:rgba(var(--glow),.10);
  border-color:rgba(var(--glow),.30);
  color:var(--accent-text);
}
.v-chip.is-mono{font-family:var(--v-mono);letter-spacing:.02em}

/* Success ring — concentric glow, for completion moments */
.v-success-ring{
  position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:96px;height:96px;border-radius:50%;
  background:radial-gradient(circle,rgba(var(--glow),.20),transparent 68%);
  border:2px solid rgba(var(--glow),.55);
  color:#fff;font-size:40px;
  box-shadow:0 0 34px rgba(var(--glow),.42),inset 0 0 28px rgba(var(--glow),.20);
}
.v-success-ring::before,.v-success-ring::after{
  content:"";position:absolute;border-radius:50%;border:1px solid rgba(var(--glow),.26);
  pointer-events:none;
}
.v-success-ring::before{inset:-14px;animation:vPulse 2.6s var(--v-ease) infinite}
.v-success-ring::after{inset:-28px;border-color:rgba(var(--glow),.14);animation:vPulse 2.6s var(--v-ease) .5s infinite}
@keyframes vPulse{0%{opacity:.85;transform:scale(.96)}70%{opacity:0;transform:scale(1.10)}100%{opacity:0;transform:scale(1.10)}}

/* Brand plate — replaces the flat #111 tile that boxed the logo in */
.v-brand-plate{
  display:inline-flex;align-items:center;justify-content:center;
  padding:12px 16px;border-radius:var(--v-r-md);
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.02));
  border:1px solid rgba(var(--glow),.18);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.10);
}
.v-brand-plate img{display:block;max-width:100%;height:auto;object-fit:contain;
  filter:drop-shadow(0 2px 10px rgba(0,0,0,.55))}
/* Opt-in light backing for dark customer logos */
.v-brand-plate.is-light{background:linear-gradient(180deg,#fff,#eceef2);border-color:rgba(255,255,255,.30)}
.v-brand-plate-fallback{
  font-weight:800;letter-spacing:.06em;color:var(--accent-text);
  text-shadow:0 0 18px rgba(var(--glow),.45);
}

/* ============================================================
   ADMIN CHROME
   ============================================================ */

/* ---- Icons ----
   app.css sized .side-ico as a text glyph (21px box, 14.5px font). The set
   is SVG now, so the box sizes the artwork directly and every icon lands on
   the same optical weight — which text emoji never did. */
.side-ico{
  display:inline-flex;align-items:center;justify-content:center;
  width:20px!important;height:20px!important;flex:0 0 auto;
}
.side-ico-svg{width:18px;height:18px;display:block}
.topbar-ico{width:19px;height:19px;display:block}
.notif-ico{width:17px;height:17px;display:block;flex:0 0 auto;margin-top:1px}
.timeline-ico{width:16px;height:16px;display:block}
/* Inline with a label: nudged onto the text baseline. */
.head-ico{width:15px;height:15px;flex:0 0 auto;vertical-align:-2px}
.badge-ico{width:14px;height:14px;flex:0 0 auto;vertical-align:-2px}
.v-size-tag-ico{width:12px;height:12px;flex:0 0 auto}
.notif-bell-btn{font-size:0!important}
.notif-bell-btn .topbar-ico{margin:0 auto}
.timeline-icon{
  display:flex!important;align-items:center;justify-content:center;
  color:#a6a6b1;
}
.v-notice-ico{display:inline-flex;flex:0 0 auto;color:#c9c9d2}
.v-notice-ico .vp-ico{width:19px;height:19px}
.badge{gap:7px}

/* ---- Buttons ----
   .btn-primary already carried the brand gradient, rim and glow, while
   success/warning/danger were flat muddy fills (#184f35, #6c4a12, #611818)
   with no rim and no relationship to anything else on screen — three
   unrelated colour blocks in one row. They are tinted glass with a coloured
   rim now: same construction as .btn, distinguished by hue rather than by
   being a different kind of object. .btn-primary stays the only filled,
   glowing button, so a screen still reads as having ONE primary action. */
.btn-success,.btn-warning,.btn-danger,.btn-secondary{
  background:rgba(255,255,255,.045)!important;
  border:1px solid var(--v-line-strong)!important;
  transition:color .18s var(--v-ease),background .18s var(--v-ease),
             border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.btn-success{color:#6ee7a8!important;border-color:rgba(110,231,168,.30)!important;background:rgba(110,231,168,.09)!important}
.btn-success:hover{background:rgba(110,231,168,.16)!important;border-color:rgba(110,231,168,.52)!important;box-shadow:0 0 18px rgba(110,231,168,.18)}
.btn-warning{color:#f0c778!important;border-color:rgba(240,199,120,.30)!important;background:rgba(240,199,120,.09)!important}
.btn-warning:hover{background:rgba(240,199,120,.16)!important;border-color:rgba(240,199,120,.52)!important;box-shadow:0 0 18px rgba(240,199,120,.18)}
.btn-danger{color:#f2a3ad!important;border-color:rgba(248,113,113,.28)!important;background:rgba(248,113,113,.08)!important}
.btn-danger:hover{color:#ffccd2!important;background:rgba(248,113,113,.15)!important;border-color:rgba(248,113,113,.50)!important;box-shadow:0 0 18px rgba(248,113,113,.18)}
.btn-secondary{color:#c9c9d2!important}
.btn-secondary:hover{color:#fff!important;border-color:rgba(var(--glow),.32)!important}
/* A translateY on every button in a dense admin row reads as twitch, not
   feedback — the tint and rim already carry the hover state. */
.btn-success:hover,.btn-warning:hover,.btn-danger:hover,.btn-secondary:hover{transform:none}
.btn:disabled,.btn[disabled]{
  opacity:1;cursor:not-allowed;
  color:#6f6f7a!important;background:rgba(255,255,255,.03)!important;
  border-color:var(--v-line)!important;box-shadow:none!important;transform:none!important;
}
.btn .head-ico,.btn .badge-ico{margin-right:2px}
.sidebar-logo{background:transparent!important;padding:0!important;max-height:64px!important}
.sidebar-brand{border-bottom-color:rgba(var(--glow),.16)!important}
.brand-logo,.login-brand-logo,.installer-logo{background:transparent!important}

/* App-shell scrolling: the rail itself no longer scrolls, only its nav does,
   so the brand plate and the user footer stay put instead of sliding away
   when the menu is taller than the viewport. */
.sidebar{overflow:hidden!important}
.sidebar-brand,.sidebar-user{flex:0 0 auto}
/* min-height:0 is what actually lets a column flex child scroll — without it
   the nav grows to its content height and pushes the user footer off-screen. */
.sidebar-nav{
  flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:10px 11px 14px!important;gap:2px!important;
}
/* app.css gives .sidebar its own grey scrollbar, which left the one element
   on the page not using the accent bar. Hand it back to the global rule. */
.sidebar,.sidebar-nav{scrollbar-color:rgba(var(--glow),.34) transparent!important}
.sidebar-nav::-webkit-scrollbar{width:8px}
.sidebar-nav::-webkit-scrollbar-track{background:transparent}
.sidebar-nav::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,rgba(var(--glow),.40),rgba(var(--glow),.20));
  border-radius:var(--v-r-pill);border:2px solid transparent;background-clip:padding-box;
}

/* Section labels were 10px #63636c — effectively invisible, so the five
   groups read as one 17-item list. Given a hairline they act as dividers. */
.side-section{
  display:flex;align-items:center;gap:10px;
  font-size:10px!important;color:#7c7c88!important;
  padding:16px 10px 6px!important;
}
.side-section::after{
  content:"";flex:1;height:1px;background:var(--v-line);
}
.sidebar-nav .side-section:first-child{padding-top:4px!important}

/* Active item: tinted glass with a lit rail, rather than a solid red slab.
   The rail is the accent; the fill just seats it. */
.side-link{position:relative}
.side-link.active{
  background:linear-gradient(90deg,rgba(var(--glow),.16),rgba(var(--glow),.05))!important;
  border-color:rgba(var(--glow),.26)!important;
  color:#fff!important;
  box-shadow:inset 0 0 22px rgba(var(--glow),.06)!important;
}
.side-link.active::before{
  content:"";position:absolute;left:-1px;top:7px;bottom:7px;width:3px;
  border-radius:var(--v-r-pill);
  background:linear-gradient(180deg,var(--accent2),var(--accent));
  box-shadow:0 0 10px rgba(var(--glow),.70);
}
.side-link.active .side-ico{filter:drop-shadow(0 0 8px rgba(var(--glow),.70))}

/* ---- Sidebar user footer ----
   "My Account" and "Logout" used to be the last two rows of the System
   section. They are not navigation, they belong to whoever is signed in. */
.sidebar-user{
  display:flex;align-items:center;gap:8px;
  padding:12px 12px calc(12px + env(safe-area-inset-bottom));
  border-top:1px solid var(--v-line);
  background:rgba(0,0,0,.24);
}
.sidebar-user-main{
  flex:1;min-width:0;
  display:flex;align-items:center;gap:10px;
  padding:8px;border-radius:var(--v-r-sm);
  text-decoration:none;border:1px solid transparent;
  transition:background .18s var(--v-ease),border-color .18s var(--v-ease);
}
.sidebar-user-main:hover{background:rgba(255,255,255,.055)}
.sidebar-user-main.active{
  background:rgba(var(--glow),.10);
  border-color:rgba(var(--glow),.24);
}
.sidebar-avatar{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:34px;height:34px;border-radius:50%;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border:1px solid rgba(var(--glow),.40);
  box-shadow:0 0 14px rgba(var(--glow),.22);
  color:#fff;font-family:var(--head-font);font-weight:700;font-size:14px;letter-spacing:.04em;
}
.sidebar-user-text{display:flex;flex-direction:column;min-width:0;gap:1px}
.sidebar-user-name{
  font-size:13px;font-weight:600;color:#e7e7ec;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.sidebar-user-mail{
  font-size:11px;color:#7c7c88;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.sidebar-user-out{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:34px;height:34px;border-radius:var(--v-r-sm);
  border:1px solid var(--v-line);
  color:#8f8f9a;text-decoration:none;font-size:15px;line-height:1;
  transition:all .18s var(--v-ease);
}
.sidebar-user-out:hover{
  color:var(--accent-text);
  border-color:rgba(var(--glow),.40);
  box-shadow:0 0 16px rgba(var(--glow),.20);
}

.topbar{background:rgba(8,8,11,.82)!important;border-bottom-color:var(--v-line)!important}

/* ============================================================
   FOOTER — accent hairline + version chip
   ============================================================ */
.footer{
  position:relative;
  border-top:1px solid var(--v-line)!important;
  background:rgba(7,7,10,.72)!important;
  padding:20px 22px calc(20px + env(safe-area-inset-bottom))!important;
}
.footer::before{
  content:"";position:absolute;left:50%;transform:translateX(-50%);top:-1px;
  width:min(420px,60%);height:1px;
  background:linear-gradient(90deg,transparent,rgba(var(--glow),.55),transparent);
}
/* Ownership line, then the version beneath it on its own row. */
.v-footer{display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center;
  padding:20px 16px 8px}
.v-footer-text{color:var(--v-muted,#8a8a94);font-size:12px;letter-spacing:.02em;line-height:1.6}

/* The attribution link.
   Every surface that renders a footer loads this file — installer, login,
   booking wizard, portal and admin — but each of them sets its own default
   anchor colour, so an unstyled link here came out blue on one page and a
   bare underline on the next. Styling it once, in the one stylesheet they
   all share, is what keeps it identical everywhere.
   Colour is inherited on purpose: the footer is muted, and a bright link in
   it reads as an advert rather than a signature. */
.v-footer-text a{
  color:inherit;text-decoration:none;
  border-bottom:1px solid rgba(255,255,255,.24);
  padding-bottom:1px;
  transition:color .16s ease,border-color .16s ease}
.v-footer-text a:hover,
.v-footer-text a:focus-visible{
  color:var(--v-text,#f4f4f5);
  border-bottom-color:currentColor;
  text-decoration:none}
.v-footer-text a:focus-visible{outline:2px solid rgba(var(--glow),.5);outline-offset:3px;border-radius:2px}

/* ============================================================
   CUSTOMER PORTAL
   ============================================================
   Every portal page used to carry its own ~120-line inline
   <style> block. They were copies of each other that had drifted:
   five different shell widths (960/900/800/720/640), two h1 sizes
   for the same heading, three definitions of the same button, and
   a "← Dashboard" bar repeated under a nav that already linked
   there. All of it is described here once instead, so the portal
   is one surface rather than seven near-misses.

   Everything is scoped to .vp-body / .vp- classes: this stylesheet
   also loads on admin, where app.css owns the equivalents.
   ============================================================ */

/* ---- Page frame ---- */
body.vp-body{
  margin:0;min-height:100vh;
  display:flex;flex-direction:column;
  font:14.5px/1.55 Inter,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  color:#e9e9ef;
  -webkit-font-smoothing:antialiased;
}
.vp-body *,.vp-body *::before,.vp-body *::after{box-sizing:border-box}
.vp-body h1,.vp-body h2,.vp-body h3,.vp-body h4,.vp-body p,.vp-body figure{margin:0}
.vp-body ul,.vp-body ol{margin:0;padding:0;list-style:none}
.vp-body h1,.vp-body h2,.vp-body h3,.vp-body h4{
  font-family:var(--head-font);font-weight:700;letter-spacing:.01em;
}
/* One width for every portal page. There is no narrow variant on purpose:
   capping the inner pages at 640–800px made them look like a phone
   screenshot floating in a wide dark page while the dashboard ran full
   width. Pages that would otherwise stretch a form across 1060px split
   into columns with .vp-cols instead. */
.vp-shell{width:100%;max-width:1060px;margin:0 auto;padding:0 20px}
/* The sticky top bar and the page body share one wrapper: sticky can only
   travel inside its own parent, so a bar in a box of its own would unstick
   the moment the page scrolled past that box. */
.vp-page{flex:1 0 auto}
.vp-main{padding-bottom:44px}

/* Skip link — the tab bar and nav sit ahead of the content on every
   page, so a keyboard user had to walk the whole menu each time. */
.vp-skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  padding:11px 18px;border-radius:0 0 var(--v-r-sm) 0;
  background:var(--accent);color:#fff;font-size:13px;font-weight:700;text-decoration:none;
}
.vp-skip:focus{left:0}

/* Inline icons. The nav used a mix of a text glyph (⌂) and colour
   emoji, which render at different weights and sizes on every OS —
   the single biggest reason the portal read as unpolished next to
   the admin panel. One stroked SVG set replaces them. */
.vp-ico{display:inline-block;flex:0 0 auto;width:18px;height:18px;vertical-align:-3px}
.vp-ico-sm{width:15px;height:15px}
.vp-ico-lg{width:22px;height:22px}

/* ---- Top bar ---- */
.v-portal-nav{
  position:sticky;top:10px;z-index:50;
  display:flex;align-items:center;gap:14px;
  padding:9px 10px 9px 12px;margin:14px 0 26px;
  background:linear-gradient(180deg,rgba(19,19,25,.86),rgba(11,11,15,.90));
  backdrop-filter:blur(18px) saturate(140%);-webkit-backdrop-filter:blur(18px) saturate(140%);
  border:1px solid var(--v-line-strong);
  border-radius:18px;
  box-shadow:0 18px 44px rgba(0,0,0,.46),inset 0 1px 0 rgba(255,255,255,.05);
}
.v-portal-nav::before{
  content:"";position:absolute;left:22px;right:22px;top:0;height:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.22),transparent);
}
.v-portal-nav .v-brand-plate{padding:6px 10px}
.v-portal-nav .v-brand-plate img{max-height:34px;max-width:120px}
.v-nav-brand{display:flex;align-items:center;gap:11px;min-width:0;text-decoration:none}
.v-nav-name{
  font-family:var(--head-font);
  font-weight:700;font-size:16px;letter-spacing:.06em;text-transform:uppercase;
  color:#f2f2f5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.v-nav-links{display:flex;align-items:center;gap:3px;margin-left:auto}
.v-nav-link{
  position:relative;
  display:inline-flex;align-items:center;gap:9px;
  padding:10px 15px;border-radius:12px;
  color:#9d9da9;font-size:13.5px;font-weight:600;text-decoration:none;
  border:1px solid transparent;
  transition:color .18s var(--v-ease),background .18s var(--v-ease),
             border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-nav-link:hover{color:#fff;background:rgba(255,255,255,.055)}
/* Active is tinted glass with a lit rail, matching the admin sidebar —
   the rail is the accent, the fill only seats it. The old solid red
   slab shouted louder than the page's actual primary action. */
.v-nav-link.is-active{
  color:#fff;
  background:linear-gradient(180deg,rgba(var(--glow),.15),rgba(var(--glow),.05));
  border-color:rgba(var(--glow),.28);
  box-shadow:inset 0 0 22px rgba(var(--glow),.07);
}
.v-nav-link.is-active::after{
  content:"";position:absolute;left:15px;right:15px;bottom:-1px;height:2px;
  border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 10px rgba(var(--glow),.70);
}
.v-nav-link.is-active .vp-ico{filter:drop-shadow(0 0 7px rgba(var(--glow),.65))}

/* Account cluster — who you are signed in as, which the portal never
   showed at all. Mirrors the admin sidebar's user footer. */
.v-nav-account{
  display:flex;align-items:center;gap:6px;
  margin-left:6px;padding-left:12px;
  border-left:1px solid var(--v-line);
}
.v-nav-user{
  display:flex;align-items:center;gap:10px;min-width:0;
  padding:5px 12px 5px 5px;border-radius:var(--v-r-pill);
  border:1px solid transparent;text-decoration:none;
  transition:background .18s var(--v-ease),border-color .18s var(--v-ease);
}
.v-nav-user:hover{background:rgba(255,255,255,.055);border-color:var(--v-line)}
.v-nav-user.is-active{background:rgba(var(--glow),.10);border-color:rgba(var(--glow),.26)}
.v-nav-avatar{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:34px;height:34px;border-radius:50%;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border:1px solid rgba(var(--glow),.40);
  box-shadow:0 0 14px rgba(var(--glow),.22);
  color:#fff;font-family:var(--head-font);font-weight:700;font-size:13.5px;letter-spacing:.04em;
}
.v-nav-user-text{display:flex;flex-direction:column;min-width:0;gap:1px}
.v-nav-user-name{
  font-size:13px;font-weight:600;color:#e7e7ec;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;
}
.v-nav-user-sub{font-size:10.5px;letter-spacing:.10em;text-transform:uppercase;color:#77777f}
.v-nav-logout{
  display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:38px;height:38px;border-radius:12px;
  color:#8f8f9a;text-decoration:none;
  border:1px solid var(--v-line);
  transition:all .18s var(--v-ease);
}
.v-nav-logout:hover{color:var(--accent-text);border-color:rgba(var(--glow),.40);
  box-shadow:0 0 18px rgba(var(--glow),.24)}

/* Mobile: full-bleed sticky header + app-style bottom tab bar */
.v-tabbar{display:none}
@media (max-width:880px){
  .v-portal-nav{
    top:0;margin:0 -20px 22px;padding:11px 18px;
    border-radius:0;border-left:none;border-right:none;border-top:none;
  }
  .v-portal-nav::before{left:0;right:0}
  .v-nav-links,.v-nav-account{display:none}
  .v-tabbar{
    display:flex;gap:2px;position:fixed;left:0;right:0;bottom:0;z-index:60;
    background:linear-gradient(180deg,rgba(13,13,17,.92),rgba(8,8,11,.97));
    backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
    border-top:1px solid var(--v-line-strong);
    padding:7px 8px calc(7px + env(safe-area-inset-bottom));
  }
  .v-tabbar::before{
    content:"";position:absolute;left:50%;transform:translateX(-50%);top:-1px;
    width:62%;height:1px;
    background:linear-gradient(90deg,transparent,rgba(var(--glow),.50),transparent);
  }
  .v-tab{
    flex:1 1 0;min-width:0;
    display:flex;flex-direction:column;align-items:center;gap:5px;
    padding:8px 2px 7px;border-radius:12px;
    color:#82828e;text-decoration:none;
    font-size:10.5px;font-weight:600;letter-spacing:.02em;
    border:1px solid transparent;
    transition:color .18s var(--v-ease),background .18s var(--v-ease);
  }
  /* Five tabs on a 360px screen leaves ~68px each — a long label has to
     clip rather than wrap onto a second line and shove the bar taller. */
  .v-tab > span:last-child{
    max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  .v-tab-ico{width:21px;height:21px;display:block;transition:filter .18s var(--v-ease)}
  .v-tab.is-active{
    color:var(--accent-text);
    background:rgba(var(--glow),.10);border-color:rgba(var(--glow),.22);
  }
  .v-tab.is-active .v-tab-ico{filter:drop-shadow(0 0 9px rgba(var(--glow),.80))}
  /* Clearance for the fixed bar. It goes on the footer rather than
     <body>, so a short page still pins its footer to the bottom. */
  body.has-tabbar .vp-footer{padding-bottom:calc(96px + env(safe-area-inset-bottom))}
}

/* ---- Page heading ---- */
.vp-page-head{
  display:flex;align-items:flex-end;justify-content:space-between;gap:20px;
  flex-wrap:wrap;margin:0 0 24px;
}
.vp-page-head-main{min-width:0}
.vp-page-head .v-eyebrow{margin-bottom:12px}
.vp-page-head h1{
  font-size:clamp(26px,4.6vw,34px);line-height:1.06;
  text-transform:uppercase;color:#f5f5f7;overflow-wrap:break-word;
}
.vp-page-head h1::after{
  content:"";display:block;width:46px;height:3px;margin-top:12px;
  border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--glow),.55);
}
.vp-page-sub{margin-top:12px;max-width:62ch;font-size:13.5px;color:#9a9aa6}
.vp-page-meta{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-top:14px}
.vp-page-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* User-generated titles (a ticket subject, a booking reference) take a
   smaller, sentence-case treatment — display-size uppercase is for page
   names we chose, not for whatever someone typed into a field. */
.vp-page-head.is-compact h1{
  font-size:clamp(21px,3.2vw,26px);text-transform:none;letter-spacing:0;line-height:1.2;
}
.vp-page-head.is-compact h1::after{margin-top:14px}

/* ---- Section heading between cards ---- */
.vp-section{margin-top:32px}
.vp-section-head{display:flex;align-items:center;gap:12px;margin:0 0 14px}
.vp-section-head h2{
  font-size:16px;text-transform:uppercase;letter-spacing:.09em;color:#c6c6d0;
}
.vp-section-head::after{content:"";flex:1;height:1px;background:var(--v-line)}

/* ---- Cards ---- */
.vp-card{padding:24px}
.vp-card.is-tight{padding:16px}
.vp-card + .vp-card{margin-top:16px}
.vp-card-head{display:flex;align-items:center;gap:13px;margin-bottom:18px}
.vp-card-head h2{
  font-size:17px;text-transform:uppercase;letter-spacing:.06em;color:#ededf2;
}
.vp-card-head-meta{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}

/* ---- Buttons ---- */
.vp-btn{
  appearance:none;-webkit-appearance:none;
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:11px 18px;border-radius:12px;
  background:rgba(255,255,255,.045);
  border:1px solid var(--v-line-strong);
  color:#d9d9e0;
  font:600 13px/1 Inter,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  text-decoration:none;cursor:pointer;white-space:nowrap;
  transition:color .18s var(--v-ease),background .18s var(--v-ease),
             border-color .18s var(--v-ease),box-shadow .18s var(--v-ease),transform .18s var(--v-ease);
}
.vp-btn:hover{
  color:#fff;background:rgba(255,255,255,.075);
  border-color:rgba(var(--glow),.32);box-shadow:0 0 18px rgba(var(--glow),.14);
}
.vp-btn.is-sm{padding:8px 13px;font-size:12px;border-radius:10px;gap:7px}
.vp-btn.is-block{width:100%}
/* Primary carries .v-cta as well, which supplies the gradient, rim and sheen
   shared with the admin panel and the booking wizard. */
.vp-btn.is-primary{
  padding:13px 26px;border-radius:12px;
  font-family:var(--head-font);font-size:15px;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;
}
.vp-btn.is-primary.is-sm{padding:10px 18px;font-size:13px}
.vp-btn.is-danger{color:#f2a3ad;border-color:rgba(248,113,113,.26)}
.vp-btn.is-danger:hover{
  color:#ffccd2;background:rgba(248,113,113,.10);
  border-color:rgba(248,113,113,.45);box-shadow:0 0 18px rgba(248,113,113,.16);
}
.vp-btn.is-gold{
  color:#f6dfa4;border-color:rgba(248,208,103,.30);background:rgba(248,208,103,.08);
}
.vp-btn.is-gold:hover{
  color:#fff0c8;border-color:rgba(248,208,103,.55);
  background:rgba(248,208,103,.14);box-shadow:0 0 20px rgba(248,208,103,.20);
}

/* ---- Status pills ---- */
.vp-status{
  display:inline-flex;align-items:center;gap:7px;
  padding:4px 11px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line-strong);background:rgba(255,255,255,.04);color:#9a9aa6;
  font-size:10.5px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;
  white-space:nowrap;
}
.vp-status::before{
  content:"";width:6px;height:6px;border-radius:50%;flex:0 0 auto;
  background:currentColor;box-shadow:0 0 8px currentColor;
}
.vp-status.is-live{color:var(--accent-text);border-color:rgba(var(--glow),.34);background:rgba(var(--glow),.10)}
.vp-status.is-ok{color:#6ee7a8;border-color:rgba(110,231,168,.30);background:rgba(110,231,168,.09)}
.vp-status.is-warn{color:#f0c778;border-color:rgba(240,199,120,.30);background:rgba(240,199,120,.09)}

/* ---- Key/value + price rows ---- */
.vp-kv{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding:11px 0;border-bottom:1px solid var(--v-line);font-size:13.5px;
}
.vp-kv:last-child{border-bottom:none;padding-bottom:0}
.vp-kv-label{color:#8a8a95;flex:0 0 auto}
.vp-kv-value{color:#ededf2;font-weight:600;text-align:right;min-width:0;overflow-wrap:anywhere}
.vp-kv.is-plain{border-bottom:none;padding:6px 0}
.vp-kv.is-credit .vp-kv-value{color:#6ee7a8}
.vp-kv.is-charge .vp-kv-value{color:#f2a3ad}
.vp-kv.is-total{
  margin-top:8px;padding-top:14px;border-top:1px solid var(--v-line-strong);border-bottom:none;
}
.vp-kv.is-total .vp-kv-label{color:#d6d6de;font-weight:600}
.vp-kv.is-total .vp-kv-value{
  font-family:var(--head-font);font-size:22px;color:var(--accent-text);
  text-shadow:0 0 22px rgba(var(--glow),.26);
}

/* ---- List rows ---- */
.vp-list{
  border:1px solid var(--v-line);border-radius:var(--v-r-md);
  background:rgba(255,255,255,.022);overflow:hidden;
}
.vp-row{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  padding:15px 18px;border-bottom:1px solid var(--v-line);
  color:inherit;text-decoration:none;
}
.vp-row:last-child{border-bottom:none}
a.vp-row,button.vp-row{transition:background .18s var(--v-ease)}
a.vp-row:hover,button.vp-row:hover{background:rgba(255,255,255,.04)}
.vp-row-main{flex:1 1 220px;min-width:0}
.vp-row-title{font-size:14px;font-weight:600;color:#ededf2;overflow-wrap:anywhere}
.vp-row-sub{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-top:5px;font-size:12.5px;color:#8a8a95;
}
/* Separator between two facts on one line — reads better than a bullet
   or a slash, and inherits whatever colour it sits in. */
.vp-dot{
  width:3px;height:3px;border-radius:50%;flex:0 0 auto;
  background:currentColor;opacity:.45;
}
.vp-row-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-left:auto}

/* ---- Forms ---- */
.vp-form{display:grid;gap:18px}
.vp-field{min-width:0}
.vp-label{
  display:block;margin-bottom:8px;
  font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8a8a95;
}
.vp-input{
  width:100%;padding:12px 14px;border-radius:12px;
  border:1px solid var(--v-line);
  color:#f2f2f5;
  font:14px/1.5 Inter,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
}
.vp-input::placeholder{color:#5f5f6a}
textarea.vp-input{min-height:124px;resize:vertical}
/* The chevron is forced because the global input rule above sets the
   `background` SHORTHAND with !important, which would otherwise blank
   background-image out and leave a select with no affordance at all. */
select.vp-input{
  appearance:none;-webkit-appearance:none;padding-right:42px;cursor:pointer;
  background-color:rgba(9,9,13,.72)!important;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a8a95' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")!important;
  background-repeat:no-repeat!important;
  background-position:right 14px center!important;
  background-size:16px!important;
}
.vp-hint{margin-top:8px;font-size:12px;line-height:1.55;color:#7f7f8a}
.vp-field-row{display:grid;grid-template-columns:1fr 1fr;gap:18px}
/* Four-ish short fields across a full-width card, collapsing as it narrows. */
.vp-field-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:18px}
.vp-form-actions{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin-top:4px;padding-top:20px;border-top:1px solid var(--v-line);
}

/* ---- Two-column page body ----
   Every page except the dashboard used to cap itself at 640–800px, so on a
   desktop the portal looked like a phone screenshot floating in the middle
   of a wide dark page. They all run at the full shell width now and split
   into columns instead, which is what the admin panel does. */
.vp-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.vp-cols{display:grid;gap:20px;align-items:start;grid-template-columns:minmax(0,1fr)}
@media (min-width:960px){
  .vp-cols{grid-template-columns:minmax(0,1fr) minmax(0,360px)}
  .vp-cols.is-even{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* Sticky offset clears the sticky top bar (58px + its 14px top margin). */
  .vp-col-side.is-sticky{position:sticky;top:96px}
}
.vp-col-main,.vp-col-side{min-width:0}

/* ---- Choice cards (radio group) ---- */
.vp-choice-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(198px,1fr));gap:12px}
.vp-choice{position:relative;display:block;cursor:pointer}
.vp-choice input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.vp-choice-body{
  display:flex;flex-direction:column;gap:9px;height:100%;
  padding:15px 16px;border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);border:1px solid var(--v-line);
  transition:border-color .18s var(--v-ease),background .18s var(--v-ease),
             box-shadow .18s var(--v-ease),transform .18s var(--v-ease);
}
.vp-choice:hover .vp-choice-body{
  transform:translateY(-2px);
  border-color:rgba(var(--glow),.28);background:rgba(var(--glow),.05);
}
.vp-choice input:checked + .vp-choice-body{
  border-color:rgba(var(--glow),.55);background:rgba(var(--glow),.10);
  box-shadow:0 0 0 1px rgba(var(--glow),.26),0 8px 24px rgba(var(--glow),.18);
}
.vp-choice input:focus-visible + .vp-choice-body{
  outline:2px solid rgba(var(--glow),.75);outline-offset:2px;
}
.vp-choice-top{display:flex;align-items:center;gap:10px}
.vp-choice-title{font-size:14px;font-weight:600;color:#ededf2}
.vp-choice-mark{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  margin-left:auto;width:21px;height:21px;border-radius:50%;
  border:1px solid var(--v-line-strong);color:transparent;
  transition:all .18s var(--v-ease);
}
.vp-choice input:checked + .vp-choice-body .vp-choice-mark{
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border-color:rgba(var(--glow),.60);color:#fff;
  box-shadow:0 0 12px rgba(var(--glow),.50);
}
.vp-choice-examples{font-size:11.5px;line-height:1.55;color:#82828d}
/* Marks the option our records suggest, so an auto-fill is visible as a
   suggestion rather than silently moving a dropdown the customer never saw. */
.vp-choice-flag{
  display:none;align-self:flex-start;
  padding:3px 9px;border-radius:var(--v-r-pill);
  background:rgba(var(--glow),.14);border:1px solid rgba(var(--glow),.34);
  color:var(--accent-text);
  font-size:9.5px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;
}
.vp-choice.is-suggested .vp-choice-flag{display:inline-flex}

.vp-spinner{
  width:16px;height:16px;flex:0 0 auto;border-radius:50%;
  border:2px solid rgba(255,255,255,.18);border-top-color:var(--accent-text);
  animation:vpSpin .8s linear infinite;
}

/* ---- Avatar ---- */
.vp-avatar,.v-nav-avatar,.vp-hello-avatar{overflow:hidden}
.vp-avatar img,.v-nav-avatar img,.vp-hello-avatar img{
  display:block;width:100%;height:100%;object-fit:cover;border-radius:inherit;
}
/* With a photo in it the red gradient would only show as a rim tint. */
.vp-avatar.has-photo,.v-nav-avatar.has-photo,.vp-hello-avatar.has-photo{
  background:#131318;border-color:var(--v-line-strong);
}
.vp-avatar{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:86px;height:86px;border-radius:50%;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border:1px solid rgba(var(--glow),.45);
  box-shadow:0 0 30px rgba(var(--glow),.26);
  color:#fff;font-family:var(--head-font);font-weight:700;font-size:32px;letter-spacing:.04em;
}
.vp-avatar-edit{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.vp-avatar-controls{display:flex;flex-direction:column;gap:10px;min-width:0;flex:1 1 220px}
.vp-avatar-buttons{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* The native file input is unstylable across browsers, so it is visually
   hidden and driven by its <label>, which is a normal button. */
.vp-file-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.vp-file-input:focus-visible + .vp-btn{outline:2px solid rgba(var(--glow),.75);outline-offset:2px}

/* ---- Alerts ---- */
.vp-alert{
  display:flex;align-items:flex-start;gap:12px;
  padding:14px 16px;border-radius:14px;
  border:1px solid var(--v-line-strong);background:rgba(255,255,255,.035);
  color:#b6b6c0;font-size:13.5px;line-height:1.55;
}
.vp-alert .vp-ico{margin-top:1px}
.vp-alert.is-error{background:rgba(248,113,113,.09);border-color:rgba(248,113,113,.30);color:#fda4af}
.vp-alert.is-ok{background:rgba(110,231,168,.09);border-color:rgba(110,231,168,.28);color:#86efac}
/* Amber, not red: "this disagrees with our records" is something to read,
   not something the customer has done wrong. */
.vp-alert.is-warn{background:rgba(240,199,120,.09);border-color:rgba(240,199,120,.30);color:#f2d49a}
.vp-alert strong{color:#fff;font-weight:700}
.vp-alert-close{
  appearance:none;-webkit-appearance:none;
  margin-left:auto;flex:0 0 auto;padding:2px;
  background:none;border:none;color:inherit;opacity:.6;cursor:pointer;
}
.vp-alert-close:hover{opacity:1}

/* ---- Empty states ---- */
.vp-empty{
  display:flex;flex-direction:column;align-items:center;gap:12px;
  padding:38px 22px;text-align:center;
  border:1px dashed var(--v-line-strong);border-radius:var(--v-r-md);
  background:rgba(255,255,255,.018);
  color:#8a8a95;font-size:13.5px;
}
.vp-empty .v-icon-tile{width:48px;height:48px;border-radius:14px}
.vp-empty .v-icon-tile .vp-ico{width:22px;height:22px}
.vp-empty-title{
  font-family:var(--head-font);font-size:18px;font-weight:700;
  text-transform:uppercase;letter-spacing:.05em;color:#d6d6de;
}
.vp-empty p{max-width:44ch}

/* ---- Dashboard: identity strip ---- */
.vp-hello{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  margin-bottom:24px;
}
.vp-hello-avatar{
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:58px;height:58px;border-radius:50%;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border:1px solid rgba(var(--glow),.45);
  box-shadow:0 0 26px rgba(var(--glow),.28);
  color:#fff;font-family:var(--head-font);font-weight:700;font-size:22px;letter-spacing:.04em;
}
.vp-hello-main{min-width:0;flex:1 1 220px}
.vp-hello-eyebrow{
  font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:#8a8a95;
}
.vp-hello h1{
  margin-top:5px;
  font-size:clamp(25px,5vw,34px);line-height:1.06;
  text-transform:uppercase;color:#f5f5f7;overflow-wrap:break-word;
}
.vp-hello-chips{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
/* The avatar links to the profile page, so it needs to look reachable. */
.vp-hello > a{
  display:inline-flex;flex:0 0 auto;border-radius:50%;text-decoration:none;
  transition:transform .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.vp-hello > a:hover{transform:translateY(-1px);box-shadow:0 0 26px rgba(var(--glow),.34)}
.vp-vip{
  display:inline-flex;align-items:center;gap:7px;
  padding:6px 14px;border-radius:var(--v-r-pill);
  background:linear-gradient(135deg,rgba(248,208,103,.22),rgba(166,93,0,.12));
  border:1px solid rgba(248,208,103,.42);
  color:#f8d067;
  font-size:11px;font-weight:800;letter-spacing:.10em;text-transform:uppercase;
  box-shadow:0 0 20px rgba(248,208,103,.18);
}

/* ---- Dashboard: next appointment ---- */
.vp-next{padding:26px}
.vp-next-top{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:20px}
.vp-next-countdown{
  display:flex;flex-direction:column;align-items:center;justify-content:center;flex:0 0 auto;
  min-width:92px;padding:14px 16px;border-radius:var(--v-r-md);
  background:rgba(var(--glow),.09);border:1px solid rgba(var(--glow),.28);
  box-shadow:inset 0 0 26px rgba(var(--glow),.06);
}
.vp-next-countdown-value{
  font-family:var(--head-font);font-size:30px;line-height:1;font-weight:700;
  color:var(--accent-text);text-shadow:0 0 20px rgba(var(--glow),.35);
}
/* "Today"/"Tomorrow" instead of a figure — a word can't take the display size. */
.vp-next-countdown-value.is-word{
  font-size:17px;letter-spacing:.05em;text-transform:uppercase;
}
.vp-next-countdown-label{
  margin-top:6px;font-size:10px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:#9a9aa6;
}
.vp-next-head{flex:1 1 220px;min-width:0}
.vp-next-service{
  font-family:var(--head-font);font-size:24px;line-height:1.15;font-weight:700;
  text-transform:uppercase;color:#f5f5f7;overflow-wrap:anywhere;
}
.vp-next-facts{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;
  margin-bottom:20px;
}
.vp-fact{
  display:flex;align-items:center;gap:11px;
  padding:12px 14px;border-radius:var(--v-r-sm);
  background:rgba(255,255,255,.028);border:1px solid var(--v-line);
}
.vp-fact .vp-ico{color:var(--accent-text)}
.vp-fact-label{font-size:10.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#7f7f8a}
.vp-fact-value{margin-top:3px;font-size:13.5px;font-weight:600;color:#ededf2;overflow-wrap:anywhere}
.vp-next-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}

/* ---- Dashboard: quick actions ---- */
.vp-quick{display:grid;grid-template-columns:repeat(auto-fit,minmax(154px,1fr));gap:12px}
.vp-quick-tile{
  display:flex;flex-direction:column;gap:11px;
  padding:17px;border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);border:1px solid var(--v-line);
  text-decoration:none;
  transition:border-color .18s var(--v-ease),background .18s var(--v-ease),
             box-shadow .18s var(--v-ease),transform .18s var(--v-ease);
}
.vp-quick-tile:hover{
  transform:translateY(-2px);
  border-color:rgba(var(--glow),.32);background:rgba(var(--glow),.06);
  box-shadow:0 0 24px rgba(var(--glow),.13);
}
.vp-quick-title{display:block;font-size:13.5px;font-weight:600;color:#eeeef3}
.vp-quick-sub{display:block;margin-top:3px;font-size:11.5px;color:#8a8a95}

/* ---- Loyalty ----
   Was three equal stat tiles, the third of which crammed both reward
   counts into it as two lines of small text. On a phone that produced a
   2 + 1 ragged grid where the odd one out was the important one. It reads
   top to bottom now: what you have, how close you are, what you've won —
   with both reward types together in one list instead of split across a
   tile and a chip. */
.vp-loyalty-figures{
  display:flex;flex-wrap:wrap;
  border:1px solid var(--v-line);border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);overflow:hidden;
}
.vp-figure{flex:1 1 140px;min-width:0;padding:16px 18px}
.vp-figure + .vp-figure{border-left:1px solid var(--v-line)}
.vp-figure-value{
  display:block;font-family:var(--head-font);
  font-size:32px;line-height:1;font-weight:700;color:#f4f4f5;
}
.vp-figure.is-accent .vp-figure-value{
  color:var(--accent-text);text-shadow:0 0 22px rgba(var(--glow),.28);
}
.vp-figure-label{
  display:block;margin-top:9px;
  font-size:10.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8a8a95;
}

.vp-loyalty-rewards{margin-top:22px;padding-top:20px;border-top:1px solid var(--v-line)}
.vp-loyalty-rewards-head{
  margin-bottom:12px;
  font-size:10.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8a8a95;
}
.vp-reward{
  display:flex;align-items:center;gap:13px;
  padding:13px 15px;border-radius:var(--v-r-sm);
  border:1px solid rgba(var(--glow),.26);background:rgba(var(--glow),.07);
}
.vp-reward + .vp-reward{margin-top:10px}
.vp-reward-main{flex:1;min-width:0}
.vp-reward-title{font-size:14px;font-weight:600;color:#f2f2f5}
.vp-reward-sub{margin-top:3px;font-size:12px;line-height:1.5;color:#9a9aa6}
.vp-reward-count{
  flex:0 0 auto;font-family:var(--head-font);
  font-size:21px;font-weight:700;color:var(--accent-text);
}
/* Nothing unlocked yet — same row, no accent, so it doesn't read as a prize. */
.vp-reward.is-empty{border-color:var(--v-line);background:rgba(255,255,255,.022)}
.vp-reward.is-empty .vp-reward-title{color:#c2c2cc}
/* Milestone track. The old version laid one dot per visit out with
   flexbox, so a 10-visit programme produced ten 14px dots that
   collapsed into each other on a phone. Markers are positioned by
   percentage instead, so any milestone configuration fits. */
.vp-track-wrap{padding:0 17px;margin:30px 0 12px}
.vp-track{
  position:relative;height:8px;border-radius:var(--v-r-pill);
  background:rgba(255,255,255,.07);border:1px solid var(--v-line);
}
.vp-track-fill{
  position:absolute;top:0;bottom:0;left:0;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent),var(--accent2));
  box-shadow:0 0 14px rgba(var(--glow),.55);
  transition:width .6s var(--v-ease);
}
.vp-track-node{
  position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1;
  display:flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:50%;
  background:#131318;border:2px solid rgba(255,255,255,.14);color:#6d6d78;
}
.vp-track-node .vp-ico{width:15px;height:15px}
.vp-track-node.is-reached{
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border-color:rgba(var(--glow),.65);color:#fff;
  box-shadow:0 0 18px rgba(var(--glow),.60);
}
.vp-track-legend{
  display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  font-size:12.5px;color:#8a8a95;
}
.vp-track-legend strong{color:#ededf2;font-weight:600}

/* ---- Job packages (photo galleries) ----
   Reachable previously only from a small button on a row at the bottom of
   the dashboard's service history. These cards give the photos their own
   page and a preview strip near the top of the dashboard. */
/* Full-width rows, not a tile grid — the same shape as .vp-reward above.
   A grid of thumbnails only looks deliberate once it has enough items to
   fill a row; with one package it was a small square marooned in white
   space. A row fills the width whether there is one of them or twenty. */
.vp-package-list{display:flex;flex-direction:column;gap:12px}
.vp-package{
  display:flex;align-items:center;gap:16px;
  padding:12px;border-radius:var(--v-r-md);
  border:1px solid var(--v-line);background:rgba(255,255,255,.028);
  text-decoration:none;
  transition:border-color .18s var(--v-ease),background .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.vp-package:hover{
  border-color:rgba(var(--glow),.32);background:rgba(var(--glow),.06);
  box-shadow:0 0 22px rgba(var(--glow),.12);
}
.vp-package-cover{
  position:relative;flex:0 0 auto;overflow:hidden;
  width:112px;height:80px;border-radius:var(--v-r-sm);
  background:#131318;border:1px solid var(--v-line);
  display:flex;align-items:center;justify-content:center;color:#4d4d57;
}
.vp-package-cover img{display:block;width:100%;height:100%;object-fit:cover}
.vp-package-cover .vp-ico{width:24px;height:24px}
.vp-package-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:5px}
.vp-package-title{font-size:14.5px;font-weight:600;color:#ededf2;overflow-wrap:anywhere}
.vp-package-meta{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-size:12.5px;color:#8a8a95;
}
.vp-package-meta > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vp-package-side{
  display:flex;align-items:center;gap:12px;
  flex:0 0 auto;margin-left:auto;color:#8a8a95;
}
.vp-package-go{display:inline-flex;color:#8a8a95;transition:color .18s var(--v-ease)}
.vp-package:hover .vp-package-go{color:var(--accent-text)}
/* Expired packages stay listed — "it was here and it lapsed" is more use
   than a gallery silently disappearing — but they are visibly inert. */
.vp-package.is-expired{opacity:.5}
.vp-package.is-expired:hover{background:rgba(255,255,255,.028);box-shadow:none;border-color:var(--v-line)}
@media (max-width:520px){
  .vp-package{gap:12px;padding:10px}
  .vp-package-cover{width:88px;height:64px}
  /* The photo count is a nicety; the thumbnail and title are not. */
  .vp-package-side .v-chip{display:none}
}

/* ---- Review prompt ---- */
.vp-review{
  display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:18px 22px;border-radius:var(--v-r-md);
  background:linear-gradient(135deg,rgba(248,208,103,.10),rgba(166,93,0,.04));
  border:1px solid rgba(248,208,103,.26);
}
.vp-review-text{min-width:0;flex:1 1 260px}
.vp-review-title{
  display:flex;align-items:center;gap:9px;
  font-family:var(--head-font);font-size:17px;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;color:#f2e2b4;
}
.vp-review-sub{margin-top:4px;font-size:13px;color:#9a9aa6}

/* ---- Support thread ---- */
.vp-thread{display:flex;flex-direction:column;gap:14px}
.vp-bubble{
  max-width:min(560px,86%);padding:14px 17px;border-radius:18px;
  font-size:14px;line-height:1.55;overflow-wrap:anywhere;
}
.vp-bubble.is-me{
  align-self:flex-end;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  border:1px solid rgba(var(--glow),.42);
  border-bottom-right-radius:6px;color:#fff;
  box-shadow:0 8px 22px rgba(var(--glow),.22);
}
.vp-bubble.is-them{
  align-self:flex-start;
  background:rgba(255,255,255,.045);border:1px solid var(--v-line-strong);
  border-bottom-left-radius:6px;color:#e9e9ef;
}
.vp-bubble-meta{
  display:flex;align-items:center;gap:7px;
  margin-top:9px;font-size:11px;letter-spacing:.02em;
}
.vp-bubble.is-me .vp-bubble-meta{justify-content:flex-end;color:rgba(255,255,255,.82)}
.vp-bubble.is-them .vp-bubble-meta{color:#8a8a95}

/* ---- Gallery ---- */
.vp-photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.vp-photo{
  appearance:none;-webkit-appearance:none;
  position:relative;display:block;padding:0;border:none;background:none;cursor:pointer;
  border-radius:var(--v-r-sm);overflow:hidden;
  transition:transform .22s var(--v-ease),box-shadow .22s var(--v-ease);
}
.vp-photo img{
  display:block;width:100%;aspect-ratio:1;object-fit:cover;
  border:1px solid var(--v-line);border-radius:var(--v-r-sm);
  background:#16161c;opacity:0;transition:opacity .35s ease;
}
.vp-photo img.is-loaded{opacity:1}
.vp-photo img:not(.is-loaded){animation:vpThumb 1.4s ease-in-out infinite}
@keyframes vpThumb{0%,100%{background-color:#16161c}50%{background-color:#22222a}}
.vp-photo:hover{transform:translateY(-2px);box-shadow:0 12px 28px rgba(0,0,0,.45),0 0 20px rgba(var(--glow),.16)}
.vp-photo:hover img{border-color:rgba(var(--glow),.35)}

.vp-overlay{
  position:fixed;inset:0;z-index:900;display:none;
  background:rgba(5,5,7,.96);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.vp-overlay.is-open{display:flex}
.vp-overlay.is-lightbox{align-items:center;justify-content:center;touch-action:pan-y}
.vp-overlay.is-doc{flex-direction:column}
.vp-overlay-bar{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:16px 18px;flex:0 0 auto;
}
.vp-overlay.is-lightbox .vp-overlay-bar{position:absolute;top:0;left:0;right:0;z-index:2}
.vp-overlay.is-doc .vp-overlay-bar{border-bottom:1px solid var(--v-line)}
.vp-overlay-title{
  font-family:var(--head-font);font-size:15px;font-weight:700;
  text-transform:uppercase;letter-spacing:.06em;color:#ededf2;
  padding:6px 14px;border-radius:var(--v-r-pill);
  background:rgba(0,0,0,.45);border:1px solid var(--v-line);
}
.vp-overlay-actions{display:flex;align-items:center;gap:10px}
.vp-overlay-btn{
  appearance:none;-webkit-appearance:none;
  display:inline-flex;align-items:center;justify-content:center;
  width:42px;height:42px;border-radius:50%;
  background:rgba(255,255,255,.07);border:1px solid var(--v-line-strong);
  color:#ededf2;cursor:pointer;text-decoration:none;
  transition:all .18s var(--v-ease);
}
.vp-overlay-btn:hover{background:rgba(255,255,255,.14);border-color:rgba(var(--glow),.34)}
.vp-overlay-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2}
.vp-overlay-nav.is-prev{left:16px}
.vp-overlay-nav.is-next{right:16px}
.vp-overlay img.vp-lightbox-img{
  max-width:92vw;max-height:76vh;object-fit:contain;
  border-radius:var(--v-r-sm);user-select:none;transition:opacity .25s ease;
}
.vp-overlay iframe{flex:1;width:100%;border:none;background:#fff}
@media (max-width:640px){.vp-overlay-nav{display:none}}

/* ---- Loading splash ---- */
.vp-splash{
  position:fixed;inset:0;z-index:999;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 50% 42%,#121218,#07070a 72%);
  transition:opacity .45s ease,visibility .45s ease;
}
.vp-splash.is-done{opacity:0;visibility:hidden;pointer-events:none}
.vp-splash-wrap{position:relative;display:flex;align-items:center;justify-content:center;width:132px;height:132px}
.vp-splash-ring{
  position:absolute;inset:0;border-radius:50%;
  border:3px solid transparent;
  border-top-color:var(--accent2);border-right-color:rgba(var(--glow),.28);
  box-shadow:0 0 30px rgba(var(--glow),.42);
  animation:vpSpin 1.1s linear infinite;
}
.vp-splash-glow{
  position:absolute;inset:-16px;border-radius:50%;
  background:radial-gradient(circle,rgba(var(--glow),.30),transparent 70%);
  animation:vpPulse 1.6s ease-in-out infinite;
}
.vp-splash-mark{
  position:relative;z-index:1;
  display:flex;align-items:center;justify-content:center;
  width:74px;height:74px;border-radius:50%;overflow:hidden;
  background:#121217;border:1px solid var(--v-line-strong);
  font-family:var(--head-font);font-weight:800;font-size:25px;letter-spacing:.05em;
  color:var(--accent-text);text-shadow:0 0 18px rgba(var(--glow),.55);
}
/* contain, not cover. A logo is usually a wide wordmark; cover scaled it to
   fill this 74px circle and cropped away everything outside the centre square,
   which for most logos is blank canvas — so the splash looked logo-less even
   when the file loaded perfectly. Explicit max dimensions rather than
   width:100% so an SVG exported with width="100%" resolves against a definite
   box instead of collapsing to zero. */
.vp-splash-mark img{max-width:82%;max-height:82%;width:auto;height:auto;object-fit:contain}
@keyframes vpSpin{to{transform:rotate(360deg)}}
@keyframes vpPulse{0%,100%{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.07)}}

/* ---- Floating contact button ---- */
.vp-fab{
  position:fixed;right:20px;bottom:calc(22px + env(safe-area-inset-bottom));z-index:70;
  display:flex;align-items:center;justify-content:center;
  width:54px;height:54px;border-radius:50%;
  background:linear-gradient(135deg,#25d366,#12a350);
  border:1px solid rgba(255,255,255,.20);
  color:#042a15;text-decoration:none;
  box-shadow:0 14px 32px -10px rgba(37,211,102,.62);
  transition:transform .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.vp-fab:hover{transform:scale(1.06);box-shadow:0 18px 38px -10px rgba(37,211,102,.75)}
.vp-fab .vp-ico{width:26px;height:26px}
@media (max-width:880px){
  .vp-fab{bottom:calc(86px + env(safe-area-inset-bottom));width:50px;height:50px}
  .vp-fab .vp-ico{width:23px;height:23px}
}

/* ---- Portal footer ---- */
.vp-footer{
  position:relative;flex:0 0 auto;margin-top:auto;
  padding:26px 20px calc(26px + env(safe-area-inset-bottom));
  border-top:1px solid var(--v-line);background:rgba(7,7,10,.55);
}
.vp-footer::before{
  content:"";position:absolute;left:50%;transform:translateX(-50%);top:-1px;
  width:min(420px,60%);height:1px;
  background:linear-gradient(90deg,transparent,rgba(var(--glow),.50),transparent);
}

@media (max-width:560px){
  .vp-field-row{grid-template-columns:1fr}
  .vp-card{padding:19px}
  .vp-next{padding:20px}
  .vp-page-head{align-items:flex-start}
  /* Row actions drop under the row's text rather than being squeezed
     beside it — on a phone the old layout left two buttons at about
     70px each with their labels clipped. */
  .vp-row-actions{margin-left:0;width:100%}
}

/* ============================================================
   COMPONENT POLISH
   Targets the class names that ALREADY exist across admin, wizard
   and portal, so those screens gain the futuristic treatment with
   no markup changes. Anything genuinely new uses a .v- class.
   ============================================================ */

/* ---- Inputs: accent focus glow ---- */
input,select,textarea{
  background:rgba(9,9,13,.72)!important;
  border-color:var(--v-line)!important;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
input:focus,select:focus,textarea:focus{
  outline:none;
  border-color:rgba(var(--glow),.55)!important;
  box-shadow:0 0 0 3px rgba(var(--glow),.14),0 0 18px rgba(var(--glow),.20);
}
/* app.css marks an invalid field with .field-error{border-color:#c84d4d}, but
   the !important above was silently winning — so since the restyle, a field
   that failed validation looked identical to a valid one. */
input.field-error,select.field-error,textarea.field-error{
  border-color:#c84d4d!important;
  background:rgba(200,77,77,.06)!important;
}
input.field-error:focus,select.field-error:focus,textarea.field-error:focus{
  border-color:#e06a6a!important;
  box-shadow:0 0 0 3px rgba(200,77,77,.16);
}

/* ---- Admin: KPI + revenue ---- */
.kpi-value,.dashboard-revenue-value{text-shadow:0 0 22px rgba(var(--glow),.26)}
.kpi-label{color:#8f8f9a}
.kpi-today-revenue{
  border-color:rgba(var(--glow),.40)!important;
  background:linear-gradient(180deg,rgba(45,16,20,.58),rgba(16,16,21,.90))!important;
  box-shadow:var(--v-glow-md),var(--v-shadow)!important;
}
.kpi-today-revenue::before{background:linear-gradient(90deg,transparent,rgba(var(--glow),.85),transparent)!important}
.dashboard-kpi-card:hover,.dashboard-job-card:hover{
  border-color:rgba(var(--glow),.26)!important;
  box-shadow:0 16px 38px rgba(0,0,0,.40),0 0 26px rgba(var(--glow),.16)!important;
}

/* ---- Admin: active states get the glow ---- */
.settings-nav-btn.active,.tab-bar a.active,.pill-filters a.active,.filter-btn.active{
  box-shadow:0 8px 22px rgba(var(--glow),.32),0 0 0 1px rgba(var(--glow),.26);
}
.list-card-accent{
  background:linear-gradient(180deg,var(--accent2),var(--accent))!important;
  box-shadow:0 0 14px rgba(var(--glow),.60);
}
.installer-progress-item.active{
  box-shadow:0 8px 24px rgba(var(--glow),.26),0 0 0 1px rgba(var(--glow),.24);
}
.installer-progress-item.active span{box-shadow:0 0 16px rgba(var(--glow),.60)}
.timeline-icon{background:rgba(255,255,255,.04);border-color:var(--v-line)}

/* Section headings get a subtle accent underline */
.section-head h2,.page-head h1{position:relative}
.page-head h1::after{
  content:"";display:block;width:46px;height:3px;margin-top:10px;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--glow),.55);
}

/* ---- Booking wizard (book.php) ---- */
.step-eyebrow{
  display:inline-flex;align-items:center;
  padding:6px 13px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.34);
  background:rgba(var(--glow),.08);
  color:var(--accent-text)!important;
  font-size:10.5px!important;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  box-shadow:0 0 18px rgba(var(--glow),.16);
}
.progress-seg{background:rgba(255,255,255,.07)}
.progress-seg .fill{
  background:linear-gradient(90deg,var(--accent),var(--accent2))!important;
  box-shadow:0 0 12px rgba(var(--glow),.75);
}
.size-btn.selected,.service-card.selected,.slot-btn.selected{
  border-color:rgba(var(--glow),.60)!important;
  background:rgba(var(--glow),.12)!important;
  box-shadow:0 0 0 1px rgba(var(--glow),.28),0 8px 24px rgba(var(--glow),.26);
}
.running-total{
  background:linear-gradient(180deg,rgba(23,23,29,.82),rgba(16,16,21,.88))!important;
  border-color:var(--v-line)!important;
}
/* Success moment → glowing concentric ring, no markup change needed */
.submit-success .icon{
  display:inline-flex;align-items:center;justify-content:center;
  width:96px;height:96px;margin:0 auto 18px;border-radius:50%;
  font-size:40px!important;
  background:radial-gradient(circle,rgba(var(--glow),.20),transparent 68%);
  border:2px solid rgba(var(--glow),.55);
  box-shadow:0 0 34px rgba(var(--glow),.42),inset 0 0 28px rgba(var(--glow),.20);
}

/* The customer portal's own polish used to live here, patching class
   names (.portal-header, .hero-eyebrow, .vip-badge, .back-link,
   .splash-*) that each page declared for itself. The portal is
   described in full in the CUSTOMER PORTAL section above now, so
   there is nothing left to patch. */

/* ============================================================
   AUTH SHELL — one login page, five URLs
   The admin login, portal login, forgot-password, set-password and
   request-access screens were each styled independently and had
   drifted apart (different card, different red, only one with a
   logo). They all carry .v-auth now and are described only here.
   ============================================================ */
/* One card, seven auth screens. Every dimension is forced, because each
   surface had its own idea of the size: the portal pages cap the card at
   380px inline, while app.css sizes the admin one via
   `.login-screen .hero-card` (560px) and rounds it via
   `.login-screen .card` (24px) — all of which outweigh a bare .v-auth. */
.v-auth{
  width:100%;
  max-width:420px!important;
  padding:32px 30px 28px!important;
  border-radius:var(--v-r-lg)!important;
  text-align:left;
}
.login-screen .v-auth{width:100%;max-width:420px!important;border-radius:var(--v-r-lg)!important}

/* An auth page is a centred card over a footer — identical on the admin
   and portal sides. `margin:auto` centres the card in whatever space is
   left once the footer has taken its own height. */
body.v-auth-page{
  display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
  min-height:100vh;padding:20px;
}
body.v-auth-page .v-auth{margin:auto}
body.v-auth-page .footer{
  flex:0 0 auto;background:transparent!important;border-top:none!important;
  padding:18px 12px calc(18px + env(safe-area-inset-bottom))!important;
}
body.v-auth-page .footer::before{display:none}

.v-auth-head{text-align:center;margin-bottom:24px}
.v-auth-head .v-brand-plate{margin:0 auto 16px}
.v-auth-head .v-brand-plate img{max-height:64px;max-width:200px}
.v-auth-head .v-eyebrow{margin-bottom:14px}
.v-auth h1,.v-auth-head h1{
  font-family:var(--head-font);
  font-size:26px;line-height:1.1;font-weight:700;
  text-transform:uppercase;letter-spacing:.01em;
  margin:0 0 8px;color:#f4f4f5;
}
.v-auth-head .sub,.v-auth-head p.sub{
  color:#9a9aa6;font-size:13.5px;line-height:1.55;margin:0;
}
/* The portal pages put <h1>/<p class="sub"> straight in the card. */
.v-auth > h1{text-align:center}
.v-auth > p.sub{text-align:center;color:#9a9aa6;font-size:13.5px;line-height:1.55;margin:0 0 22px}

.v-auth .v-cta,.v-auth .btn-primary{
  width:100%;display:flex;align-items:center;justify-content:center;text-align:center;
  padding:13px;border-radius:12px;
  font-family:var(--head-font);font-weight:700;font-size:15px;
  letter-spacing:.04em;text-transform:uppercase;
  cursor:pointer;
}
.v-auth-links{
  display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;
  margin-top:20px;padding-top:18px;
  border-top:1px solid var(--v-line);
  font-size:12.5px;
}
.v-auth-links a{color:#9a9aa6;text-decoration:none}
.v-auth-links a:hover{color:var(--accent-text)}

/* ============================================================
   WIZARD / PORTAL HEADER BAR
   book.php's header was a bare 34px <img> with plain body text —
   the only surface with no brand treatment at all.
   ============================================================ */
header.topbar .wrap{display:flex;align-items:center;gap:14px}
header.topbar .v-brand-plate{padding:7px 10px;flex:0 0 auto}
header.topbar .v-brand-plate img{max-height:34px;max-width:120px}
header.topbar .name{
  font-family:var(--head-font);
  font-size:17px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:#f2f2f5;
}

/* ============================================================
   HOVER POLISH
   Additive only — no colours or layout.

   Two effects are deliberately absent. This file loads on admin as
   well as the portal, so either would have become global:
     · the entrance animation — a rise-and-fade on 20+ cards every
       time an admin dashboard loads is noise, not polish;
     · the hover lift (translateY) — admin cards are large and dense,
       and app.css already gives them a hover glow with no movement.
   The hover treatment below is that same glow, applied everywhere,
   which is the point of the exercise.
   ============================================================ */
.btn-primary,.v-cta,.wizard-btn.primary{position:relative;overflow:hidden}
.btn-primary::after,.v-cta::after,.wizard-btn.primary::after{
  content:"";position:absolute;top:0;left:-120%;width:80%;height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.22),transparent);
  transform:skewX(-18deg);pointer-events:none;
}
.btn-primary:hover::after,.v-cta:hover::after,
.wizard-btn.primary:hover:not(:disabled)::after{animation:vBtnSheen .7s ease}
.wizard-btn.primary:disabled::after,.v-cta:disabled::after{display:none}
@keyframes vBtnSheen{0%{left:-120%}100%{left:140%}}

/* ============================================================
   MOTION — fully disabled when the OS asks for reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  body::after{animation:none}
  .v-success-ring::before,.v-success-ring::after{animation:none}
  .card{transition:none}
  .btn-primary,.wizard-btn.primary,.v-cta,.v-glow,.v-nav-link,.v-tab,.v-nav-logout{transition:none}
  .btn-primary:hover,.wizard-btn.primary:hover:not(:disabled),.v-cta:hover,.v-glow:hover{transform:none}
  .btn-primary:hover::after,.v-cta:hover::after,
  .wizard-btn.primary:hover:not(:disabled)::after{animation:none}
  /* Customer portal */
  .vp-btn,.vp-quick-tile,.vp-photo,.vp-package,.vp-fab,.vp-track-fill,
  .vp-overlay-btn,.v-nav-user,.vp-choice-body{transition:none}
  .vp-btn:hover,.vp-quick-tile:hover,.vp-photo:hover,.vp-package:hover,
  .vp-fab:hover,.vp-choice:hover .vp-choice-body{transform:none}
  .vp-splash-ring,.vp-splash-glow{animation:none}
  .vp-photo img:not(.is-loaded){animation:none}
}
