/* Parent Portal PWA — hand-built mobile-first shell (no Bootstrap/OWL). */

/* Brighton brand palette — the school's official colour palette (maroon
   751020, red D6212E, gold CEB32C, black, white), not brighton_website's
   homepage.css (that file's navy was one web developer's arbitrary pick for
   a different module and has no relationship to the school's mark). These
   were originally hand-sampled off the crest before the official palette
   PDF existed (#750e20 / #d71e2e / #cfb42c) and landed within a couple of
   hex units of the real values, so nothing below actually moves. There is
   no blue anywhere in the crest.

   Role split (deliberate — each colour has exactly one job, so a parent can
   tell a heading from a thing they can tap to pay):
     oxblood — structural chrome only: topbar, bottom nav, section rules,
               table totals, headings. Deliberately pushed darker and less
               saturated than the shield sample so it reads as near-black
               maroon "furniture" and never competes with the CTA.
     crimson — money and action: primary CTAs, outstanding balances,
               overdue/urgent status. Brighter and more saturated than the
               chrome, which is what makes it jump off it. It is the colour
               a parent's eye should land on first.
     gold    — identity hairline only: section rules, the top edge of a
               payable card, the "current year" marker. Never a fill. */
:root {
  --pp-brand: #4f1720;       /* oxblood — logo shield, deepened for chrome */
  --pp-brand-dark: #360f16;  /* near-black maroon: gradients, toast, scrim */
  --pp-brand-light: #6d2130; /* lighter oxblood, for gradients + focus ring */
  --pp-brand-soft: #f4eced;  /* oxblood at ~7%, for neutral info chips */

  --pp-accent: #c41e33;      /* crimson — logo linework, pulled back just far
                                enough to keep white button text at 5.8:1 */
  --pp-accent-dark: #a3182a; /* pressed state */
  --pp-accent-soft: #fceef0; /* crimson at ~6%, for urgent chips */

  --pp-gold: #c9a227;        /* logo gold (#cfb42c), warmed slightly */
  --pp-gold-soft: #faf1d9;

  --pp-bg: #faf8f8;          /* warm paper, so the neutrals sit under maroon
                                rather than under a cold blue-grey */
  --pp-card: #ffffff;
  --pp-border: #e5dfe0;
  --pp-border-soft: #f1eced; /* inner rules inside a card */
  --pp-text: #201a1b;        /* warm near-black */
  --pp-muted: #6a5f61;
  --pp-faint: #9b9092;       /* tertiary — eyebrows, table headers */

  --pp-danger: #c41e33;
  --pp-success: #28705d;
  --pp-success-bg: #e9f5ef;

  /* Spacing scale — every margin/padding in this file comes from here, so
     vertical rhythm stays consistent instead of drifting into ad-hoc rems. */
  --pp-s1: 0.25rem;
  --pp-s2: 0.5rem;
  --pp-s3: 0.75rem;
  --pp-s4: 1rem;
  --pp-s5: 1.5rem;
  --pp-s6: 2rem;

  --pp-radius: 12px;
  --pp-radius-sm: 8px;
  --pp-radius-lg: 16px;

  /* Two elevations only: resting cards sit almost flat, the one hero panel
     per screen lifts. More levels than this reads as decoration, not depth. */
  --pp-shadow-1: 0 1px 2px rgba(45, 20, 25, 0.06);
  --pp-shadow-2: 0 4px 16px rgba(45, 20, 25, 0.10), 0 1px 3px rgba(45, 20, 25, 0.07);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--pp-bg);
  color: var(--pp-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
}

#app { min-height: 100%; display: flex; flex-direction: column; }

button { font-family: inherit; }

/* ── Top bar ─────────────────────────────────────────────────────────── */
.pp-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--pp-brand);
  color: #fff;
  padding: env(safe-area-inset-top, 0) 0 0;
  /* Gold hairline along the bottom edge — the single most repeated brand
     cue in the app, present on every screen without costing any space. */
  box-shadow: inset 0 -2px 0 var(--pp-gold);
}
.pp-topbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--pp-s3) var(--pp-s4) var(--pp-s2);
  gap: var(--pp-s3);
}
.pp-topbar-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; }
.pp-topbar-actions { display: flex; align-items: center; gap: var(--pp-s2); }
.pp-icon-btn {
  background: rgba(255,255,255,0.15);
  border: none;
  color: #fff;
  width: 32px;
  height: 32px;
  border-radius: var(--pp-radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.pp-icon-btn svg { width: 17px; height: 17px; }
.pp-student-switcher {
  padding: 0 var(--pp-s4) var(--pp-s3);
}
.pp-student-switcher select {
  width: 100%;
  min-height: 44px;
  font-size: 1rem;
  padding: var(--pp-s2) var(--pp-s3);
  border-radius: var(--pp-radius-sm);
  border: none;
  background: rgba(255,255,255,0.95);
  color: var(--pp-text);
  font-weight: 600;
}

/* ── Content area ────────────────────────────────────────────────────── */
.pp-content {
  flex: 1;
  padding: var(--pp-s4) var(--pp-s4) 5.5rem;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
}

.pp-loading, .pp-empty { text-align: center; color: var(--pp-muted); padding: var(--pp-s6) var(--pp-s4); }
.pp-error {
  background: var(--pp-accent-soft);
  color: var(--pp-accent);
  border-radius: var(--pp-radius);
  border-left: 3px solid var(--pp-accent);
  padding: var(--pp-s3) var(--pp-s4);
  margin-bottom: var(--pp-s3);
  font-size: 0.9rem;
  line-height: 1.45;
}
.pp-success-banner {
  background: var(--pp-success-bg);
  color: var(--pp-success);
  border-radius: var(--pp-radius);
  border-left: 3px solid var(--pp-success);
  padding: var(--pp-s3) var(--pp-s4);
  margin-bottom: var(--pp-s3);
  font-size: 0.9rem;
  line-height: 1.45;
}

/* ── Designed empty state ────────────────────────────────────────────── */
/* Replaces a bare centred sentence: muted icon, one short headline in the
   text colour, one explanatory line in muted. Reads as authored copy. */
.pp-empty-state {
  text-align: center;
  padding: var(--pp-s6) var(--pp-s4);
  color: var(--pp-muted);
}
.pp-empty-state svg {
  width: 40px;
  height: 40px;
  color: var(--pp-border);
  margin-bottom: var(--pp-s3);
}
.pp-empty-title { font-weight: 600; color: var(--pp-text); font-size: 0.98rem; }
.pp-empty-text { font-size: 0.86rem; margin-top: var(--pp-s1); line-height: 1.5; }

/* ── Skeleton loading ────────────────────────────────────────────────── */
/* A shaped placeholder that matches the card it is about to become, so the
   layout does not jump and the wait reads as designed rather than blank. */
.pp-skeleton-card {
  background: var(--pp-card);
  border-radius: var(--pp-radius);
  border: 1px solid var(--pp-border);
  padding: var(--pp-s4);
  margin-bottom: var(--pp-s3);
}
.pp-skel {
  height: 10px;
  border-radius: 999px;
  background: linear-gradient(90deg, #f3eeef 25%, #e8e0e1 37%, #f3eeef 63%);
  background-size: 400% 100%;
  animation: pp-shimmer 1.4s ease infinite;
}
.pp-skel + .pp-skel { margin-top: var(--pp-s2); }
.pp-skel-title { height: 13px; width: 55%; }
.pp-skel-line { width: 78%; }
.pp-skel-short { width: 35%; }
@keyframes pp-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) { .pp-skel { animation: none; } }

/* ── Cards ───────────────────────────────────────────────────────────── */
.pp-card {
  background: var(--pp-card);
  border-radius: var(--pp-radius);
  border: 1px solid var(--pp-border);
  box-shadow: var(--pp-shadow-1);
  padding: var(--pp-s4);
  margin-bottom: var(--pp-s3);
}
/* Status accent rail: a 3px left edge is enough to make a list scannable by
   colour alone, without turning the card itself into a coloured block. */
.pp-card-accent { border-left: 3px solid var(--pp-border); }
.pp-card-accent-due { border-left-color: var(--pp-accent); }
.pp-card-accent-open { border-left-color: var(--pp-gold); }
.pp-card-accent-paid { border-left-color: var(--pp-success); }
.pp-card-accent-brand { border-left-color: var(--pp-brand); }

.pp-card-clickable { cursor: pointer; transition: box-shadow 0.14s ease, transform 0.14s ease; }
.pp-card-clickable:active { box-shadow: none; transform: translateY(1px); }
.pp-card-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--pp-s3); }
.pp-card-main { flex: 1; min-width: 0; }
.pp-card-title {
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -0.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pp-card-meta { color: var(--pp-muted); font-size: 0.85rem; margin-top: var(--pp-s1); line-height: 1.45; }
/* Rich-text bodies (event/club notes authored as HTML in the backend) --
   basic, sane typography for whatever tags come through. */
.pp-rich-text { color: var(--pp-text); font-size: 0.9rem; line-height: 1.55; }
.pp-rich-text p { margin: 0 0 var(--pp-s2); }
.pp-rich-text p:last-child { margin-bottom: 0; }
.pp-rich-text ul, .pp-rich-text ol { margin: 0 0 var(--pp-s2); padding-left: 1.2em; }
.pp-rich-text a { color: var(--pp-brand); }
.pp-rich-text strong, .pp-rich-text b { font-weight: 650; }
.pp-rich-text img { max-width: 100%; border-radius: var(--pp-radius-sm); }
.pp-card-amount {
  font-weight: 700;
  font-size: 1.05rem;
  white-space: nowrap;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  padding-top: 0.05rem;
}
/* Right-hand money column: a micro-label above the figure tells the parent
   what the number actually means (balance vs total) — the single most
   common source of confusion on a fee statement. */
.pp-amount-col { flex-shrink: 0; text-align: right; }
.pp-amount-label {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 700;
  color: var(--pp-faint);
  margin-bottom: 0.1rem;
}
.pp-amount-due { color: var(--pp-accent); }
.pp-amount-settled { color: var(--pp-success); }

.pp-chevron { color: var(--pp-faint); flex-shrink: 0; width: 20px; height: 20px; align-self: center; }

/* Eyebrow — small uppercase kicker above a title (academic term, year). */
.pp-eyebrow {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--pp-faint);
  margin-bottom: 0.15rem;
}

/* Meta line with a leading icon — used for schedules, dates, capacity. */
.pp-meta-line {
  display: flex;
  align-items: center;
  gap: var(--pp-s2);
  color: var(--pp-muted);
  font-size: 0.85rem;
  margin-top: var(--pp-s2);
  line-height: 1.35;
}
.pp-meta-line svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--pp-faint); }

/* Row list — several short records (e.g. courses) share one card, separated
   by hairlines, instead of each getting its own bordered box. Same content,
   far less visual noise, and the group reads as one thing. */
.pp-row-list { padding: 0; }
.pp-row-item {
  display: flex;
  align-items: center;
  gap: var(--pp-s3);
  padding: var(--pp-s3) var(--pp-s4);
}
.pp-row-item + .pp-row-item { border-top: 1px solid var(--pp-border-soft); }
.pp-row-item .pp-card-main { min-width: 0; }
/* Initial chip — gives a text-only list an anchor at the left margin so the
   eye has a column to travel down. Uses the muted oxblood tint, never the
   crimson accent: these are neutral records, not things needing attention. */
.pp-avatar {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--pp-radius-sm);
  background: var(--pp-brand-soft);
  color: var(--pp-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 0.85rem;
  text-transform: uppercase;
}
.pp-row-title { font-weight: 600; font-size: 0.93rem; line-height: 1.3; }
.pp-row-sub { color: var(--pp-muted); font-size: 0.8rem; margin-top: 0.1rem; line-height: 1.35; }

/* ── Badges ──────────────────────────────────────────────────────────── */
.pp-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 0.2rem 0.5rem;
  border-radius: 5px;
  background: #f1eced;
  color: var(--pp-muted);
  margin-top: var(--pp-s2);
  white-space: nowrap;
}
.pp-badge-paid, .pp-badge-done { background: var(--pp-success-bg); color: var(--pp-success); }
.pp-badge-open, .pp-badge-pending { background: var(--pp-gold-soft); color: #8a6317; }
.pp-badge-overdue { background: var(--pp-accent-soft); color: var(--pp-accent); }
.pp-badge-info { background: var(--pp-brand-soft); color: var(--pp-brand); }
/* Gold outline chip — reserved for "this is the one that matters now"
   (current academic year). Deliberately the only gold-bordered element. */
.pp-badge-current {
  background: transparent;
  color: #8a6317;
  border: 1px solid var(--pp-gold);
}
.pp-badge-inline { margin-top: 0; }

/* Buttons placed directly in a card-row (e.g. Enrolments' "Drop" button)
   must not shrink and wrap under the title/meta column next to them. */
.pp-card-row > .pp-btn { flex-shrink: 0; }

/* ── Section titles ──────────────────────────────────────────────────── */
/* Oxblood small-caps label with a gold rule running to the right edge. This
   is the app's signature typographic move: it costs nothing, appears on
   every screen, and pairs the crest's two identity colours directly. */
.pp-section-title {
  display: flex;
  align-items: center;
  gap: var(--pp-s3);
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: var(--pp-s5) 0 var(--pp-s3);
  color: var(--pp-brand);
}
.pp-section-title::after {
  content: "";
  flex: 1;
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--pp-gold), rgba(201, 162, 39, 0));
}
/* Section title with a trailing action (e.g. "Clubs — Join a Club"). */
.pp-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-s3);
  margin: var(--pp-s5) 0 var(--pp-s3);
}
.pp-section-head .pp-section-title { margin: 0; flex: 1; }

/* ── Statement-style summary rows ────────────────────────────────────── */
.pp-summary-rows { margin-top: var(--pp-s3); }
/* Club-join detail view's illustrative image, when the activity has one. */
.pp-club-detail-image { width: 100%; max-height: 220px; object-fit: cover; border-radius: var(--pp-radius); margin-bottom: var(--pp-s3); }
.pp-summary-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--pp-s3);
  padding: var(--pp-s2) 0;
  font-size: 0.9rem;
}
.pp-summary-row + .pp-summary-row { border-top: 1px solid var(--pp-border-soft); }
.pp-summary-row span:first-child { color: var(--pp-muted); }
.pp-summary-row span:last-child {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 600;
}
/* The closing line of a statement block gets a full-weight rule above it,
   the way a printed invoice separates the total from its components. */
.pp-summary-row-total {
  border-top: 1.5px solid var(--pp-border) !important;
  margin-top: var(--pp-s1);
  padding-top: var(--pp-s3);
}
.pp-summary-row-total span:first-child { color: var(--pp-text); font-weight: 700; }
.pp-summary-row-total span:last-child { font-weight: 700; font-size: 1.05rem; color: var(--pp-brand); }

/* ── Hero amount panel (invoice detail) ──────────────────────────────── */
/* One panel per screen carries the number that matters. Gold top edge marks
   it as the brand's focal surface; the figure itself goes maroon when money
   is owed and green when it is settled, so status is legible pre-reading. */
.pp-hero {
  background: var(--pp-card);
  border: 1px solid var(--pp-border);
  border-top: 3px solid var(--pp-gold);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-2);
  padding: var(--pp-s5) var(--pp-s4) var(--pp-s4);
  margin-bottom: var(--pp-s4);
}
.pp-hero-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--pp-s3);
  margin-bottom: var(--pp-s4);
}
.pp-hero-name { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; }
.pp-hero-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 700;
  color: var(--pp-faint);
}
.pp-hero-amount {
  font-size: 2.1rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  margin-top: var(--pp-s1);
  color: var(--pp-accent);
}
.pp-hero-amount-settled { color: var(--pp-success); }
.pp-hero-note { font-size: 0.82rem; color: var(--pp-muted); margin-top: var(--pp-s2); }

/* Paid-progress bar — turns two numbers (paid / total) into one glanceable
   fact. Green fill because it measures progress, not debt. */
.pp-progress {
  height: 6px;
  border-radius: 999px;
  background: var(--pp-border-soft);
  overflow: hidden;
  margin-top: var(--pp-s4);
}
.pp-progress-fill { height: 100%; border-radius: 999px; background: var(--pp-success); transition: width 0.3s ease; }
.pp-progress-caption {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  color: var(--pp-muted);
  margin-top: var(--pp-s2);
  font-variant-numeric: tabular-nums;
}

/* Compact stat strip — total outstanding across a list. */
.pp-stat-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-s3);
  background: var(--pp-card);
  border: 1px solid var(--pp-border);
  border-left: 3px solid var(--pp-accent);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-1);
  padding: var(--pp-s3) var(--pp-s4);
  margin-bottom: var(--pp-s4);
}
.pp-stat-strip-settled { border-left-color: var(--pp-success); }
/* Neutral variant: a count that is information, not money — the accent is
   reserved for figures the parent may need to act on. */
.pp-stat-strip-brand { border-left-color: var(--pp-brand); }
.pp-stat-strip-brand .pp-stat-value { color: var(--pp-brand); }
.pp-stat-icon { flex-shrink: 0; width: 34px; height: 34px; color: var(--pp-border); }
.pp-stat-icon svg { width: 100%; height: 100%; }
.pp-stat-value {
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--pp-accent);
  margin-top: 0.1rem;
}
.pp-stat-strip-settled .pp-stat-value { color: var(--pp-success); }

/* ── Home ────────────────────────────────────────────────────────────── */
/* Greeting: one line of human address before any figure, so the first thing
   on the app's first screen is a person and not an account balance. */
.pp-greeting { margin-bottom: var(--pp-s4); }
.pp-greeting-hello { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.015em; }
.pp-greeting-sub { color: var(--pp-muted); font-size: 0.85rem; margin-top: 0.15rem; }

/* Summary tile on Home — the app's only filled brand surface. Oxblood
   gradient with the gold crest hairline on top; the balance is set in white
   rather than crimson because on a maroon field crimson would disappear,
   and the one crimson-equivalent element here is the white Pay button. */
.pp-summary-tile {
  background: linear-gradient(135deg, var(--pp-brand-dark), var(--pp-brand-light));
  color: #fff;
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-2);
  padding: var(--pp-s5) var(--pp-s4) var(--pp-s4);
  margin-bottom: var(--pp-s4);
  border-top: 3px solid var(--pp-gold);
}
.pp-summary-tile .pp-summary-label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700; opacity: 0.75; }
.pp-summary-tile .pp-summary-amount { font-size: 1.9rem; font-weight: 800; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; margin: var(--pp-s1) 0 0; }
.pp-tile-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--pp-s3); }
/* Translucent chip on the tile — status without introducing a third colour
   onto a surface that is already fully brand-filled. */
.pp-tile-chip {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.25rem 0.55rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  white-space: nowrap;
}
.pp-tile-chip svg { width: 13px; height: 13px; }
.pp-tile-note { font-size: 0.8rem; opacity: 0.8; margin-top: var(--pp-s2); line-height: 1.4; }
.pp-summary-tile .pp-btn { margin-top: var(--pp-s4); }

/* Identity header — avatar chip + name + admission number, so the student
   card opens with who this is instead of a run of unlabelled meta lines. */
.pp-identity { display: flex; align-items: center; gap: var(--pp-s3); }
.pp-avatar-lg {
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  border-radius: var(--pp-radius-sm);
  background: var(--pp-brand-soft);
  color: var(--pp-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 1rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.pp-identity-name { font-size: 1.02rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1.25; }
.pp-identity-sub { color: var(--pp-muted); font-size: 0.82rem; margin-top: 0.1rem; }
/* Field rows under the identity header: labelled left, value right — the
   same statement grammar used on invoice detail, so the two screens read as
   one system rather than two different card styles. */
.pp-field-rows { margin-top: var(--pp-s4); border-top: 1px solid var(--pp-border-soft); }
.pp-field-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--pp-s4);
  padding: var(--pp-s2) 0;
  font-size: 0.88rem;
}
.pp-field-row + .pp-field-row { border-top: 1px solid var(--pp-border-soft); }
.pp-field-row > span:first-child { color: var(--pp-muted); flex-shrink: 0; }
.pp-field-row > span:last-child { font-weight: 600; text-align: right; min-width: 0; }

/* The ECA row is the one field row that's actually a button -- it opens
   Enrolments straight to the clubs section. Given its own accent pill
   (rather than blending into the plain grade/stream/transport rows above)
   so it reads as something to tap, not just another fact about the student. */
.pp-field-row-eca {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-s4);
  width: 100%;
  margin-top: var(--pp-s2);
  padding: var(--pp-s3);
  background: var(--pp-brand-soft);
  border: 1px solid var(--pp-brand-light);
  border-radius: 10px;
  cursor: pointer;
  font: inherit;
  font-size: 0.88rem;
  text-align: inherit;
}
.pp-field-row-eca > span:first-child { color: var(--pp-brand); font-weight: 700; }
.pp-field-row-eca-icons { display: flex; align-items: center; gap: var(--pp-s2); color: var(--pp-brand); flex-shrink: 0; }
.pp-field-row-eca-icons svg { width: 16px; height: 16px; }
.pp-field-row-eca:active { opacity: 0.75; }

/* Reach the campus for this student -- one row of equal-weight actions,
   WhatsApp given its brand green since that's the channel most parents
   reach for first, everything else neutral. */
.pp-contact-row {
  display: flex;
  gap: var(--pp-s2);
  margin-top: var(--pp-s4);
  padding-top: var(--pp-s4);
  border-top: 1px solid var(--pp-border-soft);
}
.pp-contact-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: var(--pp-s3) var(--pp-s2);
  border-radius: var(--pp-radius-sm);
  border: 1px solid var(--pp-border);
  background: var(--pp-card);
  color: var(--pp-brand);
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: none;
}
.pp-contact-btn svg { width: 19px; height: 19px; }
.pp-contact-btn:active { background: var(--pp-bg); }
.pp-contact-btn-whatsapp { color: #25D366; border-color: #25D36680; }

/* Quick-jump grid — replaces two stray buttons with a proper launcher, so
   every tab is reachable from the landing screen at thumb size. */
.pp-quick-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--pp-s3); }
.pp-quick-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--pp-s2);
  padding: var(--pp-s3) var(--pp-s2);
  min-height: 82px;
  background: var(--pp-card);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-1);
  color: var(--pp-brand);
  font-size: 0.75rem;
  font-weight: 650;
  cursor: pointer;
  position: relative;
  transition: transform 0.08s ease, box-shadow 0.14s ease;
}
.pp-quick-tile:active { transform: translateY(1px); box-shadow: none; }
.pp-quick-tile svg { width: 22px; height: 22px; }
/* Count pill on a launcher tile — crimson, because a number here always
   means "something is waiting for you". */
.pp-quick-count {
  position: absolute;
  top: 6px;
  right: 6px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--pp-accent);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Calendar ────────────────────────────────────────────────────────── */
/* Events are grouped under a day header rather than listed flat, so the
   parent scans dates first and titles second — the way a diary is read. */
.pp-day-group + .pp-day-group { margin-top: var(--pp-s5); }
.pp-day-head { display: flex; align-items: center; gap: var(--pp-s3); margin-bottom: var(--pp-s3); }
.pp-day-date {
  font-size: 0.74rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pp-brand);
  white-space: nowrap;
}
.pp-day-head::after { content: ""; flex: 1; height: 1px; border-radius: 999px; background: var(--pp-border); }
/* Today's header is the one place on this screen that earns the accent. */
.pp-day-today .pp-day-date { color: var(--pp-accent); }
.pp-day-today .pp-day-head::after { background: linear-gradient(90deg, var(--pp-accent), rgba(196, 30, 51, 0)); }

.pp-event { display: flex; gap: var(--pp-s3); align-items: flex-start; }
/* Fixed-width time gutter gives the day's events a common left edge, so a
   column of start times is readable without reading any titles. */
.pp-event-time {
  flex: 0 0 64px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--pp-text);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  padding-top: 0.05rem;
}
.pp-event-time small { display: block; font-size: 0.68rem; font-weight: 600; color: var(--pp-faint); text-transform: uppercase; letter-spacing: 0.05em; }
.pp-event-body { flex: 1; min-width: 0; }

/* ── Documents ───────────────────────────────────────────────────────── */
/* Extension chip instead of a generic icon: the parent can tell a PDF
   report from a photo before opening it, at no extra vertical cost. */
.pp-file-chip {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--pp-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: var(--pp-brand-soft);
  color: var(--pp-brand);
}
.pp-file-pdf { background: var(--pp-accent-soft); color: var(--pp-accent); }
.pp-file-img { background: var(--pp-gold-soft); color: #8a6317; }
.pp-file-sheet { background: var(--pp-success-bg); color: var(--pp-success); }
.pp-file-chip svg { width: 18px; height: 18px; }

/* ── Buttons ─────────────────────────────────────────────────────────── */
.pp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--pp-s2);
  border: 1px solid transparent;
  border-radius: var(--pp-radius-sm);
  padding: var(--pp-s3) var(--pp-s4);
  font-size: 0.92rem;
  font-weight: 650;
  min-height: 44px;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease, transform 0.08s ease;
}
.pp-btn svg { width: 17px; height: 17px; flex-shrink: 0; }
.pp-btn:active { transform: translateY(1px); }
.pp-btn-primary { background: #fff; color: var(--pp-brand); }
.pp-btn-secondary { background: rgba(255,255,255,0.18); color: #fff; }
.pp-btn-light { background: var(--pp-card); color: var(--pp-brand); border-color: var(--pp-border); }
.pp-btn-light:active { background: var(--pp-bg); }
/* Maroon is the app's primary action colour — it marks the one thing on a
   screen the parent is meant to do (pay, enrol, submit). Exactly one per
   surface; everything else is the bordered .pp-btn-light. */
.pp-btn-solid, .pp-btn-accent { background: var(--pp-accent); color: #fff; }
.pp-btn-solid:active, .pp-btn-accent:active { background: var(--pp-accent-dark); }
/* Text-weight button for low-stakes secondary actions (Statement, Drop). */
.pp-btn-ghost {
  background: transparent;
  color: var(--pp-brand);
  border-color: transparent;
  padding: var(--pp-s2) var(--pp-s2);
  min-height: 36px;
  font-size: 0.85rem;
}
.pp-btn-ghost:active { background: var(--pp-brand-soft); }
.pp-btn-ghost-danger { color: var(--pp-accent); }
.pp-btn-ghost-danger:active { background: var(--pp-accent-soft); }
/* A real, visibly-bordered button for a destructive-but-legitimate action
   (Drop out) -- distinct from .pp-btn-ghost, which reads as plain text. */
.pp-btn-danger {
  background: rgba(196, 30, 51, 0.08);
  color: var(--pp-danger);
  border-color: var(--pp-danger);
}
.pp-btn-danger:active { background: rgba(196, 30, 51, 0.16); }
.pp-btn-sm { min-height: 36px; padding: var(--pp-s2) var(--pp-s3); font-size: 0.82rem; }
.pp-btn-block { width: 100%; }
.pp-btn:disabled { opacity: 0.45; cursor: default; transform: none; }
.pp-btn-row { display: flex; gap: var(--pp-s2); flex-wrap: wrap; margin-top: var(--pp-s3); }
/* Action pair where the primary should dominate the width. */
.pp-btn-row-split { flex-wrap: nowrap; }
.pp-btn-row-split > .pp-btn { flex: 1; min-width: 0; }
.pp-btn-row-split > .pp-btn-accent { flex: 1.4; }

.pp-back-row { display: flex; align-items: center; gap: var(--pp-s1); margin-bottom: var(--pp-s3); color: var(--pp-brand); font-weight: 600; }
.pp-back-row button { background: none; border: none; display: flex; align-items: center; gap: var(--pp-s1); padding: var(--pp-s1) 0; font-size: 0.9rem; color: inherit; cursor: pointer; }
.pp-back-row svg { width: 18px; height: 18px; }

/* ── Account statement rows ──────────────────────────────────────────
   A plain 3-column table (Date/Description/Balance) wrapped the
   description column awkwardly on narrow phones and squeezed the amount
   until it read as truncated/blank. Each transaction is now its own
   bordered tile: date + description on top, a single signed/colored
   Amount + a bold Balance cell pinned to the right below. */
.pp-stmt-list { display: flex; flex-direction: column; gap: var(--pp-s2); }
.pp-stmt-row {
  padding: var(--pp-s3) var(--pp-s4);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-sm);
  background: var(--pp-card);
}
.pp-stmt-row-opening { background: var(--pp-brand-soft); }
.pp-stmt-row-main { display: flex; align-items: baseline; gap: var(--pp-s3); margin-bottom: var(--pp-s2); }
.pp-stmt-date { flex-shrink: 0; color: var(--pp-muted); font-size: 0.76rem; white-space: nowrap; }
.pp-stmt-desc { flex: 1; min-width: 0; overflow-wrap: break-word; color: var(--pp-text); font-weight: 500; font-size: 0.86rem; }
.pp-stmt-row-amounts { display: flex; justify-content: flex-end; gap: var(--pp-s5); }
.pp-stmt-amt { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; min-width: 64px; }
.pp-stmt-amt-label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--pp-faint); }
.pp-stmt-amt span:last-child { font-variant-numeric: tabular-nums; font-weight: 600; font-size: 0.86rem; }
/* Charge (debit): balance owed goes up -- red, "+". Payment (credit):
   balance owed goes down -- green, "-". */
.pp-stmt-amt-debit { color: var(--pp-danger); }
.pp-stmt-amt-credit { color: var(--pp-success); }
/* Balance is the number a parent actually scans for -- bold and larger
   so it stands out from the transaction amount next to it. */
.pp-stmt-amt-balance span:last-child { color: var(--pp-brand); font-weight: 700; font-size: 0.92rem; }

/* ── Tables ──────────────────────────────────────────────────────────── */
.pp-list-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-bottom: var(--pp-s4);
  border-radius: var(--pp-radius);
  border: 1px solid var(--pp-border);
  box-shadow: var(--pp-shadow-1);
  background: var(--pp-card);
}
.pp-list-table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
.pp-list-table th, .pp-list-table td { text-align: left; padding: var(--pp-s3); border-bottom: 1px solid var(--pp-border-soft); vertical-align: top; }
.pp-list-table tbody tr:last-child td { border-bottom: none; }
.pp-list-table th {
  color: var(--pp-faint);
  font-weight: 700;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  background: var(--pp-bg);
  border-bottom-color: var(--pp-border);
}
.pp-list-table td:first-child { word-break: break-word; }
.pp-list-table .pp-col-num {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* Totals row, styled like the closing line of a printed statement. */
.pp-list-table tfoot td {
  border-top: 1.5px solid var(--pp-border);
  border-bottom: none;
  font-weight: 700;
  background: var(--pp-bg);
}
.pp-list-table tfoot .pp-col-num { color: var(--pp-brand); }
/* Secondary detail under a table cell's main text. */
.pp-cell-sub { display: block; color: var(--pp-faint); font-size: 0.76rem; margin-top: 0.15rem; }
/* A discount is a savings, not just metadata -- give it the green success
   color and a check mark so it reads as a positive, not a footnote. */
.pp-cell-discount { display: flex; align-items: center; gap: 0.3rem; color: var(--pp-success); font-weight: 600; }
.pp-cell-discount svg { width: 13px; height: 13px; flex-shrink: 0; }
/* Total discount row in the invoice summary card -- same "savings" green. */
.pp-discount-value { color: var(--pp-success); font-weight: 600; }

/* ── Forms ───────────────────────────────────────────────────────────── */
.pp-form-field { margin-bottom: var(--pp-s4); }
.pp-form-field label {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pp-muted);
  margin-bottom: var(--pp-s2);
}
.pp-form-field input, .pp-form-field select, .pp-form-field textarea {
  width: 100%;
  min-height: 44px;
  font-size: 1rem;
  font-family: inherit;
  color: var(--pp-text);
  padding: var(--pp-s2) var(--pp-s3);
  border-radius: var(--pp-radius-sm);
  border: 1px solid var(--pp-border);
  background: var(--pp-card);
}
.pp-form-field input:focus, .pp-form-field select:focus, .pp-form-field textarea:focus {
  outline: none;
  border-color: var(--pp-brand-light);
  box-shadow: 0 0 0 3px var(--pp-brand-soft);
}
.pp-form-field textarea { min-height: 90px; resize: vertical; }
/* Account details' WhatsApp toggle -- a plain labelled checkbox row,
   not a full form-field (no label-above-input pattern needed for one
   binary choice). */
.pp-checkbox-row { display: flex; align-items: center; gap: var(--pp-s2); margin: var(--pp-s2) 0 var(--pp-s3); font-size: 0.88rem; color: var(--pp-text); }
.pp-checkbox-row input { width: 18px; height: 18px; flex-shrink: 0; accent-color: var(--pp-accent); }
/* Two date inputs side by side rather than stacked — halves the scroll on
   the statement dialog and reads as a single "range" control. */
.pp-field-pair { display: flex; gap: var(--pp-s3); }
.pp-field-pair > .pp-form-field { flex: 1; min-width: 0; }
/* Statement's date range: the apply action sits right next to the two
   inputs as a square icon button, instead of a full-width text button
   below them -- reads as "go" on the row you're already editing. */
.pp-field-pair-with-action { align-items: flex-end; }
.pp-field-pair-with-action .pp-btn-icon-only { flex-shrink: 0; }
.pp-btn-icon-only { min-height: 44px; width: 44px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.pp-btn-icon-only svg { width: 19px; height: 19px; }
/* Attendance's Apply button (and the Report Card's Download button) sit in
   their own field-slot with an invisible label matching the other fields'
   label height, instead of relying on align-items:flex-end to line them up
   -- some mobile browsers render <input type="date"> a few px taller than
   its CSS height, which throws off flex-end bottom-alignment against a
   plain, label-less button.
   Specificity note: `.pp-field-pair > .pp-form-field` (two classes) beats a
   plain `.pp-form-field-button` (one class) regardless of source order, so
   this button variant must repeat both classes to actually win and shrink
   to the button's own width -- otherwise it shares equal flex-grow with the
   text fields, squashing them and leaving dead space around the button. */
.pp-field-pair > .pp-form-field.pp-form-field-button { flex: 0 0 44px; width: 44px; }
.pp-form-field-button label { visibility: hidden; }

/* ── Login screen ────────────────────────────────────────────────────── */
.pp-login-screen {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--pp-s6) var(--pp-s5);
  background: var(--pp-bg);
}
.pp-login-logo { width: 64px; height: 64px; border-radius: var(--pp-radius); margin-bottom: var(--pp-s4); }
.pp-login-title { font-size: 1.15rem; font-weight: 700; color: var(--pp-brand); margin: 0 0 var(--pp-s5); }
.pp-login-form {
  width: 100%;
  max-width: 340px;
  background: var(--pp-card);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-lg);
  box-shadow: var(--pp-shadow-2);
  padding: var(--pp-s5) var(--pp-s4);
}
.pp-login-form .pp-btn { margin-top: var(--pp-s2); }

/* Attendance: AM/PM sit as their own columns instead of stacking "session"
   as a row per session, and the absence reason drops to a full-width row
   underneath instead of a cramped cell, so it never wraps mid-word --
   starting under the AM column (not the date) to match what it explains. */
.pp-attendance-table th:nth-child(2), .pp-attendance-table th:nth-child(3),
.pp-attendance-table td:nth-child(2), .pp-attendance-table td:nth-child(3) { text-align: center; }
.pp-attendance-reason-row td { padding-top: 0; padding-bottom: var(--pp-s3); font-size: 0.8125rem; color: var(--pp-muted); border-top: none; }
/* Zebra stripe by day (a day is 1-2 <tr>s: the AM/PM row plus an optional
   reason row), so alternating shading reads as "every other day", not
   "every other physical row". */
.pp-attendance-table tbody tr.pp-row-alt { background: var(--pp-bg); }
/* The present/absent pill has its own margin-top (for when it sits under a
   card title elsewhere) which pushed it visibly below the plain date text
   in the sibling cell -- cancel that when it's the cell's only content. */
.pp-attendance-table .pp-badge-flush { margin-top: 0; }

/* A quiet inline note above a destructive/consequential form. */
.pp-notice {
  display: flex;
  gap: var(--pp-s3);
  background: var(--pp-gold-soft);
  border-radius: var(--pp-radius-sm);
  padding: var(--pp-s3);
  font-size: 0.83rem;
  line-height: 1.45;
  color: #7a5a13;
  margin-bottom: var(--pp-s4);
}
.pp-notice svg { width: 17px; height: 17px; flex-shrink: 0; margin-top: 1px; }

/* Payment dialog */
.pp-pay-summary {
  background: var(--pp-bg);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius);
  padding: var(--pp-s4);
  text-align: center;
  margin-bottom: var(--pp-s4);
}
.pp-pay-summary-label { font-size: 0.68rem; color: var(--pp-faint); text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700; }
.pp-pay-summary-amount { font-size: 1.6rem; font-weight: 800; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; margin-top: var(--pp-s1); color: var(--pp-accent); }
.pp-phone-input { display: flex; }
.pp-phone-prefix {
  display: flex;
  align-items: center;
  padding: 0 var(--pp-s3);
  background: var(--pp-bg);
  border: 1px solid var(--pp-border);
  border-right: none;
  border-radius: var(--pp-radius-sm) 0 0 var(--pp-radius-sm);
  font-weight: 600;
  color: var(--pp-muted);
  min-height: 44px;
}
.pp-phone-input input { border-radius: 0 var(--pp-radius-sm) var(--pp-radius-sm) 0; }
.pp-field-hint { font-size: 0.78rem; color: var(--pp-muted); margin-top: var(--pp-s2); line-height: 1.45; }

/* ── Shop ────────────────────────────────────────────────────────────── */
/* Search gets its own full-width row with the icon inside the field, rather
   than competing for width with the Orders button on one line. */
.pp-search-wrap { position: relative; margin-bottom: var(--pp-s3); }
.pp-search-wrap > svg {
  position: absolute;
  left: var(--pp-s3);
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--pp-faint);
  pointer-events: none;
}
.pp-shop-search {
  width: 100%;
  min-height: 44px;
  padding: var(--pp-s2) var(--pp-s3) var(--pp-s2) 2.4rem;
  border-radius: var(--pp-radius-sm);
  border: 1px solid var(--pp-border);
  background: var(--pp-card);
  font-size: 1rem;
  font-family: inherit;
  color: var(--pp-text);
}
.pp-shop-search:focus {
  outline: none;
  border-color: var(--pp-brand-light);
  box-shadow: 0 0 0 3px var(--pp-brand-soft);
}

.pp-product-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--pp-s3); }
/* Cards are equal-height columns with the action pinned to the bottom, so a
   two-up grid never ends up with buttons at two different heights. */
.pp-product-card { display: flex; flex-direction: column; padding: var(--pp-s3); margin-bottom: 0; }
.pp-product-card img,
.pp-product-thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--pp-radius-sm);
  background: var(--pp-bg);
}
.pp-product-thumb { display: flex; align-items: center; justify-content: center; border: 1px solid var(--pp-border-soft); }
.pp-product-thumb svg { width: 30px; height: 30px; color: var(--pp-border); }
.pp-product-name {
  font-weight: 600;
  font-size: 0.88rem;
  margin-top: var(--pp-s3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.3em;
  line-height: 1.15;
}
/* Price is the second thing read on a product card, so it is set at card
   weight in the text colour — not buried inside the variant dropdown label. */
.pp-product-price {
  font-weight: 700;
  font-size: 0.98rem;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  margin-top: var(--pp-s1);
}
.pp-product-card select {
  width: 100%;
  min-height: 38px;
  margin-top: var(--pp-s2);
  padding: 0 var(--pp-s2);
  border-radius: var(--pp-radius-sm);
  border: 1px solid var(--pp-border);
  background: var(--pp-card);
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--pp-text);
}
.pp-product-foot { margin-top: auto; padding-top: var(--pp-s3); }
/* Quantity control is a single segmented unit, not three loose controls —
   it only appears once the item is actually in the cart. */
.pp-qty-row { display: flex; align-items: center; }
/* Before anything is in the cart the foot holds a single Add button, so the
   default state is one decision rather than a stepper sitting at zero. */
.pp-qty-row > .pp-btn { flex: 1; height: 38px; min-height: 38px; }
.pp-qty-row button {
  flex: 0 0 38px;
  height: 38px;
  border: 1px solid var(--pp-border);
  background: var(--pp-card);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1;
  color: var(--pp-brand);
  cursor: pointer;
}
.pp-qty-row button:first-child { border-radius: var(--pp-radius-sm) 0 0 var(--pp-radius-sm); }
.pp-qty-row button:last-child { border-radius: 0 var(--pp-radius-sm) var(--pp-radius-sm) 0; }
.pp-qty-row button:active { background: var(--pp-bg); }
.pp-qty-row span {
  flex: 1;
  text-align: center;
  font-weight: 700;
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--pp-border);
  border-bottom: 1px solid var(--pp-border);
  line-height: 36px;
  height: 38px;
}

/* Cart bar — floats above the nav, matched to the content column's width,
   carrying both the count and the running total. Gold hairline ties it to
   the topbar; the Checkout button stays white so it reads as the action on
   a filled brand surface (crimson-on-oxblood would sink). */
.pp-cart-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: 64px;
  max-width: 640px;
  margin: 0 auto;
  background: var(--pp-brand-dark);
  color: #fff;
  border-top: 2px solid var(--pp-gold);
  padding: var(--pp-s3) var(--pp-s4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-s3);
  box-shadow: 0 -6px 20px rgba(34, 14, 18, 0.22);
  z-index: 6;
}
.pp-cart-spacer { height: 76px; }
.pp-cart-count { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; opacity: 0.78; }
.pp-cart-total { font-size: 1.15rem; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; margin-top: 0.1rem; }

/* ── Bottom nav ──────────────────────────────────────────────────────── */
.pp-bottom-nav {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  background: #fff;
  border-top: 1px solid var(--pp-border);
  padding-bottom: env(safe-area-inset-bottom, 0);
  z-index: 6;
}
.pp-nav-item {
  flex: 1;
  border: none;
  background: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: var(--pp-s2) var(--pp-s1) var(--pp-s1);
  color: var(--pp-faint);
  font-size: 0.66rem;
  font-weight: 600;
  min-height: 56px;
  position: relative;
}
.pp-nav-item svg { width: 22px; height: 22px; }
.pp-nav-item.pp-nav-active { color: var(--pp-brand); }
/* Gold marker on the active tab — the brand cue at the app's most-looked-at
   control, and the only decoration in the nav bar. */
.pp-nav-item.pp-nav-active::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 26px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--pp-gold);
}

/* ── Modal ───────────────────────────────────────────────────────────── */
.pp-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(34, 14, 18, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 20;
}
.pp-modal {
  background: var(--pp-bg);
  border-radius: var(--pp-radius-lg) var(--pp-radius-lg) 0 0;
  width: 100%;
  max-width: 640px;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  box-shadow: 0 -8px 32px rgba(34, 14, 18, 0.22);
}
@media (min-width: 640px) {
  .pp-modal-backdrop { align-items: center; }
  .pp-modal { border-radius: var(--pp-radius-lg); max-height: calc(100vh - 4rem); }
}
.pp-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--pp-s4);
  border-bottom: 1px solid var(--pp-border);
  position: sticky;
  top: 0;
  background: #fff;
  z-index: 1;
}
.pp-modal-title { font-weight: 700; font-size: 1rem; letter-spacing: -0.01em; }
.pp-modal-close { background: none; border: none; font-size: 1.1rem; color: var(--pp-muted); cursor: pointer; padding: var(--pp-s1); }
.pp-modal-body { padding: var(--pp-s4); }

/* ── Toast ───────────────────────────────────────────────────────────── */
.pp-toast {
  position: fixed;
  left: 50%;
  bottom: 76px;
  transform: translate(-50%, 20px);
  background: var(--pp-brand-dark);
  color: #fff;
  padding: var(--pp-s3) var(--pp-s4);
  border-radius: 999px;
  font-size: 0.88rem;
  font-weight: 600;
  box-shadow: var(--pp-shadow-2);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 30;
  max-width: 90vw;
}
.pp-toast-show { opacity: 1; transform: translate(-50%, 0); }
.pp-toast-error { background: var(--pp-accent); }

/* ── Update banner ───────────────────────────────────────────────────── */
/* Unlike the toast, this doesn't auto-dismiss -- it sits until the parent
   taps Refresh, on their own schedule (see shared.js showUpdateBanner). */
.pp-update-banner {
  position: fixed;
  left: var(--pp-s3);
  right: var(--pp-s3);
  bottom: 76px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-s3);
  background: var(--pp-brand-dark);
  color: #fff;
  padding: var(--pp-s3) var(--pp-s4);
  border-radius: var(--pp-radius-lg);
  font-size: 0.88rem;
  font-weight: 600;
  box-shadow: var(--pp-shadow-2);
  z-index: 30;
}
.pp-update-banner button {
  flex-shrink: 0;
  background: var(--pp-brand);
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: var(--pp-s2) var(--pp-s3);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}
