/*
 * The PROP2SHARE stylesheet.
 *
 * The application shell — sidebar, top bar, page frame — comes from the Master
 * Admin theme under /assets/theme (see its README for provenance and licence).
 * This file is everything above that: our own components, stated in the theme's
 * visual language so the two read as one product.
 *
 * Load order matters and is set in src/layout.php:
 *   bootstrap -> theme style -> skin_color -> theme_primary -> this file.
 * This file is last, so it wins ties without needing !important.
 *
 * Nothing here should restyle theme chrome. If a theme component looks wrong,
 * fix it here in the small "Theme corrections" section rather than editing
 * anything under /assets/theme, which is derived and gets overwritten.
 */

/* Noto Sans Thai, served from this site instead of Google Fonts. The three
   files are the variable font split by unicode-range exactly as Google splits it
   (so a page only fetches the ones its characters need), and are preloaded in the
   <head> so the text does not swap from the fallback after first paint: that swap
   re-wrapped Thai text and was the largest layout shift measured on the projects
   page. Licence: SIL Open Font License 1.1. Update = re-download from Google Fonts. */
@font-face {
  font-family: 'Noto Sans Thai'; font-style: normal; font-weight: 100 900; font-stretch: 100%; font-display: swap;
  src: url('/assets/fonts/noto-sans-thai-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Noto Sans Thai'; font-style: normal; font-weight: 100 900; font-stretch: 100%; font-display: swap;
  src: url('/assets/fonts/noto-sans-thai-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Noto Sans Thai'; font-style: normal; font-weight: 100 900; font-stretch: 100%; font-display: swap;
  src: url('/assets/fonts/noto-sans-thai-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Contrast on white is 7.10:1 (the old green was 5.02:1), so every place
     --primary backs white button text or carries link text clears WCAG AA
     with room. It sits close to --color-purple (#7c3aed) below, which is why
     the "สนใจ" lead status was moved off that shade onto
     --color-pink-bright — the two read as one colour side by side, and lead
     status chips are the place they sit side by side most. --color-purple's
     other users (visit type, admin role) were left alone: they never render
     next to a large block of brand chrome the way those chips do. */
  --primary: #6d28d9;
  --primary-dark: #5b21b6;
  --primary-deep: #4c1d95;
  --primary-rgb: 109, 40, 217;
  /* Theme colour (src/theme_palette.php): these five are the purple defaults; a non-default palette overrides them per page. Every
     other use of the brand colour in this file reads them, never a hex. */
  --primary-soft: rgba(var(--primary-rgb), 0.08);
  /* The one shade this app actually uses for "success" (#047857) and
     "danger" (#b91c1c) — distinct from --primary, which is the brand
     accent, not a status colour. Both were already consistent everywhere
     they appeared; this just gives that existing consistency a name
     (audit round-2 P3-02) instead of leaving it as sixteen and fifteen
     repeated hex literals respectively. */
  --color-success: #047857;
  --color-danger: #b91c1c;
  /* --color-warning is the alert-banner text/border shade; --color-amber-strong
     is a separate, more saturated amber used for solid "needs attention" chips
     (exec-alert-count, the overdue-followup indicators) — the two read close
     but are deliberately different weights, not a duplicate of each other. */
  --color-warning: #92400e;
  --color-amber-strong: #b45309;
  /* The qualitative badge palette below (audit "no colour tokens" finding):
     each of these is reused across unrelated dimensions — lead status, visit
     type, co-agent grade, user role — as the same named colour, so giving it
     one name here is what the badges already assumed implicitly. Naming them
     also makes two near-identical ambers/teals visibly deliberate rather than
     an accidental near-duplicate: --color-amber-deep and --color-amber are
     both "amber" but distinct values, same for --color-teal-deep/--color-teal. */
  --color-blue: #1e5eea;
  --color-purple: #7c3aed;
  --color-amber-deep: #a15904;
  --color-amber: #975c07;
  --color-teal-deep: #0f766e;
  --color-teal: #06748f;
  --color-pink: #be185d;
  /* Brighter than --color-pink, and its own value rather than a reuse of it,
     because the one status wearing it ("สนใจ") shares a donut chart with
     loan_rejected (#9f1239) — #be185d sits only 48 RGB units from that and
     the two segments would read as one. */
  --color-pink-bright: #db2777;
  --color-slate: #4b5563;
  --color-gray: #646a77;
  --color-red: #dc2626;
  --surface: #ffffff;
  --text: #172b4c;
  --muted: #5b6472; /* was #6b7280: 4.37:1 on the page background, under AA for small text */
  --placeholder: #5f6673;
  --border: rgba(72, 94, 144, 0.16);
  --head-bg: #f7f9fa;
  /* Neutrals (2026-10-09). Solid greys for places --border (translucent) cannot serve:
     a line drawn over a tinted surface, a dashed placeholder, a disabled field. */
  --border-solid: #e5e7eb;   /* opaque light line: card-in-card, timeline, row divider */
  --border-strong: #d1d5db;  /* dashed placeholder, chip outline, stronger edge */
  --surface-subtle: #f3f4f6; /* disabled field, empty thumbnail, segmented-control track */
  --control-h: 34px;  /* THE height of every button and every single-line field (owner 2026-10-09) — enforced at the end of this file */
  --radius: 5px;
  --radius-lg: 12px;   /* cards, panels, popovers; --radius is controls; 999px is a pill */
  --shadow-card: 0 0 10px rgba(72, 94, 144, 0.06);
  --font-stack: 'Noto Sans Thai', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* Type scale (apple-design audit: 16 distinct font-size values in use with
     no defensible reason for several — e.g. .exec-panel-header's h2/h3 sat
     1px apart at 17/16). These 10 steps are the values already doing real
     work across the app; every font-size below now names one of them. Two
     kinds of size intentionally stay OUTSIDE this scale rather than being
     forced onto the nearest step: icon/glyph-sized single characters
     (.p2s-avatar's initial, .exec-period-nav's ‹/› arrows, the nav badge
     digit) aren't part of a reading hierarchy, and bending their pixel-
     tuned sizing for token purity risked visibly breaking their centering
     for zero real consistency gain. */
  --text-2xs: 12px; /* 2xs and xs were 11 and 12: small for Thai, whose vowel and tone marks need more room than Latin letters */
  --text-sm: 13px;   /* --text-xs (also 13px) merged into this one 2026-10-09: one value, one name */
  --text-base: 14px;
  --text-md: 16px;
  --text-lg: 18px;
  --text-xl: 20px;
  --text-2xl: 22px;
  --text-3xl: 24px;
  --text-4xl: 28px;
  /* Transition/animation durations (apple-design audit: 100/120/180/200/400ms
     used with no shared token, drifting for interactions that should feel
     the same weight). --duration-slow is deliberately its own single-use
     value — the account-menu flip is the one animation in the app doing
     more than a fade, and borrowing --duration-base would make the 3D
     rotation feel rushed. */
  --duration-fast: 100ms;
  --duration-base: 180ms;
  --duration-slow: 250ms;
  --space-2xs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
}

/* ======================================================================
   Theme corrections
   ====================================================================== */

/* Bootstrap's own reset sets `:root { scroll-behavior: smooth }`. That is
   fine for an in-page anchor click on an already-loaded page, but Chromium
   has a long-standing bug where a fresh navigation to a URL that already
   carries a #fragment (e.g. clicking "ไปอนุมัติ" from pending_approvals.php
   to /bu/projects.php#pending-approval) never actually scrolls to it — the
   smooth-scroll animation races the rest of the page load and silently
   loses, leaving the browser at the top with no visible error. Overriding
   back to the native instant jump fixes every cross-page #anchor link in
   the app; the cost is same-page anchor nav (project_detail.php's own
   section links) snaps instead of animating, which is a fair trade for
   "the link actually works." */
:root { scroll-behavior: auto; }

/* Neither font the theme ships (IBM Plex Sans, Rubik) has Thai glyphs, so
   without this every Thai string falls back to a system font mid-sentence.
   The theme no longer even loads those two fonts (audit round-2 P2-04 —
   confirmed via network trace that neither was ever actually fetched, since
   Noto Sans Thai covers the Latin text here too), so this comment is really
   about why Noto Sans Thai has to come first, not about those two by name. */
body,
h1,
h2,
h3,
h4,
h5,
h6,
.btn,
.form-control,
table,
input,
select,
textarea,
button {
  font-family: var(--font-stack);
}
body { line-height: 1.6; }

/* Bootstrap 5.3 underlines every link; the theme's chrome is drawn without
   them. Table cells put theirs back, because there they are the affordance. */
a, a:hover { text-decoration: none; }
.data-table td a:hover { text-decoration: underline; }

.p2s-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
  margin: 0 0 12px;
  color: var(--muted);
  font-size: var(--text-sm);
}
.p2s-breadcrumb a { color: var(--primary-dark); font-weight: 600; }
.p2s-breadcrumb svg {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
}
.p2s-breadcrumb span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The theme pins `body` and `.wrapper` to height:100% and clips .wrapper's
   overflow, then relies on template.js to undo it on load:
   $('body, html, .wrapper').css({height:'auto', 'min-height':'100%'}).
   Without that line a page taller than the viewport is simply cut off with no
   way to scroll to the rest. This is that line, as CSS. */
html, body, .wrapper { height: auto; min-height: 100%; }

/* template.js also measured the window to set this height on every load and
   resize. It is a constant: 81px is the theme's own .fixed header offset,
   64px its footer. */
.fixed .content-wrapper { min-height: calc(100vh - 81px - 64px); }

/* No photo to show, so the user's initial stands in. Centred with flex rather
   than line-height: the old version set line-height to the circle's height,
   which only works at that one size — shrinking the circle for the topbar left
   the 64px line-height behind and dropped the letter to the bottom edge. */
.p2s-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 64px; height: 64px; flex: none;
  border-radius: 50%;
  background: var(--primary); color: #fff;
  font-size: 26px; font-weight: 700; line-height: 1;
}

.dropdown-item .menu-icon {
  width: 16px; height: 16px;
  vertical-align: -3px; margin-right: 8px;
  color: var(--muted);
}

/* The user menu was the only thing on the site using animate.css, for one
   animation out of the eighty-odd in its 78KB. This is that one. */
@keyframes p2sFlipInX {
  from { transform: perspective(400px) rotate3d(1, 0, 0, 60deg); opacity: 0; }
  60%  { transform: perspective(400px) rotate3d(1, 0, 0, -10deg); opacity: 1; }
  to   { transform: perspective(400px); }
}
.dropdown-menu.animated.flipInX {
  /* 0.4s read as slow for a menu open (modern norm is ~150-200ms) and was
     the one animation in the app left unguarded by reduced-motion — a 3D
     rotation is worse for vestibular sensitivity than the fades that
     already are guarded (apple-design audit). Trimmed, not removed: the
     flip is still legible at 250ms, just tighter. */
  animation: p2sFlipInX var(--duration-slow) both;
  backface-visibility: visible !important;
}

/* ======================================================================
   Page furniture
   ====================================================================== */

.content > h1 {
  font-size: var(--text-2xl); font-weight: 700; color: var(--text);
  margin: 0 0 1.25rem;
}

/* Headings that break a long page into blocks. */
.content h2.section-title {
  font-size: var(--text-md); font-weight: 700; color: var(--text);
  margin: 2rem 0 1rem;
  padding-left: 10px;
  border-left: 3px solid var(--primary);
}
.content h2.section-title:first-of-type { margin-top: 0; }

.alert {
  padding: 12px 16px; border-radius: var(--radius);
  font-size: var(--text-sm); margin-bottom: 1.5rem;
}
/* theme_primary.css turns both of these into solid red and green banners with
   white text — `.theme-primary .alert-error { background-color: #FF2829
   !important }` — which outranked the plain class and had been winning since the
   theme went in, unnoticed because no screenshot in review happened to have an
   alert on it. Beating an !important takes another one. */
.alert.alert-error {
  background: rgba(220,38,38,0.08) !important;
  color: var(--color-danger) !important;
  border: 1px solid rgba(220,38,38,0.2) !important;
}
.alert.alert-success {
  background: rgba(4,120,87,0.08) !important;
  color: var(--color-success) !important;
  border: 1px solid rgba(4,120,87,0.2) !important;
}
.alert.alert-warning {
  background: rgba(217,119,6,0.1) !important;
  color: var(--color-warning) !important;
  border: 1px solid rgba(217,119,6,0.25) !important;
}
/* Success vs. error was colour-only before this — the classic red/green
   confusion pair, and nothing else told them apart (apple-design audit).
   A background-image icon (not a ::before flex item) so none of the ~80
   call sites' markup — some plain text, some <p>+<ul>, some with a <form>
   inside — needs to change; each icon's colour is baked into its own SVG
   rather than referencing var(), since a data: URI can't read a custom
   property. */
.alert.alert-error, .alert.alert-success, .alert.alert-warning {
  padding-left: 44px;
  background-repeat: no-repeat !important;
  background-position: 14px 13px !important;
  background-size: 18px 18px !important;
}
.alert.alert-error {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b91c1c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='15' y1='9' x2='9' y2='15'/%3E%3Cline x1='9' y1='9' x2='15' y2='15'/%3E%3C/svg%3E") !important;
}
.alert.alert-success {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23047857' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E") !important;
}
.alert.alert-warning {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2392400e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z'/%3E%3Cline x1='12' y1='9' x2='12' y2='13'/%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'/%3E%3C/svg%3E") !important;
}
/* The theme's plain `.alert a { color: #ffffff }` (public/assets/theme/css/
   style.css) was built for its own solid-color banners; against the light
   tinted backgrounds above, a white link is nearly invisible. Reusing each
   variant's own already-legible text color for its links keeps the same
   contrast the surrounding alert text already has, instead of introducing a
   new color that would need its own check. */
.alert.alert-error a { color: var(--color-danger); }
.alert.alert-success a { color: var(--color-success); }
.alert.alert-warning a { color: var(--color-warning); }
.alert a {
  text-decoration: underline;
  font-weight: 600;
}
.alert a:hover { text-decoration-thickness: 2px; }
.alert a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* toast.js promotes a .toast-flash success message (a one-off "action just
   worked" notice, not persistent page context) out of the document flow into
   a self-dismissing corner toast. Without JS it just renders as a normal
   inline banner — this is progressive enhancement, not a requirement.
   Bottom-right rather than top: the app's own fixed header already owns the
   top of the screen, and its rendered height differs by breakpoint in a way
   that's easy to get wrong (a sticky-table-header attempt earlier hit
   exactly this); the bottom corner has no such collision to account for. */
.toast-flash.toast-active {
  position: fixed;
  right: 20px;
  z-index: 1100;
  max-width: min(360px, calc(100vw - 40px));
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
  margin: 0;
  animation: toast-in var(--duration-base) ease-out;
}
.toast-flash.toast-leaving { animation: toast-out var(--duration-base) ease-in forwards; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(8px); }
}
@media (prefers-reduced-motion: reduce) {
  .toast-flash.toast-active, .toast-flash.toast-leaving { animation: none; }
  
  .exec-data-disclosure[open] > *:not(summary),
  .form-section-collapsible[open] > *:not(summary) { animation: none; }
  /* The 3D rotate3d flip is exactly the kind of motion reduced-motion exists
     to remove — swap it for a plain, short opacity fade instead of nothing,
     so the menu still visibly arrives rather than just teleporting in. */
  .dropdown-menu.animated.flipInX { animation: p2sFadeIn 120ms ease-out both; }
}
@keyframes p2sFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ======================================================================
   Cards
   ====================================================================== */

.summary-cards,
.kpi-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); /* 170: five cards fit one row at 1280 (200 left a lone 5th card) */
  gap: 1rem;
  margin-bottom: 1.5rem;
}
/* A grid item defaults to min-width:auto — min-content — so a 1fr track cannot
   shrink below its widest child, and one child wider than the phone silently
   blows the whole grid past the viewport. */
.summary-cards > *,
.kpi-cards > *,
.form-grid > *,
.option-list-grid > * { min-width: 0; }

/* Chart.js hands a canvas a 300px default and only resizes it to a container
   that already has a definite width. On a phone that default was the widest
   thing in the card, and therefore the thing that set the card's width. */
canvas { max-width: 100%; }
.summary-card,
.kpi-card,
.chart-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.5rem;
}
.summary-card-label,
.kpi-card-label { font-size: var(--text-sm); color: var(--muted); margin-bottom: 6px; }
/* Same component under two names from two different build passes — a big
   headline number in a card. Kept both class names (renaming would touch
   every page using either), but a card's number meant the same "this is
   the highlight of this card" regardless of which name built it, so it
   should not have carried a different color by accident (audit P3-03). */
.summary-card-value,
.kpi-card-value { font-size: var(--text-3xl); font-weight: 700; color: var(--card-accent, var(--primary)); }

/* Opt-in colour for a row of these cards, same idea as the exec KPI strip:
   a rule across the top so a row can be aimed at rather than read. Only the
   pages that set a role class below change — --card-accent is unset
   everywhere else, so the other eight pages using .summary-card keep the
   brand purple they have today.
   The value takes the accent rather than the label, because unlike the exec
   strip these numbers were already coloured; this swaps that colour instead
   of adding a second one. Names say what the card means, not what shade it
   is, so a row can be recoloured later without renaming anything. */
.summary-card, .kpi-card { position: relative; }
.summary-card::before,
.kpi-card::before {
  content: "";
  position: absolute; inset: 0 0 auto; height: 3px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--card-accent, var(--primary)); /* every number card carries the bar now — it was on some pages and not others (UI review 2026-10-09) */
}
.summary-card.is-brand, .kpi-card.is-brand { --card-accent: var(--primary); }
.summary-card.is-info, .kpi-card.is-info { --card-accent: var(--primary); }   /* info + activity were blue / teal: a number card is brand-purple unless its colour means something (critique 2026-10-15) */
.summary-card.is-activity, .kpi-card.is-activity { --card-accent: var(--primary); }
.summary-card.is-idle, .kpi-card.is-idle { --card-accent: var(--color-slate); }
.summary-card.is-progress, .kpi-card.is-progress { --card-accent: var(--color-amber-deep); }
.summary-card.is-done, .kpi-card.is-done { --card-accent: var(--color-success); }
/* Reuses --color-amber-strong, which this palette already keeps apart from
   --color-amber-deep precisely to mean "needs attention" rather than
   "in progress". Deliberately close to is-progress and deliberately not it. */
.summary-card.is-attention, .kpi-card.is-attention { --card-accent: var(--color-amber-strong); }
.kpi-card-note { font-size: var(--text-sm); color: var(--muted); margin-top: 4px; }

/* A read-only "here's the whole record" panel — project_detail.php's project
   info block. Same card chrome as .summary-card/.chart-card, but organized
   as small label/value pairs grouped under section headers rather than a
   handful of big KPI numbers, since there are ~15 fields to show at once. */

.project-info-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}
.project-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1rem 1.5rem;
}
.project-info-item { min-width: 0; }
.project-info-item-full { grid-column: 1 / -1; }
.project-info-label { font-size: var(--text-sm); color: var(--muted); margin-bottom: 3px; }
.project-info-value { font-size: var(--text-base); color: var(--text); font-weight: 600; }
.project-info-value-note { font-weight: 400; white-space: pre-line; }
.project-info-section-label {
  grid-column: 1 / -1;
  font-size: var(--text-2xs); font-weight: 700;
  color: var(--muted);
  margin-top: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--border);
}
.project-info-section-label:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* The one thing every visitor to this page actually needs first — is this
   project overdue for contact — used to sit as two fields among 16 equal-
   weight ones. Pulled out to a highlighted strip above the grid instead of
   duplicated there. */
.project-status-strip {
  display: flex; flex-wrap: wrap; gap: 24px;
  padding: 14px 1.5rem;
  margin: -1.5rem -1.5rem 1.5rem;
  background: var(--head-bg);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
.project-status-strip-item { display: flex; flex-direction: column; gap: 4px; }
.project-status-strip-label {
  font-size: var(--text-2xs); font-weight: 700;
  color: var(--muted);
}
.project-status-strip .status-badge { padding: 4px 10px; align-self: flex-start; } /* size from .status-badge, like every other badge */
.project-status-strip-value { font-size: var(--text-md); font-weight: 700; color: var(--text); }

.chart-card { margin-bottom: 1.5rem; }
.chart-card-canvas-wrap { position: relative; height: 220px; }
/* Two ranked-bar chart-cards side by side (co_agents.php's top-10 widgets) — a plain
   grid, not exec-project-grid's asymmetric split, since both cards carry equal weight
   here. Collapses to one column before a phone-width chart gets too cramped to read. */
.p2s-chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
.p2s-chart-row .chart-card { margin-bottom: 0; }
@media (max-width: 767.98px) {
  .p2s-chart-row { grid-template-columns: 1fr; }
}
/* A chart-card's own label, sized down from h2.section-title's page-section
   styling (2rem top margin, a left accent bar) — wrong weight for a heading
   that sits inside an already-boxed card, not between two page sections. */
.p2s-chart-card-title { font-size: var(--text-md); font-weight: 700; color: var(--text); margin: 0 0 var(--space-sm); }
/* A doughnut drawn at 240px looked lost in a full-width card. Capping the card
   fixed that and broke something worse — a half-width card sitting in a row of
   full-width ones. Grow the chart instead. */
.chart-card-canvas-wrap.donut { height: 260px; margin: 0 auto; }

/* ======================================================================
   Executive dashboard
   ====================================================================== */

.exec-dashboard {
  width: 100%;
  max-width: 1440px;
  margin: 0 auto;
  color: var(--text);
}
.exec-dashboard-header,
.exec-domain-header,
.exec-panel-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-lg);
}
.exec-dashboard-header { margin-bottom: var(--space-lg); }
.exec-dashboard-header h1 {
  margin: 0 0 var(--space-2xs);
  color: var(--text);
  font-size: var(--text-2xl);   /* the same page-title size as every other page (was 28px/750 here only) */
  font-weight: 700;
  line-height: 1.3;
}
.exec-dashboard-header p,
.exec-domain-header p,
.exec-panel-header p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
}
.exec-period-nav {
  display: inline-grid;
  grid-template-columns: var(--control-h) minmax(150px, auto) var(--control-h);
  align-items: stretch;
  overflow: hidden;
  flex: 0 0 auto;
  height: var(--control-h);  /* the one control height — 34px, no exceptions */
  box-sizing: border-box;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.exec-period-nav a,
.exec-period-nav span {
  display: flex;
  align-items: center;
  justify-content: center;
}
.exec-period-nav a {
  color: var(--text);
  font-size: 25px; /* ‹/› glyph, deliberately outside the type scale — see :root comment */
  line-height: 1;
}
.exec-period-nav a:first-child { border-right: 1px solid var(--border); }
.exec-period-nav a:last-child { border-left: 1px solid var(--border); }
.exec-period-nav a:hover, .exec-period-nav a:active { background: var(--head-bg); color: var(--primary); }
.exec-period-nav span { padding: 0 var(--space-md); font-size: var(--text-sm); font-weight: 700; }

.exec-kpi-strip {
  display: grid;
  grid-template-columns: minmax(225px, 1.45fr) repeat(5, minmax(125px, 1fr));
  overflow: hidden;
  margin-bottom: var(--space-xl);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.exec-kpi {
  display: flex;
  min-width: 0;
  min-height: 124px;
  padding: 20px var(--space-lg);
  border-right: 1px solid var(--border);
  flex-direction: column;
  justify-content: center;
}
.exec-kpi:last-child { border-right: 0; }
.exec-kpi-featured { background: var(--primary-soft); }

/* No icons, no coloured rules (owner critique 2026-10-17: the tinted icon disc and the colour bar on every cell were decoration that
   read as a template). The number is the content; colour is kept for meaning only — the change line below goes green or red. */
.exec-kpi { position: relative; }
.exec-kpi-label {
  margin-bottom: var(--space-2xs);
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 700;
}
.exec-kpi strong {
  color: var(--text);
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.exec-kpi-featured strong { color: var(--primary-dark); font-size: var(--text-4xl); }
.exec-kpi strong small { color: inherit; font-size: var(--text-sm); font-weight: 600; }
.exec-kpi > span:last-child,
.exec-kpi-change { margin-top: var(--space-2xs); color: var(--muted); font-size: var(--text-sm); }
.exec-kpi-change.is-positive { color: var(--color-success); }
.exec-kpi-change.is-negative { color: var(--color-danger); }

.exec-dashboard-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-lg);
}
.exec-panel {
  min-width: 0;
  padding: var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.exec-panel-wide { grid-column: span 8; }
.exec-panel-attention { grid-column: span 4; }
.exec-panel-seven { grid-column: span 7; }
.exec-panel-five { grid-column: span 5; }
.exec-panel-flat { height: 100%; }
.exec-panel-header { margin-bottom: var(--space-md); }
.exec-panel-header h2,
.exec-panel-header h3 {
  margin: 0 0 2px;
  color: var(--text);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: 1.4;
}
/* Was 16px against the shared rule's 17px — 1px apart is drift, not a
   readable h2/h3 distinction; --text-md gives a real 2px step instead. */
.exec-panel-header h3 { font-size: var(--text-md); }
.exec-panel-total { flex: 0 0 auto; color: var(--primary-dark); font-size: var(--text-base); font-weight: 700; }
.exec-chart { position: relative; height: 250px; }
.exec-chart-primary { height: 320px; }
/* The channel chart is a set of gauges with a legend and a note beneath, not a
   bar chart in a fixed box; a fixed height clipped the legend behind the note. */
.exec-chart-channel { height: auto; min-height: 340px; }
.exec-chart-projects { height: 300px; }
/* A chart with nothing to draw is one short line, not a 250px empty frame (owner critique 2026-10-17). */
.exec-chart-empty,
.exec-empty-line {
  display: flex;
  flex-wrap: wrap;
  height: auto !important;
  min-height: 0;
  margin: 0;
  padding: 10px 12px;
  align-items: baseline;
  gap: 2px 8px;
  border-radius: var(--radius-sm, 6px);
  background: var(--head-bg);
  color: var(--muted);
  font-size: var(--text-sm);
  text-align: left;
}
.exec-chart-empty strong { color: var(--text); font-weight: 600; }

.exec-alert-count {
  display: inline-grid;
  min-width: 28px;
  height: 28px;
  padding: 0 8px;
  place-items: center;
  border-radius: 999px;
  background: var(--color-amber-strong);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 700;
}
.exec-attention-main {
  display: flex;
  margin-bottom: var(--space-md);
  padding: var(--space-md);
  border: 1px solid rgba(217, 119, 6, 0.24);
  border-radius: var(--radius);
  background: rgba(217, 119, 6, 0.08);
  color: #7c3f00;
  flex-direction: column;
}
.exec-attention-main:hover { border-color: #d97706; color: #633300; }
.exec-attention-main > span { font-size: var(--text-sm); font-weight: 700; }
.exec-attention-main strong { margin: 2px 0; font-size: var(--text-3xl); }
.exec-attention-main small { color: inherit; font-size: var(--text-sm); }
.exec-rank-list,
.exec-agenda { margin: 0; padding: 0; list-style: none; }
.exec-rank-list { margin-bottom: var(--space-md); counter-reset: rank; }
.exec-rank-list li {
  display: flex;
  min-height: 35px;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-size: var(--text-sm);
}
.exec-rank-list li span::before {
  content: counter(rank) '. ';
  counter-increment: rank;
  color: var(--muted);
}
.exec-rank-list li strong { color: var(--color-amber-deep); }
.exec-positive-state {
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius);
  background: rgba(4, 120, 87, 0.08);
  color: var(--color-success);
  font-size: var(--text-sm);
}
.exec-agenda-summary {
  display: flex;
  gap: var(--space-lg);
  margin-bottom: var(--space-sm);
  color: var(--muted);
  font-size: var(--text-sm);
}
.exec-agenda-summary strong { color: var(--text); font-size: var(--text-md); }
.exec-agenda li {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: var(--space-sm);
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.exec-agenda time { color: var(--primary-dark); font-size: var(--text-sm); font-weight: 700; }
.exec-agenda li div { min-width: 0; }
.exec-agenda li strong,
.exec-agenda li span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exec-agenda li strong { color: var(--text); font-size: var(--text-sm); }
.exec-agenda li span { color: var(--muted); font-size: var(--text-2xs); }
.exec-panel-link {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  margin-top: var(--space-xs);
  color: var(--primary-dark);
  font-size: var(--text-sm);
  font-weight: 700;
}
.exec-empty { margin: 0; color: var(--muted); font-size: var(--text-sm); }

.exec-domain { margin-top: var(--space-2xl); }
.exec-domain-first { margin-top: 0; }
.exec-domain-header { align-items: flex-end; margin-bottom: var(--space-md); }
.exec-domain-header h2 { margin: 0 0 2px; color: var(--text); font-size: var(--text-xl); font-weight: 700; }
.exec-domain-stats { display: flex; gap: var(--space-lg); color: var(--muted); font-size: var(--text-sm); }
.exec-domain-stats strong { color: var(--text); font-size: var(--text-md); }
.exec-project-grid {
  display: grid;
  grid-template-columns: minmax(280px, 4fr) minmax(0, 8fr);
  gap: var(--space-lg);
}
.exec-data-disclosure { margin-top: var(--space-md); border-top: 1px solid var(--border); }
.exec-data-disclosure-standalone {
  margin-top: var(--space-md);
  padding: 0 var(--space-lg) var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.exec-data-disclosure summary {
  display: flex;
  min-height: 44px;
  align-items: center;
  color: var(--primary-dark);
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 700;
}
.exec-data-disclosure summary:hover, .exec-data-disclosure summary:active { color: var(--primary); }
.exec-data-disclosure summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
.exec-data-disclosure .data-table-wrap { margin: 0; }
.exec-data-disclosure .field-hint { margin: 0 0 var(--space-sm); }

/* Both disclosure patterns in the app (this one and .form-section-collapsible)
   snapped open/closed instantly with zero transition — a native <details>'s
   closed state is `display: none`, which nothing can animate without JS, so
   only the open direction gets a reveal here; closing stays an instant snap,
   same trade-off most non-JS disclosure widgets make (apple-design audit:
   "cheap to make this week's own new UI the most-polished moment"). */
.exec-data-disclosure[open] > *:not(summary),
.form-section-collapsible[open] > *:not(summary) {
  animation: details-reveal var(--duration-base) ease-out;
}
@keyframes details-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
.exec-attention-secondary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
  padding: 10px var(--space-sm);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--head-bg);
  color: var(--muted);
  font-size: var(--text-sm);
}
.exec-attention-secondary strong { color: var(--text); font-size: var(--text-sm); }
.exec-chart-support { height: 330px; }
.exec-progress-state {
  display: inline-flex;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 700;
}
.exec-progress-state.is-on-track { background: rgba(4, 120, 87, 0.09); color: var(--color-success); }
.exec-progress-state.is-behind { background: rgba(217, 119, 6, 0.1); color: var(--color-warning); }

/* The six-across strip is measured against the content column, not the screen:
   the sidebar takes its share (250px, or 70px when collapsed, or none as a
   drawer), so a viewport width is the wrong thing to ask. It was, at 1499px, and
   collapsing the sidebar changed nothing. Now the wrapper is a size container and
   the strip is 3 x 2 whenever the column itself is under 1200px. */
.exec-kpi-wrap { container-type: inline-size; container-name: kpi; }
@container kpi (max-width: 1199px) {
  .exec-kpi-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .exec-kpi:nth-child(3n) { border-right: 0; }
  .exec-kpi:nth-child(-n+3) { border-bottom: 1px solid var(--border); }
}
@media (max-width: 1199.98px) {
  .exec-panel-wide,
  .exec-panel-attention { grid-column: 1 / -1; }
  .exec-panel-attention .exec-rank-list,
  .exec-panel-attention .exec-agenda { max-width: 720px; }
}
@media (max-width: 991.98px) {
  .exec-kpi-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .exec-kpi:nth-child(odd) { border-right: 1px solid var(--border); }
  .exec-kpi:nth-child(even) { border-right: 0; }
  .exec-kpi:nth-child(-n+4) { border-bottom: 1px solid var(--border); }
  .exec-panel-seven,
  .exec-panel-five { grid-column: 1 / -1; }
  .exec-project-grid { grid-template-columns: 1fr; }
}
@media (max-width: 767.98px) {
  .exec-dashboard-header,
  .exec-domain-header { align-items: stretch; flex-direction: column; gap: var(--space-md); }
  .exec-period-nav { width: 100%; grid-template-columns: var(--control-h) 1fr var(--control-h); }
  /* Two per row (the featured figure and the last one span the full width): six cells stacked one per
     row took three screens of scrolling before anything else on the page. */
  .exec-kpi-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .exec-kpi:nth-child(n) { min-height: 96px; padding: 14px var(--space-md); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
  .exec-kpi:nth-child(odd) { border-right: 0; }
  .exec-kpi:first-child, .exec-kpi:last-child { grid-column: 1 / -1; border-right: 0; }
  .exec-kpi:last-child { border-bottom: 0; }
  .exec-kpi-featured strong { font-size: var(--text-3xl); /* mobile size, unified with the h1's own mobile shrink below */ }
  .exec-panel { padding: var(--space-md); }
  .exec-panel-header { gap: var(--space-sm); }
  .exec-panel-total { font-size: var(--text-sm); }
  .exec-chart { height: 230px; }
  .exec-chart-primary { height: 250px; }
  .exec-chart-channel { height: auto; min-height: 350px; }
  .exec-chart-projects { height: 280px; }
  .exec-chart-support { height: 330px; }
  .exec-domain { margin-top: var(--space-xl); }
  .exec-domain-header .exec-panel-link { margin-top: 0; }
  .exec-domain-stats { flex-wrap: wrap; }
  .exec-data-disclosure-standalone { padding: 0 var(--space-md) var(--space-md); }
}
@media (max-width: 575.98px) {
  .exec-panel-header { flex-direction: column; }
  .exec-panel-total { align-self: flex-start; }
  .exec-chart { height: 210px; }
  .exec-chart-primary { height: 230px; }
  .exec-chart-channel { height: auto; min-height: 350px; }
  .exec-chart-support { height: 330px; }
}

/* /admin/settings.php's section cards. */
.settings-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 20px; margin-bottom: 24px;
}
.settings-logo-row {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 0; border-bottom: 1px solid var(--border);
}
.settings-logo-row:last-of-type { border-bottom: none; }
.settings-logo-preview {
  max-width: 120px; max-height: 60px; object-fit: contain;
  background: var(--surface-subtle); border-radius: var(--radius); padding: 4px;
}
.settings-logo-info { flex: 1; min-width: 160px; }
.settings-logo-form { display: flex; align-items: center; gap: 8px; }
.settings-logo-form input[type="file"] { font-size: var(--text-sm); max-width: 200px; }

/* ======================================================================
   Pills and badges
   ====================================================================== */

.status-summary { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 1.5rem; }
/* leads.php: the month nav sits at the left of the list toolbar (2026-10-01, replacing the separate filter panel);
   icon-only ‹ › keep it compact at 390px. */
.list-toolbar .p2s-lead-monthnav { margin-bottom: 0; flex-wrap: nowrap; }
.p2s-lead-monthnav .status-pill { min-width: 40px; text-align: center; }
/* leads.php "ตัวกรอง": one button in the toolbar row opens a small panel (view radios + channel select). Positioned by
   list-menu.js (position:fixed, like .p2s-mobile-sort) so it cannot be pushed off-screen when the row wraps on a phone. */
.search-row .p2s-filter-pop > summary { height: 40px; min-height: 34px; box-sizing: border-box; padding-top: 5px; padding-bottom: 5px; }
.p2s-filter-count {
  display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--primary); color: #fff; font-size: var(--text-2xs); font-weight: 700;
}
@media (max-width: 575.98px) {
  /* icon + count only, so add / search / filter / download share one row on a phone; the aria-label still names the button. */
  .search-row .p2s-filter-pop > summary { padding-left: 10px; padding-right: 10px; }
  .p2s-filter-label { display: none; }
}
.p2s-filter-panel { min-width: 260px; padding: var(--space-sm); }
.p2s-filter-form { display: grid; gap: var(--space-sm); }
.p2s-filter-group { border: 0; margin: 0; padding: 0; display: grid; gap: 2px; }
.p2s-filter-group legend { font-size: var(--text-2xs); font-weight: 600; color: var(--muted); padding: 0; margin-bottom: 2px; }
.p2s-filter-form label { display: flex; align-items: center; gap: 8px; margin: 0; min-height: 36px; font-size: var(--text-sm); }
.p2s-filter-form .p2s-filter-field { display: grid; gap: 4px; min-height: 0; font-size: var(--text-2xs); font-weight: 600; color: var(--muted); }
.p2s-filter-form .p2s-filter-field select { width: 100%; font-size: var(--text-sm); font-weight: 400; color: var(--text); }
.p2s-filter-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: var(--space-2xs); }
/* Sized to match .status-btn/.status-badge (below) — a filter/toggle chip
   and a row's status indicator used to read as two different sizes of the
   same idea (2026-09-24: "เปลี่ยน status-pill เป็น status-btn แทนทั้งหมดเพราะ
   status-pill ดูใหญ่และสูงเกิน"). Kept as its own class rather than actually
   merged into .status-btn: .active/:hover and the trailing count span below
   are specific to a filter toggle and have no equivalent on a plain status
   badge, so reusing that selector directly would mean re-deriving them
   under a new name anyway. */
/* Chips carry Velzon's "badge rounded-pill" on top of this class (owner 2026-10-06: status-pill -> rounded-pill everywhere). Velzon's
   .badge would make them white-on-nothing 10px text with a .95 line-height; this rule, loaded later, keeps the chip's own colours, size
   and tap height. The selected chip is a solid pill (like the badges), the others a quiet outline. */
.status-pill {
  display: inline-block;
  padding: 4px 10px; border-radius: 999px;
  font-size: var(--text-2xs); font-weight: 400; line-height: 1.5;
  border: 1px solid var(--border); color: var(--muted);
  background: var(--surface);
}
.status-pill:hover, .status-pill:active { box-shadow: var(--shadow-card); }
.status-pill.active {
  border-color: var(--primary); background: var(--primary); color: #fff;
}
.status-pill span { margin-left: 4px; } /* the count inherits the chip's text colour (it was opacity .7 once: 2.7:1 on a tint) */

/* Badges (owner 2026-10-06): Velzon's "Rounded Pill Badges", "badge rounded-pill bg-*", with white text, on every coloured status / type /
   grade / role / reason. src/badges.php (badge_classes) and the two JS sites (bu-projects.js, projects-map.js) put these classes on the
   element together with the app's own type token (lead-status-booked, grade-A ...) and a sizing hook (.status-badge / .status-btn /
   .followup-reason). Velzon (public/assets/velzon, loaded on every signed-in page) supplies .badge, .rounded-pill and white badge text.
   The palette holds TWENTY distinct hues, so one family (the 14 lead statuses, say) gives every member its own colour with room to
   grow: the first ten are the owner's colour set (2026-10-06), the other ten were added to match them and clear 4.5:1 under white text.
   2026-10-09 (UI audit + owner): the owner's lighter colours (secondary, sky, info, warning, danger, success, lime, gray) were
   1.7-4.2:1 under white text; they are now darker shades of the same families so EVERY badge clears 4.5:1, and any two hues are
   still at least 19 apart (CIE76). Gray is a cool dark gray so it stays apart from the warm --hue-stone. */
:root {
  --hue-secondary: #496ce5;
  --hue-primary: #435185;
  --hue-sky: #277ab6;
  --hue-info: #1176d3;
  --hue-warning: #966f0c;
  --hue-danger: #c45139;
  --hue-dark: #222529;
  --hue-success: #038833;
  --hue-lime: #478400;
  --hue-gray: #4f4f61;
  --hue-violet: #7c3aed;
  --hue-fuchsia: #c026d3;
  --hue-pink: #d6336c;
  --hue-rose: #be123c;
  --hue-orange: #c2570c;
  --hue-brown: #7c2d12;
  --hue-teal: #0e7490;
  --hue-olive: #6b7a1a;
  --hue-stone: #78716c;
  --hue-plum: #86198f;
  /* the same hues darkened to >= 5:1 on white, for outline text (the filter chips) */
  --ink-secondary: #3e69cf;
  --ink-primary: #435185;
  --ink-sky: #4b7488;
  --ink-info: #3b74a0;
  --ink-warning: #876b36;
  --ink-danger: #ac553e;
  --ink-dark: #222529;
  --ink-success: #007f3f;
  --ink-lime: #5f7700;
  --ink-gray: #6f6f6f;
  --ink-violet: #7c3aed;
  --ink-fuchsia: #b824cb;
  --ink-pink: #cb3067;
  --ink-rose: #be123c;
  --ink-orange: #b4510b;
  --ink-brown: #7c2d12;
  --ink-teal: #0e7490;
  --ink-olive: #687619;
  --ink-stone: #746e69;
  --ink-plum: #86198f;
}
.badge.bg-secondary { background-color: var(--hue-secondary) !important; color: #fff !important; }
.badge.bg-primary { background-color: var(--hue-primary) !important; color: #fff !important; }
.badge.bg-sky { background-color: var(--hue-sky) !important; color: #fff !important; }
.badge.bg-info { background-color: var(--hue-info) !important; color: #fff !important; }
.badge.bg-warning { background-color: var(--hue-warning) !important; color: #fff !important; }
.badge.bg-danger { background-color: var(--hue-danger) !important; color: #fff !important; }
.badge.bg-dark { background-color: var(--hue-dark) !important; color: #fff !important; }
.badge.bg-success { background-color: var(--hue-success) !important; color: #fff !important; }
.badge.bg-lime { background-color: var(--hue-lime) !important; color: #fff !important; }
.badge.bg-gray { background-color: var(--hue-gray) !important; color: #fff !important; }
.badge.bg-violet { background-color: var(--hue-violet) !important; color: #fff !important; }
.badge.bg-fuchsia { background-color: var(--hue-fuchsia) !important; color: #fff !important; }
.badge.bg-pink { background-color: var(--hue-pink) !important; color: #fff !important; }
.badge.bg-rose { background-color: var(--hue-rose) !important; color: #fff !important; }
.badge.bg-orange { background-color: var(--hue-orange) !important; color: #fff !important; }
.badge.bg-brown { background-color: var(--hue-brown) !important; color: #fff !important; }
.badge.bg-teal { background-color: var(--hue-teal) !important; color: #fff !important; }
.badge.bg-olive { background-color: var(--hue-olive) !important; color: #fff !important; }
.badge.bg-stone { background-color: var(--hue-stone) !important; color: #fff !important; }
.badge.bg-plum { background-color: var(--hue-plum) !important; color: #fff !important; }

/* Sizing for the badge hooks. The colours come from the classes above, never from these rules. Velzon's .badge is .75em (about 10px
   here), too small for Thai at a glance, so the hooks set the size the app has always used, with line-height 1.5 (owner; Velzon's .badge is .95). */
.status-btn, .status-badge {
  display: inline-block;
  padding: 4px 10px; border: 0; /* padding 4px 10px (owner); a <button> would otherwise keep the browser's 2px border */
  font-size: var(--text-2xs); font-weight: 400; line-height: 1.5; /* normal weight and line-height 1.5 (owner); Velzon's .badge is 600 / .95 */
}
.status-badge { cursor: default; }
.status-btn { cursor: pointer; }
.status-btn:hover { filter: brightness(.92); }
.followup-reason { font-size: var(--text-2xs); font-weight: 400; line-height: 1.5; padding: 4px 10px; } /* same size as .status-badge */
.grade-none { font-size: var(--text-sm); color: var(--muted); }

/* The filter chips that carry a type token (.status-pill.lead-status-*, .status-pill.status-*) are not badges: a row of fourteen
   solid pills would shout, so they stay an outline in the hue the token maps to in src/badges.php (badge_hue_map — keep in step). */
.status-none { --b: var(--hue-gray); --ink: var(--ink-gray); }
.status-contact { --b: var(--hue-warning); --ink: var(--ink-warning); }
.status-done { --b: var(--hue-success); --ink: var(--ink-success); }
.status-reject { --b: var(--hue-danger); --ink: var(--ink-danger); }
.status-closed { --b: var(--hue-dark); --ink: var(--ink-dark); }
.lead-status-new { --b: var(--hue-gray); --ink: var(--ink-gray); }
.lead-status-callback { --b: var(--hue-secondary); --ink: var(--ink-secondary); }
.lead-status-no_answer { --b: var(--hue-primary); --ink: var(--ink-primary); }
.lead-status-unavailable { --b: var(--hue-orange); --ink: var(--ink-orange); }
.lead-status-info_sent { --b: var(--hue-info); --ink: var(--ink-info); }
.lead-status-interested { --b: var(--hue-pink); --ink: var(--ink-pink); }
.lead-status-appointment_set { --b: var(--hue-violet); --ink: var(--ink-violet); }
.lead-status-booked { --b: var(--hue-success); --ink: var(--ink-success); }
.lead-status-not_interested { --b: var(--hue-danger); --ink: var(--ink-danger); }
.lead-status-closed { --b: var(--hue-dark); --ink: var(--ink-dark); }
.lead-status-undecided { --b: var(--hue-warning); --ink: var(--ink-warning); }
.lead-status-transferred { --b: var(--hue-lime); --ink: var(--ink-lime); }
.lead-status-loan_rejected { --b: var(--hue-rose); --ink: var(--ink-rose); }
.lead-status-booking_cancelled { --b: var(--hue-stone); --ink: var(--ink-stone); }
.status-pill:is([class*="lead-status-"], .status-none, .status-contact, .status-done, .status-reject, .status-closed):not(.active):not(.is-active) {
  border-color: var(--b); color: var(--ink); background: transparent;
}
.status-pill:is([class*="lead-status-"], .status-none, .status-contact, .status-done, .status-reject, .status-closed):is(.active, .is-active) {
  border-color: var(--b); background: var(--b); color: #fff;
}

/* ======================================================================
   Buttons and row actions
   ====================================================================== */

a.btn-primary, button.btn-primary, summary.btn-primary,
a.btn-secondary, button.btn-secondary, summary.btn-secondary {
  /* One height for every button (owner 2026-10-06): 34px, label centred; a label that wraps still grows it.
     align-self: center keeps it from stretching to a neighbouring 44px input in a flex row. */
  display: inline-flex; align-items: center; justify-content: center; align-self: center;
  box-sizing: border-box; min-height: 34px;
  padding: 5px 18px; border-radius: var(--radius);
  font-size: var(--text-base); font-weight: 600;
  border: 1px solid transparent; cursor: pointer;
  /* :active alone (no transition on the base rule) means the scale-down
     below applies instantly on press with no lag, exactly the "respond on
     pointer-down" behaviour touch needs — a transition here would delay the
     very feedback :active exists to give. */
  transition: transform var(--duration-fast) ease-out;
}
a.btn-primary, button.btn-primary, summary.btn-primary {
  background: var(--primary); border-color: var(--primary); color: #fff;
}
a.btn-primary:hover, button.btn-primary:hover, summary.btn-primary:hover {
  background: var(--primary-dark); border-color: var(--primary-dark); color: #fff;
}
/* :active fires on touchstart, unlike :hover which never fires on a touch
   device at all — this is the only feedback a mobile field agent gets that
   their tap registered (business-adoption audit: apple-design review). */
a.btn-primary:active, button.btn-primary:active, summary.btn-primary:active {
  background: var(--primary-dark); border-color: var(--primary-dark); color: #fff;
  transform: scale(0.97);
}
a.btn-secondary, button.btn-secondary, summary.btn-secondary {
  /* The outline is --border-strong, not the faint --border: next to a filled .btn-primary a barely-visible outline made the grey button look
     2px shorter than the purple one although both are exactly --control-h (owner 2026-10-15). */
  background: var(--surface); color: var(--muted); border-color: var(--border-strong);
}
a.btn-secondary:hover, button.btn-secondary:hover { border-color: var(--muted); color: var(--text); }
a.btn-secondary:active, button.btn-secondary:active {
  border-color: var(--muted); color: var(--text); background: var(--head-bg);
  transform: scale(0.97);
}
/* An icon-only .btn-secondary — the CSV download button app-wide (2026-09-24:
   "เป็นรูป download ไม่ต้องมีคำอธิบาย ... ลดความสูงของปุ่ม"), replacing its text label
   with just an icon. Sized to match .p2s-view-switch-btn's own compact icon
   footprint (40px, 18px glyph) rather than the wide text-padded default. */
a.btn-secondary.btn-icon-only, button.btn-secondary.btn-icon-only, summary.btn-secondary.btn-icon-only {
  min-width: 34px; padding: 5px; flex: none;
}
a.btn-secondary.btn-icon-only .p2s-action-icon, button.btn-secondary.btn-icon-only .p2s-action-icon,
summary.btn-secondary.btn-icon-only .p2s-action-icon {
  width: 18px; height: 18px;
}
/* The sort menu's own icon button (below) turns primary-colored once a
   non-default sort is applied — a hint of what is active without opening
   the menu, since the select box that used to show the choice as text is
   gone now. */
summary.btn-secondary.btn-icon-only.is-active { border-color: var(--primary); color: var(--primary); }
/* The "+ เพิ่ม..." toolbar button, icon-only and green (2026-09-24: "ปุ่มเพิ่ม
   ขอให้ใช้เป็น icon แทน ... และใส่สีเขียว") — reuses --color-success, the same
   token every "approve/activate"-type action already tints green with, so
   this reads as one more member of that accent family rather than a new
   one-off color. Icon only, no colored border: a saturated green stroke
   around the same 40px box as download/filter's plain gray one drew more
   visual weight and read as a bigger button even at an identical size
   (2026-09-24: "เอา stroke ออก มันทำให้ดูขนาดใหญ่กว่าปุ่มอื่น") — the default
   .btn-secondary border (var(--border)) is inherited as-is. */
a.btn-secondary.btn-icon-only.is-add { color: var(--color-success); }
/* leads.php's CSV icon button when a restricted role has used up today's
   export quota (csv_menu_item()) — a <span>, not a link, since there is
   nowhere to navigate; the caption/quota badge that used to be visible menu
   text lives in its title attribute instead. */
span.btn-secondary.btn-icon-only[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

/* The "+ add" button and a name search sit on one row — search-row's
   margin-left:auto keeps it pinned right whether or not the button is
   present (a role without add rights still gets to search). */
.list-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 14px 0; }
.list-toolbar p { margin: 0; }
.search-form { display: flex; gap: 8px; }
.search-form input[type="search"] {
  padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
  min-width: 220px; height: 40px;
}
/* The search box and its "ค้นหา" button rendered ~44px tall, a few pixels
   taller than the 40px icon buttons (download/filter/+) beside them in
   .search-row — "ช่อง search และปุ่มค้นหา ขอความสูงที่ 40 ให้เท่ากับปุ่มอื่นๆ ทำทุกหน้า".
   The button needs inline-flex centering, not just a height override: its
   base .btn-secondary rule is display:inline-block, which centers text
   vertically via padding/line-height math, not a fixed box. */
.search-form button[type="submit"] { height: 40px; display: inline-flex; align-items: center; justify-content: center; }
/* Pairs a .search-form with the CSV download button beside it so they stay on
   one row (2026-09-24, "ขอทำให้อยู่แถวเดียวกัน ปุ่ม ดาวน์โหลด CSV") — margin-left:auto
   used to sit on .search-form itself, so once that form went full-width on a
   phone (below) it pushed the CSV button onto a line of its own. Now the
   WRAPPER goes full-width instead and the form just shrinks to share it.
   flex-wrap: on the narrowest phones .p2s-mobile-sort (below) is a third
   item in this same row — wrap lets it drop to a line of its own rather
   than overflow when the search box has already shrunk as far as it can. */
.search-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.list-toolbar .search-row { margin-left: auto; }
.search-row .search-form { flex: 1 1 auto; min-width: 0; }
@media (max-width: 575.98px) {
  /* The bare .search-form rule further down floors the search box at 200px
     (sized for Marketing's page, which pairs a channel select + button on
     the same line) — inside .search-row, with the add/download/filter icon
     buttons also sharing the row, that floor left no room for them and
     wrapped the icons onto a line of their own even though each is only
     40px (2026-09-24: "ลดขนาดช่อง search ให้สั้นลงเพื่อให้ download และ filter
     จะขึ้นไปอยู่แถวเดียวกัน ทำทุกหน้า"). A smaller floor here — scoped to
     .search-row so Marketing's own search-form is untouched — still leaves
     the box tappable while letting the whole row actually fit together.
     The FORM itself also needs an explicit (not auto) flex-basis: as a flex
     container in its own right, "auto" resolved to its max-content size for
     the purposes of .search-row's own wrap decision, which ignored the
     smaller basis set on the input inside it and wrapped anyway. */
  .search-row .search-form { flex: 1 1 168px; min-width: 0; }
  .search-row .search-form input[type="search"] { flex: 1 1 90px; min-width: 90px; }
}

/* Row actions that submit rather than navigate, styled to match the links
   beside them so the action column reads as one list. */
.inline-form { display: inline; }
.inline-form > .link-btn, .inline-form > button { vertical-align: middle; }
.link-btn {
  /* Vertical padding only — a boxed hit area, not a boxed look. At the
     inherited 13px/1.6 line-height this read as a ~21px-tall click target,
     under WCAG 2.2 AA's 24px minimum; buttons are inline-block by default
     so padding genuinely grows the hit area without adding a visible border
     or background. */
  background: none; border: none; padding: 4px 0; margin-left: 10px;
  font: inherit; color: var(--primary); cursor: pointer; text-decoration: underline;
}
.link-btn:hover, .link-btn:active { color: var(--primary-dark); }
.link-btn.danger { color: var(--color-danger); }
/* ตั้งประจำเดือน (feature) is neither a plain edit-family action (--primary) nor a
   removal (--color-danger) — its own accent keeps it visually distinct from both. */
.link-btn.feature { color: var(--color-amber-strong); }
.link-btn:disabled { color: var(--muted); cursor: not-allowed; text-decoration: none; }

/* Action-type accent colors, on top of the default/.danger/.feature above —
   the same currentColor-driven pill below tints and outlines itself from
   whichever of these wins, so adding a category here never needs a matching
   change to the pill rule itself. Two hooks, both already present in the
   markup for every action that isn't a plain edit/save/view (the common
   case, left at the default --primary): the p2s_icon() name via
   data-icon (src/icons.php), and a submit button's own name="action"
   value="..." — prefix-matched (^=) because some pairs share a suffix
   (activate/deactivate, feature/unfeature) that a suffix or substring match
   would confuse. Order here matters: .danger/.feature above already cover
   delete/reject/unfeature, so nothing below needs to repeat them (2026-09-24,
   owner: "ให้ใช้ class=badge style แทน class ปุ่ม action ต่างๆตั้งหมดทุกหน้า"). */
.link-btn:has(svg[data-icon="check"]),
.link-btn[value^="approve"], .link-btn[value^="accept"], .link-btn[value^="activate"] {
  color: var(--color-success);
}
.link-btn[value^="deactivate"] { color: var(--color-warning); }
.link-btn:has(svg[data-icon="calendar"]) { color: color-mix(in srgb, var(--color-blue) 80%, #000); }   /* darker: 4.3:1 on its tint was under AA (audit 2026-10-16) */
.link-btn:has(svg[data-icon="chart"]) { color: var(--color-teal-deep); }
.link-btn:has(svg[data-icon="share"]) { color: var(--color-pink); }
.link-btn:has(svg[data-icon="clock"]) { color: var(--color-slate); }

/* src/icons.php's p2s_icon(): sits right before the action word it labels
   (แก้ไข/ลบ/เปิด-ปิดการใช้งาน/ตั้งประจำเดือน), inside whatever link or button
   already carries the color (plain table link, .link-btn.danger/.feature). */
.p2s-action-icon { width: 14px; height: 14px; flex: none; vertical-align: -2px; }

/* The pill shown in chat and approved: an action reads as a soft-tinted,
   fully-rounded chip rather than a bare underlined link — tint and text
   both come from whatever color the link/button already resolves to
   (default primary, .danger, .feature, or one of the action-type accents
   above), so this never needs its own per-action color list. Originally
   scoped to icon-labelled actions only; every action already carries
   .link-btn, so the same pill now applies there directly too (2026-09-24
   — domain-specific verbs no longer stay text-only, superseding that
   choice). Excluded from .btn-secondary/.btn-primary, which already have
   their own bordered-button chrome; doubling that with a pill shape and a
   tint fill would read as two different button languages on the same
   element. */
.link-btn:not(.btn-secondary):not(.btn-primary),
a:has(> .p2s-action-icon):not(.btn-secondary):not(.btn-primary),
button:has(> .p2s-action-icon):not(.btn-secondary):not(.btn-primary):not(.p2s-star-btn) {
  /* min-width + centered content: action labels range from 1 Thai
     character (ลบ) to 6+ (บันทึกผลชม), and with a fully-rounded pill
     shape that width swing reads as wildly different SIZES sitting side
     by side — a short label collapses toward a near-circle next to a
     long one's stadium shape (owner screenshot, "ใหญ่เล็กดูตลกมาก",
     2026-09-24). A shared floor doesn't erase the swing (truncating or
     stretching text to force identical widths would look worse), but it
     keeps every short action from reading as a stub. */
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-width: 76px;
  margin: 2px 6px 2px 0; padding: 5px 12px 5px 10px;
  border-radius: 999px; background: color-mix(in srgb, currentColor 10%, transparent);
  text-decoration: none;
}
.link-btn:not(.btn-secondary):not(.btn-primary):hover,
a:has(> .p2s-action-icon):not(.btn-secondary):not(.btn-primary):hover,
button:has(> .p2s-action-icon):not(.btn-secondary):not(.btn-primary):not(.p2s-star-btn):hover {
  background: color-mix(in srgb, currentColor 18%, transparent);
}

/* ======================================================================
   Tables
   ====================================================================== */

/* The table sits bare in the page body, so ITS WRAPPER carries the card and
   the scrolling — not the table itself.
   Putting display:block + overflow-x:auto directly on <table> (the previous
   approach) breaks the table's own width algorithm: with thead/tbody forced
   back to table-header-group/row-group, the browser has to fabricate an
   anonymous table box to lay them out, and that anonymous box sizes itself to
   its content instead of the 100% declared on the real (now block) <table>
   element. A table with only a few short header labels — or an empty-state
   row — then renders far narrower than the card around it, looking like a
   truncated header. A plain wrapper div sidesteps this entirely: the table
   stays a real <table> (auto layout stretches it to fill the wrapper exactly
   like a normal 100%-wide table does), and the wrapper is what scrolls when
   content is too wide for .wrapper's own overflow-x:hidden to allow — at
   768px the leads table used to lose four columns, status among them, with
   nothing to scroll. */
.data-table-wrap {
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  margin-bottom: 1.5rem;
}
/* A table that is really a list of records (users, leads, visits: on a phone): each
   row becomes a card with a label beside every value, so nothing sits off-screen to
   the right. Opt in with class="data-table-cards" on the <table>; shell.js reads the
   headers to label each cell, titles the card with the first column that is not
   "No.", and treats a column with an empty header as the actions. Not for tables
   whose headers sort (the header row is hidden here) or that are wider than about ten
   columns (a card that long stops being scannable). */
@media (max-width: 575.98px) {
  .data-table-cards, .data-table-cards tbody, .data-table-cards tr, .data-table-cards td { display: block; }
  .data-table-cards thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  /* Each row is its own card (background/border/radius/shadow, like
     .p2s-contact-card) with a gap to the next one, rather than one flat list
     of rows sharing the wrap's single box — a row-by-row stack of thin
     divider lines read as one undifferentiated block on a phone (2026-09-24). */
  .data-table-wrap:has(.data-table-cards) { background: none; border: 0; box-shadow: none; }
  .data-table-cards tr {
    padding: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-sm);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-card);
  }
  .data-table-cards tr:last-child { margin-bottom: 0; }
  /* table.data-table-cards, not .data-table-cards alone: the shared
     `.data-table th, .data-table td { border-bottom: 1px solid ... }`
     desktop rule has the SAME specificity as a bare `.data-table-cards td`
     and is declared later in this file, so it was winning even here — every
     non-actions field carried a stray border-bottom, invisible against the
     old flat-list look but a visible extra line once each row became its
     own boxed card (found on site_visits.php, 2026-09-24, but this was
     already true for every data-table-cards table). The `table` type
     selector is what pushes this rule's specificity above the desktop
     one, not source order — moving this block later would not fix it. */
  table.data-table-cards td {
    display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
    padding: 5px 0; border: 0; text-align: right; white-space: normal; overflow-wrap: anywhere;
  }
  /* A <select> (bu/projects.php's "เจ้าของ (BU)" owner picker) has no width of
     its own in a flex row — a flex item's default min-width:auto floors it at
     its widest OPTION's text, which can be wider than the card itself, and
     the box just runs off the right edge (2026-09-24, "ปุ่มเจ้าของ BU ล้นตาราง").
     min-width:0 lets it actually shrink to the space justify-content:
     space-between leaves it beside the ::before label. */
  .data-table-cards td .inline-form, .data-table-cards td select {
    min-width: 0; max-width: 100%;
  }
  .data-table-cards td[data-label]::before {
    content: attr(data-label); flex: 0 0 auto; max-width: 45%;
    color: var(--muted); font-size: var(--text-sm); font-weight: 600; text-align: left;
  }
  /* shell.js marks the cell that titles the card (running text, no label) and hides
     the "No." row-sequence column, which means nothing in a stack of cards. */
  /* The row is a column so the title can be moved to the top even when the page
     picks a title that is not the first column (order: -1). */
  .data-table-cards tr { display: flex; flex-direction: column; }
  .data-table-cards td.card-title { order: -1; display: block; text-align: left; font-weight: 700; padding-bottom: 8px; }
  .data-table-cards td.card-title::before { display: none; }
  .data-table-cards td.card-no { display: none; }
  .data-table-cards td.data-table-actions {
    display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: 8px 16px;
    margin-top: 6px; padding-top: 10px; border-top: 1px solid var(--border);
  }
  .data-table-cards td.data-table-actions .inline-form { display: contents; }
}

/* bu/projects.php's main table sorts by clicking its headers, which the card layout
   above hides — this replaces that with an explicit control, shown on phones only
   (same breakpoint as data-table-cards; a mouse still has the header links). Lives
   inside .search-row, beside the CSV download button (2026-09-24: "ตรง การจัดเรียง
   ใช้ icon นี้แทน วางข้างๆ download"). A <select> here was still its own visible box
   next to the icon — "ขอเป็น action บนปุ่มแทนเพื่อไม่ให้รก UI/UX" replaced it with a
   details/summary popover instead, the same .p2s-more component (and its
   list-menu.js click-outside/Escape close) the toolbar's "เพิ่มเติม" menu already
   uses: just an icon button until tapped, nothing sitting in the row unopened. */
.p2s-mobile-sort { display: none; }
@media (max-width: 575.98px) {
  .p2s-mobile-sort { display: block; }
}
.p2s-more-item[aria-current="true"] { color: var(--primary); font-weight: 700; background: var(--primary-soft); }

/* Mirrors .p2s-mobile-sort's pattern: data-table-cards hides <thead> below
   576px, taking a table's "select all" checkbox out of reach on a phone.
   This sits above the table, visible only at that same breakpoint. */
.p2s-mobile-select-all { display: none; }
@media (max-width: 575.98px) {
  .p2s-mobile-select-all { display: flex; align-items: center; gap: 8px; padding: 8px 0; font-size: var(--text-sm); }
}

/* A budget tick can pass right through a short bar's label (a channel with nothing
   spent has its "0" at the far left, where a small budget's tick sits). A white
   halo round the text cuts the tick out behind it so the figure stays readable.
   ApexCharts' own label plate cannot do this: it uses one colour for plate and text. */
#chartChannelSummary .apexcharts-datalabel {
  paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round;
}

/* Injected by shell.js above a card that scrolls sideways. */
.p2s-scroll-hint { margin: 0 0 6px; text-align: right; font-size: var(--text-sm); color: var(--muted); }
/* On a phone the hint is inserted after the first paint, which pushed the table
   down (layout shift 0.06-0.22 on a page with four tables). So there it takes no
   room of its own: a zero-height line whose text overflows into a gap that is
   already reserved above any table of four or more columns (the ones that
   overflow a phone). display:flow-root stops the empty box's margins collapsing
   through it, which would swallow the gap the text sits in. Tables that fit keep
   the gap as ordinary spacing. */
@media (max-width: 575.98px) {
  .p2s-scroll-hint { display: flow-root; height: 0; margin: 0; overflow: visible; line-height: 1.4; }
  .p2s-scroll-hint[hidden] { display: none; }
  .data-table-wrap:has(thead th:nth-child(4)) { margin-top: 28px; }
}
.data-table {
  width: 100%;
  border-collapse: separate; border-spacing: 0;
}
.data-table th,
.data-table td {
  padding: 12px 16px; text-align: left;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-sm); vertical-align: middle;
}
.data-table thead th {
  background: var(--head-bg); color: var(--muted);
  font-size: var(--text-2xs); font-weight: 700;
  white-space: nowrap; /* no uppercase / letter-spacing: it only shouted the English words (LEAD, SUPPORT) and spaces out Thai */
}
/* Zebra-striping (2026-09-24): a real, reusable .table-striped class (the
   theme's Bootstrap build only ever shipped the --bs-table-striped-bg
   *variable*, never the rule that uses it) — apply it to any <table> to
   stripe its <tbody> rows, and .data-table carries it by default so every
   existing table stripes with no markup changes. --head-bg is already the
   app's own subtle gray (used for the header row), reused here rather than
   inventing a second "faint gray" token. Desktop only (min-width: 576px):
   data-table-cards turns each row into its own boxed mobile card below
   that width, where alternating a background color would fight the card's
   own surface/border/shadow rather than read as a table. */
@media (min-width: 576px) {
  .table-striped > tbody > tr:nth-of-type(odd),
  .data-table > tbody > tr:nth-of-type(odd) {
    background: var(--head-bg);
  }
}
/* sortable_column_arrow() (src/pagination.php): a fixed-width slot for the
   ▲/▼, present (empty) even on an unsorted column — without a reserved
   width, a header's own rendered width changed the moment a click added or
   removed the arrow character, which shifted every column after it
   sideways on every single click. */
.p2s-sort-arrow { display: inline-block; width: 0.75em; }

/* bu/developers.php: a plain (auto-layout) table sizes each column to its
   widest visible cell, so sorting or searching to a different set of rows —
   a page full of long BU names in "เพิ่มโดย", or a long developer name —
   changed that column's width and shifted every column after it sideways.
   Fixed widths (summing to 100%) make every column's width depend only on
   the table's own width, never on which rows happen to be on screen; the
   two free-text columns get overflow-wrap so one long unbroken word (an
   English company name) wraps inside its column instead of overflowing it. */
/* table-layout:fixed distributes its % widths across whatever width the
   table is actually given — and .data-table is width:100% of its
   .data-table-wrap, which on a phone is the full (narrow) viewport. With no
   floor, the table just shrank to fit instead of overflowing, so every
   column (and the header text inside it) got crushed to a handful of
   pixels — the exact "one character per line" look reported on production.
   A min-width forces the table to stay wide enough to read; .data-table-wrap's
   existing overflow-x:auto (and its scroll hint) takes over once the
   viewport is narrower than that, same as any other wide data-table already
   does without table-layout:fixed. */
.p2s-developers-table { table-layout: fixed; min-width: 700px; }
.p2s-developers-table th:nth-child(1), .p2s-developers-table td:nth-child(1) { width: 4%; }
.p2s-developers-table th:nth-child(2), .p2s-developers-table td:nth-child(2) { width: 22%; overflow-wrap: anywhere; }
.p2s-developers-table th:nth-child(3), .p2s-developers-table td:nth-child(3) { width: 12%; }
.p2s-developers-table th:nth-child(4), .p2s-developers-table td:nth-child(4) { width: 10%; }
.p2s-developers-table th:nth-child(5), .p2s-developers-table td:nth-child(5) { width: 16%; overflow-wrap: anywhere; }
.p2s-developers-table th:nth-child(6), .p2s-developers-table td:nth-child(6) { width: 8%; }
.p2s-developers-table th:nth-child(7), .p2s-developers-table td:nth-child(7) { width: 28%; }
/* sales/agent_companies.php: same fixed-width fix as .p2s-developers-table
   above, now that this table sorts too. Dropped data-table-cards when
   sorting was added — the mobile card layout explicitly assumes a table
   without sortable headers (its own comment above says so: the header row
   is hidden there, and this table's own fixed % widths leaked into the
   card view's flex layout besides), so it now scrolls on a phone like
   bu/developers.php instead.
   The table's global .data-table thead th is white-space:nowrap (so a
   header's own width never changes) — fine when an overflowing header
   bleeds into an *empty* neighbor (developers.php), but here every header
   has real text, so an overflowing "CO-AGENT" visibly overlapped
   "สถานะอนุมัติ" next to it. Headers here wrap instead; actions gets the
   same generous 28% developers.php gives the identical แก้ไข/อนุมัติ/
   ปฏิเสธ/ปิดการใช้งาน button set, or its Thai text wraps one character per
   line in a cramped column. */
.p2s-agent-companies-table { table-layout: fixed; min-width: 820px; }
.p2s-agent-companies-table thead th { white-space: normal; }
.p2s-agent-companies-table th:nth-child(1), .p2s-agent-companies-table td:nth-child(1) { width: 4%; }
.p2s-agent-companies-table th:nth-child(2), .p2s-agent-companies-table td:nth-child(2) { width: 17%; overflow-wrap: anywhere; }
.p2s-agent-companies-table th:nth-child(3), .p2s-agent-companies-table td:nth-child(3) { width: 8%; overflow-wrap: anywhere; }
.p2s-agent-companies-table th:nth-child(4), .p2s-agent-companies-table td:nth-child(4) { width: 15%; overflow-wrap: anywhere; }
.p2s-agent-companies-table th:nth-child(5), .p2s-agent-companies-table td:nth-child(5) { width: 12%; }
.p2s-agent-companies-table th:nth-child(6), .p2s-agent-companies-table td:nth-child(6) { width: 11%; }
.p2s-agent-companies-table th:nth-child(7), .p2s-agent-companies-table td:nth-child(7) { width: 9%; }
.p2s-agent-companies-table th:nth-child(8), .p2s-agent-companies-table td:nth-child(8) { width: 24%; }
/* data-table-cards restored on both tables above (2026-09-24: "ใช้เป็น card
   style เหมือน packages.php") — the same conflict the 1447-comment above
   documents dropping it for, this time actually fixed instead of worked
   around: table-layout:fixed + min-width + per-column % widths are
   desktop-only alignment tricks, and the nth-child width rules have higher
   specificity than data-table-cards' own td reset, so on a phone they were
   still forcing each field's flex row to a fixed desktop column width
   (visible as fields overlapping/wrapping mid-card). Reset to the card
   layout's own natural sizing below the same 575.98px breakpoint
   data-table-cards itself uses — thead's fixed widths need no reset in
   turn, since data-table-cards already hides <thead> entirely there. */
@media (max-width: 575.98px) {
  .p2s-developers-table.data-table-cards,
  .p2s-agent-companies-table.data-table-cards {
    table-layout: auto; min-width: 0;
  }
  .p2s-developers-table.data-table-cards td,
  .p2s-agent-companies-table.data-table-cards td {
    width: auto;
  }
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover { background: rgba(72, 94, 144, 0.03); }
/* The logged-in viewer's own row on sales/leaderboard.php — reuses the same
   tint .lead-picked already uses elsewhere, so it reads as "this one is
   about you" rather than a new, unrelated color. */
.data-table tbody tr.leaderboard-me { background: var(--primary-soft); font-weight: 600; }
.data-table tbody tr.leaderboard-me:hover { background: var(--primary-soft); }
/* Past its due date: the row's text is red (bu/stale_negotiations.php); links keep the same colour. */
.data-table tbody tr.p2s-late, .data-table tbody tr.p2s-late a { color: var(--color-danger); }
/* A generic "this row needs attention" tint — first used for leads waiting
   more than 30 days on public/sales/pending_transfer.php, now shared with
   public/bu/stale_negotiations.php's severely-overdue projects. A similar
   warning tint to .lead-status-not_interested (same hue, lighter alpha
   since it's a whole row rather than a small badge). */
.data-table tbody tr.row-overdue { background: rgba(185, 28, 28, 0.06); }
.data-table tbody tr.row-overdue:hover { background: rgba(185, 28, 28, 0.1); }
.data-table tfoot th {
  background: var(--head-bg); font-size: var(--text-sm); color: var(--text);
  text-transform: none; letter-spacing: 0;
  border-top: 2px solid var(--border);
}
.data-table td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* A numeric column's header must right-align too, or the header text sits
   left while every value under it sits right — exactly the "ไม่ balance"
   look flagged on the โครงการประจำเดือน table. */
.data-table th.num { text-align: right; }
/* With sixteen columns competing for width, a name or job title gets torn into
   one word per line. These stay on one line and the table scrolls instead. */
.data-table td.nowrap { white-space: nowrap; }
/* In a narrow column these wrap mid-word ("ยังไม่ / ได้คุย"), which reads as
   two separate labels. */
.data-table .status-btn, .data-table .status-badge { white-space: nowrap; }

/* Our pagination is a <div class="pagination">, which collides with
   Bootstrap's list-based component of the same name. */
div.pagination {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 1.5rem;
}
.pagination-info { font-size: var(--text-sm); color: var(--muted); margin-right: 4px; }
.pagination-per-page { font-size: var(--text-sm); color: var(--muted); display: flex; align-items: center; gap: 4px; }
.pagination-per-page select { font-size: var(--text-sm); padding: 2px 4px; }

/* A <select> outside .project-form — the page-size picker, and the channel and
   sale filters above tables — had no rule at all. The Master Admin theme used
   to border every select globally; Velzon does not, so they fell back to the
   browser's own control: square corners, a dark grey frame, 28px tall beside
   44px inputs. No error, nothing to catch — it just looked like a different
   product. This gives them the same frame as our text inputs.
   Written as a plain `select` rule that .project-form select then overrides by
   being more specific, rather than :not(.project-form select): a complex
   selector inside :not() is unsupported in older Safari, and a rule the browser
   cannot parse is dropped whole, silently — invisible from here, and exactly
   the kind of thing a customer on an older iPad would meet. */
select {
  border: 1px solid var(--border); border-radius: var(--radius);
  background-color: var(--surface); color: var(--text);
  font-family: inherit; padding: 6px 28px 6px 10px;
}
/* Same gap, same cause, for text-like inputs outside .project-form: the date
   filter on the follow-up list and the per-person daily target on the executive
   dashboard both rendered as the browser's own grey-framed square box. Found by
   sweeping 23 pages for any control still showing a browser-default frame, not
   by any error. Excludes the types that are not boxes (checkbox/radio/range)
   and file inputs, which have their own button. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="color"]) {
  border: 1px solid var(--border); border-radius: var(--radius);
  background-color: var(--surface); color: var(--text); font-family: inherit;
}
/* ...and a locked box reads as locked: the rule above outranks every plain ":disabled" rule (8 :not()s), so the disabled state needs the same
   selector (2026-10-16, found when the project form's unit-priced boxes stayed white; Support's locked commission boxes had the same issue). */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="color"]):disabled {
  background-color: var(--surface-subtle); color: var(--muted); cursor: not-allowed;
}
.list-toolbar select, .search-form select { min-height: 44px; }
.pagination-per-page select { min-height: 0; padding: 3px 24px 3px 8px; }

/* ======================================================================
   Forms
   ====================================================================== */

.project-form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.5rem;
  /* Single-column forms: wide enough not to leave a dead strip beside them,
     capped because a text input spanning 1000px is not easier to fill in. */
  max-width: 760px;
}
/* A form with this many fields reads badly as one long column, so it is split
   into labelled sections with the short inputs paired up — and once it is two
   columns there is no reason to hold it back from the full content width. */
.form-wide { max-width: 100%; }
/* At full width two columns leave the fields very wide; a third is better use
   of the room, and the 720px breakpoint below still collapses it on a phone. */
.form-wide .form-grid { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.project-form label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 14px; }
.project-form input:not([type="radio"]):not([type="checkbox"]),
.project-form select,
.project-form textarea {
  display: block; width: 100%; margin-top: 4px;
  padding: 9px 12px;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text);
  background: var(--surface);
}
.project-form textarea { resize: vertical; }
.project-form select { padding: 9px 12px; }

/* iOS Safari zooms the whole page in whenever a focused form control has text
   under 16px, and every control in this app is 13-14px. The result on a phone
   is that tapping any field — search, a filter select, any form input — jerks
   the layout larger and leaves it there, and the user has to pinch back out
   before the next tap. Raising them to 16px for touch input is the only thing
   Safari accepts; it ignores maximum-scale in the viewport meta, and disabling
   zoom would be the wrong trade anyway.
   Keyed to pointer: coarse, so pointer-driven screens keep the denser text
   this app's data views are designed around. */
@media (pointer: coarse) {
  input, select, textarea { font-size: 16px; }
}

/* iOS Safari draws date and time inputs as a native control that sizes itself
   to its content and will not shrink below it, so `width: 100%` is quietly
   ignored and the field runs off the right edge of the card — the value is
   centred rather than left-aligned, which is the tell. Turning the native
   appearance off makes it an ordinary box that honours the width it is given;
   the picker still opens on tap. The ::-webkit- parts are Safari's inner
   wrapper, which keeps its own alignment and padding otherwise. */
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"] {
  -webkit-appearance: none;
  appearance: none;
  min-width: 0;
  max-width: 100%;
  text-align: left;
}
input[type="date"]::-webkit-date-and-time-value,
input[type="datetime-local"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value,
input[type="month"]::-webkit-date-and-time-value {
  text-align: left;
  margin: 0;
}
input[type="date"]::-webkit-datetime-edit,
input[type="datetime-local"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit,
input[type="month"]::-webkit-datetime-edit { padding: 0; }
.project-form input:not([type="radio"]):not([type="checkbox"]):focus,
.project-form select:focus,
.project-form textarea:focus {
  border-color: var(--primary);
}
/* A field shown for reference only — it must not look like somewhere to type. */
.project-form input:disabled { background: #f4f6f7; color: var(--muted); cursor: not-allowed; }
/* Six of the seven save buttons in the app carry no class of their own — the
   old stylesheet reached them with a selector, and rewriting it I kept only the
   colour half. They were rendering as native buttons: 1px of padding, square
   corners, sitting next to a properly drawn "ยกเลิก". */
.project-form button[type="submit"] {
  padding-left: 18px; padding-right: 18px;
  border-radius: var(--radius);
  font-size: var(--text-base); font-weight: 600;
  border: 1px solid var(--primary);
  background: var(--primary); color: #fff;
  cursor: pointer;
}
.project-form button[type="submit"]:hover {
  background: var(--primary-dark); border-color: var(--primary-dark);
}
.project-form button[type="submit"]:active {
  background: var(--primary-dark); border-color: var(--primary-dark);
  transform: scale(0.97); transition: transform var(--duration-fast) ease-out;
}
/* data-actions.js's double-submit guard sets `disabled` and swaps the label
   to "กำลังบันทึก..." the instant a real submission goes through, on every
   form in the app — not just this one. Without this rule a themed button
   (explicit background-color) shows no native dimming at all when disabled,
   so the only signal a tap registered was the text change, easy to miss
   mid-tap on a slow connection (apple-design audit: pending state needs
   visible weight, not just a label swap). Covers both the plain submit
   buttons inside .project-form and any button carrying .btn-primary/
   .btn-secondary directly, since submit buttons in this app use either. */
.project-form button[type="submit"]:disabled,
button[type="submit"].btn-primary:disabled,
button[type="submit"].btn-secondary:disabled {
  background: var(--muted); border-color: var(--muted); opacity: 0.7; cursor: not-allowed;
  transform: none;
}

/* Show/hide toggle for password fields — placed after .project-form's own
   input rule (and .login-form's, which is lower-specificity anyway) so
   padding-right wins the cascade without needing !important. */
.password-field { position: relative; }
.password-field input:not([type="radio"]):not([type="checkbox"]) { padding-right: 40px; }
.password-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: none; border: none; padding: 6px; cursor: pointer; color: var(--muted);
  display: flex; align-items: center; justify-content: center; border-radius: 4px;
}
.password-toggle:hover, .password-toggle:active { color: var(--text); }
.password-toggle svg { width: 18px; height: 18px; display: block; }

.form-section + .form-section {
  border-top: 1px solid var(--border); padding-top: 1.5rem; margin-top: 6px;
}
.form-section-title {
  font-size: var(--text-sm); font-weight: 700; color: var(--primary);
  letter-spacing: 0.3px; margin-bottom: 14px;
}
/* lead_form.php's "ความต้องการ" section, collapsed by default on a brand-new
   lead — same summary treatment as .exec-data-disclosure (44px touch target,
   no custom marker, matching hover/focus feedback) so the two disclosure
   patterns in the app read as one convention. */
.form-section-collapsible > summary.form-section-title {
  display: flex; align-items: center; min-height: 44px; cursor: pointer; margin-bottom: 4px;
}
.form-section-collapsible > summary.form-section-title:hover, .form-section-collapsible > summary.form-section-title:active { color: var(--primary-dark); }
.form-section-collapsible > summary.form-section-title:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px;
}
.form-section-collapsible[open] > summary.form-section-title { margin-bottom: 14px; }

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
.form-grid-full { grid-column: 1 / -1; }
/* Overrides .form-wide .form-grid's auto-fit, which packs 3+ narrow fields
   per row — this keeps them paired two at a time regardless of width. Needs
   two classes to out-specify .form-wide .form-grid, not just one. */
.form-grid.form-grid-pairs { grid-template-columns: repeat(2, minmax(0, 1fr));  align-items: end; }

.field-group { margin-bottom: 16px; }
.field-group-label { font-size: var(--text-sm); font-weight: 600; margin-bottom: 8px; }
.field-hint { font-size: var(--text-sm); color: var(--muted); margin: -8px 0 14px; }
.label-note { font-weight: 400; color: var(--muted); font-size: var(--text-sm); }
.label-note-danger { color: var(--color-danger); font-weight: 600; }
.p2s-legacy-agent-link { font-weight: 600; color: var(--primary); }
/* The share-mismatch warning on the commission page sits inside a
   right-aligned .num cell, which is nowrap — so the sentence ran straight out
   of the cell and over the next column instead of wrapping, and the theme
   clips overflow without a scrollbar to hint at it. Needs white-space back to
   normal for the width cap to mean anything. Scoped rather than folded into
   .label-note, which several other pages use in narrow cells. */
.commission-share-note { max-width: 240px; margin-left: auto; text-align: right; white-space: normal; }
.required-mark { color: var(--color-red); }

/* Per-field validation messages (audit P1-04) — src/form_errors.php pairs
   these with real aria-invalid/aria-describedby on the input itself, so
   this text is announced to a screen reader as part of the field, not just
   sitting nearby visually. */
.field-error {
  display: block;
  margin: 4px 0 10px;
  color: var(--color-danger);
  font-size: var(--text-sm);
  font-weight: 600;
}
.error-summary ul { margin: 8px 0 0; padding-left: 20px; }
.error-summary li { margin-bottom: 4px; }
.error-summary a { font-weight: 600; }
/* tabindex="-1" makes this focusable by script (form-error-summary.js) but
   not by Tab — it is a landing spot after a failed submit, not a normal
   stop in the page's tab order. */
.error-summary:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

.option-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* Two fixed columns left each tile ~570px wide with a checkbox and three words
   in it. Fit as many as the row takes instead. */
.option-list-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* Each option is a whole clickable tile rather than a small square with a label
   loose beside it — easier to hit, and the chosen ones read at a glance. */
.option-chip {
  display: flex; align-items: center; gap: 9px; margin: 0;
  padding: 10px 13px;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-sm); font-weight: 500; line-height: 1.4; cursor: pointer;
  background: var(--surface);
  transition: border-color var(--duration-fast), background var(--duration-fast);
}
.option-chip:hover, .option-chip:active { border-color: var(--primary); }
.option-chip:has(input:checked) {
  border-color: var(--primary); background: var(--primary-soft);
  color: var(--primary); font-weight: 700;
}
/* The theme parks every native radio and checkbox off-screen (position:absolute;
   left:-9999px; opacity:0) and draws its own control in their place — which needs
   wrapper markup we do not emit. Left alone, our forms render with the labels but
   no controls at all. Put the native ones back; the selector has to name both
   :checked and :not(:checked) to outweigh the theme's. */
input[type="radio"]:not(:checked), input[type="radio"]:checked,
input[type="checkbox"]:not(:checked), input[type="checkbox"]:checked {
  position: static; left: auto; opacity: 1;
  width: 16px; height: 16px; margin: 0; flex: none;
  accent-color: var(--primary);
}

.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 4px; } /* wraps -> button-stack.js stacks it */

/* The "search before you add" panel on lead_form.php and co_agent_form.php —
   a lead lookup uses the same .lead-match/.lead-search-result styling below. */
.search-before-add {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 20px;
}
.search-before-add label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 8px; }
.search-before-add .search-form { margin-top: 0; }
.search-before-add input[type="text"] {
  padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
  flex: 1; min-width: 0;
}
/* .lead-search-result's -8px pull-up (below) is tuned for its plain,
   unboxed spot on site_visit_form.php — inside this bordered/padded panel
   it crowded the match rows right up against the search row above them. */
.search-before-add .lead-search-result { margin: 10px 0 0; }
.search-before-add .lead-match:last-child { margin-bottom: 0; }
.search-before-add .lead-search-result > .field-hint { margin: 0; }

/* Lead lookup on the site-visit form. */
.lead-search-result { margin: -8px 0 14px; }
.lead-match, .lead-picked {
  font-size: var(--text-sm); padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  margin-bottom: 6px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.lead-picked { background: var(--primary-soft); border-color: var(--primary); }
.lead-match .btn-secondary, .lead-picked .btn-secondary { padding: 4px 10px; font-size: var(--text-sm); margin-left: auto; }

/* project_form.php's "ค้นหาด้วย AI" review card — a tight list of rows
   (label + value + source + accept), not boxed per-row like .lead-match,
   since a row here has more competing content (a value and a source link)
   than .lead-match's single line of text was designed for. */
.ai-autofill-panel { padding: 14px 16px; margin: 4px 0 20px; }
.ai-autofill-panel .field-hint { margin: 6px 0 0; }
.ai-autofill-rows { margin-top: 12px; border-top: 1px solid var(--border); }
.ai-autofill-row {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 8px 0; border-bottom: 1px solid var(--border); font-size: var(--text-sm);
}
.ai-autofill-label { color: var(--muted); flex: 0 0 90px; }
.ai-autofill-value { font-weight: 600; }
.ai-autofill-source { font-size: var(--text-sm); color: var(--muted); }
.ai-autofill-source a { color: inherit; }
.ai-autofill-accept { margin-left: auto; padding: 4px 0; }
.ai-autofill-accept-all { margin-top: 14px; }
.ai-autofill-manual { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.ai-autofill-manual textarea { margin-top: 8px; width: 100%; font-size: var(--text-sm); }

/* ======================================================================
   Login — the one page with no shell around it
   ====================================================================== */

/* Every other page gets Bootstrap's `*, *::before, *::after { box-sizing:
   border-box }` reset from the theme's own CSS, loaded by layout.php. Login
   loads nothing but this file, so its elements are on the browser default
   instead — and Chrome's default box-sizing differs by element: border-box
   for <button>, content-box for <input>. An input's own padding (10px 12px)
   and border then add on top of its 100%-width instead of fitting inside it,
   rendering it wider than the same 100%-width button beneath it and spilling
   into the card's own right padding. Reset just this page's own subtree so
   every box here measures the same way. */
.login-body, .login-body *, .login-body *::before, .login-body *::after {
  box-sizing: border-box;
}

.login-body {
  min-height: 100vh;
  /* iOS reports 100vh as the viewport with the address bar hidden, which is
     taller than what is actually on screen; dvh is the visible height. */
  min-height: 100dvh; margin: 0;
  display: flex; align-items: center; justify-content: center;
  /* The illustrated skyline (public/assets/city-skyline.svg, not a licensed
     photo) reads as "this manages real buildings" without a stock-photo
     licensing or hotlinking dependency. --skyline-url is set inline by
     login.php via asset_url(), since this static stylesheet can't call it
     itself — the gradient over the image keeps the floating card's contrast
     readable regardless of where a building or a lit window lands behind it. */
  background-color: #1f2437;
  background-image:
    linear-gradient(180deg, rgba(31, 36, 55, 0.62) 0%, rgba(31, 36, 55, 0.86) 100%),
    var(--skyline-url, none);
  background-size: cover, cover;
  background-position: center, center bottom;
  background-repeat: no-repeat, no-repeat;
  padding: 16px;
  font-family: var(--font-stack);
}

.login-card {
  width: 100%; max-width: 920px;
  display: grid; grid-template-columns: 0.85fr 1fr;
  background: var(--surface); border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: 0 30px 80px rgba(0,0,0,0.35);
}

.login-brand-panel {
  display: flex; flex-direction: column; justify-content: space-between; gap: 24px;
  padding: 40px 32px;
  color: #fff;
  background:
    radial-gradient(circle at 88% 8%, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0) 42%),
    radial-gradient(circle at 6% 96%, rgba(167, 139, 250, 0.30) 0%, rgba(167, 139, 250, 0) 46%),
    linear-gradient(150deg, #1f2437 0%, var(--primary-deep) 58%, var(--primary) 100%);
  position: relative; isolation: isolate;
}
/* A faint grid over the gradient gives the panel texture without an image file
   to license, host or 404 — the theme's own cover uses a PNG that this build
   does not ship. Sits behind the copy (isolation + z-index) so text contrast is
   set by the gradient alone. */
.login-brand-panel::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 36px 36px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 40%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 40%, #000 100%);
}
/* align-self overrides .login-brand-panel's flex default (align-items:
   stretch), which was stretching this to the full column width and then
   clamping it at max-width — object-fit:contain then centered the actual
   logo pixels inside that oversized box, reading as shoved right of the
   heading below it instead of lined up with it. */
.login-brand-logo { display: block; height: 56px; width: auto; max-width: 240px; object-fit: contain; align-self: flex-start; }
/* Not a real <h1> — the brand panel is decorative and hidden on mobile
   (audit P2-08), so the page's one real heading lives in .login-form
   instead and stays visible at every breakpoint. */
.login-brand-title { margin: 0 0 12px; font-size: var(--text-2xl); line-height: 1.35; font-weight: 700; }
.login-brand-copy p { margin: 0; font-size: var(--text-base); line-height: 1.6; color: rgba(255,255,255,0.85); }
.login-brand-quote {
  margin: 0; font-size: var(--text-sm); line-height: 1.6; font-style: italic;
  color: rgba(255,255,255,0.75); border-top: 1px solid rgba(255,255,255,0.2); padding-top: 16px;
}

.login-form {
  background: var(--surface); padding: 40px 36px;
}
.login-form h1 { margin: 0 0 4px; font-size: var(--text-2xl); }
.login-sub { color: var(--muted); font-size: var(--text-sm); margin-bottom: 20px; }
/* "Continue with Google" style button: white, rounded, soft shadow, the four-colour G on the left. */
.p2s-google-btn {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 52px; padding: 0 16px; margin-bottom: 16px;
  background: #fff; color: #757575; border: 1px solid rgba(0, 0, 0, .08); border-radius: var(--radius-lg);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
  font-size: var(--text-md); font-weight: 600; text-decoration: none;
}
.p2s-google-btn:hover { color: #5f6368; box-shadow: 0 3px 12px rgba(0, 0, 0, .18); }
.p2s-google-btn:focus-visible { outline: 2px solid #4285f4; outline-offset: 2px; }
.p2s-google-btn svg { flex: none; width: 24px; height: 24px; }
/* "Log in with LINE": LINE's own green, white mark — the third-party brand colour, like the Google button's four-colour G (not a theme colour). */
.p2s-line-btn {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 52px; padding: 0 16px; margin-bottom: 16px;
  background: #06c755; color: #fff; border: 1px solid #06c755; border-radius: var(--radius-lg);
  font-size: var(--text-md); font-weight: 600; text-decoration: none;
}
a.p2s-line-btn, a.p2s-line-btn:visited, .login-form a.p2s-line-btn:not(.btn-secondary) { color: #fff; }   /* beats `.login-form a:not(.btn-secondary)` (the link colour) */
a.p2s-line-btn:hover, .login-form a.p2s-line-btn:not(.btn-secondary):hover { background: linear-gradient(rgba(0, 0, 0, .1), rgba(0, 0, 0, .1)), #06c755; color: #fff; }   /* LINE's hover: #000 at 10% over the base green */
.p2s-line-btn:focus-visible { outline: 2px solid #06c755; outline-offset: 2px; }
.p2s-line-btn-icon { align-self: stretch; display: flex; align-items: center; padding-right: 12px; border-right: 1px solid rgba(0, 0, 0, .08); }   /* LINE's vertical divider: #000 at 8% */
.p2s-line-btn-icon img { display: block; width: 24px; height: 24px; }   /* the official LINE icon from LINE's button kit (white, transparent) */
.login-form label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 14px; }
.login-form input {
  display: block; width: 100%; margin-top: 4px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius); font-size: var(--text-base);
}
.login-form input:focus {
  border-color: var(--primary);
}
/* The email field is the only autofocus input anywhere in the app, so it is
   the only place the focus-visible ring (2px, floating 2px outside the
   border — see "Keyboard focus" below) appears on page load rather than
   after a deliberate Tab press. Floating outward there, it visibly sticks out
   past the password field beneath it, reading as misaligned rather than as a
   focus indicator. Pull it back inside the border instead, the same fix
   already used for the sidebar and table links below. */
.login-form input:focus-visible { outline-offset: -2px; }
/* Links in the form (forgot password, register, back to sign-in) had no rule and
   drew in the browser's default blue — the one colour on the page that belongs
   to no palette here. */
.login-form a:not(.btn-secondary) { color: var(--primary); font-weight: 600; }
.login-form a:not(.btn-secondary):hover { color: var(--primary-dark); text-decoration: underline; }
/* Was `.login-form button`, which matched every button in the form — including
   the show/hide-password toggle, which then rendered as a full-width purple bar
   laid over the password field, hiding it. It is a bug on production today, not
   something the theme change caused: .login-form button is declared after
   .password-toggle and so beats it. Only the submit button is meant to be the
   big purple one, and the toggle is type="button", so the type separates them
   without touching the four pages' markup. */
.login-form button[type="submit"] {
  width: 100%; padding: 6px 12px; min-height: 34px; margin-top: 8px; /* 34px like every other text button (owner 2026-10-06) */
  background: var(--primary); color: #fff; border: none; border-radius: var(--radius);
  font-weight: 700; font-size: var(--text-base); cursor: pointer;
}
.login-form button[type="submit"]:hover, .login-form button[type="submit"]:active { background: var(--primary-dark); }

/* The brand panel's copy needs real column width to read well, and its
   quote earns a spot only when there is a second column beside it — below
   this the card collapses to just the functional form, full width, still
   floating on the same skyline. */
@media (max-width: 767.98px) {
  .login-card { grid-template-columns: 1fr; max-width: 420px; }
  /* A phone used to get a bare form with no hint what the system is: keep the brand panel as a compact header strip
     (logo + heading + the one-sentence description of what the system is), dropping only the quote. */
  .login-brand-panel { display: flex; flex-direction: column; justify-content: center; gap: 8px; padding: 20px 24px; min-height: 0; }
  .login-brand-logo { height: 40px; }
  .login-brand-title { margin: 0; font-size: var(--text-lg); }
  .login-brand-copy p:not(.login-brand-title) { font-size: var(--text-sm); line-height: 1.5; }
  .login-brand-quote { display: none; }
}

/* The 403 page, and anywhere else a page is one short statement rather than
   a list of records. */
.page-message {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 3rem 2rem;
  max-width: 520px;
  margin: 2rem auto;
  text-align: center;
}
.page-message svg { width: 48px; height: 48px; color: var(--muted); opacity: 0.6; }
.page-message h1 { font-size: var(--text-lg); font-weight: 700; color: var(--text); margin: 1rem 0 0.5rem; }
.page-message p { font-size: var(--text-base); color: var(--muted); margin-bottom: 1rem; }
.page-message p:last-child { margin-bottom: 0; }

/* Pages rendered without the shell (there is one: the "not enabled yet" stub). */
body > main.page { max-width: 1000px; margin: 0 auto; padding: 24px 16px 60px; }

/* ======================================================================
   Narrow screens
   ====================================================================== */

@media (max-width: 767.98px) {
  .form-grid, .option-list-grid { grid-template-columns: minmax(0, 1fr); }
  .list-toolbar .search-row { margin-left: 0; width: 100%; }
  .search-form input[type="search"] { flex: 1; min-width: 0; }
}
@media (max-width: 767.98px) {
  .summary-cards, .kpi-cards { grid-template-columns: 1fr 1fr; gap: 1rem; }
  .summary-card, .kpi-card, .chart-card { padding: 1rem; }
  /* At 24px a figure like "7,430,000.00 บาท" wraps onto a second line and the
     card beside it ends up a different height. */
  .summary-card-value, .kpi-card-value { font-size: var(--text-lg); }
}

/* Filter pills, the project negotiation-status toggle, and table row
   actions (bare "แก้ไข" links and .link-btn) size themselves from their
   text alone — fine with a mouse, but under the ~44px a finger needs, most
   visibly on Sales Leads, BU Projects and Admin Users at 390px (audit
   P2-03). More padding only, confined to this breakpoint, so desktop
   tables stay exactly as dense as before.
   P2-03's first pass only grew the vertical dimension — a round-2 audit
   found short labels like "ลบ" (2 Thai characters) still only ~15px wide,
   44px tall: a real finger still misses left/right. min-width plus centering
   fixes the width without changing anything for links whose text is already
   wider than 44px (min-width is a floor, not a fixed size), and box-sizing
   keeps that 44px inclusive of the padding rather than adding to it. */
@media (max-width: 767.98px) {
  /* .status-pill's own touch-target padding (this rule, until 2026-09-24)
     was the third of these to go — "เปลี่ยน status-pill เป็น status-btn แทน
     ทั้งหมดเพราะ status-pill ดูใหญ่และสูงเกิน". Dropped, matching the earlier
     removal of .link-btn's and .status-btn's own equivalent rules for the
     same reason: the owner's call is visual consistency over this
     particular touch-target inflation. */
  .data-table td a {
    display: inline-block; box-sizing: border-box;
    padding: 12px 2px; min-width: 44px; text-align: center;
  }
  .data-table td .link-btn { padding: 12px 0; min-width: 44px; }
  /* Sortable column headers (bu/projects.php) are a bare <a> that only ever
     wrapped its own text — 58×17px measured live, nowhere near the th cell
     around it. The negative margin cancels the th's own 12px/16px padding
     so the link's box reaches the cell's actual edges, then the matching
     padding puts the visible label right back where it was — the whole
     header cell becomes the tap target, not just the letters inside it. */
  .data-table thead th a { display: block; margin: -12px -16px; padding: 12px 16px; }
}

/* Nine navigation links stand between the top of every page and its content.
   The link is off-screen until it is focused, which is the first thing Tab
   reaches — so it costs a sighted mouse user nothing. */
/* The theme recolours every focused link green (`.theme-primary a:focus`), so
   without matching that specificity this reads as green on green. */
a.skip-link,
a.skip-link:focus,
a.skip-link:hover,
a.skip-link:active { color: #fff; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  padding: 10px 18px;
  background: var(--primary);
  color: #fff;
  font-size: var(--text-base); font-weight: 600;
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* ======================================================================
   Keyboard focus

   The theme switches the focus ring off — `a:hover, a:active, a:focus
   { outline: 0 }` and `.btn:focus` — and Bootstrap does the same for
   `.btn:focus-visible` and `.nav-link:focus-visible`. Between them nothing in
   this app showed where the keyboard was: 36 of 36 focusable elements looked
   identical focused and unfocused.

   :focus-visible rather than :focus, so a mouse click still does not draw a
   ring. Each selector matches the specificity of the rule it is overriding and
   this file loads last, which is what settles it.
   ====================================================================== */

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible,
.link-btn:focus-visible,
.status-pill:focus-visible {
  /* !important because Velzon's app.min.css says `a, button { outline: 0 !important }`,
     which beats any ordinary rule however specific — measured on the menu button,
     the profile button and ordinary content links: all reported outline:none while
     :focus-visible matched. Only an !important of our own gets past it. */
  outline: 2px solid var(--primary) !important;
  outline-offset: 2px;
  border-radius: 3px;
}

/* A radio or checkbox in an option tile is 16px in a 280px target, so the ring
   goes round the tile that the eye is actually on. */
.option-chip:has(input:focus-visible) {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
.option-chip input:focus-visible { outline: none; }

/* main is focusable only so the skip link has somewhere to land. Ringing the
   whole content area would be shouting; the heading arriving at the top of the
   viewport is the confirmation. */
main.content:focus, main.content:focus-visible { outline: none; }

/* On the dark sidebar the brand purple is about 2:1; a white ring is what reads. */
.app-menu a:focus-visible, .app-menu button:focus-visible { outline-color: #fff !important; }

/* Inside the sidebar and the table an offset ring is clipped by the row. */
.data-table a:focus-visible,
.dropdown-item:focus-visible { outline-offset: -2px; }

/* ---- งานวันนี้ ------------------------------------------------------------
   Cards, not a table. The theme sets .wrapper { overflow-x: hidden }, so a
   table wider than the phone is clipped with no scrollbar and no error — this
   page is opened on a phone more than anywhere else in the app. */
.followup-intro { color: var(--color-slate); margin: 0 0 20px; }
.followup-block { margin-bottom: 32px; }
.followup-heading { font-size: var(--text-md); font-weight: 600; margin: 0 0 12px; }
.followup-group { font-size: var(--text-base); font-weight: 600; margin: 20px 0 10px; display: flex; align-items: center; gap: 8px; }
.followup-group-late { color: var(--color-danger); }
.followup-group-due { color: var(--color-amber-strong); }
.followup-count { font-size: var(--text-sm); font-weight: 700; padding: 1px 8px; border-radius: 999px; background: rgba(0,0,0,.06); }

/* Landing on #card-… / #suggest-… after an action: stop below the fixed top bar, not under it. */
.followup-card, .p2s-suggest, .followup-heading { scroll-margin-top: 80px; }
/* A Co-Agent messaged in the last 7 days on project_detail.php's match list: amber text so the same person is not messaged twice. */
.p2s-late-soft { color: #a15904; font-weight: 600; white-space: nowrap; }
/* In the phone card layout a cell is a label + value row; the open offer panel takes the whole row instead of being squeezed to the right. */
.data-table-cards td > details.p2s-use-details { flex: 1 1 100%; min-width: 0; }
/* ความถนัด card on sales/co_agent_detail.php. */
.p2s-strength-group { margin-top: 12px; }
.p2s-strength-label { font-size: var(--text-sm); font-weight: 600; color: var(--muted); margin-bottom: 4px; }
.p2s-strength-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 8px; }
.p2s-strength-list li { background: var(--primary-soft); border-radius: 999px; padding: 4px 12px; }
.p2s-strength-line { margin: 0; }

/* Developer Battlecard: the read-only card on project_detail.php and the manage page bu/battlecard.php. */
.p2s-bc { padding: 16px 18px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .06); }
.p2s-bc-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.p2s-bc-title { margin: 0; font-size: var(--text-xl); }
.p2s-bc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); gap: 16px 24px; }
.p2s-bc-group h3 { margin: 0 0 6px; font-size: var(--text-base); font-weight: 700; }
.p2s-bc-comp { margin-top: 16px; }
.p2s-bc-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 8px; }
.p2s-bc-answer { color: var(--text); font-weight: 500; }
.p2s-bc-src { display: block; font-size: var(--text-sm); color: var(--muted); }
.p2s-bc-state { display: inline-block; font-size: var(--text-sm); font-weight: 600; padding: 1px 8px; border-radius: 999px; background: var(--primary-soft); color: var(--text); }
.p2s-bc-state.is-ok { color: #047857; }
.p2s-bc-state.is-warn { color: #a15904; }
.p2s-bc-state.is-draft { color: var(--muted); }
.p2s-bc-v { display: block; font-size: var(--text-sm); font-weight: 600; }
.p2s-bc-v.is-better { color: #047857; }
.p2s-bc-v.is-worse { color: #b91c1c; }
.p2s-bc-self td { background: var(--primary-soft); }
.p2s-bc-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 12px 0; }
.p2s-bc-add { margin: 8px 0 16px; }
.p2s-bc-form { display: flex; flex-direction: column; gap: 10px; max-width: 720px; margin-top: 8px; }
.p2s-bc-bulkbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 8px 0; }
.p2s-bc-item { margin-bottom: 12px; }
.p2s-bc-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-bottom: 4px; }
.p2s-bc-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px 12px; margin-top: 8px; }
.p2s-bc-confirm { display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; }
/* bu/projects.php summary cards jump to these two sections; keep them clear of the sticky top bar. */
#featured-projects, #all-projects { scroll-margin-top: 80px; }
.followup-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.followup-card {
  min-width: 0;
  background: #fff;
  border: 1px solid var(--border-solid);
  border-left: 4px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 12px 14px;
  display: grid;
  gap: 6px;
}
.followup-card-late { border-left-color: var(--color-red); }
.followup-card-due { border-left-color: #f59e0b; }
/* Urgency is graded, not all red (design audit 2026-10-16): a few days late reads amber, a week or more red. */
.followup-card-late.is-recent { border-left-color: var(--color-amber-strong); }
.followup-card-late.is-recent .followup-when { color: var(--color-amber-strong); }

.followup-main { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; min-width: 0; }
.followup-name { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.followup-phone { font-variant-numeric: tabular-nums; }
.followup-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: var(--text-sm); color: var(--color-slate); min-width: 0; }
.followup-project { min-width: 0; overflow-wrap: anywhere; }
.followup-owner { color: var(--muted); min-width: 0; overflow-wrap: anywhere; }
.followup-when { font-size: var(--text-sm); font-weight: 600; color: var(--color-danger); }
.followup-card-due .followup-when { color: var(--color-amber-strong); }


.followup-filter { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.followup-empty { padding: 32px 16px; text-align: center; color: var(--color-slate); }
.followup-empty strong { color: #065f46; }

.followup-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 4px; }
.followup-inline { display: inline; margin: 0; }
.btn-sm { padding: 4px 12px; font-size: var(--text-sm); line-height: 1.6; }

.followup-postpone summary {
  display: inline-flex; align-items: center; /* its label sat higher than the buttons beside it */
  cursor: pointer;
  list-style: none;
}
.followup-postpone summary::-webkit-details-marker { display: none; }
/* Open, the panel takes the whole row of the card: as a flex item it shrank to the width of its own "เลื่อน" summary (68px)
   and squeezed พรุ่งนี้ / 3 วัน / 1 สัปดาห์ into stacked, clipped 48px buttons (UI audit 2026-10-09). */
.followup-postpone[open] { flex-basis: 100%; }
.followup-postpone-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 8px; padding: 10px; border-radius: var(--radius); background: #f9fafb;
}
/* Both forms carry .followup-postpone-form for their layout, which also
   carries its own grey panel — so the presets and the custom date each drew
   their own box. This rule wins the tie (declared after, same specificity)
   and drops the panel from the second form so the pair reads as one. */
.followup-postpone-other {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  margin-top: 0; padding: 0; background: none;
}
/* The native iOS date control ignores width and overflows its parent; the app
   already normalises this globally, and the panel must not undo it. */
.followup-postpone-other input[type="date"] { min-width: 0; max-width: 100%; }
.followup-open { font-size: var(--text-sm); }

/* ======================================================================
   Print

   These pages get printed and handed round — a month's leads, the channel
   summary, today's site visits. Printed as-is they waste the first third of
   every page on a sidebar that cannot be clicked, and lose the last column of
   every table to "แก้ไข / ลบ".
   ====================================================================== */

@media print {
  @page { margin: 12mm; }

  /* The screen shell is all navigation, and none of it survives being on paper.
     The logo stays as the only thing that says where the page came from. */
  .main-sidebar { display: none !important; }

  
  
  

  html,
body,
.wrapper,
.content-wrapper,
.content {
    margin: 0 !important;
    padding: 0 !important;
    min-height: 0 !important;
    height: auto !important;
    background: #fff !important;
  }
  .content-wrapper { margin-top: 0 !important; }

  /* Controls do nothing on paper. Status pills stay: they carry the counts. */
  a.btn-primary, a.btn-secondary,
  button, .link-btn, .inline-form,
  div.pagination,
  .form-actions { display: none !important; }

  /* Every link inside a table cell in this app is a row action — "แก้ไข" —
     and none of them is content. */
  .data-table td a { display: none !important; }

  /* Month navigation, but not the status filters beside it: the arrows are the
     only pills with no count in a <span>, and the month itself is a <span>
     rather than a link, so it stays and says which month this covers. */
  .status-summary a.status-pill:not(:has(span)) { display: none !important; }

  /* Nothing should be cut in half by a page break, and a table running over
     several pages needs its headings repeated on each one. */
  thead { display: table-header-group; }
  tr, .summary-card, .kpi-card, .chart-card, .form-section { break-inside: avoid; }
  h1, h2, .section-title { break-after: avoid; }

  /* Browsers drop backgrounds when printing, which would turn every status
     badge into identical black-on-white text. The tint is the information. */
  .status-badge, .status-btn, .status-pill,
  .data-table thead th, .data-table tfoot th {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* On paper a card is a box, not a floating panel. */
  .summary-card, .kpi-card, .chart-card, .data-table-wrap, .project-form {
    box-shadow: none !important;
    border: 1px solid var(--border-strong) !important;
  }

  a { color: inherit; text-decoration: none; }

  /* On paper the assumptions should read as part of the summary rather than as
     fields waiting to be filled in. The buttons need no rule here: the print
     block already hides `button` and `.form-actions` a few lines above.
     The screen rule at .project-form input:not(...):not(...) is (0,3,1); this
     selector must repeat both the class and the :not() pair — not just
     `.loan-calc input` — to reach (0,4,1) and actually outrank it. Dropping
     the extra class as "redundant" silently brings back the empty-box look. */
  .project-form.loan-calc input:not([type="radio"]):not([type="checkbox"]) {
    border: 0;
    padding-left: 0;
    font-weight: 700;
  }
  .loan-calc-disclaimer { border-top: 1px solid var(--border-strong); padding-top: 8px; margin-top: 12px; }

  /* Same specificity requirement as .loan-calc's rule above — must repeat
     both the class and the :not() pair to outrank the screen rule at
     .project-form input:not(...):not(...). */
  .project-form.commission-calc input:not([type="radio"]):not([type="checkbox"]) {
    border: 0;
    padding-left: 0;
    font-weight: 700;
  }
  .commission-calc-disclaimer { border-top: 1px solid var(--border-strong); padding-top: 8px; margin-top: 12px; }

  /* Same specificity requirement as .loan-calc's rule above — must repeat
     both the class and the :not() pair to outrank the screen rule at
     .project-form input:not(...):not(...). */
  .project-form.roi-calc input:not([type="radio"]):not([type="checkbox"]) {
    border: 0;
    padding-left: 0;
    font-weight: 700;
  }
  .roi-calc-disclaimer { border-top: 1px solid var(--border-strong); padding-top: 8px; margin-top: 12px; }

  /* Same specificity requirement as .loan-calc's rule above — must repeat
     both the class and the :not() pair to outrank the screen rule at
     .project-form input:not(...):not(...). */
  .project-form.transfer-cost-calc input:not([type="radio"]):not([type="checkbox"]) {
    border: 0;
    padding-left: 0;
    font-weight: 700;
  }
  .transfer-cost-calc-disclaimer { border-top: 1px solid var(--border-strong); padding-top: 8px; margin-top: 12px; }

  /* This tool's value on paper is the comparison table — the Lead search box
     and manual project picker are interaction chrome with nothing to print. */
  .pr-lead-search,
  .pr-manual-picker,
  .pr-manual-toggle-wrap { display: none !important; }
}

/* The theme's .sidebar-menu > li > a is display:block, so margin-left:auto on
   this inline <span> was inert and, with no display of its own, min-width and
   text-align did not apply either — the badge sat right after the label
   instead of at the row's right edge. float:right is the lowest-risk fix: it
   does not touch the anchor's own display mode, so it cannot disturb the
   theme's icon/label layout or its collapsed-rail rules. */
.nav-badge {
  display: inline-block;
  float: right;
  min-width: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: #f59e0b;
  color: #fff;
  font-size: var(--text-2xs);
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

/* Header bell — admin_approvals's count again, but in the top bar rather
   than the sidebar, so "something needs me" is visible even with the
   sidebar collapsed or scrolled past. */
.p2s-bell { position: relative; color: var(--text) !important; }
.p2s-bell-icon { width: 22px; height: 22px; display: block; margin: 0 auto; }
.p2s-bell-badge {
  position: absolute; top: 4px; right: 4px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--color-red); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
}
.p2s-bell-badge.is-info { background: var(--primary); } /* งานวันนี้ with nothing late yet — red only when something is overdue, same as the menu badge */

/* Header help link — same treatment as .p2s-bell above (dark, 22px, no
   background) so it reads as a sibling toolbar icon instead of standing out.
   Without this it inherited the theme's default link color on just the
   question-mark icon's own stroke, which rendered as a pale, oversized
   circle next to the bell and avatar. */
.p2s-help { color: var(--text) !important; }
.p2s-help-icon { width: 22px; height: 22px; display: block; margin: 0 auto; }

/* !important on every property: the theme's own `.sidebar-menu li > a > span
   { position: relative }` outranks this class on specificity alone (3 type
   selectors beat 1 class), and once position isn't absolute, width/height/
   clip stop applying to a span at all — the "hidden" text then renders at
   full visible size right in the flow, which is exactly what happened to
   the nav badge's sr-only count text (audit 2026-09-16, found live). A
   utility whose whole job is "never visible, anywhere" has to out-rank
   whatever specific selector it lands inside. */
.sr-only {
  position: absolute !important; width: 1px !important; height: 1px !important;
  padding: 0 !important; margin: -1px !important; overflow: hidden !important;
  clip: rect(0 0 0 0) !important; white-space: nowrap !important; border: 0 !important;
}

/* ======================================================================
   Calendar
   ====================================================================== */
.calendar-legend {
  display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 1rem;
  font-size: var(--text-sm); color: var(--muted);
}
.calendar-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.calendar-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  flex-shrink: 0;
}
.evt-co_agent_visit { background: var(--color-blue); }
.evt-lead_visit { background: var(--color-purple); }
.evt-co_agent_customer { background: var(--color-success); }
.evt-bu_visit { background: var(--color-amber-deep); }
.evt-marketing_visit { background: var(--color-teal-deep); }
.evt-exec_visit { background: #db2777; }
.evt-support_visit { background: #475569; }
.evt-lead_appointment { background: var(--color-danger); }
.evt-lead_contract { background: #0891b2; }
.evt-lead_transfer { background: #ea580c; }

.calendar-wrap {
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  margin-bottom: 1.5rem;
}
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(120px, 1fr));
  min-width: 840px;
  gap: 1px;
  background: var(--border);
}
.calendar-weekday {
  background: var(--head-bg); color: var(--text); font-weight: 600;
  font-size: var(--text-sm); text-align: center; padding: 8px 4px;
}
.calendar-day {
  background: var(--surface); min-height: 96px; padding: 6px;
  display: flex; flex-direction: column; gap: 4px;
}
.calendar-day-empty { background: var(--head-bg); }
.calendar-day-today { box-shadow: inset 0 0 0 2px var(--primary); }
.calendar-day-num { font-size: var(--text-sm); font-weight: 600; color: var(--muted); }
.calendar-day-today .calendar-day-num { color: var(--primary); }
/* Named p2s- to stay clear of the vendored theme's own unrelated
   .calendar-event (its dashboard mini-calendar widget) — that class forces
   a solid accent box via the template theme's .theme-primary .calendar-event background (the brand colour)
   in theme_primary.css, which PurgeCSS has no way to know is the wrong
   .calendar-event to keep since our own calendar.php also happens to
   contain that exact string. A plain rename avoids the collision instead
   of trying to out-specificity a rule from a file nothing here should
   hand-edit anyway (audit-adjacent request 2026-09-17 — reported as the
   calendar's colours looking "off": every event was rendering in that
   theme green regardless of its actual type). */
.p2s-calendar-event {
  display: flex; align-items: center; gap: 4px; font-size: var(--text-sm);
  color: var(--text); text-decoration: none; line-height: 1.3;
}
.p2s-calendar-event:hover, .p2s-calendar-event:active { color: var(--primary); }
/* The full "who/what/where" a compact dot+time row leaves out — opened by
   calendar.js (mouseenter/focus on desktop with no delay, first tap on
   touch, since a touchscreen has no hover at all). position:fixed with
   coordinates calendar.js computes at open time, not position:absolute
   anchored to the row: .calendar-wrap is overflow-y:hidden (a horizontal-
   scroll card, same pattern as .data-table-wrap), which silently clipped
   this for any event in the calendar's bottom rows when it was absolute —
   confirmed live before switching (a row near the bottom cut the popover
   off by ~26px). Fixed positioning escapes that ancestor's clipping
   entirely since it is relative to the viewport, not any scroll container. */
.p2s-calendar-popover {
  display: none;
  position: fixed; z-index: 1000;
  min-width: 200px; max-width: 260px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 8px 10px;
  font-size: var(--text-sm); line-height: 1.4; color: var(--text);
}
.p2s-calendar-popover p { margin: 0; }
.p2s-calendar-popover a { display: inline-block; margin-top: 6px; color: var(--primary); font-weight: 600; text-decoration: none; }
.p2s-calendar-popover a:hover { text-decoration: underline; }
.p2s-calendar-popover.is-open { display: block; }
.calendar-more summary {
  display: inline-block; cursor: pointer; list-style: none;
  font-size: var(--text-sm); font-weight: 600; color: var(--primary); margin-top: 2px;
}
.calendar-more summary::-webkit-details-marker { display: none; }
.calendar-more .p2s-calendar-event-wrap { margin-top: 4px; }

/* The calendar is seven columns, and 120px each (840px) does not fit a phone: it
   was a sideways-scrolling card that showed Sun-Tue and hid Wed-Sat with nothing
   to say so. Up to 991px the seven columns share the width instead. */
@media (max-width: 991.98px) {
  .calendar-grid { grid-template-columns: repeat(7, minmax(0, 1fr)); min-width: 0; }
}
/* On a phone a cell is ~48px, so the day number and a dot per booking is all
   that fits. The time stays in the DOM (visually hidden) for screen readers, and
   the tap-for-details popover (calendar.js) still carries the full text. */
@media (max-width: 575.98px) {
  .calendar-weekday { padding: 6px 0; font-size: var(--text-sm); }
  .calendar-day {
    min-height: 60px; padding: 4px 2px;
    flex-direction: row; flex-wrap: wrap; align-content: flex-start; justify-content: center; gap: 0;
  }
  .calendar-day-num { flex: 0 0 100%; text-align: center; font-size: var(--text-sm); }
  .p2s-calendar-event-wrap { flex: 0 0 auto; }
  .p2s-calendar-event { padding: 4px; }
  .calendar-dot { width: 10px; height: 10px; }
  .calendar-event-time {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .calendar-more { flex: 0 0 100%; text-align: center; }
  .calendar-more-word { display: none; }
  .calendar-more[open] { display: flex; flex-wrap: wrap; justify-content: center; }
  .calendar-more[open] summary { flex: 0 0 100%; }
  .calendar-more .p2s-calendar-event-wrap { margin-top: 0; }
  /* The dots cannot be tapped accurately at 18px, so a tap lands on the day
     (calendar.js shows its bookings below). They remain links for the keyboard. */
  .calendar-day[data-day] { cursor: pointer; }
  .calendar-day .p2s-calendar-event, .calendar-more summary { pointer-events: none; }
  .calendar-day.is-selected { background: var(--primary-soft); }
}

/* The tapped day's bookings — shown on phones only. */
.calendar-agenda { display: none; margin: -0.75rem 0 1.5rem; }
.calendar-agenda-title { margin: 0 0 var(--space-sm); font-size: var(--text-base); font-weight: 700; color: var(--text); }
.calendar-agenda-empty { margin: 0; color: var(--muted); font-size: var(--text-sm); }
.calendar-agenda-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-xs); }
.calendar-agenda-item {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 12px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text); text-decoration: none;
}
.calendar-agenda-item .calendar-dot { width: 12px; height: 12px; }
.calendar-agenda-body { display: flex; flex-direction: column; min-width: 0; }
.calendar-agenda-text { font-size: var(--text-sm); font-weight: 600; overflow-wrap: anywhere; }
.calendar-agenda-type { font-size: var(--text-sm); color: var(--muted); }
@media (max-width: 575.98px) { .calendar-agenda { display: block; } }

/* ======================================================================
   Visit history (leads.php, co_agents.php)
   ====================================================================== */
.visit-history summary {
  display: inline-block;
  cursor: pointer;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--primary);
  margin-top: 4px;
}
.visit-history summary::-webkit-details-marker { display: none; }
.visit-history-list {
  margin: 6px 0 0;
  padding-left: 16px;
  font-size: var(--text-sm);
  color: var(--muted);
}

/* Co-Agent contact view (co_agents.php?view=contacts).
   A list-plus-detail pairing for the half of the job the 14-column table does
   badly: reading one person whole. The table stays the default — it is what
   can be scanned, sorted, exported and acted on in bulk.
   Prefixed p2s- because "contact", "avatar" and "card" are all names the
   vendored theme already uses for its own unrelated components. */
.p2s-view-switch { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; flex: none; }
.p2s-view-switch-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; padding: 8px;
  color: var(--muted); text-decoration: none;
  background: var(--surface);
  transition: background var(--duration-fast), color var(--duration-fast);
}
.p2s-view-switch-btn svg { width: 18px; height: 18px; display: block; }
.p2s-view-switch-btn + .p2s-view-switch-btn { border-left: 1px solid var(--border); }
.p2s-view-switch-btn:hover { background: var(--primary-soft); color: var(--primary); }
.p2s-view-switch-btn.active { background: var(--primary); color: #fff; }

.p2s-contacts-divider {
  display: flex; align-items: center; gap: var(--space-xs);
  margin: 0; padding: 0;
  font-size: var(--text-sm); font-weight: 700; color: var(--muted);
}
.p2s-contacts-divider span:last-child { font-weight: 600; color: var(--placeholder); }
.p2s-contact-avatar {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--primary); color: #fff;
  font-size: var(--text-sm); font-weight: 700;
}
.p2s-contact-avatar.lg { width: 52px; height: 52px; font-size: var(--text-lg); }

.p2s-contact-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: var(--space-lg);
}
.p2s-contact-card-head { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; }
.p2s-contact-card-title { min-width: 0; }
.p2s-contact-card-title h2,
.p2s-contact-card-title h3 { margin: 0; font-size: var(--text-xl); }
.p2s-contact-card-title p { margin: var(--space-2xs) 0 0; display: flex; gap: var(--space-xs); align-items: center; flex-wrap: wrap; }
/* align-items: center, not the flex default (normal/stretch): without it
   a .link-btn pill here (e.g. ดูผลงาน) stretches to the row's cross-axis
   size instead of its own content height — 32px instead of the ~31px
   every other action pill in the app renders at, one more source of the
   "ปุ่มขนาดไม่เท่ากันทั้ง project" the owner flagged (2026-09-24). */
.p2s-contact-card-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs);
  margin: var(--space-md) 0 0; padding-top: var(--space-sm); border-top: 1px solid var(--border);
}
/* Phone: the action pills sit in an even two-column grid (equal widths, no ragged wrap). The admin's open/close + delete form
   becomes its own row under a dashed divider so the destructive buttons are visibly apart from the everyday ones. */
@media (max-width: 767.98px) {
  .p2s-contact-card-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-xs); }
  .p2s-contact-card-actions > .inline-form { display: contents; }
  /* nowrap + tighter padding: the longest label (มอบหมายพาชมแทน) must stay one line so every pill is the same height. */
  .p2s-contact-card-actions .link-btn:not(.btn-secondary):not(.btn-primary) { width: 100%; min-width: 0; margin: 0; box-sizing: border-box; text-align: center; white-space: nowrap; padding: 5px 8px; gap: 4px; }
  .p2s-contact-card-actions > .label-note { grid-column: 1 / -1; }
  .p2s-contact-card-actions > .p2s-card-danger-row {
    grid-column: 1 / -1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-xs);
    margin-top: var(--space-2xs); padding-top: var(--space-xs); border-top: 1px dashed var(--border);
  }
}
@media (max-width: 349.98px) {
  .p2s-contact-card-actions .link-btn:not(.btn-secondary):not(.btn-primary) { font-size: var(--text-2xs); }
}
.p2s-contact-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-sm) var(--space-md);
  margin: var(--space-lg) 0 0;
  padding-top: var(--space-md); border-top: 1px solid var(--border);
}
.p2s-contact-fields dt { font-size: var(--text-2xs); font-weight: 600; color: var(--muted); }
.p2s-contact-fields dd { margin: 2px 0 0; font-size: var(--text-sm); color: var(--text); word-break: break-word; }
.p2s-contact-abilities { display: flex; flex-wrap: wrap; gap: var(--space-2xs); margin: var(--space-md) 0 0; }
/* Absent, not wrong: a capability nobody recorded should not read as a failure. */
.p2s-contacts-empty { padding: var(--space-md); color: var(--muted); font-size: var(--text-sm); }

/* Content cards (2026-10-01): the list view is one full-width card per person, grouped under status / grade headings. */
.p2s-contact-cards { display: grid; gap: var(--space-lg); }
.p2s-contact-group { display: grid; gap: var(--space-sm); }
.p2s-contact-card-sub { margin: 2px 0 0; font-size: var(--text-sm); color: var(--muted); }
.p2s-contact-fields-key { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.p2s-contact-card .form-section-collapsible { margin-top: var(--space-md); }
@media (max-width: 767.98px) {
  .p2s-contact-fields-key { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Keyed to the input device, not the screen: a small laptop with a mouse can
   keep the compact control, and a large tablet still needs the bigger target.
   The switch measured 31px, under the 44px a finger needs. */
@media (pointer: coarse) {
  .p2s-view-switch-btn { min-width: 44px; min-height: 44px; }
}
/* ── Velzon shell ─────────────────────────────────────────────────────────
   Velzon is themed by CSS custom properties (--vz-*), so bringing it onto our
   palette is a matter of setting those, not overriding rules. These load after
   app.min.css and only ever set variables or fill a gap the theme leaves.

   Brand accent. Kept in step with --primary above; the same three places rule
   from the purple rebrand still applies, and this is a fourth. */
:root,
[data-bs-theme="light"],
[data-theme-colors="default"] {
  --vz-primary: var(--primary);
  --vz-primary-rgb: var(--primary-rgb);
  --vz-primary-text-emphasis: var(--primary-deep);
  --vz-primary-bg-subtle: rgba(var(--primary-rgb), 0.12);
  --vz-primary-border-subtle: rgba(var(--primary-rgb), 0.3);
  --vz-link-color: var(--primary);
  /* Bootstrap draws links from the -rgb pair, not the plain one: without these, links stayed the theme's indigo under every palette (design audit 2026-10-16). */
  --vz-link-color-rgb: var(--primary-rgb);
  --vz-link-hover-color-rgb: var(--primary-rgb);
  --bs-link-color-rgb: var(--primary-rgb);
  --bs-link-hover-color-rgb: var(--primary-rgb);
  --vz-link-hover-color: var(--primary-dark);
  --vz-font-sans-serif: var(--font-stack);
  --vz-body-font-family: var(--font-stack);
  /* The theme's sidebar asks for "hkgrotesk", a font file this project never
     vendored: the menu fell back to a system sans and every page logged a 404. */
  /* The theme's profile block in the topbar is #f3f3f9, which is also the page
     background, so it read as a hole in the header. A tint of the brand instead. */
  --vz-topbar-user-bg: color-mix(in srgb, var(--primary) 12%, #fff);
  --vz-font-family-primary: var(--font-stack);
  --vz-font-family-secondary: var(--font-stack);
}

/* Velzon hides the topbar's logo in the vertical layout at every width, and
   [data-sidebar=dark] hides every .logo-dark on the page, so the phone header had
   only a menu button and an avatar. On a phone the wordmark goes in the middle of
   the bar, and the drawer keeps no logo of its own (the theme already hides it
   there). Desktop is untouched: its logo lives in the sidebar. */
/* The sidebar must scroll on its own. Velzon scrolled #scrollbar with SimpleBar,
   but app.js (which started it) is no longer shipped, so with a group or two
   expanded the menu ran off the bottom of the screen and could not be reached.
   Native overflow does the job and works on touch; 70px is the brand box,
   which the drawer layouts below hide. */
#main-sidebar #scrollbar {
  max-height: calc(100vh - 70px);
  max-height: calc(100dvh - 70px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
@media (max-width: 1199.98px) {
  #main-sidebar #scrollbar { max-height: calc(100vh - 40px); max-height: calc(100dvh - 40px); }
}

/* Tablets get the phone layout, landscape included. Velzon turns the sidebar into
   a drawer under 768px only; from there up it is a permanent 250px column that
   eats a quarter of an iPad. These redo, up to 1199px (an 11" iPad Pro is 1194
   wide), what the theme does below 768:
   slide the menu off-screen, give the page its width back, hide the menu's own
   logo. The selectors are deliberately heavier than the theme's size-variant ones
   (sm / md), which would otherwise win. shell.js's DRAWER_MAX must match. */
@media (min-width: 768px) and (max-width: 1199.98px) {
  html[data-layout="vertical"] .app-menu { margin-left: -100%; padding: 10px 0 20px 0; }
  html[data-layout="vertical"][data-sidebar-size] .main-content { margin-left: 0; }
  html[data-layout="vertical"][data-sidebar-size] #page-topbar { left: 0; }
  html[data-layout="vertical"][data-sidebar-size] .footer { left: 0; }
  html[data-layout="vertical"] .app-menu .navbar-brand-box { display: none; }
}

@media (max-width: 1199.98px) {
  #page-topbar .navbar-header { position: relative; }
  /* Just after the hamburger, not centred: with search, bell, help and user on the right of every page (owner 2026-10-09)
     a centred logo ran under the search box at tablet widths and under the icons on a phone. */
  #page-topbar .horizontal-logo { order: 1; margin-left: 2px; display: inline-flex; align-items: center; padding: 0; }
  #page-topbar .horizontal-logo .logo { display: inline-flex; align-items: center; }
  #page-topbar .horizontal-logo .logo-sm { display: none; }
  #page-topbar .horizontal-logo .logo-lg { display: inline-block; }
  #page-topbar .horizontal-logo .logo-lg img { height: 32px; width: auto; }
}
@media (max-width: 575.98px) {
  #page-topbar .horizontal-logo .logo-lg img { height: 26px; } /* a little smaller so the four controls keep their room */
}

/* The sidebar. [data-sidebar=dark] defaults --vz-vertical-menu-bg to the
   primary colour, which reads as a purple rail; ours is the near-black navy
   chosen for it earlier. Every text colour below was measured against this
   background — 4.5:1 for labels, 3:1 for icons. */
/* Velzon sets these on [data-theme="default"][data-sidebar="dark"], which is
   (0,2,0); a bare [data-sidebar="dark"] loses to it and the rail stayed purple.
   Matching the shape is what wins, same as with .light-skin .main-sidebar
   under the old shell. */
[data-theme="default"][data-sidebar="dark"] {
  --vz-vertical-menu-bg: #1f2437;
  --vz-vertical-menu-border: rgba(255, 255, 255, 0.08);
  --vz-vertical-menu-item-color: #cbd5e1;
  --vz-vertical-menu-item-bg: rgba(255, 255, 255, 0.08);
  --vz-vertical-menu-item-hover-color: #ffffff;
  --vz-vertical-menu-item-active-color: #ffffff;
  --vz-vertical-menu-item-active-bg: rgba(255, 255, 255, 0.10);
  --vz-vertical-menu-sub-item-color: #cbd5e1;
  --vz-vertical-menu-sub-item-hover-color: #ffffff;
  --vz-vertical-menu-sub-item-active-color: #ffffff;
  --vz-vertical-menu-title-color: #94a3b8;
}

/* The expand/collapse chevron. Velzon draws it from a Material Design Icons
   glyph, a 1.2MB font the build deliberately does not ship — without this the
   arrow renders as an empty box on every group. A mask keeps it recolourable
   through currentColor like any icon. */
.navbar-menu .navbar-nav .nav-link[data-bs-toggle="collapse"]::after {
  content: "";
  /* Velzon makes this absolute (right: 18px) with no positioned link around it,
     so it anchored to the fixed sidebar and stayed put while the menu scrolled. */
  position: relative; right: 0; top: 0;
  width: 16px; height: 16px; margin-left: auto;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E") center / contain no-repeat;
  font-family: inherit;
  transition: transform var(--duration-base);
}
.navbar-menu .navbar-nav .nav-link[data-bs-toggle="collapse"][aria-expanded="true"]::after { transform: rotate(180deg); }

/* nav.php's icons are stroke SVGs with no size of their own; Velzon sizes its
   font icons via font-size, so give ours the same footprint. The group colour
   accents are lighter twins of the --color-* set, because seven of the eight
   originals failed 3:1 on a dark background (worst: 2.03:1). */
.navbar-menu .navbar-nav .nav-link svg {
  width: 18px; height: 18px; flex: none;
  fill: none; stroke: var(--nav-accent, currentColor); stroke-width: 2;
  margin-right: 10px; vertical-align: middle;
}
.navbar-menu .navbar-nav .nav-link.active svg { stroke: #fff; }
.navbar-nav > .nav-item.is-overview { --nav-accent: #a78bfa; }
.navbar-nav > .nav-item.is-projects { --nav-accent: #22d3ee; }
.navbar-nav > .nav-item.is-sales { --nav-accent: #34d399; }
.navbar-nav > .nav-item.is-marketing { --nav-accent: #fbbf24; }
.navbar-nav > .nav-item.is-support { --nav-accent: #f472b6; }
.navbar-nav > .nav-item.is-appointments { --nav-accent: #60a5fa; }
.navbar-nav > .nav-item.is-tools { --nav-accent: #94a3b8; }
.navbar-nav > .nav-item.is-system { --nav-accent: #9ca3af; }

/* Sub-items live inside the group panel; the icon there is a size down. */
.navbar-menu .menu-dropdown .nav-link svg { width: 16px; height: 16px; }
.navbar-menu .nav-link { display: flex; align-items: center; }
.navbar-menu .nav-link > span:first-of-type { flex: 1; min-width: 0; }

/* Avatar in the topbar; the sidebar variant no longer exists. */
.p2s-avatar-sm { width: 36px; height: 36px; font-size: var(--text-base); }
.topbar-user .p2s-avatar { margin-right: 0; }
/* The user button is a 34px control like every other (control height standard): photo + name + position must fit inside it.
   Two lines at the theme's 1.5 line-height were 38px and got cut off at the bottom (owner screenshot 2026-10-09). */
#page-header-user-dropdown { overflow: visible; }
#page-header-user-dropdown .header-profile-user, #page-header-user-dropdown .p2s-avatar-sm { width: 32px; height: 32px; flex: none; }
#page-header-user-dropdown .user-name-text { line-height: 16px; }
#page-header-user-dropdown .user-name-sub-text { line-height: 14px; margin-top: 0; }

/* Velzon draws a short dash before each sub-item, a stand-in for the icon its
   own sub-items don't have. Ours all carry one, so the dash is a second marker
   on the same row — dropped, and the row pulled left to use the room. */
.navbar-menu .navbar-nav .nav-sm .nav-link::before { display: none; }
/* 38px tall before; a finger wants 44. */
.navbar-menu .navbar-nav .nav-sm .nav-link { padding-left: 3.2rem; display: flex; align-items: center; min-height: 44px; }

/* ── Chart chrome ─────────────────────────────────────────────────────────
   Custom tooltip for the sales-pace chart (see exec/dashboard.php): the gap to
   last month is the headline, so it is computed and written out rather than
   left as two figures for the reader to subtract. */
.p2s-tip {
  min-width: 220px; padding: 10px 12px;
  font-family: var(--font-stack); font-size: var(--text-sm);
  color: var(--text); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(23, 43, 76, 0.12);
}
.p2s-tip-head { margin-bottom: 6px; font-weight: 700; color: var(--muted); }
.p2s-tip-row { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.p2s-tip-row b { margin-left: auto; padding-left: 14px; font-variant-numeric: tabular-nums; }
.p2s-tip-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.p2s-tip-gap {
  margin-top: 8px; padding: 5px 8px; border-radius: var(--radius);
  font-weight: 700; text-align: center;
}
.p2s-tip-gap.is-up { color: var(--color-success); background: rgba(4, 120, 87, 0.10); }
.p2s-tip-gap.is-down { color: var(--color-danger); background: rgba(185, 28, 28, 0.08); }
.exec-chart-note { margin: 6px 0 0; font-size: var(--text-sm); color: var(--muted); text-align: center; }
/* ApexCharts draws its own tooltip chrome around a custom one; ours supplies
   the border and shadow, so theirs would double it. */
.apexcharts-tooltip.apexcharts-theme-light { border: 0; background: transparent; box-shadow: none; }

/* The channel gauge's key: HTML, not ApexCharts' legend (see exec/dashboard.php
   for why that one was clipped out of existence). Each row carries the amounts
   as well as the percentage, so nobody has to open a tooltip for the figure
   behind the ring. */
.p2s-gauge-key {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-xs) var(--space-lg);
  margin: var(--space-md) 0 0; padding: 0; list-style: none;
}
.p2s-gauge-key li {
  display: grid; grid-template-columns: auto 1fr auto; align-items: baseline;
  column-gap: 8px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius);
}
.p2s-gauge-key li.is-over { border-color: rgba(185, 28, 28, 0.35); background: rgba(185, 28, 28, 0.04); }
.p2s-gauge-key .p2s-gauge-dot { width: 9px; height: 9px; border-radius: 50%; align-self: center; }
.p2s-gauge-key .p2s-gauge-name { font-weight: 600; color: var(--text); }
.p2s-gauge-key b { font-variant-numeric: tabular-nums; }
.p2s-gauge-key li.is-over b { color: var(--color-danger); }
.p2s-gauge-key small { grid-column: 2 / -1; color: var(--muted); font-size: var(--text-sm); }

/* Gauge on the left, its key beside it — the card is wide and a gauge is not,
   so stacking them left a wide empty band. Falls back to stacked on a phone. */
.exec-chart-channel.is-split {
  display: grid; grid-template-columns: minmax(280px, 460px) 1fr;
  gap: var(--space-lg); align-items: center; height: auto;
}
.exec-chart-channel.is-split .p2s-gauge-key { grid-template-columns: 1fr; margin: 0; }
.exec-chart-channel.is-split .exec-chart-note { grid-column: 1 / -1; }
@media (max-width: 991.98px) {
  .exec-chart-channel.is-split { grid-template-columns: 1fr; }
}

/* Velzon's app.min.css sets `.apexcharts-pie text { fill: var(--vz-white) }` —
   right for a label sitting on a coloured slice, wrong for a donut's centre
   readout, which sits on the white card and vanished. ApexCharts itself sets
   the correct dark fill as an attribute; the stylesheet overrode it.
   Only the centre labels are restored, so labels drawn on slices stay white.
   (An earlier attempt to fix this in the chart's own config could never have
   worked — the config was already right.) */
.apexcharts-pie text.apexcharts-datalabel-label { fill: var(--muted); }
.apexcharts-pie text.apexcharts-datalabel-value { fill: var(--text); }

/* ── Velzon overrides for ApexCharts, in one place ─────────────────────────
   app.min.css restyles ApexCharts with !important, in ways that silently beat
   a chart's own options. They are answered here once so that no chart has to
   fight them, and so a chart that "sets a colour and doesn't get it" has an
   obvious place to look first.

   Axis text and legend text: Velzon pins both to #878a99 at 13px. The chart
   options for label colour therefore do nothing for axes or legends. The value
   below is the app's muted grey (better contrast on white: 4.83:1 vs 3.6:1). */
.apexcharts-xaxis text,
.apexcharts-yaxis text { fill: var(--muted) !important; }
.apexcharts-legend-text { color: var(--text) !important; font-size: var(--text-sm) !important; }

/* Velzon gives every tooltip a bordered panel with a shaded title bar. Ours are
   either the stock look (fine, keep it) or a custom card that draws its own
   border (.p2s-tip) — for those the wrapper must be invisible, or the card sits
   inside a second frame. Already handled above for the light theme; this makes
   the same true when Velzon's more specific rule applies. */
.apexcharts-tooltip.apexcharts-theme-light:has(.p2s-tip) {
  border: 0 !important; background: transparent !important; box-shadow: none !important;
}
.apexcharts-tooltip.apexcharts-theme-light:not(:has(.p2s-tip)) .apexcharts-tooltip-title {
  font-size: var(--text-sm) !important; font-weight: 700; padding: 6px 10px;
}

/* ── Donut + key (P2S.statusDonut) ─────────────────────────────────────────
   Ring on the left, an HTML key beside it carrying each part's count and
   share. The key is HTML rather than ApexCharts' legend for the same reason as
   the gauge key: the legend lives inside an overflow:hidden SVG and can be
   clipped out of existence; HTML also gives screen readers real text. */
.chart-card-canvas-wrap.p2s-donut-split {
  display: grid; grid-template-columns: 260px 1fr; gap: var(--space-lg);
  align-items: center; margin: 0;
}
.p2s-donut-key {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); max-width: 680px; min-width: 0;
  gap: var(--space-2xs) var(--space-lg); margin: 0; padding: 0; list-style: none;
}
.p2s-donut-key li {
  display: grid; grid-template-columns: auto 1fr auto 3ch; align-items: baseline;
  column-gap: 8px; padding: 6px 4px; border-bottom: 1px solid var(--border);
  font-size: var(--text-sm);
}
.p2s-donut-key .p2s-gauge-dot { width: 10px; height: 10px; border-radius: 50%; align-self: center; }
.p2s-donut-key .p2s-gauge-name { color: var(--text); }
.p2s-donut-key b { font-variant-numeric: tabular-nums; text-align: right; }
.p2s-donut-key small { color: var(--muted); font-size: var(--text-sm); text-align: right; font-variant-numeric: tabular-nums; }
.chart-card-canvas-wrap.p2s-donut-split .exec-chart-note { grid-column: 1 / -1; text-align: left; }
@media (max-width: 767.98px) {
  .chart-card-canvas-wrap.p2s-donut-split { grid-template-columns: minmax(0, 1fr); }
}
.p2s-tip-total { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); color: var(--muted); }
.p2s-tip-total b { color: var(--text); }
/* P2S.empty() puts the title and hint inside the (block) chart host; as inline
   elements they ran together on one line. */
.chart-card-canvas-wrap.exec-chart-empty > div > strong,
.chart-card-canvas-wrap.exec-chart-empty > div > span { display: block; }

/* ---- Lead detail: conversation timeline (public/sales/lead_detail.php) ----
   p2s- prefix on purpose: the vendored theme already owns several generic
   class names, and a collision inherits its styling silently. */
.p2s-lead-summary-main { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 0 0 12px; }
.p2s-lead-tel { font-size: var(--text-lg); font-weight: 600; font-variant-numeric: tabular-nums; min-height: 44px; display: inline-flex; align-items: center; }
.p2s-lead-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px 16px; margin: 0; }
.p2s-lead-facts dt { font-size: var(--text-sm); color: var(--color-slate); }
.p2s-lead-facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

.p2s-chip-group { border: 0; padding: 0; margin: 0 0 14px; display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }
.p2s-chip-group legend { width: 100%; padding: 0; margin-bottom: 6px; font-weight: 600; }
.p2s-chip { position: relative; }
.p2s-chip input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.p2s-chip span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px;
  border: 1px solid var(--border-strong); border-radius: 999px; background: #fff; font-size: var(--text-sm);
}
.p2s-chip input:checked + span { background: var(--primary); border-color: var(--primary); color: #fff; }
.p2s-chip input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: 2px; }
.p2s-log-form textarea { width: 100%; }
.p2s-chip-group input[type="date"] { min-height: 44px; }

.p2s-timeline { list-style: none; margin: 0 0 24px; padding: 0 0 0 18px; border-left: 2px solid var(--border-solid); }
.p2s-timeline-item { position: relative; padding: 0 0 16px 14px; min-width: 0; }
.p2s-timeline-item::before {
  content: ""; position: absolute; left: -25px; top: 5px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--primary); border: 2px solid #fff;
}
.p2s-timeline-status_change::before { background: var(--color-amber-strong); }
.p2s-timeline-site_visit::before { background: var(--color-success); }
.p2s-timeline-head { display: flex; flex-wrap: wrap; gap: 2px 12px; align-items: baseline; }
.p2s-timeline-when { font-size: var(--text-sm); color: var(--color-slate); }
.p2s-timeline-note { margin: 4px 0 0; overflow-wrap: anywhere; }
.p2s-timeline-next { margin: 2px 0 0; font-size: var(--text-sm); color: var(--color-slate); }

/* Transfer-date card on the lead history page */
.p2s-timeline-transfer_date::before { background: var(--color-blue); }
.p2s-transfer-status { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0 0 6px; font-size: var(--text-lg); }
.p2s-bucket { font-size: var(--text-sm); font-weight: 600; padding: 3px 10px; border-radius: 999px; border: 1px solid transparent; }
.p2s-bucket-this { color: var(--color-success); background: rgba(4, 120, 87, 0.08); border-color: var(--color-success); }
.p2s-bucket-next, .p2s-bucket-overdue { color: var(--color-danger); background: rgba(185, 28, 28, 0.08); border-color: var(--color-danger); }
.p2s-bucket-none, .p2s-bucket-later { color: var(--color-slate); background: var(--surface-subtle); border-color: var(--border-strong); }
.p2s-transfer-form label { display: block; margin-bottom: 12px; }
.p2s-transfer-form input[type="date"] { min-height: 44px; }
.p2s-transfer-form textarea { width: 100%; }
.p2s-transfer-hint { margin: -4px 0 12px; font-size: var(--text-sm); font-weight: 600; }
.p2s-transfer-hint.this { color: var(--color-success); }
.p2s-transfer-hint.next { color: var(--color-danger); }

/* Why a lead is on the งานวันนี้ list */
.followup-why { margin: 8px 0 0; font-size: var(--text-sm); min-width: 0; }
.followup-why strong { display: block; overflow-wrap: anywhere; }
.followup-why span { display: block; color: var(--color-slate); overflow-wrap: anywhere; }
.followup-why-late strong { color: var(--text); } /* red stays on the card's edge and the 'เกิน N วัน' line; the reason itself reads as text (UI review 2026-10-09) */
.followup-why-due strong { color: var(--color-amber-strong); }

/* "ตามแล้ว" panel on a งานวันนี้ lead card — same disclosure look as .followup-postpone */
.followup-more { list-style: none; }
.followup-more > details > summary { list-style: none; cursor: pointer; text-align: center; margin: 4px 0 8px; }
.followup-more > details > summary::-webkit-details-marker { display: none; }
.followup-more > details[open] > summary { margin-bottom: 12px; }
.followup-log summary { list-style: none; cursor: pointer; }
.followup-log summary::-webkit-details-marker { display: none; }
.followup-log-form { margin-top: 8px; padding: 12px; border: 1px solid var(--border-solid); border-radius: var(--radius); background: #fff; min-width: 0; }
.followup-log-form textarea { width: 100%; }
.followup-log-form label { display: block; margin-bottom: 10px; }
.followup-log-form .p2s-chip-group label { display: inline-block; margin-bottom: 0; }

/* Post-visit result page (public/sales/visit_result.php): one phone screen */
.p2s-visit-summary { display: flex; flex-direction: column; gap: 2px; }
.p2s-visit-summary span { color: var(--color-slate); font-size: var(--text-sm); }
.p2s-result-form { display: flex; flex-direction: column; gap: 14px; max-width: 520px; }
.p2s-result-group { border: 0; padding: 0; margin: 0; min-width: 0; }
.p2s-result-group legend { padding: 0; margin-bottom: 8px; font-weight: 600; }
.p2s-result-opts { display: grid; gap: 8px; }
.p2s-result-opt { position: relative; display: block; margin: 0; }
.p2s-result-opt input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.p2s-result-opt span {
  display: flex; flex-direction: column; justify-content: center; gap: 1px; min-height: 52px; padding: 8px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg); background: #fff;
}
.p2s-result-opt small { color: var(--color-slate); font-size: var(--text-sm); }
.p2s-result-opt input:checked + span { border-color: var(--primary); background: var(--primary-soft); box-shadow: inset 0 0 0 1px var(--primary); }
.p2s-result-opt input:checked + span small { color: var(--primary); font-weight: 600; }
.p2s-result-opt input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: 2px; }
.p2s-will { margin: 0; padding: 8px 12px; border-radius: var(--radius-lg); background: rgba(4, 120, 87, 0.1); color: var(--color-success); font-size: var(--text-sm); font-weight: 600; min-height: 1.5em; }
.p2s-will:empty { display: none; }
.p2s-result-note textarea { width: 100%; margin-top: 6px; }
.p2s-result-save {
  width: 100%; border: 0; border-radius: var(--radius); background: var(--primary); color: #fff;
  font-size: var(--text-md); font-weight: 600; cursor: pointer;
}
.p2s-chip-group[hidden] { display: none; }
.p2s-timeline-visit_result::before { background: var(--color-success); }

/* ---- Fixes found on production 2026-09-19 (screenshot of lead_detail.php) ----
   1. A global rule (~line 1545) puts every native radio back on screen with
      specificity (0,2,1); the chip and result-option radios must beat it or the
      circle shows next to the pill. They are hidden and stretched over their label.
   2. <legend> inherits the theme's heading size (and float): make it a small label.
   3. .field-hint carries margin-top:-8px (it is meant to sit under an input);
      .p2s-note is the same text style for free-standing notes.
   4. Labels/textareas in these forms need the .project-form treatment without
      being .project-form (chips are labels too, so they are excluded). */
.p2s-chip input[type="radio"]:not(:checked), .p2s-chip input[type="radio"]:checked,
.p2s-result-opt input[type="radio"]:not(:checked), .p2s-result-opt input[type="radio"]:checked {
  position: absolute; inset: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer;
}
.p2s-chip-group legend, .p2s-result-group legend {
  float: none; width: 100%; padding: 0; margin: 0 0 6px;
  font-size: var(--text-sm); font-weight: 600; line-height: 1.4;
}
.p2s-chip-group .p2s-chip { display: inline-block; margin-bottom: 0; }
.p2s-note { margin: 8px 0 14px; }
.p2s-lead-summary .p2s-note, .p2s-transfer-card .p2s-note { margin-top: 10px; }
.p2s-log-form, .p2s-transfer-form { display: block; }
.p2s-log-form label:not(.p2s-chip), .p2s-transfer-form label:not(.p2s-chip), .followup-log-form label:not(.p2s-chip) {
  display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 12px;
}
.p2s-log-form textarea, .p2s-transfer-form textarea, .followup-log-form textarea, .p2s-result-note textarea {
  display: block; width: 100%; margin-top: 4px; padding: 9px 12px; resize: vertical;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
}
.p2s-log-form input[type="date"], .p2s-transfer-form input[type="date"] { margin-top: 4px; }

/* Agent company page: contact block (agent_company_detail.php) */
.p2s-company-info .p2s-lead-facts dd a { overflow-wrap: anywhere; }

/* Two-up form grids are cramped on a phone (a website field showed six characters):
   this opt-in class stacks them into one column below 576px. */
@media (max-width: 575.98px) {
  .form-grid.p2s-stack-on-phone { grid-template-columns: 1fr; }
}
.p2s-timeline-commission_payment::before { background: var(--color-amber-strong); }

/* Commission payment status (public/sales/commissions.php) */
.p2s-payment-cards .summary-card-label { white-space: normal; }
.p2s-payment-bulk .btn-primary { margin-top: 4px; }
.p2s-payment-bulk input[type="text"], .p2s-payment-bulk select, .p2s-payment-bulk input[type="date"] { display: block; width: 100%; margin-top: 4px; }

/* Project cover photo card (public/bu/project_form.php) */
.p2s-cover-preview { display: block; width: 100%; max-width: 480px; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--border); margin: 0 0 12px; }
.p2s-cover-form label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 8px; }
.p2s-cover-form input[type="file"] { display: block; margin-top: 6px; max-width: 100%; }
.p2s-cover-form .btn-primary { margin-top: 4px; }

/* ---- Customer offer links (lead_detail.php #links) ------------------------ */
.p2s-links-card h3 { font-size: var(--text-md); margin: 14px 0 8px; }
.p2s-links-card h3 small { font-weight: 400; color: var(--muted); margin-left: 6px; }
.p2s-link-request { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; justify-content: space-between; }
.p2s-link-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.p2s-inline-form { display: inline; margin: 0; }
.p2s-link-row { border-top: 1px solid var(--border-solid); padding: 10px 0; }
.p2s-link-row:first-of-type { border-top: 0; }
.p2s-link-expired, .p2s-link-revoked { opacity: .65; }
.p2s-link-title { margin: 0 0 2px; display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; }
.p2s-link-copy { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 8px; }
.p2s-link-copy input[type="text"] { flex: 1 1 240px; min-width: 0; min-height: 44px; font-size: var(--text-sm); }
.p2s-link-answers { border-top: 1px solid var(--border-solid); margin-top: 6px; padding-top: 4px; }
.p2s-link-projects { display: grid; grid-template-columns: 1fr; gap: 8px; }
@media (min-width: 768px) { .p2s-link-projects { grid-template-columns: 1fr 1fr; } }
.p2s-link-projects legend { grid-column: 1 / -1; }
.p2s-link-project { display: flex !important; gap: 10px; align-items: flex-start; border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 10px 12px; min-height: 44px; cursor: pointer; margin: 0 !important; }
.p2s-link-project input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; flex: none; accent-color: var(--primary); }
.p2s-link-project span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.p2s-link-project small { color: var(--muted); font-size: var(--text-sm); }
.p2s-link-project:has(input:checked) { border-color: var(--primary); background: rgba(var(--primary-rgb), 0.05); }
.p2s-link-fit, .p2s-link-nocover { font-style: normal; font-size: var(--text-2xs); font-weight: 600; }
.p2s-link-fit { color: var(--color-success); }
.p2s-link-nocover { color: var(--color-amber-strong); }
.alert.alert-warning a.btn-primary { color: #fff; text-decoration: none; }

/* ---- Cover thumbnail in project list rows ---------------------------------- */
.data-table td.p2s-thumb-cell, .data-table th.p2s-thumb-cell { width: 1%; padding-left: 8px; padding-right: 8px; }
.data-table td.p2s-thumb-cell { padding-top: 6px; padding-bottom: 6px; }
.p2s-thumb { display: block; width: 56px; height: 42px; object-fit: cover; border-radius: var(--radius); background: var(--surface-subtle); }
/* The group budget page (marketing/group_budget.php) has no cover photo: this keeps the same 56px first column as a project row. */
.p2s-thumb-group { display: flex; align-items: center; justify-content: center; color: var(--primary); }
.p2s-thumb-group svg { width: 22px; height: 22px; }
/* bu/project_groups.php: name takes the room, the numbers/date are narrow and aligned, the buttons sit together at the right edge. */
@media (min-width: 576px) {
  .p2s-groups-table { table-layout: fixed; }
  .p2s-groups-table .p2s-gt-name { width: auto; }
  .p2s-groups-table .p2s-gt-count { width: 90px; padding-right: 20px; }
  .p2s-groups-table .p2s-gt-date { width: 150px; }
  .p2s-groups-table .p2s-gt-opp { width: 200px; }
  .p2s-groups-table .p2s-gt-sample { width: 210px; }
  .p2s-groups-table .p2s-gt-acts { width: 300px; text-align: right; }
  .p2s-groups-table td.p2s-gt-acts { display: table-cell; }
  .p2s-groups-table .p2s-gt-acts .inline-form { display: inline-block; margin-left: 6px; vertical-align: middle; }
}
.p2s-groups-table td .field-hint { margin: 3px 0 0; }
/* A switched-off project group (bu/project_groups.php, marketing/group_budget.php): greyed and listed last. */
.data-table tr.is-inactive td, .pb-row.is-inactive { color: var(--muted); }
.data-table tr.is-inactive td a:not(.link-btn), .pb-row.is-inactive .pb-name b { color: var(--muted); }
.data-table tr.is-inactive td strong { font-weight: 600; }
.pb-row.is-inactive { background: var(--surface-subtle, #f3f4f6); }
.pb-row.is-inactive .pb-main, .data-table tr.is-inactive td:not(:last-child) { opacity: .65; }
.pb-row.is-inactive .p2s-thumb-group { color: var(--muted); }
.pb-acts-ghost { visibility: hidden; }
.p2s-thumb-empty { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--border-strong); background: transparent; color: var(--muted); font-size: var(--text-2xs); line-height: 1.1; text-align: center; }
/* bu/projects.php's card tables (data-table-cards, "โครงการประจำเดือน" /
   "โครงการที่กำลังติดต่อ"): width:1% above is a desktop table-layout trick
   to shrink this column to its thumbnail's content size, but a card-view td
   is a flex row, not a table cell — width:1% there shrinks the whole row
   box to a sliver, squeezing the "ปก" label into one character per line.
   Reset it back to auto once the card breakpoint's own media query has
   turned this td into a flex row. */
@media (max-width: 575.98px) {
  .data-table-cards td.p2s-thumb-cell { width: auto; }
}

/* Country locked to Thailand while the nationality is ชาวไทย (lead form) */
#country_code:disabled { background-color: var(--surface-subtle); color: var(--muted); cursor: not-allowed; opacity: 1; }

/* Cover photo row at the top of the project comparison table */
.pr-thumb { display: block; width: 168px; max-width: 100%; height: 126px; object-fit: cover; border-radius: var(--radius); background: var(--surface-subtle); }
.pr-thumb-empty { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--border-strong); background: transparent; color: var(--muted); font-size: var(--text-sm); }

/* ---- Project picker (public/assets/project-picker.js): search + filters + tray ---- */
.p2s-pk { position: relative; --pk-line: var(--border-solid); --pk-line-strong: var(--border-strong); --pk-accent: var(--primary); --pk-accent-soft: rgba(var(--primary-rgb), 0.1); --pk-accent-line: rgba(var(--primary-rgb), 0.35); --pk-muted: var(--muted); }
.p2s-pk * { box-sizing: border-box; }
.p2s-pk .pk-head { margin-bottom: 10px; }
.p2s-pk .pk-title { font-size: var(--text-md); font-weight: 700; margin: 0; }
.p2s-pk .pk-subtitle { color: var(--pk-muted); font-size: var(--text-sm); margin: 2px 0 0; }
.p2s-pk .pk-body { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 16px; align-items: start; }
.p2s-pk.pk-m .pk-body { grid-template-columns: minmax(0, 1fr); padding-bottom: 12px; } /* minmax: 1fr grew to its content, 45px past the card (UI audit 2026-10-09) */
.p2s-pk.pk-m .pk-body > * { min-width: 0; }
.p2s-pk .pk-card { background: #fff; border: 1px solid var(--pk-line); border-radius: var(--radius-lg); }
.p2s-pk .pk-search { padding: 12px; }
.p2s-pk .pk-search-row { display: flex; gap: 8px; }
.p2s-pk input.pk-q { flex: 1; min-width: 0; min-height: 46px; margin: 0; border: 1px solid var(--pk-line-strong); border-radius: var(--radius-lg); padding: 0 14px 0 40px; font: inherit; color: inherit; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%236a667f' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E") no-repeat 13px center; }
.p2s-pk .pk-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; align-items: center; }
.p2s-pk .pk-filters select { width: auto; min-height: 40px; margin: 0; border: 1px solid var(--pk-line-strong); border-radius: var(--radius); padding: 0 30px 0 10px; font: inherit; font-size: var(--text-base); background-color: #fff; color: inherit; }
.p2s-pk .pk-linkbtn { border: 0; background: none; color: var(--pk-accent); font: inherit; font-size: var(--text-base); cursor: pointer; min-height: 40px; padding: 0 6px; text-decoration: underline; text-underline-offset: 2px; }
.p2s-pk .pk-btn-filter { display: none; min-height: 46px; margin: 0; border: 1px solid var(--pk-line-strong); background: #fff; color: inherit; border-radius: var(--radius-lg); padding: 0 14px; font: inherit; font-size: var(--text-base); cursor: pointer; white-space: nowrap; align-items: center; gap: 6px; }
.p2s-pk.pk-m .pk-btn-filter { display: inline-flex; }
.p2s-pk.pk-m .pk-filters { display: none; }
.p2s-pk .pk-dot { background: var(--pk-accent); color: #fff; font-size: 11px; border-radius: 999px; min-width: 18px; height: 18px; display: inline-grid; place-items: center; padding: 0 5px; }
.p2s-pk .pk-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 10px 4px 6px; color: var(--pk-muted); font-size: var(--text-sm); }
.p2s-pk .pk-meta b { color: inherit; font-weight: 700; }
.p2s-pk .pk-list { overflow: hidden; }
.p2s-pk:not(.pk-m) .pk-list-scroll { max-height: 520px; overflow-y: auto; }
.p2s-pk .pk-grp { position: sticky; top: 0; z-index: 1; background: #faf9fe; border-bottom: 1px solid var(--pk-line); padding: 7px 14px; font-size: var(--text-sm); font-weight: 600; color: var(--pk-muted); display: flex; justify-content: space-between; }
.p2s-pk .pk-grp-reco { color: var(--pk-accent); background: var(--pk-accent-soft); }
.p2s-pk .pk-row { display: grid; grid-template-columns: 62px minmax(0, 1fr) 28px; gap: 12px; align-items: center; padding: 10px 14px; margin: 0; border: 0; border-bottom: 1px solid var(--pk-line); border-radius: 0; background: #fff; width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer; min-height: 64px; }
.p2s-pk .pk-row:last-child { border-bottom: 0; }
.p2s-pk .pk-row:hover { background: var(--head-bg); }
.p2s-pk .pk-row:focus-visible, .p2s-pk .pk-linkbtn:focus-visible, .p2s-pk .pk-x:focus-visible, .p2s-pk input.pk-q:focus-visible, .p2s-pk select:focus-visible { outline: 2px solid var(--pk-accent); outline-offset: -2px; }
.p2s-pk .pk-row[aria-checked="true"] { background: var(--pk-accent-soft); }
.p2s-pk .pk-row[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.p2s-pk .pk-thumb { display: block; width: 62px; height: 46px; border-radius: var(--radius); object-fit: cover; background: var(--surface-subtle); flex: none; }
.p2s-pk .pk-thumb-none { display: grid; place-items: center; border: 1px dashed var(--pk-line-strong); color: var(--muted); font-size: var(--text-2xs); background: transparent; text-align: center; }
.p2s-pk .pk-name { font-weight: 600; overflow-wrap: anywhere; }
.p2s-pk .pk-sub { color: var(--pk-muted); font-size: var(--text-sm); overflow-wrap: anywhere; }
.p2s-pk .pk-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 3px; }
.p2s-pk .pk-tag { font-size: var(--text-2xs); font-weight: 600; padding: 1px 8px; border-radius: 999px; background: #e3f4ec; color: var(--color-success); }
.p2s-pk .pk-tag-warn { background: #fdf0df; color: var(--color-warning); } /* 4.47:1 with amber-strong; warning clears 4.5 */
.p2s-pk .pk-tag-recent { background: rgba(var(--primary-rgb), 0.11); color: var(--primary-dark); }
.p2s-pk .pk-grp-recent { color: var(--primary-dark); background: rgba(var(--primary-rgb), 0.06); }
.p2s-pk .pk-check { width: 24px; height: 24px; border-radius: var(--radius); border: 2px solid var(--pk-line-strong); display: grid; place-items: center; background: #fff; }
.p2s-pk .pk-row[aria-checked="true"] .pk-check { background: var(--pk-accent); border-color: var(--pk-accent); }
.p2s-pk .pk-check svg { width: 14px; height: 14px; stroke: #fff; opacity: 0; }
.p2s-pk .pk-row[aria-checked="true"] .pk-check svg { opacity: 1; }
.p2s-pk .pk-empty { padding: 30px 16px; text-align: center; color: var(--pk-muted); }
.p2s-pk .pk-more { display: block; width: calc(100% - 28px); margin: 12px 14px; min-height: 44px; border: 1px solid var(--pk-line-strong); background: #fff; color: inherit; border-radius: var(--radius-lg); font: inherit; cursor: pointer; }
.p2s-pk .pk-tray { padding: 14px; position: sticky; top: 12px; }
.p2s-pk .pk-tray h3, .p2s-pk .pk-sheet h3 { margin: 0 0 2px; font-size: var(--text-md); }
.p2s-pk .pk-count { color: var(--pk-accent); }
.p2s-pk .pk-cap { color: var(--pk-muted); font-size: var(--text-sm); margin: 0 0 10px; }
.p2s-pk .pk-slot { display: grid; grid-template-columns: 62px minmax(0, 1fr) 36px; gap: 10px; align-items: center; border: 1px solid var(--pk-accent-line); background: var(--pk-accent-soft); border-radius: var(--radius-lg); padding: 8px; margin-bottom: 8px; }
.p2s-pk .pk-slot-empty { border: 1px dashed var(--pk-line-strong); background: transparent; display: block; color: var(--muted); font-size: var(--text-sm); padding: 14px; text-align: center; }
.p2s-pk .pk-x { width: 36px; height: 36px; margin: 0; padding: 0; border: 0; background: transparent; border-radius: var(--radius); cursor: pointer; color: var(--pk-muted); font-size: var(--text-xl); line-height: 1; }
.p2s-pk .pk-x:hover { background: #e7dcfb; color: var(--color-danger); }
.p2s-pk .pk-s-name { font-weight: 600; font-size: var(--text-base); overflow-wrap: anywhere; }
.p2s-pk .pk-s-sub { color: var(--pk-muted); font-size: var(--text-sm); }
.p2s-pk .pk-cta { width: 100%; margin: 4px 0 0; border: 0; border-radius: var(--radius-lg); background: var(--pk-accent); color: #fff; font: inherit; font-weight: 700; cursor: pointer; }
.p2s-pk .pk-cta:disabled { background: #cfc6e6; cursor: not-allowed; }
.p2s-pk .pk-bar { display: none; }
.p2s-pk.pk-m .pk-bar { display: flex; position: sticky; bottom: 0; z-index: 5; align-items: center; gap: 10px; background: #fff; border-top: 1px solid var(--pk-line); padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px)); box-shadow: 0 -6px 18px rgba(34,31,54,.08); margin-top: 8px; border-radius: var(--radius-lg) 12px 0 0; }
.p2s-pk.pk-m .pk-tray-wrap { display: none; }
.p2s-pk .pk-stack { display: flex; }
.p2s-pk .pk-stack .pk-thumb { width: 38px; height: 38px; border-radius: 50%; border: 2px solid #fff; margin-left: -10px; }
.p2s-pk .pk-stack .pk-thumb:first-child { margin-left: 0; }
.p2s-pk .pk-stack .pk-thumb-none { font-size: 0; }
.p2s-pk .pk-bar-count { flex: 1; min-width: 0; font-size: var(--text-md); line-height: 1.3; white-space: nowrap; }
.p2s-pk .pk-bar .pk-cta { white-space: nowrap; }
.p2s-pk .pk-stack .pk-thumb-none { background: var(--surface-subtle); border-style: solid; }
.p2s-pk .pk-bar-count small { display: block; color: var(--pk-muted); font-size: var(--text-sm); }
.p2s-pk .pk-peek { margin: 0; border: 1px solid var(--pk-line-strong); background: #fff; color: inherit; border-radius: var(--radius-lg); padding-left: 12px; padding-right: 12px; font: inherit; font-size: var(--text-base); cursor: pointer; }
.p2s-pk .pk-bar .pk-cta { width: auto; margin: 0; padding: 5px 16px; min-height: 34px; }
.p2s-pk .pk-sheet-back { position: fixed; inset: 0; background: rgba(29,27,46,.45); z-index: 1040; display: none; }
.p2s-pk .pk-sheet-back.open { display: block; }
.p2s-pk .pk-sheet { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; border-radius: var(--radius-lg) 18px 0 0; padding: 8px 16px calc(18px + env(safe-area-inset-bottom, 0px)); max-height: 82vh; overflow-y: auto; z-index: 1041; transform: translateY(102%); transition: transform .22s ease; visibility: hidden; }
.p2s-pk .pk-sheet.open { transform: none; visibility: visible; }
.p2s-pk:not(.pk-m) .pk-sheet, .p2s-pk:not(.pk-m) .pk-sheet-back { display: none; }
.p2s-pk .pk-grab { width: 40px; height: 4px; border-radius: 4px; background: var(--pk-line-strong); margin: 4px auto 12px; }
.p2s-pk .pk-field { margin-bottom: 12px; }
.p2s-pk .pk-field label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 4px; }
.p2s-pk .pk-sheet select { width: 100%; min-height: 46px; border: 1px solid var(--pk-line-strong); border-radius: var(--radius-lg); padding: 0 10px; font: inherit; background-color: #fff; }
.p2s-pk .pk-acts { display: flex; gap: 10px; margin-top: 8px; }
.p2s-pk .pk-acts .pk-cta { flex: 1; margin: 0; }
.p2s-pk .pk-ghost { flex: 0 0 auto; min-height: 48px; border: 1px solid var(--pk-line-strong); background: #fff; color: inherit; border-radius: var(--radius-lg); padding: 0 16px; font: inherit; cursor: pointer; }
.p2s-pk .pk-toast { position: fixed; left: 50%; transform: translateX(-50%); bottom: 90px; background: #221f36; color: #fff; border-radius: var(--radius-lg); padding: 9px 14px; font-size: var(--text-base); opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 1050; max-width: 90vw; text-align: center; }
.p2s-pk .pk-toast.show { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .p2s-pk .pk-sheet, .p2s-pk .pk-toast { transition: none; } }
@media print { .p2s-pk { display: none !important; } }
.p2s-link-picker { margin-bottom: 18px; }
.pr-manual-picker { margin-bottom: 16px; }

/* ---- Budget per project (marketing/project_budget.php) ---------------------- */
.pb-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 6px 0 14px; }
.pb-tab { padding: 9px 14px; border-radius: var(--radius) 9px 0 0; color: var(--muted); text-decoration: none; font-size: var(--text-base); }
.pb-tab.on { color: var(--primary); font-weight: 700; border-bottom: 3px solid var(--primary); }
.pb-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; margin-bottom: 14px; }
.pb-step { display: flex; gap: 10px; align-items: flex-start; background: #fff; border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 10px 12px; font-size: var(--text-sm); }
.pb-step .n { flex: none; width: 24px; height: 24px; border-radius: 50%; background: rgba(var(--primary-rgb), 0.1); color: var(--primary); font-weight: 700; display: grid; place-items: center; font-size: var(--text-sm); }
.pb-step b { display: block; font-size: var(--text-base); }
.pb-step span:not(.n) { color: var(--muted); }
@media (max-width: 767.98px) { .pb-steps { display: none; } }
.pb-todo { border: 1px solid #f3d9a4; background: #fffaf0; border-radius: var(--radius-lg); padding: 12px 14px; margin-bottom: 14px; }
.pb-todo h2 { margin: 0 0 8px; font-size: var(--text-md); color: var(--color-amber-strong); }
.pb-todo-item { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; padding: 7px 0; border-top: 1px solid #f6e6c4; font-size: var(--text-base); }
.pb-todo-item:first-of-type { border-top: 0; }
.pb-toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 10px; }
.pb-toolbar input[type="search"] { flex: 1 1 220px; min-height: 42px; margin: 0; }
.pb-month { display: flex; align-items: center; gap: 8px; font-size: var(--text-base); margin: 0; }
.pb-month select { width: auto; min-height: 42px; margin: 0; }
.pb-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.pb-chip { border: 1px solid var(--border-strong); background: #fff; border-radius: 999px; padding: 5px 12px; min-height: 36px; font-size: var(--text-base); color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; }
.pb-chip span { opacity: .7; font-size: var(--text-sm); }
.pb-chip.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.pb-list { background: #fff; border: 1px solid var(--border-solid); border-radius: var(--radius-lg); overflow: hidden; }
.pb-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center; border-bottom: 1px solid var(--border-solid); padding-right: 12px; }
.pb-row:last-child { border-bottom: 0; }
.pb-row.sel { background: rgba(var(--primary-rgb), 0.1); }
.pb-main { display: grid; grid-template-columns: 56px minmax(160px, 1.3fr) minmax(200px, 1.6fr); gap: 14px; align-items: center; padding: 12px 0 12px 14px; color: inherit; text-decoration: none; min-width: 0; }
.pb-main:hover { background: var(--head-bg); }
.pb-main:focus-visible, .pb-chip:focus-visible, .pb-tab:focus-visible, .pb-close:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.pb-name { display: block; min-width: 0; }
.pb-name b { display: block; overflow-wrap: anywhere; }
.pb-name small { display: block; color: var(--muted); font-size: var(--text-sm); }
.pb-badges { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
.pb-badge { font-size: var(--text-2xs); font-weight: 600; line-height: 1.5; padding: 4px 10px; border-radius: 999px; white-space: nowrap; }
.pb-badge.warn { background: #fdf0df; color: var(--color-amber-strong); } .pb-badge.bad { background: #fde8e8; color: var(--color-danger); }
.pb-badge.good { background: #e3f4ec; color: var(--color-success); } .pb-badge.info { background: rgba(var(--primary-rgb), 0.1); color: var(--primary); }
.pb-meter { display: block; height: 9px; border-radius: var(--radius); background: #ece9f6; overflow: hidden; }
.pb-meter i { display: block; height: 100%; background: var(--primary); border-radius: var(--radius); }
.pb-meter.mid i { background: #d97706; } .pb-meter.over i { background: var(--color-danger); }
.pb-nums { display: flex; justify-content: space-between; gap: 8px; font-size: var(--text-sm); margin-top: 5px; color: var(--muted); }
.pb-nums b { color: #1d1b2e; font-variant-numeric: tabular-nums; }
.pb-nums b.pos, .pb-kpis .pos b { color: var(--color-success); } .pb-nums b.neg, .pb-kpis .neg b, td.neg { color: var(--color-danger); }
.pb-none { color: var(--muted); font-size: var(--text-sm); }
.pb-acts { display: flex; gap: 6px; }
.pb-empty { padding: 26px; text-align: center; color: var(--muted); }
@media (max-width: 991.98px) {
  .pb-row { grid-template-columns: 1fr; padding: 0 0 12px; }
  .pb-main { grid-template-columns: 48px 1fr; padding-right: 14px; }
  .pb-main .pb-meterwrap { grid-column: 1 / -1; }
  .pb-acts { padding-inline: 14px; } .pb-acts a { flex: 1; text-align: center; }
}
.pb-back { position: fixed; inset: 0; background: rgba(29,27,46,.4); z-index: 1030; }
.pb-panel { position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 100%); background: #f3f2f9; z-index: 1031; overflow-y: auto; box-shadow: -10px 0 30px rgba(34,31,54,.18); }
.pb-panel-head { display: flex; align-items: center; gap: 12px; padding: 16px; background: #fff; border-bottom: 1px solid var(--border-solid); position: sticky; top: 0; z-index: 2; }
.pb-panel-head h2 { margin: 0; font-size: var(--text-lg); flex: 1; overflow-wrap: anywhere; }
.pb-panel-head small { display: block; font-weight: 400; color: var(--muted); font-size: var(--text-sm); }
.pb-close { width: 40px; height: 40px; display: grid; place-items: center; font-size: 26px; line-height: 1; color: var(--muted); text-decoration: none; border-radius: var(--radius); }
.pb-close:hover { background: #f1eff8; }
.pb-panel-body { padding: 14px 16px 30px; display: grid; gap: 14px; }
.pb-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.pb-kpis > div { background: #fff; border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 10px; }
.pb-kpis small { display: block; color: var(--muted); font-size: var(--text-2xs); }
.pb-kpis b { font-size: var(--text-lg); font-variant-numeric: tabular-nums; }
.pb-panel-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.pb-card { background: #fff; border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 14px; }
.pb-card h3 { margin: 0 0 10px; font-size: var(--text-base); }
.pb-card h3 small { font-weight: 400; color: var(--muted); margin-left: 6px; }
.pb-form { display: grid; gap: 10px; }
.pb-form label { font-size: var(--text-sm); font-weight: 600; display: grid; gap: 4px; margin: 0; }
.pb-form input, .pb-form select { min-height: 44px; margin: 0; }
.pb-form-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.pb-note { color: var(--muted); font-size: var(--text-sm); margin: 0; }
.pb-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.pb-table th { text-align: left; font-weight: 600; color: var(--muted); font-size: var(--text-sm); padding: 6px; border-bottom: 1px solid var(--border-solid); }
.pb-table td { padding: 8px 6px; border-bottom: 1px solid var(--border-solid); }
.pb-table tr:last-child td { border-bottom: 0; } .pb-table .r { text-align: right; font-variant-numeric: tabular-nums; }
.pb-table td.pos { color: var(--color-success); }
.pb-hist { list-style: none; margin: 0; padding: 0; }
.pb-hist li { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 9px 0; border-top: 1px solid var(--border-solid); font-size: var(--text-sm); }
.pb-hist li:first-child { border-top: 0; }
.pb-hist .amt { font-weight: 700; min-width: 88px; font-variant-numeric: tabular-nums; }
.pb-inline { margin-left: auto; display: flex; gap: 6px; } .pb-inline form { margin: 0; }
.pb-editlink { margin-left: auto; font-size: var(--text-sm); }
.pb-panel .p2s-thumb { flex: none; }

.pb-reason { flex: 1 0 100%; color: #4b4763; font-size: var(--text-sm); background: #f7f6fb; border-radius: var(--radius); padding: 6px 10px; overflow-wrap: anywhere; white-space: pre-line; }
.pb-form textarea { min-height: 76px; margin: 0; padding: 8px 12px; }

/* Reason box next to a "ปฏิเสธ" button (see src/rejections.php) */
.reject-reason {
  width: 11rem;
  max-width: 100%;
  padding: 4px 8px;
  font-size: var(--text-base);
  margin: 0 6px 4px 0;
}
.p2s-rejection .inline-form { display: block; }

/* Bulk cover import (bu/project_covers.php) */
.p2s-bulk-drop { display: block; padding: 16px; border: 2px dashed var(--border); border-radius: var(--radius); margin-bottom: 12px; }
.p2s-bulk-drop input[type="file"] { display: block; margin-top: 8px; max-width: 100%; }
.p2s-bulk-rows { display: grid; gap: 8px; margin-bottom: 12px; }
.p2s-bulk-row { display: flex; gap: 12px; align-items: flex-start; padding: 8px; border: 1px solid var(--border); border-radius: var(--radius); }
.p2s-bulk-thumb { flex: none; width: 96px; height: 72px; object-fit: cover; border-radius: var(--radius); background: var(--surface-subtle); }
.p2s-bulk-body { flex: 1; min-width: 0; }
.p2s-bulk-name { font-size: var(--text-sm); font-weight: 600; overflow-wrap: anywhere; margin-bottom: 4px; }
.p2s-bulk-body select { width: 100%; max-width: 100%; }
.p2s-bulk-status { font-size: var(--text-sm); color: var(--muted); margin-top: 4px; }
.p2s-bulk-row.is-ok { border-color: var(--color-success); }
.p2s-bulk-row.is-ok .p2s-bulk-status { color: var(--color-success); }
.p2s-bulk-row.is-err, .p2s-bulk-row.is-dup { border-color: var(--color-danger); }
.p2s-bulk-row.is-err .p2s-bulk-status, .p2s-bulk-row.is-dup .p2s-bulk-status { color: var(--color-danger); }
@media (max-width: 575.98px) { .p2s-bulk-row { flex-direction: column; } .p2s-bulk-thumb { width: 100%; height: 140px; } }

/* Sidebar brand: the full logo at a size that can actually be read on the dark sidebar. */
.app-menu .navbar-brand-box .logo-lg img { height: 40px; width: auto; }
.app-menu .navbar-brand-box .logo-sm img { height: 30px; width: auto; }

/* An empty list is one quiet line, not a table header over a "nothing here" row. */
.p2s-empty-note { margin: 0 0 var(--space-lg); padding: 12px 16px; border: 1px dashed var(--border); border-radius: var(--radius); color: var(--muted); font-size: var(--text-sm); }

/* Investor overview (exec/investor.php) */
.inv-controls { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: center; justify-content: flex-end; }
.inv-window { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; width: auto; }
.inv-window a.is-current { background: var(--primary-soft); color: var(--primary-dark); font-weight: 700; }
.inv-grid .inv-half { grid-column: span 6; }
.inv-grid .inv-funnel-panel { grid-column: span 5; }
.inv-grid .inv-volume-panel { grid-column: span 7; }
.inv-funnel { list-style: none; margin: 0 0 var(--space-md); padding: 0; display: grid; gap: 12px; }
.inv-funnel-row { display: flex; justify-content: space-between; align-items: baseline; font-size: var(--text-sm); }
.inv-funnel-row strong { font-size: var(--text-md); }
.inv-funnel-track { height: 12px; border-radius: var(--radius); background: var(--head-bg); overflow: hidden; margin-top: 4px; }
.inv-funnel-bar { height: 100%; border-radius: var(--radius); background: var(--primary); }
.inv-stage-0 { opacity: .35; } .inv-stage-1 { opacity: .5; } .inv-stage-2 { opacity: .65; } .inv-stage-3 { opacity: .82; }
.inv-funnel-rate { margin-top: 2px; font-size: var(--text-sm); color: var(--muted); }
.inv-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); margin: 0 0 var(--space-sm); }
.inv-facts-stack { grid-template-columns: 1fr; gap: 0; }
.inv-facts div { padding: 10px 0; border-top: 1px solid var(--border); }
.inv-facts dt { font-size: var(--text-sm); color: var(--muted); font-weight: 600; }
.inv-facts dd { margin: 2px 0 0; font-size: var(--text-lg); font-weight: 700; }
.inv-facts-stack div { display: flex; justify-content: space-between; align-items: baseline; }
.inv-facts-stack dd { margin: 0; }
.inv-channels { margin-top: var(--space-xl); }
.inv-definitions { margin-top: var(--space-xl); padding: var(--space-lg); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.inv-definitions h2 { margin: 0 0 8px; font-size: var(--text-md); }
.inv-definitions ul { margin: 0; padding-left: 20px; color: var(--muted); font-size: var(--text-sm); display: grid; gap: 6px; }
.inv-definitions strong { color: var(--text); }
@media (max-width: 991.98px) {
  .inv-grid .inv-half, .inv-grid .inv-funnel-panel, .inv-grid .inv-volume-panel { grid-column: 1 / -1; }
}
@media print {
  .app-menu, #page-topbar, .vertical-overlay, .inv-controls, .p2s-breadcrumb, footer, .footer { display: none !important; }
  .main-content { margin-left: 0 !important; }
  .inv-page .exec-panel, .inv-page .exec-kpi-strip, .inv-definitions { break-inside: avoid; }
  body { background: #fff !important; }
}

/* the window picker is its own little control, not the month stepper it borrows its border from */
.exec-period-nav.inv-window { display: inline-flex; grid-template-columns: none; }
.inv-window a { padding: 0 16px; display: flex; align-items: center; white-space: nowrap; }
.inv-window a + a { border-left: 1px solid var(--border); }
.inv-page .exec-panel-header { display: block; }
.inv-page .exec-panel-header p { margin: 0; color: var(--muted); font-size: var(--text-sm); }
.inv-grid .inv-volume-panel .exec-chart { height: 380px; }
.inv-partial { margin: 0 0 var(--space-md); }
@media (max-width: 767.98px) { .inv-controls { justify-content: flex-start; } }
.inv-window a { font-size: var(--text-sm); font-weight: 600; }
.inv-grid .inv-volume-panel .exec-chart { height: 420px; }
.inv-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.inv-facts-stack { grid-template-columns: 1fr; }
@media (max-width: 575.98px) { .inv-facts { grid-template-columns: 1fr 1fr; } }
.inv-facts.inv-facts-stack { grid-template-columns: 1fr; }

/* Project detail: back / edit / budget links as one row of actions, and the cover above the card */
.project-detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm) var(--space-md); margin: 0 0 var(--space-md); }

/* Settings: a table of contents at the top and a heading per group — eleven cards in one long list */
.settings-toc { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-md); margin: 0 0 var(--space-xl); }
.settings-toc-group { padding: var(--space-md); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); display: flex; flex-direction: column; gap: 4px; }
.settings-toc-group strong { font-size: var(--text-md); }
.settings-toc-group > span { font-size: var(--text-sm); color: var(--muted); }
.settings-toc-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.settings-toc-links a { padding: 4px 10px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
.settings-toc-links a:hover, .settings-toc-links a:focus-visible { text-decoration: underline; }
.settings-group-title { margin: var(--space-2xl) 0 var(--space-sm); padding-bottom: 8px; border-bottom: 2px solid var(--primary); font-size: var(--text-xl); font-weight: 700; scroll-margin-top: 80px; }
#group-brand { margin-top: 0; }

/* Velzon's app.min.css styles #status as its page-loader spinner (40x40, position:absolute, centred in the page).
   The Lead form's status <select> has that id, so it collapsed into an unusable 40px box. This is a form
   control, so undo the loader rules for it. */
select#status { position: static; left: auto; top: auto; margin: 4px 0 0; width: 100%; height: auto; } /* 4px: the gap under the label the other selects have */

/* Demand pool (bu/demand.php) */
.dm-filters { margin-bottom: var(--space-lg); display: grid; gap: var(--space-md); }
.dm-budget { display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: end; }
.dm-budget label { font-weight: 600; margin: 0; } /* a global label margin pushed the buttons ~8px below the inputs' bottom edge */
.dm-budget label input { font-weight: 400; }
.dm-budget label { display: grid; gap: 4px; font-size: var(--text-sm); }
/* The filter is not a .project-form, so its inputs had the browser default (~25px tall): too small to tap. */
.dm-budget input[type="number"] {
  width: 8rem; min-height: 44px; padding: 9px 12px; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
}
.dm-presets .status-pill {
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
}
.dm-presets { display: flex; flex-wrap: wrap; gap: 8px; }
.dm-cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-lg); margin-bottom: var(--space-md); }
.dm-card h2 { margin: 0 0 4px; font-size: var(--text-md); }
.dm-num { display: block; font-size: var(--text-3xl); font-weight: 700; line-height: 1.1; }
.dm-of { display: block; font-size: var(--text-sm); color: var(--muted); font-weight: 600; }
.dm-card.is-open { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary) inset; }
.dm-past { background: var(--head-bg); }
.dm-gap { margin: 0 0 var(--space-lg); }
.dm-grid .dm-half { grid-column: span 6; }
.dm-grid .dm-wide { grid-column: 1 / -1; }
.dm-list { margin-top: var(--space-xl); }
.dm-signals { display: flex; flex-wrap: wrap; gap: 4px; }
.dm-none { color: var(--muted); }
@media (max-width: 991.98px) { .dm-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } .dm-grid .dm-half { grid-column: 1 / -1; } }
@media (max-width: 575.98px) { .dm-cards { grid-template-columns: 1fr; } .dm-budget input[type="number"] { width: 100%; } .dm-budget label { flex: 1 1 8rem; } }

/* Demand page fix found by rendering: .field-hint's -8px top margin squeezes it against the number above. */
.dm-card .field-hint, .dm-page .exec-dashboard-header .field-hint { margin: var(--space-2xs) 0 var(--space-sm); }
.dm-card a { font-weight: 600; }

/* Smart zone search: one row with the budget fields; picked zones are chips under it. */
.dm-zonesearch { display: grid; gap: 4px; flex: 1 1 18rem; min-width: 0; max-width: 26rem; font-size: var(--text-sm); }
.dm-zonesearch > label { font-weight: 600; margin: 0; }
.dm-zonesearch-box { position: relative; }
.dm-zonesearch-box input[type="search"] {
  width: 100%; min-height: 44px; padding: 9px 12px; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
}
.dm-zonesearch-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 4px); margin: 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(30, 41, 59, 0.14);
  max-height: 20rem; overflow-y: auto;
}
.dm-zonesearch-list li { display: grid; gap: 1px; padding: 9px 10px; border-radius: var(--radius); cursor: pointer; font-size: var(--text-sm); }
.dm-zonesearch-list li small { color: var(--muted); font-size: var(--text-sm); }
.dm-zonesearch-list li.is-active { background: rgba(var(--primary-rgb), 0.08); }
.dm-zonesearch-list li.dm-zonesearch-none { color: var(--muted); cursor: default; }
.dm-zonechips { display: flex; flex-wrap: wrap; gap: 8px; }
.dm-zonechips[hidden], .dm-zonesearch-list[hidden] { display: none; }
.dm-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 12px; border-radius: 999px;
  background: rgba(var(--primary-rgb), 0.08); color: var(--primary); font-size: var(--text-sm); font-weight: 600;
}
.dm-chip button { border: 0; background: transparent; color: inherit; cursor: pointer; font-size: 1.1rem; line-height: 1; width: 24px; height: 24px; border-radius: 50%; }
.dm-chip button:hover { background: rgba(var(--primary-rgb), 0.16); }

/* Project detail: inventory match + what-if panel */
.pdm-hint { margin: 0 0 8px; }
.pdm-panel { margin-top: var(--space-md); padding: var(--space-md); border: 1px solid var(--border); border-radius: var(--radius-lg); display: grid; gap: 8px; }
.pdm-panel-title { font-weight: 600; }
.pdm-controls { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-md); align-items: end; }
.pdm-controls label { display: grid; gap: 4px; font-size: var(--text-sm); }
.pdm-controls select, .pdm-controls input { min-height: 44px; width: 100%; }
.pdm-out { margin: 0; }
.pdm-disclaimer { margin: 0; }

/* Touch targets (audit 2026-09-20): text links, anchor chips and the target field were 20-29px tall. On phones and
   other coarse pointers they get a 44px hit area; desktops keep the compact look. */
@media (max-width: 767.98px), (pointer: coarse) {
  .more-link { display: inline-flex; align-items: center; min-height: 44px; }
  .settings-toc-links a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; }
  .settings-toc-links { gap: 8px; }
  .target-input { min-height: 44px; box-sizing: border-box; }
  .target-save { min-width: 44px; }
}

/* ---- Audit 2026-09-20 fixes ---- */
/* Footer text was Velzon's #98a6ad on white (2.5:1). */
html[data-layout] .footer, .footer { color: var(--muted); }
/* Avatar upload row: the file input plus its button overflowed a 390px screen by 47px. */
#avatar { max-width: 100%; min-width: 0; }
/* Coarse pointers / phones: everything a finger has to hit is at least 44px (native checkboxes and radios get a
   padded label hit area via their wrapping label, the rest get real height). */
@media (max-width: 767.98px), (pointer: coarse) {
  /* .link-btn used to be a plain underlined text link with only 4px
     vertical padding (~21px tall), genuinely needing this 44px floor on
     touch devices — but every .link-btn is a padded, min-width pill now
     (2026-09-24 badge-style work), a real visible/tappable box on its
     own. This rule only ever mattered for elements carrying the literal
     .link-btn CLASS; several action links (e.g. site_visits.php's
     แก้ไข) get the same pill via the icon-only :has() selector without
     that class and were never affected by it, so on any touch device or
     ≤767.98px this rule alone made some action pills 44px tall and
     others ~31px for no reason a user could see — not a spacing choice,
     an accident of which selector happened to match (owner screenshot,
     "ทำทุก action pills class link-btn ให้มีขนาดความสูงเท่า ปุ่ม แก้ไข
     ทั้งเว็บ"). Removed rather than extended to the icon-only selector
     too, matching the shorter height the owner pointed at as correct. */
  .p2s-view-switch-btn { min-height: 44px; display: inline-flex; align-items: center; }
  .password-toggle { min-width: 44px; min-height: 44px; }
  #page-topbar .btn.btn-icon { min-width: 44px; min-height: 44px; }
  .pagination-per-page select { min-height: 44px; }
  .project-form input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]), .project-form select { min-height: 44px; }
  .project-form label:has(> input[type="checkbox"]), .project-form label:has(> input[type="radio"]) { min-height: 44px; display: flex; align-items: center; gap: 8px; }
}

/* The list search boxes now say "ชื่อ/เบอร์/อีเมล" and Marketing's row also holds a channel select and a button: at phone
   width the box was squeezed to ~137px and its placeholder was cut off. Let the row wrap so the box keeps a readable
   width and the select and button drop below it. */
@media (max-width: 575.98px) {
  .search-form { flex-wrap: wrap; }
  .search-form input[type="search"] { flex: 1 1 200px; }
  .search-form select { flex: 0 1 auto; min-width: 120px; }
}

/* Access-log page (exec/audit_log.php): the filter form is not a .project-form, so its controls had the browser's default
   height (23-36px). Same 44px controls as the demand page filter; on a phone each control takes the full row. */
.al-filters { display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: end; margin-bottom: var(--space-lg); }
.al-filters label { display: grid; gap: 4px; font-size: var(--text-sm); }
.al-filters select, .al-filters input {
  min-height: 44px; padding: 9px 12px; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: var(--radius); font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
}
@media (max-width: 575.98px) {
  .al-filters label { width: 100%; }
  .al-filters select, .al-filters input { width: 100%; }
}

/* Pool page (admin/pool.php) and the "move to the pool?" box on the users page: controls that are not inside a
   .project-form get 44px height; the reason box takes the spare width; the box's links look like buttons. */
.pool-controls { display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: end; margin: var(--space-md) 0 var(--space-xl); }
.pool-controls label { display: grid; gap: 4px; font-size: var(--text-sm); }
.pool-controls label:has(input[name="reason"]) { flex: 1 1 260px; }
.pool-controls select, .pool-controls input[type="text"] {
  min-height: 44px; padding: 9px 12px; box-sizing: border-box; width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius); font-size: var(--text-base); font-family: inherit; color: var(--text); background: var(--surface);
}
.pool-controls .field-hint { margin: 0; align-self: center; }
.pool-confirm { display: flex; flex-wrap: wrap; gap: 8px; }
.pool-confirm a.btn-secondary { text-decoration: none; }
.pool-confirm a.btn-secondary { color: var(--text); }
@media (max-width: 575.98px) {
  .pool-confirm button, .pool-confirm a.btn-secondary { width: 100%; }
  .pool-controls label, .pool-controls button { width: 100%; flex: 1 1 100%; }
}

/* Duplicate customer: ask to join / Admin override (sale-access phase 2) */
.p2s-join-form, .p2s-join-inline { display: flex; flex-direction: column; gap: 8px; margin: 8px 0 16px; max-width: 560px; }
.p2s-join-form label { font-weight: 600; }
.p2s-join-form textarea, .p2s-join-inline input[type="text"] { width: 100%; min-height: 44px; box-sizing: border-box; }
.p2s-join-form button, .p2s-join-inline button {
  padding-left: 18px; padding-right: 18px; border-radius: var(--radius);
  font-size: var(--text-base); font-weight: 600; border: 1px solid var(--primary);
  background: var(--primary); color: #fff; cursor: pointer;
}
.p2s-join-form button:hover, .p2s-join-inline button:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.p2s-join-form button:disabled, .p2s-join-inline button:disabled { opacity: .6; cursor: not-allowed; }
#force_reason { min-height: 44px; }
.project-form label.p2s-force-row { display: flex; align-items: center; gap: 10px; min-height: 44px; }
@media (max-width: 575.98px) {
  .inline-form .reject-reason { min-height: 44px; }
}

/* Masked customer phone + "แสดงเบอร์" (sale-access phase 2) */
.p2s-phone { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.p2s-phone-masked, .p2s-phone-revealed { font-variant-numeric: tabular-nums; }
.p2s-reveal {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 8px 14px;
  border-radius: var(--radius); border: 1px solid var(--primary); background: #fff; color: var(--primary);
  font-size: var(--text-sm); font-weight: 600; cursor: pointer; text-decoration: none; white-space: nowrap;
}
.p2s-reveal:hover { background: var(--primary); color: #fff; }
.p2s-reveal:disabled { opacity: .6; cursor: wait; }
a.p2s-reveal-call { background: var(--primary); color: #fff; }
a.p2s-reveal-call:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.p2s-reveal-error { flex-basis: 100%; color: var(--color-danger); }
.p2s-lead-summary-main .p2s-phone { font-size: var(--text-lg); }

/* Central search: the top-bar box with a live suggestion dropdown (top-search.js), the icon + full-width row on phones, and /search.php.
   Removed 2026-09-21, brought back 2026-10-02 as a typeahead. The dropdown is position:fixed (an ancestor clips overflow). */
.p2s-topsearch { align-items: center; gap: 4px; margin-right: 8px; }
.p2s-topsearch input[type="search"] { width: 240px; padding: 0 12px; box-sizing: border-box; }
.p2s-topsearch-btn { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border: 0; background: transparent; color: var(--text); cursor: pointer; border-radius: var(--radius); }
.p2s-topsearch-btn svg, .p2s-search-icon svg { width: 20px; height: 20px; }
.p2s-search-icon { min-width: 44px; min-height: 44px; }
.p2s-topsearch-overlay { position: fixed; top: 0; left: 0; right: 0; height: 70px; z-index: 1100; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); padding: 0 12px; }
.p2s-topsearch-overlay[hidden] { display: none !important; }
.p2s-topsearch-overlay form { display: flex; align-items: center; gap: 8px; height: 100%; }
.p2s-topsearch-overlay input[type="search"] { flex: 1 1 auto; min-height: 44px; box-sizing: border-box; }
.p2s-ts-dropdown { position: fixed; z-index: 1110; max-height: min(70vh, 420px); overflow-y: auto; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); }
.p2s-ts-dropdown[hidden] { display: none !important; }
.p2s-ts-list { list-style: none; margin: 0; padding: 4px 0; }
.p2s-ts-heading { display: flex; align-items: baseline; gap: 8px; padding: 8px 12px 2px; font-size: var(--text-2xs); font-weight: 600; color: var(--muted); }
.p2s-ts-group-count { font-variant-numeric: tabular-nums; opacity: .85; }
.p2s-ts-all { margin-left: auto; font-weight: 600; color: var(--primary); text-decoration: none; background: none; border: 0; padding: 0; cursor: pointer; font-size: inherit; }
.p2s-ts-all:hover { text-decoration: underline; }
.p2s-ts-option { display: block; padding: 0; cursor: pointer; }
.p2s-ts-link { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding: 8px 12px; color: inherit; text-decoration: none; }
.p2s-ts-mark { background: var(--primary-soft); color: inherit; padding: 0; border-radius: 2px; font-weight: 700; }
.p2s-ts-near { font-size: var(--text-2xs); padding: 0 6px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
.p2s-ts-add a { color: var(--primary); font-weight: 600; }
.p2s-ts-loading { font-size: var(--text-sm); }
.p2s-ts-option.is-active { background: var(--primary-soft); }
.p2s-ts-option.is-disabled { cursor: default; opacity: 0.7; }
.p2s-ts-main { font-weight: 600; color: var(--text); }
.p2s-ts-badge { font-size: var(--text-2xs); color: var(--primary); }
.p2s-ts-sub { flex-basis: 100%; font-size: var(--text-sm); color: var(--muted); }
.p2s-ts-msg { padding: 10px 12px; color: var(--muted); }
.p2s-ts-more { border-top: 1px solid var(--border); }
.p2s-ts-dropdown.is-stale .p2s-ts-list { opacity: .55; }
.p2s-ts-all-row .p2s-ts-main { color: var(--primary); font-weight: 600; font-size: var(--text-sm); }
.p2s-ts-more a { display: block; padding: 10px 12px; font-weight: 600; color: var(--primary); }
.p2s-ts-more a:hover { background: var(--primary-soft); }
/* Central search page (/search.php). */
.p2s-search-page-form { display: flex; gap: 8px; margin: 8px 0 12px; max-width: 640px; }
.p2s-search-page-form input[type="search"] { flex: 1 1 auto; min-height: 44px; box-sizing: border-box; }
.p2s-search-page-form button { flex: 0 0 auto; }
.p2s-search-results { list-style: none; margin: 12px 0; padding: 0; display: flex; flex-direction: column; gap: 10px; max-width: 760px; }
.p2s-search-row { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; }
.p2s-search-main { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.p2s-search-name { font-weight: 700; display: inline-flex; align-items: center; min-height: 44px; }
.p2s-search-row .field-hint { margin: 6px 0 0; }
.p2s-search-meta { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 6px; color: var(--muted); font-size: var(--text-sm); }
.p2s-search-signal { max-width: 760px; }
.p2s-search-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 12px 0 24px; }
.p2s-search-pager a { min-height: 44px; display: inline-flex; align-items: center; }

/* Co-agent contact details a sale may not see (sale-access phase 3) */

/* Use requests for a co-agent (sale-access phase 3) */
.p2s-use-details { display: block; margin: 4px 0; }
.p2s-use-details > summary { cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; }
/* ...except when the summary is itself a .link-btn pill (ขอใช้, ปฏิเสธ):
   the 44px touch-target floor above then fights the pill's own ~31px
   sizing, standing out taller than every other action pill on the same
   row (owner: "ทำทุก action pills ... ให้มีขนาดความสูงเท่า ปุ่ม แก้ไข",
   2026-09-24) — .link-btn's own padding is already an adequate target,
   consistent with how a link-btn <button> submit sits inside this same
   details panel without the same override. */
.p2s-use-details > summary.link-btn { min-height: 0; }
.p2s-use-details .p2s-join-form { margin-top: 8px; }

/* ปฏิเสธ (reject) reason box, revealed on click rather than sitting empty
   next to every row by default (2026-09-24: "จะได้ไม่รกหน้าจอ" — so it
   doesn't clutter the screen). Reuses .p2s-use-details for the disclosure
   itself; its own form here is a plain inline row, not .p2s-join-form's
   boxed request-a-favor look, since a reject confirm isn't a positive ask.
   The `display` below is scoped to details[open] on purpose: a bare
   `.p2s-reject-form { display: flex }` is MORE specific than the browser's
   own `details:not([open]) > *:not(summary) { display: none }` rule, so it
   would win and keep the reason box visible even while closed — exactly
   the clutter this change exists to remove. */
.p2s-use-details[open] .p2s-reject-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.p2s-use-details[open] .p2s-reject-form .reject-reason { flex: 1 1 200px; min-width: 0; }
.p2s-use-block { margin: 8px 0 16px; }
.p2s-grant-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.p2s-grant-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* Central search tabs + row actions (sale-access phase 3) */
.p2s-search-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 12px; }
.p2s-search-empty { max-width: 760px; }
.p2s-search-count { margin: 8px 0 0; }
.p2s-search-tabs a { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 8px 16px; border: 1px solid var(--border); border-radius: var(--radius); background: #fff; color: var(--text); text-decoration: none; font-weight: 600; }
.p2s-search-tabs a.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.p2s-tab-count { font-variant-numeric: tabular-nums; opacity: .85; }
.p2s-search-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.p2s-line-masked { font-variant-numeric: tabular-nums; }

/* Duplicate-phone report (Admin) */
.p2s-dup-filters select { min-height: 44px; box-sizing: border-box; flex: 0 1 auto; min-width: 160px; }
.p2s-dup-filters input[type="search"] { min-width: 0; }
.p2s-dup-group { max-width: 900px; }
.p2s-dup-group .section-title { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-variant-numeric: tabular-nums; }

/* Admin: undo a mistaken booking (lead_detail.php) */
.p2s-void-card { max-width: 760px; }
.p2s-void-card select, .p2s-void-card textarea { width: 100%; box-sizing: border-box; min-height: 44px; }
.p2s-void-card button[type="submit"] {
  padding-left: 18px; padding-right: 18px; border-radius: var(--radius); font-size: var(--text-base); font-weight: 600;
  border: 1px solid var(--color-danger); background: var(--color-danger); color: #fff; cursor: pointer;
}
.p2s-void-card button[type="submit"]:hover { filter: brightness(.92); }

/* Cancel a site visit (site_visit_form.php): its own card above the form, so it never reads as the form's "leave without saving" */
.p2s-visit-cancel { max-width: 760px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; border-left: 4px solid var(--color-danger); }
.p2s-visit-cancel-text { flex: 1 1 280px; min-width: 0; }
.p2s-visit-cancel-text .section-title { margin: 0 0 4px; }
.p2s-visit-cancel-text .field-hint { margin: 0; }
.p2s-visit-cancel form { margin: 0; }
.p2s-visit-cancel button[type="submit"] {
  min-height: 44px; padding: 10px 18px; border-radius: var(--radius); font-size: var(--text-base); font-weight: 600; white-space: nowrap;
  border: 1px solid var(--color-danger); background: var(--color-danger); color: #fff; cursor: pointer;
}
.p2s-visit-cancel button[type="submit"]:hover { filter: brightness(.92); }

/* Visit history under a name in the Lead / Co-Agent lists (src/visit_history.php) */
.p2s-contact-visits { margin: 14px 0; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--head-bg); }
.p2s-visits-next { margin: 0 0 8px; }
.p2s-visits-title { font-size: var(--text-md); margin: 0 0 6px; }
.p2s-visits-list { margin: 0; padding-left: 1.2em; }
.p2s-visits-list li { margin-bottom: 4px; }
.p2s-visits-more { margin-top: 6px; }
.p2s-visits-more summary { cursor: pointer; color: var(--color-purple); }

.p2s-more > summary { min-height: 44px; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.p2s-more { position: relative; }
.p2s-more > summary { list-style: none; cursor: pointer; }
.p2s-more > summary::-webkit-details-marker { display: none; }
.p2s-more-panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 280px; max-width: min(360px, 92vw); padding: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.p2s-more-item { display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; padding: 10px 12px; border-radius: var(--radius); color: var(--text); text-decoration: none; }
a.p2s-more-item:hover, a.p2s-more-item:focus-visible { background: var(--head-bg); }
.p2s-more-item.is-disabled { opacity: .6; cursor: not-allowed; }
.p2s-more-label { font-weight: 600; }
.p2s-more-badge { font-size: var(--text-2xs); font-weight: 600; color: var(--color-purple); align-self: center; white-space: nowrap; }
.p2s-more-caption { grid-column: 1 / -1; font-size: var(--text-2xs); color: var(--muted); }
/* Compact "⋮" variant of .p2s-more for a single row/card's actions (not the
   toolbar) — icon-only summary, and its panel holds raw .link-btn/<form>
   children directly rather than .p2s-more-item, since a row's actions are
   already link-btn (a plain nav link) or an inline-form submit button. */
.p2s-more-row > summary {
  min-height: 36px; min-width: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); font-size: 1.1em; line-height: 1;
}
.p2s-more-row .p2s-more-panel { min-width: 200px; padding: 4px; }
.p2s-more-row .p2s-more-panel .link-btn,
.p2s-more-row .p2s-more-panel form { display: block; width: 100%; }
.p2s-more-row .p2s-more-panel .link-btn {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 10px 12px; border-radius: var(--radius); min-height: auto;
}
.p2s-more-row .p2s-more-panel .link-btn:hover,
.p2s-more-row .p2s-more-panel form:hover .link-btn { background: var(--head-bg); }
/* Positioning (position:fixed, top/left) for .p2s-more-row panels is
   computed by list-menu.js on open, not by CSS — see that file. Any page
   using .p2s-more-row MUST load list-menu.js. */
.p2s-result-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 4px 0 12px; color: var(--muted); }
.p2s-filter-tag { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--text); text-decoration: none; font-size: var(--text-2xs); }
.p2s-filter-tag:hover { border-color: var(--color-purple); }
.p2s-filter-clear { font-size: var(--text-2xs); }
@media (max-width: 767.98px) {
  
  
  
  .p2s-more { flex: 0 0 auto; }
}
@media (max-width: 767.98px) {
  .p2s-chip-row { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .p2s-chip-row::-webkit-scrollbar { display: none; }
  .p2s-chip-row .status-pill { flex: 0 0 auto; }
}

/* Units a sale can tick when creating a customer link (public/assets/link-units.js). */
.p2s-link-units { margin: 0 0 18px; }
.p2s-link-units h4 { margin: 0 0 8px; font-size: var(--text-md); }
.p2s-link-units h4 small { font-weight: 400; color: var(--muted); }
.p2s-link-units-group { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 8px 12px 10px; margin: 0 0 10px; min-width: 0; }
.p2s-link-units-group legend { font-weight: 600; font-size: var(--text-md); line-height: 1.4; float: none; width: auto; padding: 0 6px; margin: 0; }
.p2s-link-form .p2s-link-units .p2s-link-unit { display: grid; grid-template-columns: auto 1fr; gap: 0 10px; align-items: center; padding: 8px 0; border-top: 1px solid var(--border); cursor: pointer; }
.p2s-link-form .p2s-link-units .p2s-link-unit:first-of-type { border-top: 0; }
.p2s-link-units .p2s-link-unit input { grid-row: 1 / 3; width: 20px; height: 20px; margin: 0; }
.p2s-link-units .p2s-link-unit-name { font-weight: 600; }
.p2s-link-units .p2s-link-unit-meta { grid-column: 2; font-size: var(--text-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

/* BU units page (public/bu/project_units.php). */
.p2s-unit-add-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 12px; }
.p2s-unit-add-row label { min-width: 0; }
.p2s-units-count { margin-top: 24px; }
@media (max-width: 767.98px) { .p2s-unit-add-row { grid-template-columns: 1fr; } }
.p2s-link-units .p2s-link-unit-tag { font-style: normal; font-size: var(--text-2xs); color: var(--muted); margin-left: 6px; }
.p2s-link-units .p2s-link-unit:has(input[data-unavailable]) { opacity: .6; cursor: not-allowed; }

/* A masked contact field (Lead not the viewer's own — src/visibility.php lead_is_mine()): styled like a status
   badge but with a lock glyph, so "this is hidden on purpose" reads at a glance instead of looking like a plain
   muted placeholder. Amber: the same "something is withheld, not broken" meaning status-contact already uses. */
.p2s-masked {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px 3px 8px; border-radius: 999px;
  font-size: var(--text-2xs); font-weight: 700;
  border: 1px solid var(--color-amber-deep); color: var(--color-amber-deep); background: rgba(217,119,6,0.08);
}
.p2s-masked svg { width: 11px; height: 11px; flex: none; }
.p2s-masked-by { display: block; margin-top: 3px; color: var(--muted); font-weight: 400; font-size: var(--text-sm); }

/* "Mine" vs someone else's, at a glance in the name list (leads.php, co_agents.php): the avatar keeps the brand
   purple for the viewer's own records; anyone else's fades to neutral gray, same idea as the p2s-masked chip
   (withheld/not-mine reads as muted, not as an error). */
.p2s-contact-avatar.is-other { background: var(--color-gray); }

/* เอกสารหลังจอง (lead_detail.php) */
.p2s-doc-card .section-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.p2s-doc-method { margin-bottom: var(--space-md); }
.p2s-doc-group { margin: var(--space-md) 0 var(--space-xs); font-size: var(--text-sm); color: var(--muted); font-weight: 700; }
.p2s-doc-items { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.p2s-doc-item { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); flex-wrap: wrap; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.p2s-doc-item.is-received { background: var(--primary-soft); }
.p2s-doc-item.is-na { opacity: .65; }
.p2s-doc-name { min-width: 0; flex: 1 1 260px; font-size: var(--text-sm); }
.p2s-doc-link { margin: var(--space-md) 0; padding: var(--space-sm) 0; border-top: 1px dashed var(--border); border-bottom: 1px dashed var(--border); }
.p2s-doc-ready { font-size: var(--text-2xs); font-weight: 600; color: var(--color-success); border: 1px solid currentColor; border-radius: 999px; padding: 0 8px; margin-left: 4px; white-space: nowrap; }
.p2s-doc-name .field-hint { margin: 2px 0 0; }
.p2s-doc-optional { font-size: var(--text-2xs); color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 0 8px; margin-left: 4px; }
.p2s-doc-ticks { display: flex; gap: var(--space-sm); flex-wrap: wrap; }
.p2s-doc-tick { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font-size: var(--text-sm); cursor: pointer; }
.p2s-doc-tick input { width: 20px; height: 20px; margin: 0; }
.p2s-doc-state { font-size: var(--text-sm); color: var(--muted); }
.p2s-doc-list textarea { width: 100%; }
.p2s-timeline-documents::before { background: var(--color-teal-deep); }

.p2s-doc-files { margin-top: 4px; font-size: var(--text-sm); }
.p2s-doc-files ul { list-style: none; margin: 4px 0 0; padding: 0; display: grid; gap: 4px; }
.p2s-doc-files li { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; }
.p2s-doc-files a { overflow-wrap: anywhere; }
.p2s-doc-filecount { font-weight: 600; }
.p2s-doc-by { font-size: var(--text-2xs); font-weight: 600; color: var(--muted); border: 1px solid currentColor; border-radius: 999px; padding: 0 8px; white-space: nowrap; }
.p2s-doc-by.is-staff { color: var(--color-blue); }
.p2s-doc-staffup { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 6px; }
.p2s-doc-delreq { font-size: var(--text-2xs); font-weight: 600; color: var(--color-danger); border: 1px solid currentColor; border-radius: 999px; padding: 0 8px; white-space: nowrap; }

/* Multi-select bar on bu/projects.php (projects-bulk.js). position:fixed so no overflow-clipping ancestor can cut it. */
.p2s-pb-th, .p2s-pb-cell { width: 44px; padding-left: 4px; padding-right: 4px; }
.p2s-pb-tap { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: 0; cursor: pointer; }
.p2s-pb-tap input { width: 20px; height: 20px; margin: 0; cursor: pointer; }
.p2s-mobile-select-all input { width: 20px; height: 20px; }
.p2s-bulk-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1020;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--surface); border-top: 1px solid var(--border);
  box-shadow: 0 -6px 20px rgba(34, 31, 54, 0.14);
}
.p2s-bulk-bar[hidden] { display: none; }
.p2s-bulk-count { font-weight: 700; margin-right: 4px; }
.p2s-bulk-group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.p2s-bulk-group label { margin: 0; font-size: var(--text-sm); }
.p2s-bulk-bar select { min-height: 44px; }
.p2s-bulk-bar select { max-width: 100%; }
body.p2s-bulk-open { padding-bottom: 140px; }
/* A disabled .btn-secondary in the theme fills with its own text colour, so the label vanished: keep it readable, just faded. */
.p2s-bulk-bar .p2s-bulk-btn:disabled { background: var(--surface) !important; color: var(--muted) !important; border-color: var(--border) !important; opacity: .6; cursor: not-allowed; }
/* On desktop the sidebar stays on screen: start the bar after it instead of covering the menu. */
@media (min-width: 1025px) {
  .p2s-bulk-bar { left: var(--vz-vertical-menu-width, 250px); }
  html[data-sidebar-size="sm"] .p2s-bulk-bar, html[data-sidebar-size="sm-hover"] .p2s-bulk-bar { left: var(--vz-vertical-menu-width-sm, 70px); }
}
.p2s-bulk-skipped { margin: 6px 0 0; padding-left: 20px; }
@media (max-width: 575.98px) {
  .p2s-bulk-bar { max-height: 60vh; overflow-y: auto; }
  .p2s-bulk-bar { padding-top: 8px; gap: 6px 10px; }
  .p2s-bulk-group { display: flex; flex-wrap: nowrap; width: 100%; }
  .p2s-bulk-group select { flex: 1 1 120px; min-width: 0; }
  /* The select's first option already reads "— เลือก… —": drop the visible label (kept for screen readers) so each action is one 44px row, not two. */
  .p2s-bulk-group label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .p2s-bulk-group .p2s-bulk-btn { flex: 0 0 auto; }
}

/* projects.php: "โครงการประจำเดือน" toggle as a star at the front of the row (owner 2026-10-02):
   featured = solid red star, not featured = outline only. */
.p2s-star-cell { width: 48px; text-align: center; }
th.p2s-star-cell svg { width: 16px; height: 16px; color: var(--muted); vertical-align: middle; }
.p2s-star-btn { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0; margin: 0;
  background: none; border: none; border-radius: 50%; cursor: pointer; color: var(--muted); }
.p2s-star-btn svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; }
.p2s-star-btn:hover { background: var(--head-bg); color: var(--color-danger); }
.p2s-star-btn.is-on { color: var(--color-danger); }
.p2s-star-btn.is-on svg { fill: currentColor; }
.p2s-star-btn.is-static { cursor: default; }
.p2s-star-btn.is-static:hover { background: none; }
/* The star is an icon-only toggle, not a pill: the action-icon pill rule above excludes .p2s-star-btn (2026-10-09; it used to be
   undone here with a stack of !important). */
/* 2026-10-02 owner feedback: star too big, columns too far apart — keep the checkbox and star columns as narrow as their content
   (width:1% + nowrap shrinks a table column to fit), star the same size as the header icon. Tap area stays usable (32px). */
.data-table th.p2s-star-cell, .data-table td.p2s-star-cell, .data-table th.p2s-pb-th, .data-table td.p2s-pb-cell {
  width: 1%; white-space: nowrap; padding-left: 4px !important; padding-right: 4px !important;
}
button.p2s-star-btn:has(> .p2s-action-icon), span.p2s-star-btn:has(> .p2s-action-icon) { width: 32px; min-width: 32px; height: 32px; min-height: 32px; gap: 0; }
.p2s-star-btn .p2s-action-icon, .p2s-star-btn svg { width: 16px; height: 16px; margin: 0; }
/* 2nd pass (owner screenshot 18:42): header star was left-aligned while the row star was centred, so the column looked skewed;
   and the checkbox / star / No. columns still had wide gutters. Centre both stars, cut the gutters, shrink No. to its digits. */
.data-table th.p2s-star-cell, .data-table td.p2s-star-cell { text-align: center; padding-left: 0 !important; padding-right: 0 !important; }
.data-table th.p2s-star-cell svg { display: block; margin: 0 auto; }
.data-table td.p2s-star-cell .inline-form { display: inline-block; margin: 0; }
.data-table td.p2s-star-cell .p2s-star-btn { vertical-align: middle; }
.data-table th.p2s-pb-th, .data-table td.p2s-pb-cell { padding-right: 0 !important; }
.data-table th.p2s-star-cell + th.num, .data-table td.p2s-star-cell + td.num { width: 1%; white-space: nowrap; padding-left: 4px !important; }

/* projects.php on a phone (owner screenshot 18:46): the star cell borrowed the header "โครงการประจำเดือน" as its label and
   overlapped it, and the cover sat as a tiny thumb beside a "ปก" label. Cover = full-width banner on top of the card (no label,
   hidden when there is no photo); star = icon in the card's top-right corner (no label). */
@media (max-width: 575.98px) {
  .data-table-cards tr:has(> td.p2s-star-cell), .data-table-cards tr:has(> td.p2s-thumb-cell) { position: relative; overflow: hidden; }
  table.data-table-cards td.p2s-thumb-cell {
    order: -2; display: block; width: auto; padding: 0 !important;
    margin: calc(-1 * var(--space-sm)) calc(-1 * var(--space-md)) 10px;
  }
  .data-table-cards td.p2s-thumb-cell::before, .data-table-cards td.p2s-star-cell::before { display: none !important; content: none !important; }
  .data-table-cards td.p2s-thumb-cell .p2s-thumb { width: 100%; height: 160px; border-radius: 0; }
  .data-table-cards td.p2s-thumb-cell:has(.p2s-thumb-empty) { display: none; }
  table.data-table-cards td.p2s-star-cell {
    position: absolute; top: 6px; right: 6px; z-index: 1; display: block; width: auto; padding: 0 !important;
  }
  .data-table-cards td.p2s-star-cell .p2s-star-btn { background: rgba(255, 255, 255, .92); box-shadow: 0 1px 4px rgba(0, 0, 0, .18); }
  /* keep the title clear of the star when there is no banner above it */
  .data-table-cards tr:has(> td.p2s-star-cell) td.card-title { padding-right: 40px; }
}
@media (max-width: 575.98px) {
  /* the bulk-select checkbox: top-left corner of the card, opposite the star, no "เลือก" label */
  .data-table-cards tr:has(> td.p2s-pb-cell) { position: relative; }
  table.data-table-cards td.p2s-pb-cell {
    position: absolute; top: 6px; left: 6px; z-index: 1; display: block; width: auto; padding: 0 !important;
  }
  .data-table-cards td.p2s-pb-cell::before { display: none !important; content: none !important; }
  .data-table-cards td.p2s-pb-cell .p2s-pb-tap { width: 36px; min-width: 36px; height: 36px; min-height: 36px; border-radius: 50%;
    background: rgba(255, 255, 255, .92); box-shadow: 0 1px 4px rgba(0, 0, 0, .18); }
  .data-table-cards tr:has(> td.p2s-pb-cell) td.card-title { padding-left: 40px; }
  /* with a cover banner the title sits below the photo, so it needs no room for the corner controls */
  .data-table-cards tr:has(> td.p2s-thumb-cell .p2s-thumb:not(.p2s-thumb-empty)) td.card-title { padding-left: 0; padding-right: 0; }
}

/* project_detail.php: ลิงก์ Landing page ของฉัน */
.p2s-landing-card { margin-top: 16px; }
.p2s-landing-title { font-size: var(--text-lg); margin: 0 0 4px; }
.p2s-landing-intro { font-size: var(--text-sm); color: var(--muted); margin: 0 0 12px; }
/* "create a link" sits in its own tinted box so it reads as an action, apart from the list below */
.p2s-landing-create { background: var(--head-bg); border-radius: var(--radius-lg); padding: 12px; margin: 0 0 16px; }
.p2s-landing-create label { display: block; margin: 0 0 6px; font-weight: 600; }
.p2s-landing-create-row { display: flex; flex-wrap: wrap; gap: 8px; }
.p2s-landing-create-row select { min-height: 44px; flex: 1 1 180px; max-width: 280px; }
.p2s-landing-notices { border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 10px 12px; margin-bottom: 14px; }
.p2s-landing-notices h3 { font-size: var(--text-md); margin: 0 0 4px; }
.p2s-landing-row { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; justify-content: space-between; padding: 6px 0; }
.p2s-landing-subtitle { font-size: var(--text-md); margin: 0 0 8px; }
.p2s-landing-empty { color: var(--muted); margin: 0; }
.p2s-landing-list { display: grid; gap: 10px; }
.p2s-landing-link { border: 1px solid var(--border-solid); border-radius: var(--radius-lg); padding: 12px; }
.p2s-landing-head { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; }
.p2s-landing-channel { font-weight: 700; font-size: var(--text-md); }
.p2s-landing-state { font-size: var(--text-2xs); font-weight: 600; border-radius: 999px; padding: 2px 10px; }
.p2s-landing-state.is-open { background: #e7f6ec; color: #1e7b3c; }
.p2s-landing-owner { color: var(--muted); font-size: var(--text-sm); }
.p2s-landing-close { margin-left: auto; }
.p2s-landing-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 10px 0 0; }
.p2s-landing-stats > div { background: var(--head-bg); border-radius: var(--radius); padding: 8px 10px; }
.p2s-landing-stats dt { font-size: var(--text-2xs); color: var(--muted); font-weight: 400; margin: 0; }
.p2s-landing-stats dd { font-size: var(--text-lg); font-weight: 700; margin: 2px 0 0; }
.p2s-landing-stats dd small { font-size: var(--text-2xs); font-weight: 400; color: var(--muted); }
.p2s-landing-closed { margin-top: 14px; color: var(--muted); }
.p2s-landing-closed summary { cursor: pointer; font-weight: 600; padding: 4px 0; }
.p2s-landing-closed ul { list-style: none; margin: 6px 0 0; padding: 0; }
.p2s-landing-closed li { padding: 6px 0; border-top: 1px solid var(--border-solid); font-size: var(--text-base); }
.p2s-landing-closed-stats { display: block; font-size: var(--text-2xs); }

/* admin/usage.php: a never/long-ago login is flagged red */
.usage-flag { color: var(--color-danger); }

/* Blue "Verify" seal, Facebook style (src/verified.php p2s_verified_badge()). Symbol only; sized in em so it follows the text it sits
   next to (table cell, card title, link, h1) and glued to the end of the name so it never wraps alone. Blue on purpose: the brand is
   purple, and a verified mark must not read as just another brand accent. The colour lives in the SVG itself. */
.p2s-verified {
  display: inline; /* NOT inline-block: the word joiner inside must take part in the line breaking of the name before it */
  flex: none;
  margin-left: .25em;
  line-height: 1;
  white-space: nowrap;
  cursor: help;
}
.p2s-verified svg { display: inline-block; width: 1.05em; height: 1.05em; vertical-align: -0.14em; }

/* Verify card on project_detail.php and the Admin queue form on admin/pending_approvals.php (Project UI). */
.p2s-verify-card { margin-top: 16px; }
.p2s-verify-title { font-size: var(--text-lg); margin: 0 0 4px; display: flex; align-items: center; }
.p2s-verify-state { margin: 4px 0 10px; font-size: var(--text-sm); }
.p2s-verify-state.is-lapsed { color: var(--color-warning); }
.p2s-verify-checks { list-style: none; margin: 0 0 12px; padding: 0; }
.p2s-verify-checks li { display: flex; gap: 8px; align-items: baseline; padding: 2px 0; }
.p2s-verify-checks .p2s-verify-mark { flex: none; width: 1.2em; font-weight: 700; text-align: center; }
.p2s-verify-checks .is-ok .p2s-verify-mark { color: var(--color-success); }
.p2s-verify-checks .is-no .p2s-verify-mark { color: var(--color-danger); }
.p2s-verify-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 8px 0; }
.p2s-verify-form input[type="date"] { min-height: 36px; }
.p2s-verify-form .p2s-verify-noexp { display: inline-flex; align-items: center; gap: 6px; }

/* Verify card on co_agent_detail.php (Co-Agent UI): Admin's approve / refuse row. */
.p2s-verify-admin { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 4px 12px; margin: 8px 0; }

/* One-row list toolbar on phones (owner 2026-10-03, with a screenshot of month nav / add-filter-CSV / view switch stacked on three lines:
   "จัดวางให้อยู่ในแถวเดียวกัน และทำแบบนี้ในทุกหน้าที่มีเครื่องมือ"). Every page that builds its toolbar with .list-toolbar (Lead, Co-Agent, projects,
   developers, packages, agent companies, site visits, legacy lists, month/leaderboard pages) gets it from this one block: no wrapping, 3px gaps and
   ONE 34px control height (month pills, icon buttons, view-switch buttons all the same, so they sit on one line visually). 34px is above WCAG 2.2's
   24px target minimum; the (pointer: coarse) 44px view-switch rule is overridden here on purpose — the Lead toolbar (month nav + add/filter/CSV +
   table/list switch = 8 controls) has to fit a 327px row on a 375px phone. Below 350px there is genuinely not enough room, so wrapping comes back. */
@media (max-width: 575.98px) {
  .list-toolbar { flex-wrap: nowrap; gap: 3px; }
  .list-toolbar .search-row { flex: 0 1 auto; width: auto; margin-left: auto; flex-wrap: nowrap; gap: 3px; }
  .list-toolbar .p2s-lead-monthnav { flex: 0 0 auto; gap: 3px; }
  .list-toolbar .p2s-lead-monthnav .status-pill { min-width: 28px; min-height: 34px; height: 34px; padding: 0 6px; font-size: var(--text-sm); display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; }
  .list-toolbar a.btn-secondary.btn-icon-only,
  .list-toolbar button.btn-secondary.btn-icon-only,
  .list-toolbar span.btn-secondary.btn-icon-only,
  .list-toolbar summary.btn-secondary,
  .list-toolbar .search-row .p2s-filter-pop > summary,
  .list-toolbar .p2s-more > summary { min-width: 34px; min-height: 34px; height: 34px; padding: 5px; box-sizing: border-box; }
  .list-toolbar .p2s-view-switch-btn { min-width: 34px; min-height: 34px; height: 34px; padding: 0; }
  /* The labelled "เพิ่ม …" button (a.btn-primary.p2s-add-btn) is a purple icon in this one-row toolbar — its text would not fit the row. */
  .list-toolbar a:is(.btn-primary, .btn-secondary).p2s-add-btn { min-width: 34px; height: 34px; padding: 5px; }
  .list-toolbar .p2s-add-btn .p2s-add-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}
@media (max-width: 349.98px) {
  .list-toolbar, .list-toolbar .search-row { flex-wrap: wrap; }
}

/* Project coordinates card + mini map (public/project_detail.php, assets/project-map-mini.js). Leaflet's own CSS is vendored. */
.p2s-pmap-card h2 { margin: 0 0 .75rem; font-size: var(--text-lg); }
.p2s-pmap-coords { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 .75rem; }
.p2s-pmap-coords input[type="text"] { flex: 1 1 200px; min-width: 0; min-height: 44px; font-size: var(--text-base); font-variant-numeric: tabular-nums; }
.p2s-pmap-box { position: relative; height: 260px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-subtle); z-index: 0; }
.p2s-pmap-fallback { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; padding: 1rem; text-align: center; color: var(--muted); z-index: 500; background: var(--surface-subtle); }
.p2s-pmap-box.is-failed .p2s-pmap-fallback { display: flex; }
.p2s-pmap-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: .75rem; }
.p2s-pmap-pin { display: block; width: 18px; height: 18px; border-radius: 50%; background: var(--primary); border: 3px solid #fff; box-shadow: 0 1px 4px rgba(0, 0, 0, .45); box-sizing: border-box; }
@media (max-width: 767.98px) { .p2s-pmap-box { height: 220px; } }

/* ---- All-projects map (/bu/projects_map.php, public/assets/projects-map.js) ---- */
.p2s-map-summary { margin: 0 0 .5rem; }
/* position:relative on the two scrolling rows: their visually-hidden (.sr-only, position:absolute) labels otherwise anchor to a
   far ancestor, sit at the row's scrolled-off x (~600px) and stretch the whole page's layout viewport on a phone. */
.p2s-map-filters { position: relative; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 .75rem; }
.p2s-map-filter { cursor: pointer; min-height: 36px; font-family: inherit; }
.p2s-map-filter.is-active { border-color: var(--primary); color: #fff; background: var(--primary); }
.p2s-map-transit { position: relative; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 .75rem; }
.p2s-map-transit-lines { display: flex; flex-wrap: wrap; gap: 6px; }
.p2s-map-transit-lines[hidden] { display: none; }
.p2s-map-transit-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--card, #fff); color: inherit; font: inherit; font-size: var(--text-sm); cursor: pointer; opacity: .55; }
.p2s-map-transit-chip.is-active { opacity: 1; border-color: var(--primary); }
.p2s-map-line-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; flex: 0 0 auto; }
.p2s-map-transit-chip .p2s-map-line-dot { margin-right: 0; }
.p2s-map-station-label { background: rgba(255,255,255,.9); border: 0; box-shadow: none; padding: 0 4px; font-size: var(--text-2xs); font-weight: 600; color: #222; }
.p2s-map-station-label::before { display: none; }
.p2s-map-search input { min-height: 36px; width: 220px; max-width: 100%; font-size: var(--text-base); }
.p2s-map-province select { min-height: 36px; max-width: 240px; font-size: var(--text-base); }
.p2s-map-msg { margin: 0 0 .75rem; padding: .6rem .8rem; border-radius: var(--radius); background: rgba(185, 28, 28, 0.08); color: var(--color-danger); }
.p2s-map-box { position: relative; z-index: 0; isolation: isolate; height: 70vh; min-height: 360px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-subtle); }
.p2s-map-box.is-failed { opacity: .7; }
.p2s-map-box .leaflet-bar a { width: 36px; height: 36px; line-height: 36px; font-size: 20px; }
.p2s-map-pin-wrap { background: none; border: 0; }
.p2s-map-pin { display: block; width: 22px; height: 22px; border-radius: 50%; border: 3px solid #fff; box-shadow: 0 1px 4px rgba(0, 0, 0, .45); box-sizing: border-box; background: #656d7c; }
.p2s-map-pin-box { position: relative; display: block; width: 22px; height: 22px; }
/* Name + price beside the pin: only when zoomed right in (JS sets .has-pin-labels at zoom >= 16) */
.p2s-map-pin-label { display: none; position: absolute; left: 26px; top: 50%; transform: translateY(-50%); max-width: 220px; padding: 1px 6px; border-radius: 4px; background: rgba(255, 255, 255, .94); box-shadow: 0 1px 3px rgba(0, 0, 0, .35); color: #222; font-size: var(--text-2xs); font-weight: 600; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.p2s-map-box.has-pin-labels .p2s-map-pin-label { display: block; }
.p2s-map-pin-name, .p2s-map-pin-price { display: block; overflow: hidden; text-overflow: ellipsis; }
.p2s-map-pin-price { font-weight: 700; color: var(--color-success); }
.p2s-map-sqm { font-weight: 600; color: var(--color-amber-deep); }
.p2s-map-pin.is-none { background: #656d7c; }
.p2s-map-pin.is-contact { background: var(--color-amber-deep); }
.p2s-map-pin.is-done { background: var(--color-success); }
.p2s-map-pin.is-reject { background: var(--color-danger); }
.p2s-map-pin.is-closed { background: #374151; }
/* Old projects not in the system yet: a cyan DIAMOND, so colour and shape both say "not a real project" (status pins are round) */
.p2s-map-pin.is-legacy { border-radius: 3px; transform: rotate(45deg) scale(.82); background: #0e7490; }
.p2s-map-box .marker-cluster.p2s-legacy-cluster { background-color: rgba(14, 116, 144, 0.28); border-radius: 12px; }
.p2s-map-box .marker-cluster.p2s-legacy-cluster div { background-color: #0e7490; color: #fff; font-weight: 700; border-radius: 9px; }
.p2s-legacy-tag { display: inline-block; padding: 2px 8px; border-radius: 999px; background: rgba(14, 116, 144, .12); color: #0b5c72; font-size: var(--text-sm); font-weight: 600; }
.p2s-legacy-swatch { display: inline-block; width: 11px; height: 11px; margin-right: 6px; border-radius: 2px; transform: rotate(45deg) scale(.85); background: #0e7490; vertical-align: -1px; }
.p2s-map-filter.p2s-map-legacy-btn.is-active { border-color: #0e7490; background: #0e7490; color: #fff; }
.p2s-map-filter.p2s-map-legacy-btn.is-active .p2s-legacy-swatch { background: #fff; }
.p2s-map-box .marker-cluster-small, .p2s-map-box .marker-cluster-medium, .p2s-map-box .marker-cluster-large { background-color: rgba(var(--primary-rgb), 0.28); }
.p2s-map-box .marker-cluster-small div, .p2s-map-box .marker-cluster-medium div, .p2s-map-box .marker-cluster-large div { background-color: var(--primary); color: #fff; font-weight: 700; }
.p2s-map-card { min-width: 180px; font-size: var(--text-base); line-height: 1.4; }
.p2s-map-card-title { font-weight: 700; margin-bottom: 4px; }
.p2s-map-card-title .p2s-verified { margin-left: 4px; }
.p2s-map-card-dev { color: var(--muted); margin-bottom: 4px; }
.p2s-map-card-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 8px; }
.p2s-map-empty { margin: .75rem 0; padding: .75rem 1rem; border: 1px dashed var(--border); border-radius: var(--radius); text-align: center; }
.p2s-map-empty p { margin: .25rem 0; }
.p2s-map-missing { margin-top: 1rem; }
.p2s-map-missing summary { cursor: pointer; font-weight: 700; min-height: 36px; display: flex; align-items: center; }
.p2s-map-missing ul { list-style: none; margin: .5rem 0 0; padding: 0; display: grid; gap: 6px; }
.p2s-map-missing-item { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
@media (max-width: 767.98px) {
  .p2s-map-filters { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
  .p2s-map-filters > * { flex: 0 0 auto; white-space: nowrap; }
  .p2s-map-transit { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
  .p2s-map-transit > *, .p2s-map-transit-lines { flex: 0 0 auto; flex-wrap: nowrap; white-space: nowrap; }
  .p2s-map-box { height: 70vh; min-height: 320px; }
}

/* Deal health (sales/pending_transfer.php): badge + reasons list that opens under it. */
.p2s-health summary { cursor: pointer; list-style: none; }
.p2s-health summary::-webkit-details-marker { display: none; }
.p2s-health-reasons { margin: 6px 0 0; padding-left: 18px; font-size: var(--text-sm); color: var(--color-slate); }


/* ---- Pre-release audit 2026-10-06: touch + primary-action polish ---- */
a:is(.btn-primary, .btn-secondary).p2s-add-btn { gap: 8px; }
.p2s-add-btn svg { width: 18px; height: 18px; flex: none; }
@media (max-width: 767.98px) {
  /* 14px form text makes iOS Safari zoom the page on focus; 16px does not. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]):not([type="color"]),
  select, textarea { font-size: 16px !important; }
  .search-row { gap: 8px; }
  /* calendar dots were 18px square with ~2px between them */
  .p2s-calendar-event { padding: 7px; }
}

/* Contrast fixes found by the audit (all were under 4.5:1). */
.nav-badge.bg-danger, .badge.bg-danger.nav-badge { background-color: #c53030 !important; }
.user-name-sub-text { color: #5b6472 !important; }
code { color: #a21d56; }
.pk-tag-warn, .pb-badge.warn { color: var(--color-warning); }
@media (max-width: 767.98px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]):not([type="color"]):not([type="submit"]):not([type="button"]), select { min-height: 44px; }
}

/* A .btn-secondary submit inside .project-form (e.g. "บันทึกและเพิ่มรายการถัดไป") used to pick up the filled-purple rule above and
   sat beside "บันทึก" as a second primary: keep it a secondary. */
.project-form button[type="submit"].btn-secondary { background: var(--surface); color: var(--muted); border-color: var(--border); }
.project-form button[type="submit"].btn-secondary:hover, .project-form button[type="submit"].btn-secondary:active { background: var(--head-bg); color: var(--text); border-color: var(--muted); }

/* ---- Owner 2026-10-06: every text button and standard button is 34px tall (fits small phones; matches the 34px toolbar icons) ----
   One block, last in the file, so the many one-off 40/44px button rules above do not have to be edited one by one. Only the height
   and vertical padding are set; width, colour and label are untouched, and a label that wraps still grows the button. Inputs and the
   16px form-text rule are not buttons and stay as they were. The login page's own sign-in button (login.php) is a separate form. */
.project-form button[type="submit"], .p2s-result-save, .p2s-bulk-bar .p2s-bulk-btn,
.p2s-join-form button, .p2s-join-inline button, .p2s-void-card button[type="submit"],
.p2s-pk .pk-cta, .p2s-pk .pk-peek, .p2s-search-page-form button, .al-filters button, .pool-controls button,
.pool-confirm button, .pool-confirm a.btn-secondary, .p2s-filter-actions .btn-primary, .p2s-filter-actions .btn-secondary,
.search-form button[type="submit"], .target-save {
  min-height: 34px;
  padding-top: 5px; padding-bottom: 5px;
  box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center; /* do not stretch to the height of a neighbouring 44px input in a flex row */
}

/* ---- Owner 2026-10-06: style consistency pass (items 8-9 of the pre-release audit) ---- */
/* Approvals: a count of 0 is not news — fade and shrink those cards so the ones with work in them stand out. */
.summary-card.is-zero { opacity: 1; }   /* was .55: the label fell to 2.4:1 (audit 2026-10-16); the zero reads quiet by colour instead */
.summary-card.is-zero .summary-card-label { color: var(--muted); }
.summary-card.is-zero .summary-card-value { color: var(--muted); }

/* One height for every form field (text, number, date, select): they were 42.4 / 43 / 44 / 44.4 depending on the page.
   Compact controls inside tables, list toolbars, filter popovers and the bulk bar keep their own smaller size. */
main input:not([type="hidden"], [type="checkbox"], [type="radio"], [type="file"], [type="submit"], [type="button"], [type="range"], [type="color"]):not(table *, .list-toolbar *, .p2s-filter-pop *, .p2s-bulk-bar *, .search-row *),
main select:not(table *, .list-toolbar *, .p2s-filter-pop *, .p2s-bulk-bar *, .search-row *) {
  min-height: 44px; box-sizing: border-box;
}

/* Every button is 34px (owner): the small in-table text actions and the Google sign-in button too. */
.link-btn { min-height: 34px; display: inline-flex; align-items: center; box-sizing: border-box; }
.p2s-google-btn, .p2s-line-btn { min-height: 34px !important; padding-top: 4px; padding-bottom: 4px; }

/* admin/users.php: ten columns used to be forced onto one line each (nowrap), 1990px wide at a 1590px screen. Let the long text wrap and
   the row actions flow onto a second line instead of pushing the whole table sideways. */
.p2s-users-table th, .p2s-users-table td { padding-left: 5px; padding-right: 5px; }
.p2s-users-table .p2s-users-email { white-space: nowrap; } /* an address stays on one line; the date/time and header cells give way instead */
.p2s-users-table td[data-label="เข้าใช้งานล่าสุด"] { white-space: normal; }
.p2s-users-table th { white-space: normal; vertical-align: bottom; }
.p2s-users-table .p2s-users-actions { white-space: normal; }
.p2s-users-table .p2s-users-actions .link-btn { margin-left: 0; margin-right: 4px; white-space: nowrap; }
/* the header "ครั้ง" count and linked-items columns are short numbers: no need for the table's default wide cells */
.p2s-users-table td.num { white-space: nowrap; }
.p2s-users-table .p2s-users-actions form { display: inline; }

/* Developer smart-search picker on bu/project_form.php (public/assets/smart-picker.js): was a <select> with hundreds of names. */
.p2s-devsearch { margin-bottom: 14px; }
.p2s-devsearch > label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: 4px; }
.p2s-devsearch > label .field-hint { display: block; font-weight: 400; margin: 2px 0 0; }
.p2s-devsearch-box { position: relative; }
.p2s-devsearch-box input[type="text"] { width: 100%; box-sizing: border-box; }
.p2s-devsearch-list {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px); margin: 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(30, 41, 59, 0.14);
  max-height: 20rem; overflow-y: auto;
}
.p2s-devsearch-list[hidden] { display: none; }
.p2s-devsearch-list li { display: grid; gap: 1px; padding: 9px 10px; border-radius: var(--radius); cursor: pointer; font-size: var(--text-base); }
.p2s-devsearch-list li small { color: var(--muted); font-size: var(--text-sm); }
.p2s-devsearch-list li.is-active, .p2s-devsearch-list li:hover { background: rgba(var(--primary-rgb), 0.08); }
.p2s-devsearch-list li.p2s-devsearch-none { color: var(--muted); cursor: default; }
.p2s-devsearch-list li.p2s-devsearch-other { color: var(--primary-dark); font-weight: 600; border-top: 1px solid var(--border); margin-top: 4px; }

/* The arrow button opens the full list; the field keeps room for it. The "not in the list" row stays visible at the bottom while the list scrolls. */
.p2s-devsearch-box input[type="text"] { padding-right: 40px; }
.p2s-devsearch-toggle { position: absolute; right: 0; top: 0; width: var(--control-h); height: var(--control-h); display: inline-flex; align-items: center; justify-content: center; border: 0; background: transparent; color: var(--muted); cursor: pointer; border-radius: var(--radius); min-height: 36px !important; padding: 0 !important; }
.p2s-devsearch-toggle:hover { color: var(--text); background: rgba(var(--primary-rgb), 0.08); }
.p2s-devsearch-list li.is-selected { font-weight: 700; color: var(--primary-dark); }
.p2s-devsearch-list li.p2s-devsearch-other { position: sticky; bottom: -4px; background: var(--surface); }

/* KPI ของฉัน (/sales/kpi.php): one card per measure, left border + bar coloured by status */
.p2s-kpi-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-md); margin-top: var(--space-md); }
@media (min-width: 768px) { .p2s-kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.p2s-kpi-card {
  /* the same card as sales/dashboard.php (.summary-card): shadow, 1.5rem padding, a 3px bar across the top — here in the status colour (owner 2026-10-10) */
  --kpi-accent: var(--color-slate);
  position: relative; min-width: 0; padding: 1.5rem; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card);
}
.p2s-kpi-card::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px;
  border-radius: var(--radius) var(--radius) 0 0; background: var(--kpi-accent);
}
.p2s-kpi-card.is-green { --kpi-accent: var(--color-success); }
.p2s-kpi-card.is-amber { --kpi-accent: var(--color-amber-strong); }
.p2s-kpi-card.is-red { --kpi-accent: var(--color-danger); }
.p2s-kpi-label { margin: 0 0 6px; font-size: var(--text-sm); font-weight: 400; color: var(--muted); }
.p2s-kpi-value { font-size: var(--text-3xl); font-weight: 700; color: var(--kpi-accent); overflow-wrap: anywhere; }
.p2s-kpi-value small { font-size: var(--text-sm); font-weight: 500; color: var(--muted); }
.p2s-kpi-target, .p2s-kpi-pace { font-size: var(--text-sm); color: var(--muted); }
.p2s-kpi-track { height: 10px; margin-top: var(--space-sm); border-radius: var(--radius); background: var(--head-bg); overflow: hidden; }
.p2s-kpi-fill { height: 100%; border-radius: var(--radius); background: var(--kpi-accent); }
.p2s-kpi-banner { margin: var(--space-md) 0 0; padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); border-radius: var(--radius); background: var(--head-bg); font-size: var(--text-sm); }
.p2s-kpi-note { margin-top: var(--space-md); }
.p2s-kpi-nav-end { color: var(--muted); opacity: .4; }

/* KPI score box + yearly list (/sales/kpi.php) */
.p2s-kpi-score { --kpi-accent: var(--color-slate); position: relative; display: grid; grid-template-columns: 1fr; gap: var(--space-md); margin-top: var(--space-md); padding: 1.5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.p2s-kpi-score::before { content: ""; position: absolute; inset: 0 0 auto; height: 3px; border-radius: var(--radius) var(--radius) 0 0; background: var(--kpi-accent); }
@media (min-width: 768px) { .p2s-kpi-score { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.p2s-kpi-score.is-green { --kpi-accent: var(--color-success); }
.p2s-kpi-score.is-amber { --kpi-accent: var(--color-amber-strong); }
.p2s-kpi-score.is-red { --kpi-accent: var(--color-danger); }
.p2s-kpi-score-part { min-width: 0; }
.p2s-kpi-score-label { margin: 0 0 6px; font-size: var(--text-sm); font-weight: 400; color: var(--muted); }
.p2s-kpi-score-value { font-size: var(--text-3xl); font-weight: 700; overflow-wrap: anywhere; }
.p2s-kpi-score-part:first-child .p2s-kpi-score-value { color: var(--kpi-accent); }
.p2s-kpi-score-value small { font-size: var(--text-sm); font-weight: 500; color: var(--muted); }
.p2s-kpi-score-hint { font-size: var(--text-sm); color: var(--muted); }
.p2s-kpi-months { margin-top: var(--space-lg); }
.p2s-kpi-months h2 { margin: 0 0 var(--space-sm); font-size: var(--text-base); }
.p2s-kpi-years { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-bottom: var(--space-sm); }
.p2s-kpi-years a { padding: 4px 12px; border: 1px solid var(--border); border-radius: 999px; font-size: var(--text-sm); }
.p2s-kpi-years a[aria-current="page"] { background: var(--head-bg); font-weight: 700; }
.p2s-kpi-month-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-sm); }
.p2s-kpi-month { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 2px var(--space-sm); align-items: center; min-width: 0; padding: var(--space-sm) var(--space-md); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.p2s-kpi-month.is-now { border-color: var(--kpi-accent, var(--color-slate)); border-left-width: 5px; }
.p2s-kpi-month-name { font-weight: 600; overflow-wrap: anywhere; }
.p2s-kpi-month-points { font-size: var(--text-sm); color: var(--muted); text-align: right; min-width: 4.5em; }
.p2s-kpi-month-by { grid-column: 1 / -1; font-size: var(--text-sm); color: var(--muted); overflow-wrap: anywhere; }
/* KPI Support: special points from posts */
.p2s-kpi-special { position: relative; margin-top: var(--space-md); padding: 1.5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.p2s-kpi-special::before { content: ""; position: absolute; inset: 0 0 auto; height: 3px; border-radius: var(--radius) var(--radius) 0 0; background: var(--primary); }
.p2s-kpi-special h2 { margin: 0 0 var(--space-sm); font-size: var(--text-base); }
.p2s-kpi-special-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr; gap: var(--space-sm); }
.p2s-kpi-special-list li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px var(--space-sm); align-items: baseline; min-width: 0; }
.p2s-kpi-special-name { font-weight: 600; overflow-wrap: anywhere; }
.p2s-kpi-special-count { font-weight: 700; text-align: right; }
.p2s-kpi-special-points { grid-column: 1 / -1; font-size: var(--text-sm); color: var(--muted); }
.p2s-kpi-special-total { margin: var(--space-sm) 0 0; font-size: var(--text-sm); font-weight: 600; }
@media (min-width: 768px) { .p2s-kpi-special-list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* KPI ทีม (/exec/kpi.php): team table, status pills, target form */
.p2s-kpi-actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin: var(--space-md) 0; }
.p2s-kpi-table { min-width: 960px; }
.p2s-kpi-table td { vertical-align: top; }
.p2s-kpi-pill { display: inline-block; min-width: 44px; padding: 2px 10px; border-radius: 999px; font-weight: 700; text-align: center; background: var(--head-bg); color: var(--muted); }
.p2s-kpi-pill.is-green { background: #dcfce7; color: #166534; }
.p2s-kpi-pill.is-amber { background: #fef3c7; color: var(--color-warning); }
.p2s-kpi-pill.is-red { background: #fee2e2; color: #991b1b; }
.p2s-kpi-table th a { color: inherit; text-decoration: none; }
.p2s-kpi-table th a:hover { text-decoration: underline; }
.p2s-kpi-table th a[aria-current] { font-weight: 700; color: var(--primary); }
.p2s-kpi-sub { display: block; margin-top: 2px; font-size: var(--text-sm); font-weight: 400; color: var(--muted); }
.p2s-kpi-default-row td { background: var(--head-bg); }
.p2s-kpi-form { max-width: 560px; }
.p2s-kpi-field { display: grid; gap: 4px; margin-bottom: var(--space-sm); }
.p2s-kpi-form .field-hint { margin: 2px 0 0; }

/* KPI card on งานวันนี้ */
.p2s-kpi-mini { margin: var(--space-md) 0; padding: var(--space-md); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.p2s-kpi-mini h2 { margin: 0 0 var(--space-sm); font-size: var(--text-base); }
.p2s-kpi-mini ul { list-style: none; margin: 0 0 var(--space-sm); padding: 0; display: grid; gap: var(--space-xs); }
.p2s-kpi-mini li { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px var(--space-sm); padding: 6px 10px; border-left: 5px solid var(--color-slate); border-radius: var(--radius); background: var(--head-bg); font-size: var(--text-sm); overflow-wrap: anywhere; }
.p2s-kpi-mini li.is-amber { border-left-color: var(--color-amber-strong); }
.p2s-kpi-mini li.is-red { border-left-color: var(--color-danger); }

/* lead_detail.php / co_agent_detail.php: "สิ่งที่ต้องทำต่อ" — each button opens a one-job view (src/detail_views.php).
   Owner 2026-10-09: all btn-secondary, exactly 34px tall, a label never wraps; on desktop they size to their label and wrap as a
   row, on a phone each button is the full card width, stacked. */
.p2s-next-actions { margin-top: 16px; }
.p2s-next-actions .section-title { margin-top: 0; }
.p2s-next-actions-grid { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.p2s-next-actions .p2s-next-actions-grid > a { height: 34px; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; margin: 0; text-decoration: none; }
@media (max-width: 767.98px) {
  .p2s-next-actions-grid { flex-direction: column; }
  .p2s-next-actions .p2s-next-actions-grid > a { width: 100%; }
}
.p2s-manage-link { margin-top: 24px; text-align: right; }

/* lead_detail.php / co_agent_detail.php summary card (owner 2026-10-09): read top to bottom on a phone — status + owner, a call
   button, ONE coloured line saying what to do about follow-up, the facts as label/value rows, then the buttons. */
.p2s-summary-card { display: flex; flex-direction: column; gap: 12px; }
.p2s-summary-card > * { margin: 0; }
.p2s-summary-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; }
.p2s-summary-owner { font-size: var(--text-sm); color: var(--color-slate); }
.p2s-summary-owner strong { color: var(--text); font-weight: 600; }
.p2s-call-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* The call button carries an icon, so the app-wide "icon link = tinted pill" rule (a:has(> .p2s-action-icon)) matches it too;
   this selector is one class stronger so the call button stays a solid primary button. */
.p2s-summary-card .p2s-call-row > a.p2s-call-btn[href] { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: 0; height: 34px; margin: 0; padding: 0 16px; border-radius: var(--radius); background: var(--primary); color: #fff; font-weight: 600; font-variant-numeric: tabular-nums; text-decoration: none; white-space: nowrap; }
.p2s-summary-card .p2s-call-row > a.p2s-call-btn[href]:hover, .p2s-summary-card .p2s-call-row > a.p2s-call-btn[href]:focus-visible { background: var(--primary-dark); color: #fff; }
.p2s-call-btn svg { width: 16px; height: 16px; flex: none; }
.p2s-call-masked { font-size: var(--text-lg); font-weight: 600; }
.p2s-line-chip { display: inline-flex; align-items: center; height: 34px; padding: 0 12px; border-radius: 999px; background: #e8f7ee; color: #05683a; font-size: var(--text-sm); font-weight: 600; overflow-wrap: anywhere; }
.p2s-next-strip { padding: 8px 12px; border-left: 4px solid; border-radius: var(--radius); font-size: var(--text-sm); font-weight: 600; }
.p2s-next-strip.is-danger { background: rgba(185, 28, 28, 0.08); border-color: var(--color-danger); color: var(--color-danger); }
.p2s-next-strip.is-warning { background: rgba(217, 119, 6, 0.1); border-color: var(--color-amber-strong); color: var(--color-warning); }
.p2s-next-strip.is-ok { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-dark); }
.p2s-empty { color: var(--color-slate); }
.p2s-needs-box { padding: 12px; border-radius: var(--radius); background: var(--head-bg); }
.p2s-needs-box .p2s-chip-group { margin: 0 0 8px; }
.p2s-needs-title { margin: 0 0 6px; font-weight: 600; }
.p2s-summary-card > a.p2s-edit-btn { align-self: flex-start; gap: 6px; text-decoration: none; } /* beats the site 34px block's align-self: center */
.p2s-edit-btn svg { width: 15px; height: 15px; }
@media (max-width: 767.98px) {
  .p2s-summary-card .p2s-call-row > a.p2s-call-btn[href], .p2s-edit-btn, .p2s-needs-box .btn-secondary { width: 100%; }
  .p2s-facts-rows { display: block; }
  .p2s-facts-rows > div { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .p2s-facts-rows > div:last-child { border-bottom: 0; }
  .p2s-facts-rows dt { flex: none; font-size: var(--text-sm); }
  .p2s-facts-rows dd { text-align: right; }
}

/* Settings > สีของระบบ (src/theme_palette.php): one card per preset, each painted with its own variables (inline style). */
.p2s-palette-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin: 0 0 12px; padding: 0; border: 0; }
.p2s-palette-card { position: relative; display: flex; flex-direction: column; gap: 6px; padding: 8px; border: 2px solid var(--border); border-radius: var(--radius-lg); cursor: pointer; background: #fff; }
.p2s-palette-card input { position: absolute; opacity: 0; pointer-events: none; }
.p2s-palette-card:has(input:checked) { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.18); }
.p2s-palette-card:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
.p2s-palette-swatch { display: flex; height: 72px; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--border); }
.p2s-palette-bar { width: 22px; background: var(--primary-deep); }
.p2s-palette-body { flex: 1; display: flex; flex-wrap: wrap; align-content: center; gap: 4px; padding: 6px; background: #fafafa; }
.p2s-palette-btn { display: inline-flex; align-items: center; height: 20px; padding: 0 6px; border-radius: 4px; background: var(--primary); color: #fff; font-size: 11px; font-weight: 600; }
.p2s-palette-btn.is-outline { background: #fff; color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.p2s-palette-chip { display: inline-flex; align-items: center; height: 20px; padding: 0 6px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: 11px; font-weight: 600; }
.p2s-palette-name { font-size: var(--text-sm); font-weight: 600; text-align: center; }

/* กลุ่มโครงการ (bu/project_group.php, projects.php filter, project_detail chips) */
.p2s-group-members { list-style: none; margin: 0; padding: 0; }
.p2s-group-members li { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.p2s-group-members li:last-child { border-bottom: 0; }
.p2s-group-member-name { display: flex; flex-direction: column; min-width: 0; overflow-wrap: anywhere; }
.p2s-group-zone { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.p2s-group-pick { max-height: 320px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius); padding: 4px 8px; margin: 0 0 8px; }
.p2s-group-pick-item { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border); cursor: pointer; font-weight: 400; }
.p2s-group-pick-item:last-child { border-bottom: 0; }
.p2s-group-pick-item input { width: auto; margin-top: 3px; flex: none; }
.p2s-group-pick-item[hidden] { display: none; }
.p2s-group-delete { margin-top: 16px; text-align: right; }
.p2s-group-filter { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.p2s-group-filter select { height: 34px; max-width: 220px; }
.p2s-group-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.p2s-group-chip { display: inline-flex; align-items: center; height: 26px; padding: 0 10px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: var(--text-sm); text-decoration: none; }
.p2s-link-group-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 8px; }
.p2s-link-group-pick select { max-width: 100%; }
/* project_detail.php: "โครงการประจำเดือน" badge — the same filled star (colour) as the toggle on projects.php. */
.p2s-featured-badge { display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 10px; border-radius: 999px; background: rgba(185, 28, 28, 0.08); color: var(--color-danger); font-size: var(--text-sm); font-weight: 600; }
.p2s-featured-badge svg { width: 15px; height: 15px; fill: currentColor; stroke: currentColor; stroke-width: 2; }
button.p2s-featured-badge { border: 1px solid transparent; cursor: pointer; font-family: inherit; }
button.p2s-featured-badge:hover { border-color: currentColor; }
.p2s-featured-badge.is-off { background: transparent; color: var(--color-slate); border: 1px dashed var(--border); }
.p2s-featured-badge.is-off svg { fill: none; }
.p2s-featured-badge.is-off:hover { color: var(--color-danger); border-color: currentColor; }
div.p2s-group-chips { align-items: center; margin: 0 0 12px; }
/* project_detail.php: the "โครงการประจำเดือน" star right after the project name (owner 2026-10-09: star only, no words). */
.p2s-project-title { display: flex; align-items: center; flex-wrap: wrap; gap: 2px 6px; }
.p2s-project-title .p2s-title-star { flex: none; } /* 32px button, 16px star: the shared star size, as it has rendered since it shipped */
/* bu/project_group_view.php */
.p2s-group-count { display: inline-flex; align-items: center; gap: 4px; height: 34px; font-size: var(--text-sm); color: var(--color-slate); }
.p2s-group-manage { gap: 6px; }
.p2s-inline-star.p2s-star-btn { width: 22px; height: 22px; vertical-align: middle; }
.p2s-inline-star .p2s-action-icon { width: 16px; height: 16px; }


/* A row of text buttons that no longer fits on one line is stacked instead, full width (owner 2026-10-09: easier to tap than a
   ragged wrapped row). public/assets/button-stack.js adds .p2s-btn-stack when it sees the row wrap; keep its selector in step. */
.p2s-btn-stack { flex-direction: column; align-items: stretch; }
.p2s-btn-stack > :is(a.btn-primary, button.btn-primary, a.btn-secondary, button.btn-secondary, a.btn-danger, button.btn-danger, button[type="submit"]):not(.btn-icon-only) {
  display: flex; justify-content: center; width: 100%; max-width: none; margin: 8px 0 0;
}

/* UI audit 2026-10-09 */
/* I4: on a phone the wide form's auto-fit grid (min 300px a column) out-specified the one-column phone rule and ran 8px past the
   form (38px on a 360px phone). */
@media (max-width: 767.98px) {
  .form-wide .form-grid { grid-template-columns: minmax(0, 1fr); }
}
/* I5: filter rows line their controls up on the bottom edge; a button's own align-self:center lifted it 9-12px above the inputs. */
.dm-budget > :is(button, a.btn-secondary, a.btn-primary), .al-filters > :is(button, a.btn-secondary, a.btn-primary), .pool-controls > :is(button, a.btn-secondary, a.btn-primary) { align-self: flex-end; }
/* I6: one look for every file picker — the browser's own button styled like .btn-secondary at the control height; the project
   form's text-input frame (48px box) does not apply to it. */
input[type="file"] { max-width: 100%; font-size: var(--text-sm); color: var(--muted); }
input[type="file"]::file-selector-button {
  height: var(--control-h); margin-right: 10px; padding: 0 14px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); font: inherit; font-weight: 600; cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--muted); }
.project-form input[type="file"]:not([type="radio"]) { border: 0; padding: 0; min-height: 0; background: none; box-shadow: none; }

/* "ส่งทาง LINE" next to every copy-link button (copy-link.js): LINE's green, readable on white (5.6:1). */
a.btn-secondary.p2s-line-share { color: #03783b; border-color: #06c755; margin-left: 6px; }
a.btn-secondary.p2s-line-share:hover { background: #e8f7ee; color: #03783b; border-color: #06c755; }

/* UI audit 2026-10-09, polish (I11-I20) */
.p2s-verify-title { font-size: var(--text-md); font-weight: 700; }       /* the same size/weight as the section titles beside it */
.content :is(h1, h2, h3) small { font-size: var(--text-sm); }          /* Bootstrap's .875em inside a heading gave 15.75px */
kbd { font-size: var(--text-2xs); }
dd small { font-size: var(--text-sm); }                                 /* the investor facts' "(94%)" was .875em of an 18px value */
.project-form legend { font-size: var(--text-md); font-weight: 700; } /* Bootstrap's legend calc() gave 21.57px */
.form-section-title .label-note { margin-left: 6px; }                  /* "ความต้องการ" and its hint ran together */
.p2s-pk .pk-btn-filter, .p2s-pk .pk-peek, .p2s-pk .pk-cta { border-radius: var(--radius); } /* the site's control radius, not 12px */
/* A chip row that scrolls sideways on a phone (scrollbar hidden) fades at the right edge, so it is clear there is more. */
@media (max-width: 767.98px) {
  .p2s-chip-row { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
}

/* UI review 2026-10-09, group 1 (things that looked broken) */
.p2s-usage-table td:first-child { min-width: 160px; }                 /* names wrapped to three lines */
.p2s-usage-role { display: block; }                                    /* the role on its own line under the name */
.p2s-payment-cards .status-badge { white-space: normal; max-width: 100%; text-align: left; } /* long stage names were cut off on a phone */
.p2s-closed-search { margin: 0; }
.p2s-closed-search input[type="search"] { flex: 1 1 240px; min-width: 0; }
.p2s-result-count { margin-top: 8px; }                                 /* "พบ N โครงการ" sat against the search box */
/* Row actions: a link pill and a button inside an inline form sat on different lines (baseline vs middle) — one rule for all tables. */
.data-table td > :is(a, .link-btn, button, .inline-form, details) { vertical-align: middle; }
.p2s-tpl-preview { margin-top: 8px; }                                  /* the preview toggle ran into the textarea border */
.p2s-tpl-preview > summary, .p2s-tpl-vars > summary { cursor: pointer; color: var(--primary); font-weight: 600; font-size: var(--text-sm); }
.option-list > label.option-chip { display: flex; }                    /* a generic form-label rule made it display:block, so the radio and its text did not line up */
.option-chip input { margin: 0; flex: none; }                          /* the radio circle sat above its label */

/* UI review 2026-10-09, group 2 (one look for the same thing everywhere) */
.p2s-month-nav { margin: 0 0 var(--space-md); }                          
.p2s-back { margin: 0 0 var(--space-2xs); font-size: var(--text-sm); }   /* one back link above the h1 of every form / detail page */                          /* the shared month switcher (month_nav_html) stands on its own row */
/* On a phone a row of two-up cards never leaves a lone half-width card at the end. */
@media (max-width: 767.98px) {
  :is(.summary-cards, .kpi-cards) > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}
/* No card inside a card: a framed .project-form (or another card) inside a .settings-card drew a second border and lost ~40px of
   width on a phone. The outer card keeps the frame and takes over the form's width cap. */
:is(.settings-card, .card) :is(.project-form, .settings-card) { border: 0; box-shadow: none; padding: 0; background: none; }
.settings-card:has(> .project-form:not(.form-wide)) { max-width: 760px; }
/* File inputs (file-picker.js): a Thai "เลือกไฟล์" button + the chosen name; the real input is visually hidden but focusable. */
.p2s-file { position: relative; display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; max-width: 100%; margin-top: 4px; }
.p2s-file > input[type="file"] { position: absolute; width: 1px; height: 1px; margin: 0; padding: 0; opacity: 0; overflow: hidden; clip: rect(0 0 0 0); }
.p2s-file:focus-within > label { outline: 2px solid var(--primary); outline-offset: 2px; }
.p2s-file-name { font-size: var(--text-sm); color: var(--muted); overflow-wrap: anywhere; }
.p2s-file.is-disabled > label { opacity: .5; pointer-events: none; }
/* A CSV download is an icon at the right end of the toolbar above what it exports — on every page. */
.list-toolbar > .p2s-csv-end, .list-toolbar > p:only-child:has(> .btn-icon-only:only-child) { margin-left: auto; }

/* UI review 2026-10-09, group 3 (daily sales work on a phone) */
/* Lead / Co-Agent list cards: about half the height on a phone — avatar beside the name (it sat on its own row), tighter padding. */
@media (max-width: 767.98px) {
  .p2s-contact-card { padding: var(--space-md); }
  .p2s-contact-card-head { flex-wrap: nowrap; align-items: flex-start; }
  .p2s-contact-card-head .p2s-contact-avatar.lg { width: 40px; height: 40px; font-size: var(--text-md); }
  .p2s-contact-card-title { flex: 1 1 auto; }
  .p2s-contact-card-title h3 { font-size: var(--text-lg); }
  .p2s-contact-card .p2s-contact-fields { margin-top: var(--space-sm); padding-top: var(--space-sm); }
  .p2s-contact-card .form-section-collapsible { margin-top: var(--space-xs); }
  .p2s-contact-card .p2s-contact-card-actions { margin-top: var(--space-sm); padding-top: var(--space-xs); }
}
.p2s-contact-card-title h3 a { color: inherit; text-decoration: none; }  /* the name opens the record, like a list row */
.p2s-contact-card-title h3 a:hover { color: var(--primary); text-decoration: underline; }
.p2s-needs-card .p2s-needs-box { margin: 0; }
/* งานวันนี้ summary line: one chip per list with its count, red part = late; each jumps to its section. */
.p2s-today-summary { display: flex; flex-wrap: wrap; gap: var(--space-xs); margin: 0 0 var(--space-md); }
.p2s-today-chip { display: inline-flex; align-items: center; gap: 6px; height: var(--control-h); padding: 0 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--text); font-size: var(--text-sm); text-decoration: none; }
.p2s-today-chip strong { font-variant-numeric: tabular-nums; }
.p2s-today-chip span { color: var(--color-danger); font-weight: 600; }
.p2s-today-chip.has-late { border-color: rgba(185, 28, 28, .35); }
.followup-heading { scroll-margin-top: 86px; }
/* Long daily forms on a phone (lead, co-agent, visit, agent company): the save bar stays at the bottom of the screen instead of
   waiting after the last field. The main button full width, the others side by side under it. */
@media (max-width: 767.98px) {
  .p2s-form-bar {
    position: sticky; bottom: 0; z-index: 20;
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-xs);
    margin: var(--space-md) calc(-1 * var(--space-md)) 0; padding: var(--space-xs) var(--space-md) calc(var(--space-xs) + env(safe-area-inset-bottom));
    background: var(--surface); border-top: 1px solid var(--border); box-shadow: 0 -4px 12px rgba(23, 43, 76, .08);
  }
  .p2s-form-bar > :first-child { grid-column: 1 / -1; }
  .p2s-form-bar > * { width: 100%; margin: 0; }
  .p2s-form-bar > :nth-child(2):last-child { grid-column: 1 / -1; }
}
/* Visit result: each option reads as a choice — a radio circle on the left, the result in bold, the status change muted under it;
   two columns from 768px; on a phone the save button stays at the bottom of the screen. */
.p2s-result-opt span { position: relative; padding-left: 44px; font-weight: 600; }
.p2s-result-opt small { font-weight: 400; }
.p2s-result-opt span::before {
  content: ''; position: absolute; left: 14px; top: 50%; width: 18px; height: 18px; margin-top: -9px;
  border: 2px solid var(--border-strong); border-radius: 50%; background: var(--surface); box-sizing: border-box;
}
.p2s-result-opt input:checked + span::before { border-color: var(--primary); box-shadow: inset 0 0 0 4px var(--surface); background: var(--primary); }
@media (min-width: 768px) {
  .p2s-result-form { max-width: 760px; }
  .p2s-result-opts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767.98px) {
  .p2s-result-save { position: sticky; bottom: calc(8px + env(safe-area-inset-bottom)); z-index: 20; box-shadow: 0 4px 14px rgba(23, 43, 76, .18); }
}                       /* jumps land below the fixed topbar */

/* Review of the lower halves of every page (2026-10-09, step 4). */
/* A hint that opens a second form sits under the previous form's button, not under an input: no pull-up. */
.settings-card > .project-form + .project-form > p.field-hint:first-of-type { margin-top: var(--space-sm); }
/* Pagination pills are controls too: same height as the page-size select next to them. */
.pagination :is(a.status-pill, span.status-pill) { display: inline-flex; align-items: center; min-height: var(--control-h); box-sizing: border-box; }
/* Phone save bar: two half-width buttons must hold "บันทึกและเพิ่มรายการถัดไป" without cutting it. */
@media (max-width: 575.98px) {
  .p2s-form-bar > :nth-child(n+2) { padding-left: 6px; padding-right: 6px; font-size: var(--text-sm); }
}
.project-doc-row { display: grid; gap: var(--space-xs); margin: 0 0 var(--space-md); padding-bottom: var(--space-md); border-bottom: 1px solid var(--border); }
.project-doc-row > .field-hint, .project-doc-row > .project-doc-label { margin: 0; }
.project-doc-label { font-weight: 600; }
.project-form > .form-actions, .project-form > :is(button[type="submit"], a.btn-secondary):last-child { gap: var(--space-xs); }
.project-doc-pick { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.p2s-file > label.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; margin: 0; padding: 5px 18px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--muted);
  font-size: var(--text-base); font-weight: 600; cursor: pointer;
}
.p2s-file > label.btn-secondary:hover { border-color: var(--muted); color: var(--text); }
/* Admin users on a phone: the row actions stack full width (a row of buttons that wraps is a stack). */
@media (max-width: 575.98px) {
  .p2s-users-table .p2s-users-actions { flex-direction: column; align-items: stretch; gap: var(--space-xs); }
  .p2s-users-table .p2s-users-actions .link-btn, .p2s-users-table .p2s-users-actions form { margin: 0; width: 100%; display: block; }
  .p2s-users-table .p2s-users-actions .link-btn { display: inline-flex; justify-content: center; }
}
/* A number link in a phone card sits at the right edge like the plain numbers around it (the 44px tap box centred it). */
@media (max-width: 767.98px) { .data-table-cards td.num > a { text-align: right; } }
/* A hint written between the fields of a grid spans the whole row instead of taking a field's cell. */
.form-grid > .field-hint { grid-column: 1 / -1; margin: 0; }
/* Card titles on the project page follow the app's card-title look (dark, bold) instead of the theme's grey h2. */
.p2s-pmap-card > h2, .p2s-verify-title, .p2s-landing-title { color: var(--text); font-size: var(--text-md); font-weight: 700; margin: 0 0 var(--space-sm); }
@media (max-width: 767.98px) { .data-table-cards tfoot { display: block; } .data-table-cards tfoot tr { margin-top: var(--space-sm); } }  /* a totals row becomes a full-width card like the rows above it */
/* doc_items: one table per section, so the columns are fixed to line up from one section to the next. */
@media (min-width: 768px) {
  .p2s-doc-items-table { table-layout: fixed; width: 100%; }
  .p2s-doc-items-table th:nth-child(2) { width: 14%; }
  .p2s-doc-items-table th:nth-child(3) { width: 90px; }
  .p2s-doc-items-table th:nth-child(4) { width: 80px; }
  .p2s-doc-items-table th:nth-child(5) { width: 110px; }
  .p2s-doc-items-table th:nth-child(6) { width: 250px; }
  .p2s-doc-items-table td:nth-child(6) { white-space: nowrap; }
}
/* the "ดูตัวเลขทุกโครงการ" disclosure reads as the foot of its section (divider line), not a card of its own */
.exec-data-disclosure-standalone { padding: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: none; }
@media (max-width: 767.98px) { .exec-data-disclosure-standalone { padding: 0; } }
/* Executive overview (exec/dashboard.php): "สรุปผู้บริหาร" at the top, a jump bar, and the merged growth section */
.exec-jump { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--space-lg); }
.exec-jump a { padding: 4px 12px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
.exec-jump a:hover, .exec-jump a:focus-visible { text-decoration: underline; }
.exec-dashboard :is(h2, h3)[id] { scroll-margin-top: 90px; }
.exec-brief { margin: 0 0 var(--space-xl); padding: var(--space-lg); border: 1px solid var(--border); border-left: 4px solid var(--primary); border-radius: var(--radius); background: var(--surface); }
.exec-brief.is-bad { border-left-color: var(--color-danger); }
.exec-brief.is-warn { border-left-color: var(--color-amber-strong); }
.exec-brief.is-good { border-left-color: var(--color-success); }
.exec-brief-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-sm) var(--space-lg); margin-bottom: var(--space-md); }
.exec-brief-head h2 { margin: 0 0 2px; font-size: var(--text-xl); font-weight: 700; color: var(--text); }
.exec-brief-verdict { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text); }
.exec-brief-counts { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.exec-brief-counts li { padding: 4px 12px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; background: var(--head-bg); color: var(--muted); }
.exec-brief-counts li strong { font-size: var(--text-md); color: inherit; }
.exec-brief-counts .is-bad { background: #fee2e2; color: var(--color-danger); }
.exec-brief-counts .is-warn { background: #fef3c7; color: var(--color-warning); }
.exec-brief-counts .is-good { background: #d1fae5; color: var(--color-success); }
.exec-brief-list { display: grid; margin: 0 0 var(--space-sm); padding: 0; list-style: none; }
.exec-brief-list li { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: start; gap: 12px; padding: 10px 0; border-top: 1px solid var(--border); }
.exec-brief-list li > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.exec-brief-list li strong { font-size: var(--text-base); font-weight: 600; color: var(--text); }
.exec-brief-list li span { font-size: var(--text-sm); color: var(--muted); }
.exec-brief-list li > .exec-brief-mark { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; font-size: var(--text-sm); font-weight: 700; background: var(--head-bg); color: var(--muted); }
.exec-brief-list .is-bad > .exec-brief-mark { background: var(--color-danger); color: #fff; }
.exec-brief-list .is-warn > .exec-brief-mark { background: var(--color-amber-strong); color: #fff; }
.exec-brief-list .is-good > .exec-brief-mark { background: var(--color-success); color: #fff; }
.inv-page .exec-domain .exec-kpi-wrap { margin-top: var(--space-md); }
@media (min-width: 992px) { #growth .exec-kpi-strip { grid-template-columns: minmax(225px, 1.45fr) repeat(3, minmax(125px, 1fr)); } }
.inv-subtitle { margin: var(--space-xl) 0 var(--space-sm); font-size: var(--text-lg); font-weight: 700; color: var(--text); }
.inv-subtitle small { display: block; margin-top: 2px; font-size: var(--text-sm); font-weight: 400; color: var(--muted); }
#growth .inv-channels { margin-top: 0; }
.inv-page .exec-domain + .inv-definitions { margin-top: var(--space-xl); }
@media print { .exec-jump { display: none !important; } .exec-brief { break-inside: avoid; } }
@media (max-width: 575.98px) { .exec-brief { padding: var(--space-md); } .exec-brief-list li { grid-template-columns: 28px minmax(0, 1fr); } .exec-brief-list li > a { grid-column: 2; } }

/* Topbar (owner 2026-10-10): search sits right beside the hamburger; the งานวันนี้ count rides on the hamburger where the menu is hidden */
.p2s-topsearch { margin-right: 0; margin-left: 4px; }
#page-topbar .navbar-header > .d-flex:first-child { align-items: center; }
#topnav-hamburger-icon { position: relative; }
.p2s-burger-badge { display: none; position: absolute; top: 0; right: 0; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--color-danger); color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; box-sizing: border-box; }
@media (max-width: 1199.98px) { .p2s-burger-badge { display: inline-block; } }
/* the standalone งานวันนี้ entry keeps its count at the right edge of the row */
.app-menu .nav-link.menu-link .nav-badge { margin-left: auto; }

/* Top search look (owner 2026-10-10, from his screenshot): a soft grey field with no border, the magnifier inside at the left, "Search…" in grey */
#page-topbar .p2s-topsearch { position: relative; gap: 0; }
#page-topbar .p2s-topsearch input[type="search"] { width: 320px; max-width: 34vw; padding: 0 14px 0 38px; border: 1px solid transparent; border-radius: var(--radius); background: #f4f4f4; color: var(--text); box-shadow: none; }
#page-topbar .p2s-topsearch input[type="search"]::placeholder { color: #626262; opacity: 1; }   /* ≥4.5:1 on the field's #f4f4f4 */
#page-topbar .p2s-topsearch input[type="search"]:focus { background: #fff; border-color: var(--primary); }
#page-topbar .p2s-topsearch input[type="search"]::-webkit-search-cancel-button { display: none; }
#page-topbar .p2s-topsearch .p2s-topsearch-btn { position: absolute; left: 0; top: 0; color: #5f5f5f; pointer-events: none; } /* a drawing in the field; Enter submits */
#page-topbar .p2s-topsearch .p2s-topsearch-btn svg { width: 18px; height: 18px; }
.p2s-topsearch-overlay input[type="search"] { border: 1px solid transparent; background: #f4f4f4; }

/* Phone: the logo sits in the middle of the bar (owner 2026-10-10); menu + search on the left, bell + avatar on the right */
@media (max-width: 767.98px) {
  #page-topbar .navbar-header { position: relative; }
  #page-topbar .horizontal-logo { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0; order: 0; pointer-events: none; }
  #page-topbar .horizontal-logo .logo { pointer-events: auto; }
}

/* ด่านเช็กเบอร์ก่อนสร้าง (src/entry_gate.php, public/assets/entry-gate.js) */
.p2s-gate { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; margin: 0 0 16px; }
.p2s-gate .search-form { margin-top: 6px; }
.p2s-gate .search-form input { flex: 1 1 auto; }
.p2s-qr-box { margin-top: 10px; padding: 12px; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); width: max-content; max-width: 100%; }
.p2s-qr-box svg { display: block; width: 220px; max-width: 100%; }
.p2s-gate .label-note { margin-left: 8px; }
.p2s-similar .alert { margin: 8px 0; }
.p2s-gate-result { margin-top: 10px; }
.p2s-gate-result .alert { margin: 8px 0; }
.p2s-gate-list { margin: 6px 0 0; padding-left: 18px; }
.p2s-gate-ok { color: var(--success, #166534); }
.p2s-gate-warn { color: var(--danger, #b91c1c); font-weight: 600; }

/* KPI ทีม Marketing: one cell per group, measures stacked inside it (src/kpi_team_page.php) */
.p2s-kpi-grp { min-width: 190px; }
.p2s-kpi-grp-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 2px 0; white-space: nowrap; }
.p2s-kpi-grp-label { font-size: var(--text-sm); color: var(--muted); flex: 1 1 auto; }   /* takes the slack, so the figure and its target sit at the right edge in every row (space-between used to float the figure midway) */
.p2s-kpi-grp-row .p2s-kpi-pill { min-width: 36px; padding: 1px 8px; flex: 0 0 auto; }
.p2s-kpi-grp-row .p2s-kpi-sub { display: inline-block; margin: 0 0 0 4px; min-width: 4.2em; text-align: right; flex: 0 0 auto; }   /* same width for "/ 4" and "/ 16" and "เป้า 100", so the figures line up down the column */

/* user_form: the หัวหน้าทีม (Manager) tick appears only while Sales or Marketing is the chosen role */
.p2s-manager-field { display: none; margin-top: var(--space-sm); }
form:has(#role input[value="sale"]:checked) .p2s-manager-field,
form:has(#role input[value="marketing"]:checked) .p2s-manager-field { display: block; }

/* the KPI block at the top of the merged Sales dashboard */
#kpi.p2s-kpi-page { margin-bottom: var(--space-xl, 2rem); }
#kpi-months.p2s-kpi-page { margin-top: var(--space-lg); }

/* ปุ่มดาวน์โหลด CSV/PDF/SQL (public/assets/export-format.js): <details> ที่เปิดเมนูเลือกรูปแบบ — ใช้หน้าตาปุ่มเดิม และแผงเมนูของ .p2s-more */
.p2s-export-menu { position: relative; display: inline-block; }
.p2s-export-menu > summary { list-style: none; cursor: pointer; }
.p2s-export-menu > summary::-webkit-details-marker { display: none; }
.p2s-export-menu .p2s-more-panel { min-width: 240px; }

/* AI helper on the project form: its own tinted card at the top so it is not mistaken for another field group */
.ai-help-card { margin: 0 0 20px; padding: 16px 18px; border: 1px solid var(--primary); border-left-width: 4px; border-radius: var(--radius); background: var(--primary-soft); }
.ai-help-title { margin: 0 0 4px; font-size: var(--text-base, 16px); }
.ai-help-card .field-hint { margin: 0 0 12px; }
.ai-help-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.ai-help-option { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.ai-help-option > span { color: var(--muted); font-size: var(--text-sm); }
.ai-help-option .btn-secondary { margin: auto 0 0; align-self: flex-start; }
.ai-help-card textarea { width: 100%; margin-top: 12px; }

/* พื้นที่ที่ชำนาญ picker (src/views/co_agent_areas_field.php, assets/co-agent-areas.js): chips for the picks, one search box under them. */
.co-areas { display: grid; gap: 8px; margin-top: 16px; }
.co-areas-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.co-area-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px 10px; border-radius: 999px; background: rgba(var(--primary-rgb), .12); color: var(--primary); font-size: var(--text-sm); font-weight: 600; }
.co-area-remove { border: 0; background: transparent; color: inherit; cursor: pointer; min-width: 24px; min-height: 24px; border-radius: 50%; font-size: 16px; line-height: 1; padding: 0; }
.co-area-remove:hover { background: rgba(var(--primary-rgb), .2); }
.co-areas-input { width: 100%; max-width: 480px; }
.co-areas-input:disabled { opacity: .6; }

/* "เข้าใช้งานแทน" bar (src/impersonate.php): fixed at the bottom of every page while an Admin works as another member. */
.p2s-impersonate-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 2000; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding: 8px 16px; background: #b45309; color: #fff; font-size: var(--text-sm); box-shadow: 0 -2px 8px rgba(0,0,0,.25); }
.p2s-impersonate-bar form { margin: 0; }
.p2s-impersonate-bar .btn-secondary { background: #fff; color: #7c2d12; border-color: #fff; }
body.p2s-impersonating { padding-bottom: 72px; }
@media (max-width: 575.98px) { body.p2s-impersonating { padding-bottom: 120px; } }

/* ======================================================================
   Analysis pages (sales/lead_insight.php, sales/co_agent_insight.php) — gauges, journey stepper, meters, match list
   ====================================================================== */
.p2s-insight-gauges { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); margin: var(--space-md) 0; }
.p2s-gauge-card { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); min-width: 0; }
.p2s-gauge { position: relative; flex: none; width: 104px; aspect-ratio: 1; border-radius: 50%; display: grid; place-items: center;
  background: conic-gradient(var(--g, var(--primary)) calc(var(--p, 0) * 1%), var(--head-bg) 0); }
.p2s-gauge::before { content: ""; position: absolute; inset: 11px; border-radius: 50%; background: var(--surface); }
.p2s-gauge span { position: relative; font-size: var(--text-xl); font-weight: 700; color: var(--text); }
.p2s-gauge-title { margin: 0 0 4px; font-size: var(--text-md); }
.p2s-stepper { list-style: none; margin: 0 0 var(--space-lg); padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-md); }
.p2s-step { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--muted); }
.p2s-step-dot { flex: none; width: 22px; aspect-ratio: 1; border-radius: 50%; border: 2px solid var(--border-solid); display: grid; place-items: center; }
.p2s-step-dot svg { width: 14px; }
.p2s-step.is-done { color: var(--text); }
.p2s-step.is-done .p2s-step-dot { background: var(--color-success); border-color: var(--color-success); color: #fff; }
.p2s-step-at { font-size: var(--text-sm); color: var(--muted); }
.p2s-insight-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); margin-bottom: var(--space-md); }
.p2s-insight-sub { margin: 0 0 var(--space-sm); font-size: var(--text-md); }
.p2s-meter-row { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 3px 0; font-size: var(--text-sm); }
.p2s-meter { display: block; height: 8px; border-radius: 999px; background: var(--head-bg); overflow: hidden; }
.p2s-meter i { display: block; height: 100%; border-radius: 999px; background: var(--primary); }
.p2s-meter-n { color: var(--muted); font-variant-numeric: tabular-nums; }
.p2s-insight-list, .p2s-insight-steps { margin: 0; padding: 0 0 0 18px; }
.p2s-insight-steps { list-style: none; padding: 0; }
.p2s-insight-steps li { padding: 4px 0; }
.p2s-insight-steps + .field-hint { margin-top: var(--space-sm); }
.p2s-delta { display: inline-block; min-width: 64px; padding: 1px 8px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; text-align: center; background: var(--head-bg); color: var(--text); }
.p2s-delta.is-up { background: rgba(4, 120, 87, .12); color: var(--color-success); }
.p2s-delta.is-down { background: rgba(185, 28, 28, .10); color: var(--color-danger); }
.p2s-match-list { display: grid; gap: var(--space-sm); margin-bottom: var(--space-lg); }
.p2s-match-item { padding: var(--space-sm) var(--space-md); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.p2s-match-head { display: flex; justify-content: space-between; gap: var(--space-sm); margin-bottom: 6px; }
.p2s-match-item .field-hint { margin: 6px 0 0; }
@media (max-width: 767.98px) {
  .p2s-insight-gauges, .p2s-insight-two { grid-template-columns: minmax(0, 1fr); }
  .p2s-gauge { width: 88px; }
}

/* Exec (read-only role): the follow-up cards keep only the link that opens the record; the logging / postponing forms are hidden (the server refuses their POST anyway). */
.followup-actions.is-viewer > :not(.followup-open) { display: none; }

/* Coloured cards on project_detail.php (demand match, project documents): a bar and a tinted title in the card's accent. */
.p2s-card-accent { --card-accent: var(--primary); border-left: 6px solid var(--card-accent); background: linear-gradient(to right, color-mix(in srgb, var(--card-accent) 8%, var(--surface)), var(--surface) 55%); }
.p2s-card-accent.is-demand { --card-accent: var(--color-blue); }
.p2s-card-accent.is-docs { --card-accent: var(--color-amber-deep); }
.p2s-card-accent > .p2s-card-title { margin: 0 0 var(--space-sm); font-size: var(--text-md); font-weight: 700; color: var(--card-accent); }
.p2s-card-accent.is-docs .project-doc-row { margin-bottom: 8px; padding: 8px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.p2s-card-accent.is-docs .project-doc-row > strong { color: var(--card-accent); }

/* Jump targets on the Co-Agent page (the Lead count / visited-projects links) stop below the sticky top bar. */
#coAgentLeads, #coAgentVisits { scroll-margin-top: 84px; }

/* Project visit history (project_visits.php): one coloured card per kind of visitor, date chips, the Leads/Co-Agent split. */
.p2s-card-accent.is-agent { --card-accent: var(--color-teal); }
.p2s-card-accent.is-lead { --card-accent: var(--color-pink); }
.p2s-card-accent.is-staff { --card-accent: var(--color-slate); }
.p2s-visit-tabs { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin: 0 0 var(--space-md); }
.p2s-date-chip { display: inline-block; margin: 0 4px 4px 0; padding: 1px 8px; border-radius: 999px; background: var(--head-bg); font-size: var(--text-sm); white-space: nowrap; }
.summary-card-value small { font-size: var(--text-sm); font-weight: 400; color: var(--muted); }


.p2s-card-accent.is-required { --card-accent: var(--color-amber-strong); }
.p2s-required-card { margin-bottom: 24px; }
.p2s-required-card > .p2s-card-title { font-size: var(--text-lg); }

.p2s-from-unit { display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; padding: 1px 8px; border-radius: 999px; background: color-mix(in srgb, var(--color-teal) 12%, var(--surface)); color: var(--color-teal); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; vertical-align: middle; }
.p2s-from-unit svg { width: 12px; }

.p2s-card-accent.is-units { --card-accent: var(--color-success); }

.p2s-card-accent.is-map { --card-accent: var(--color-purple); }

.p2s-card-accent.is-status { --card-accent: var(--color-pink); }

.p2s-card-accent .project-status-strip { margin: 0 0 var(--space-md); padding: 0; background: none; border: 0; border-radius: 0; }

.p2s-units-avail { color: var(--color-success); font-weight: 600; white-space: nowrap; }

.p2s-complete { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.p2s-complete-track { display: inline-block; width: 64px; height: 8px; border-radius: 999px; background: var(--border); overflow: hidden; }
.p2s-complete-fill { display: block; height: 100%; border-radius: 999px; background: var(--c-fill, var(--color-success)); }
.p2s-complete b { font-size: var(--text-sm); color: var(--text); font-variant-numeric: tabular-nums; }
.p2s-complete-good { --c-fill: var(--color-success); }
.p2s-complete-mid { --c-fill: var(--color-amber-strong); }
.p2s-complete-low { --c-fill: var(--color-red); }
.p2s-complete-link { text-decoration: none; }
.p2s-complete-card .p2s-complete-track { width: 160px; }
.p2s-complete-list { display: flex; flex-wrap: wrap; gap: 6px 8px; margin: 10px 0 0; padding: 0; list-style: none; }
.p2s-complete-list a { display: inline-block; padding: 2px 10px; border-radius: 999px; background: color-mix(in srgb, var(--color-amber-strong) 12%, var(--surface)); color: var(--color-warning); font-size: var(--text-sm); font-weight: 600; text-decoration: none; }   /* --color-warning text: amber-strong was 4.25:1 */
.p2s-complete-list a:hover, .p2s-complete-list a:focus-visible { background: color-mix(in srgb, var(--color-amber-strong) 22%, var(--surface)); }
.p2s-card-accent.is-complete { --card-accent: var(--color-teal); }
/* the red line sits in the card's side padding, so the empty box stays lined up with the others */
/* An empty optional field is marked quietly — red is kept for errors (critique 2026-10-17: nineteen red bars read as a failed form) */
.project-form label.p2s-missing, .project-form fieldset.p2s-missing, .project-form .field-group.p2s-missing { border-left: 3px solid var(--border); padding-left: 7px; margin-left: -10px; }
.p2s-complete-plain { display: inline-block; padding: 2px 10px; border-radius: 999px; background: var(--head-bg); color: var(--muted); font-size: var(--text-sm); font-weight: 600; }

.p2s-complete-card { margin: 0 0 var(--space-md); }

.p2s-company-count { color: var(--muted); white-space: nowrap; }

/* Co-Agent list: last contact and status — kept in the theme's own quiet type; only the colour of "N วันก่อน" and the status badge carry the signal */
.p2s-lastcontact { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; vertical-align: top; }
.p2s-lastcontact b { font-weight: 600; white-space: nowrap; }
.p2s-lastcontact small, .p2s-lastcontact-label { color: var(--muted); font-size: var(--text-sm); }
.p2s-lastcontact.is-good b { color: var(--color-success); }
.p2s-lastcontact.is-warn b { color: var(--color-amber-strong); }
.p2s-lastcontact.is-bad b { color: var(--color-red); }
.p2s-lastcontact-out { display: block; margin-top: 3px; width: fit-content; max-width: 100%; padding: 0 8px; border-radius: 999px; background: var(--head-bg); color: var(--muted); font-size: var(--text-sm); }
.p2s-lastcontact.is-inline { margin-left: 6px; }
.p2s-lastcontact.is-inline + .p2s-lastcontact-out { display: inline-block; margin: 0 0 0 6px; }

.p2s-card-accent.is-tools { --card-accent: var(--color-teal-deep); }
.p2s-tool-links { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.p2s-tool-icon { display: inline-flex; margin-right: 6px; }
.p2s-tool-icon svg { width: 16px; stroke: currentColor; fill: none; }

.p2s-card-accent.is-verify { --card-accent: var(--color-blue); }
.p2s-card-accent.is-landing { --card-accent: var(--color-pink-bright); }

/* ======================================================================
   CONTROL HEIGHT STANDARD — owner 2026-10-09, no exceptions
   Every button (anything whose class is or contains "btn", .link-btn, every submit) and every single-line field (input, select)
   is exactly var(--control-h) = 34px tall, on every page and every screen size. This block is LAST and uses !important on
   purpose: it is the one rule that must win over the theme (Velzon's 42px icon buttons, 70px hamburger), the 44px touch-target
   rules and any page-specific height. Change the height by changing --control-h, never by adding a rule elsewhere.
   Not covered, on purpose: <textarea> (multi-line), checkbox/radio/file/range/color, menu rows (.dropdown-item), the
   project-picker row cards (.pk-row, a whole card that happens to be a <button>) and the settings palette preview mock
   (.p2s-palette-btn, a picture of a button, not a control). Checked by tools/verify-control-height.php.
   ====================================================================== */
:is(a, button, summary, span, label):is(.btn, .link-btn, [class*="btn-"], [class$="-btn"], [class*="-btn "]):not(.p2s-palette-btn):not(.dropdown-item),
button[type="submit"]:not(.dropdown-item):not(.pk-row),
input:is([type="submit"], [type="button"], [type="reset"]),
input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"], [type="hidden"], [type="image"]),
select:not([multiple]):not([size]) {
  height: var(--control-h) !important;
  min-height: var(--control-h) !important;
  max-height: var(--control-h) !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  box-sizing: border-box !important;
}
:is(a, button, summary, span, label):is(.btn, .link-btn, [class*="btn-"], [class$="-btn"], [class*="-btn "]):not(.p2s-palette-btn):not(.dropdown-item) {
  align-items: center; /* a fixed height with no vertical padding: keep the label in the middle */
  white-space: nowrap;  /* a wrapped label cannot grow the button any more, so it does not wrap */
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; /* and a label longer than the room ends in … instead of leaving the screen */
  flex-shrink: 0;       /* overflow:hidden would otherwise let a flex row squeeze buttons below their label (clipped text) instead of wrapping */
}
/* The hamburger used to fill the 70px bar; at 34px it has to be centred in it like the other topbar controls. */
#topnav-hamburger-icon { align-self: center; }
/* Icon buttons that sit INSIDE a 34px field (show-password eye, developer-search toggle) fill the field's height exactly. */
:is(.password-toggle, .p2s-devsearch-toggle) {
  height: var(--control-h) !important;
  min-height: var(--control-h) !important;
  padding: 0 !important;
  box-sizing: border-box !important;
}
/* Icon-only buttons are square at the same size. */
:is(.btn-icon, .btn-icon-only, .p2s-star-btn, .p2s-view-switch-btn, .p2s-topsearch-btn, .password-toggle, .p2s-devsearch-toggle) {
  width: var(--control-h) !important;
  min-width: var(--control-h) !important;
}

/* Call / LINE buttons on งานวันนี้ cards and the ทำวันนี้ก่อน list (src/phone_mask.php phone_call_buttons). */
.p2s-quick-contact { display: inline-flex; gap: var(--space-xs); flex-wrap: wrap; }
.p2s-quick-call { gap: 6px; }
.p2s-quick-call svg { width: 16px; height: auto; flex: none; }
.followup-top-list { counter-reset: top; }
/* The card's "⋯ เพิ่มเติม" menu (src/icons.php p2s_row_menu): the less-used actions of a Lead / Co-Agent card, one pill instead of four. */
.p2s-card-menu > summary { gap: 6px; padding: 0 12px; font-size: var(--text-sm); font-weight: 600; }
.p2s-card-menu > summary svg { flex: none; }
@media (max-width: 767.98px) { .p2s-contact-card-actions > .p2s-card-menu > summary { width: 100%; } }
/* The panel of the card menu is a plain list of rows (icon + label, left aligned, one hover colour), not the app's colour-coded action pills:
   four different pill colours stacked in a small popover read as noise (owner 2026-10-15: "ไม่สวย ใช้งานยาก"). */
.p2s-card-menu .p2s-more-panel { min-width: 230px; max-width: min(300px, 92vw); padding: 6px; display: grid; gap: 2px; }
.p2s-card-menu .p2s-more-panel .link-btn,
.p2s-card-menu .p2s-more-panel button.link-btn {
  display: flex; align-items: center; justify-content: flex-start !important; gap: 12px; width: 100%; margin: 0; padding-left: 12px !important; padding-right: 12px !important;
  background: transparent !important; color: var(--text) !important; border: 0 !important; border-radius: var(--radius); box-shadow: none;
  font-size: var(--text-base); font-weight: 500; text-align: left; text-decoration: none; white-space: nowrap;
}
.p2s-card-menu .p2s-more-panel .link-btn svg { width: 18px; flex: none; color: var(--muted); }
.p2s-card-menu .p2s-more-panel .link-btn:hover, .p2s-card-menu .p2s-more-panel .link-btn:focus-visible,
.p2s-card-menu .p2s-more-panel form:hover .link-btn { background: var(--primary-soft) !important; color: var(--primary) !important; }
.p2s-card-menu .p2s-more-panel .link-btn:hover svg, .p2s-card-menu .p2s-more-panel .link-btn:focus-visible svg { color: var(--primary); }
.p2s-card-menu .p2s-more-panel .link-btn.danger { color: var(--color-danger) !important; }
.p2s-card-menu .p2s-more-panel .link-btn.danger svg { color: inherit; }
.p2s-card-menu > summary { color: var(--text); background: var(--surface); }
.p2s-card-menu[open] > summary { border-color: var(--primary); color: var(--primary); }
/* The call / LINE buttons and the "⋯ เพิ่มเติม" menu sit in the card's top row, at the right of the name, the menu last. */
.p2s-card-head-tools { margin-left: auto; flex: none; display: flex; align-items: center; gap: var(--space-xs); }
/* Call, LINE and เพิ่มเติม are one width (the same on Lead and Co-Agent cards); on a phone they stack in one column. */
.p2s-card-head-tools .p2s-quick-call, .p2s-card-head-tools .p2s-card-menu > summary { width: 112px; height: var(--control-h); min-height: var(--control-h); justify-content: center; box-sizing: border-box; }
@media (max-width: 767.98px) { .p2s-card-head-tools { flex-direction: column; align-items: flex-end; } .p2s-card-head-tools .p2s-quick-contact { flex-direction: column; align-items: flex-end; } }
/* A step button that is not open yet (src/lead_flow.php, p2s_locked_item): grey, not clickable, and the reason sits right under it. */
.p2s-locked { display: inline-flex; flex-direction: column; gap: 2px; max-width: 100%; vertical-align: top; }
.p2s-locked .is-locked { opacity: .55; cursor: not-allowed; pointer-events: auto; }
.p2s-locked .link-btn.is-locked { background: transparent !important; }
.p2s-locked-why { color: var(--muted); font-size: var(--text-2xs); max-width: 220px; }
.p2s-card-menu .p2s-more-panel .p2s-locked { display: flex; padding: 2px 0; }
.p2s-card-menu .p2s-more-panel .p2s-locked-why { padding: 0 12px 4px 42px; max-width: none; }
.p2s-card-menu .p2s-more-panel .link-btn.is-locked:hover { background: transparent !important; color: var(--text) !important; }

.p2s-role-help { margin: 6px 0 0; padding-left: 1.2em; }
.p2s-unsaved-note { color: var(--warning-text, #92400e); font-weight: 600; }
.p2s-assign-form { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 6px; }
.p2s-assign-form select { width: 11rem; max-width: 100%; }

/* Project pages: the tabs scroll sideways in one row on a phone; the add / developers / packages buttons share one row under them (owner 2026-10-15) */
.p2s-project-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; }
@media (max-width: 767.98px) {
  .p2s-project-tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .p2s-project-tabs::-webkit-scrollbar { display: none; }
  .p2s-project-tabs .pb-tab { white-space: nowrap; flex: 0 0 auto; }
  /* the buttons share the whole row edge to edge */
  .p2s-project-actions > a { flex: 1 1 auto; justify-content: center; text-align: center; }
}

/* Help page (public/help.php) */
.p2s-help-toc { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 16px; }
.p2s-help-toc a { padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--primary-dark); font-size: var(--text-sm); }
.p2s-help-toc a:hover { border-color: var(--primary); }
.p2s-help-steps { margin: 8px 0 12px; padding-left: 1.4em; display: grid; gap: 8px; }
.p2s-help-flow { margin: 0 0 6px; }
.p2s-help-flow strong { padding: 2px 0; }
.p2s-help-group th { background: var(--surface-subtle); text-align: left; font-weight: 700; }
.data-table th[scope="row"] { text-align: left; white-space: nowrap; vertical-align: top; }
#start, #menu, #screens, #lead, #co-agent, #central, #links, #duplicate, #projects, #marketing, #support, #kpi, #verify, #profile, #admin, #faq { scroll-margin-top: 70px; }
@media (max-width: 767.98px) { .data-table th[scope="row"] { white-space: normal; } }
@media (max-width: 767.98px) {
  /* Help tables read as stacked cards on a phone (no sideways scrolling) */
  .p2s-help .data-table thead { display: none; }
  .p2s-help .data-table, .p2s-help .data-table tbody, .p2s-help .data-table tr, .p2s-help .data-table th, .p2s-help .data-table td { display: block; width: auto; }
  .p2s-help .data-table tr { padding: 10px 12px; border-bottom: 1px solid var(--border); }
  .p2s-help .data-table th[scope="row"] { padding: 0 0 4px; }
  .p2s-help .data-table td { padding: 2px 0; border: 0; }
  .p2s-help .data-table td[data-label]::before { content: attr(data-label) ": "; color: var(--muted); font-weight: 600; }
  .p2s-help .p2s-help-group { padding: 0; }
  .p2s-help .p2s-help-group th { padding: 8px 12px; }
}

/* Dashboard control strip (src/period.php dashboard_controls_html(), owner 2026-10-16): month box + 3/6/12 + printer, one row, no
   stray margin from the stand-alone month switcher's own spacing. */
.p2s-dashboard-controls .p2s-month-nav { margin: 0; }
@media print { .p2s-dashboard-controls { display: none !important; } }
/* On a desktop the strip keeps its one row; the title column beside it is what wraps (a long hint pushed the printer to a 2nd line). */
@media (min-width: 768px) { .exec-dashboard-header > .p2s-dashboard-controls { flex: 0 0 auto; flex-wrap: nowrap; } }
/* The printer menu's "พิมพ์หน้านี้" is a <button> (data-print) among <a> rows: same look as the links (src/period.php dashboard_controls_html). */
button.p2s-more-item { width: 100%; border: 0; background: transparent; font: inherit; text-align: left; cursor: pointer; }
button.p2s-more-item:hover, button.p2s-more-item:focus-visible { background: var(--head-bg); }
.p2s-dashboard-controls .p2s-export-menu { position: relative; }

/* "ระบุขนาดเป็น" on the project form (owner 2026-10-16): a small row of three choices under a normal field label, not three full-width cards. */
.p2s-size-mode { border: 0; padding: 0; margin: 0 0 14px; min-width: 0; }
.p2s-size-mode legend { font-size: var(--text-sm); font-weight: 600; margin: 0 0 6px; padding: 0; float: none; width: auto; color: var(--text); }
.p2s-size-mode .option-chip { display: inline-flex; width: auto; margin: 0 8px 8px 0; vertical-align: top; }
/* /bu/project_types.php: the one-click "แปลงอัตโนมัติ" note sits on its own, apart from the filter pills below it. */
.p2s-type-autofix { margin: 0 0 var(--space-md); padding: var(--space-md); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.p2s-type-autofix p { margin: 0 0 var(--space-sm); }
.p2s-type-bad { color: var(--danger, #b42318); font-weight: 600; }
.p2s-type-search { margin: var(--space-sm) 0 var(--space-md); }

/* วิเคราะห์ราคา (src/price_analysis.php): ↓ green = sells below its asking price, ↑ red = above, grey = equal / not enough data. */
.p2s-price-gap { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.2; }
.project-info-value .p2s-price-gap { align-items: flex-start; }
.p2s-price-gap-num { font-weight: 700; white-space: nowrap; }
.p2s-price-gap-note { font-size: var(--text-2xs); font-weight: 500; color: var(--muted); white-space: nowrap; }
.p2s-price-gap.is-down .p2s-price-gap-num { color: var(--color-success); }
.p2s-price-gap.is-up .p2s-price-gap-num { color: var(--color-danger); }
.p2s-price-gap.is-flat .p2s-price-gap-num, .p2s-price-gap.is-none { color: var(--muted); }
.p2s-price-gap.is-fix { text-decoration: none; }
.p2s-price-gap.is-fix .p2s-price-gap-num { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
.p2s-price-gap.is-fix .p2s-price-gap-note { white-space: normal; text-align: right; max-width: 12rem; }
.project-info-value .p2s-price-gap.is-fix .p2s-price-gap-note { text-align: left; }
/* วิเคราะห์โอกาส (src/group_opportunity.php): the score chip coloured by level — สูง green, กลาง amber, ต่ำ red. */
.p2s-opp { display: inline-flex; align-items: baseline; gap: 2px; padding: 2px 10px; border-radius: 999px; border: 1px solid currentColor; font-size: var(--text-sm); white-space: nowrap; }
.p2s-opp.is-high { color: var(--color-success); }
.p2s-opp.is-mid { color: var(--color-amber-strong); }
.p2s-opp.is-low { color: var(--color-danger); }
.p2s-opp.is-none { color: var(--muted); border-color: transparent; }
.p2s-opp-link { text-decoration: none; }
.p2s-opp-ev { font-size: var(--text-2xs); color: var(--muted); margin-top: 3px; white-space: normal; text-align: right; }
.p2s-opp-big .p2s-opp { font-size: var(--text-lg, 1.25rem); padding: 4px 14px; }
.p2s-opp-parts { font-size: var(--text-2xs); color: var(--muted); white-space: nowrap; }
.p2s-opp-cell { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.3; }
.p2s-opp-formula { margin: 0 0 12px; }
/* bu/project_form.php: the price boxes give way to the units (owner 2026-10-16). */
.p2s-unit-prices-note svg { width: 16px; height: 16px; color: var(--primary); vertical-align: -3px; margin-right: 4px; }
.p2s-unit-prices-warn { margin: 4px 0 10px; }
.p2s-complete-locked { display: inline-block; padding: 2px 8px; color: var(--muted); font-size: var(--text-sm); }
.p2s-unit-price-tag { font-size: var(--text-2xs); font-weight: 500; color: var(--muted); margin-left: 4px; }
.p2s-opp-subtitle { font-size: var(--text-base, 15px); font-weight: 700; margin: 18px 0 8px; }
.p2s-opp-formula summary { cursor: pointer; color: var(--primary); font-size: var(--text-sm); }
/* A link to one box of the project form (#field_… from วิเคราะห์ราคา / ความสมบูรณ์ของข้อมูล) lands below the fixed top bar, not under it. */
.project-form :is(input, select, textarea), .project-form label { scroll-margin-top: 110px; }

/* ======================================================================
   Phone readability + touch (design audit 2026-10-16, P1-1 / P1-2)
   Back-office text was 12–13px on a phone (Thai vowel and tone marks need more room) and links / pills that act like buttons were
   17–21px tall. On a phone the whole type scale moves up one step, and every link that is an action gets the app's control height
   (--control-h, the same 34px rule as buttons and inputs). Desktop is unchanged.
   ====================================================================== */
@media (max-width: 575.98px) {
  :root {
    --vz-body-font-size: 0.9375rem;   /* 15px: the theme's body text */
    --text-2xs: 13px;
    --text-sm: 14px;
    --text-base: 15px;
  }
  body { font-size: 15px; }
  .followup-name, .followup-phone, .followup-open,
  .p2s-calendar-event, .calendar-more,
  .exec-jump a, .exec-brief a,
  .status-summary .status-pill, .status-summary a,
  .p2s-breadcrumb a,
  .data-table-cards td > a:not(.btn):not(.link-btn):not(.p2s-price-gap) {
    display: inline-flex;
    align-items: center;
    min-height: var(--control-h);
  }
  /* A bare checkbox (เฉพาะของฉัน, a row's select) gets a finger-sized box around it. */
  label:has(> input[type="checkbox"]) { min-height: var(--control-h); display: inline-flex; align-items: center; gap: 8px; }
  input[type="checkbox"] { width: 20px; height: 20px; }
  /* Links inside text (a card's phone, a hint, an alert): a taller tap area that does not move the text around it. */
  .p2s-contact-fields dd a, .field-hint a, .alert a, .summary-card-label a, .p2s-complete-link {
    display: inline-block; padding-block: 7px; margin-block: -7px;
  }
}

/* Links inside running text are underlined, so they are not told apart by colour alone (WCAG 1.4.1, design audit 2026-10-16). */
.field-hint a:not([class]), .p2s-note a:not([class]), .alert a:not([class]), main p > a:not([class]), .page-content p > a:not([class]) {
  text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px;
}
#numbers, #units, #docs, #location { scroll-margin-top: 80px; }   /* the project page's jump bar (Sale / Support) lands below the fixed top bar */
/* bu/project_promotions.php (layout 2026-10-16): list, then a full-width form with room above and below. */
.p2s-promo-form { margin: var(--space-lg, 20px) 0 var(--space-2xl, 32px); }
.p2s-promo-form-title { margin: 0 0 var(--space-md, 14px); font-size: var(--text-md); font-weight: 700; color: var(--text); }
/* Discount kind and its amount on one row (the amount appears only for บาท / %), so the dates below never shift. */
.p2s-promo-discount { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 16px; margin-bottom: 16px; }
.p2s-promo-discount .p2s-chip-group { margin: 0; }
.p2s-promo-discount > label { flex: 1 1 220px; max-width: 320px; margin: 0; }
.p2s-promo-detail { margin: 2px 0 0; font-weight: 400; }
.p2s-promo-acts { text-align: right; }
.p2s-promo-acts .inline-form { display: inline-block; margin-left: 6px; }

/* ── exec/dashboard.php after the 2026-10-17 critique: four numbers, what needs an order, the sales pace, then teams as tabs ── */
.exec-kpi-strip.is-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); margin-bottom: var(--space-lg); }
.exec-kpi-strip.is-4 .exec-kpi strong { font-size: var(--text-3xl); }
@container kpi (max-width: 1199px) {
  .exec-kpi-strip.is-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .exec-kpi-strip.is-4 .exec-kpi:nth-child(n) { border-bottom: 0; border-right: 1px solid var(--border); }
  .exec-kpi-strip.is-4 .exec-kpi:last-child { border-right: 0; }
}
@container kpi (max-width: 760px) {
  .exec-kpi-strip.is-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .exec-kpi-strip.is-4 .exec-kpi:nth-child(n) { grid-column: auto; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
  .exec-kpi-strip.is-4 .exec-kpi:nth-child(even) { border-right: 0; }
  .exec-kpi-strip.is-4 .exec-kpi:nth-child(n+3) { border-bottom: 0; }
}
@media (max-width: 767.98px) {
  .exec-kpi-strip.is-4 .exec-kpi:nth-child(n) { min-height: 0; padding: 12px var(--space-md); }
  .exec-kpi-strip.is-4 .exec-kpi strong { font-size: var(--text-2xl); }
}
.exec-brief { padding: var(--space-md) var(--space-lg); margin-bottom: var(--space-lg); }
.exec-brief .exec-brief-verdict { margin: 0; font-size: var(--text-md); font-weight: 700; }
.exec-brief .exec-brief-list { margin: var(--space-sm) 0 0; }
.exec-brief .exec-brief-list { grid-template-columns: minmax(0, 1fr); }
.exec-brief-list li { display: block; min-width: 0; padding: 0; }
.exec-brief-row strong, .exec-brief-row span span { overflow-wrap: anywhere; }
.exec-brief-row { display: grid; grid-template-columns: 24px minmax(0, 1fr); align-items: start; gap: 12px; padding: 10px 6px; margin: 0 -6px; border-radius: var(--radius-sm, 6px); color: inherit; text-decoration: none; }
.exec-brief-row:hover, .exec-brief-row:focus-visible { background: var(--head-bg); }
.exec-brief-row > span:last-child { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.exec-brief-row strong { font-size: var(--text-base); font-weight: 600; color: var(--text); }
.exec-brief-row span span { font-size: var(--text-sm); color: var(--muted); }
.exec-brief-row .exec-brief-mark { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; font-size: var(--text-sm); font-weight: 700; background: var(--head-bg); color: var(--muted); }
.exec-brief-list .is-bad .exec-brief-mark { background: var(--color-danger); color: #fff; }
.exec-brief-list .is-warn .exec-brief-mark { background: var(--color-amber-strong); color: #fff; }
.exec-brief-list .is-good .exec-brief-mark { background: var(--color-success); color: #fff; }
.exec-brief-more { margin-top: var(--space-xs, 6px); }
.exec-brief-more > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; }
.exec-pace { margin-bottom: var(--space-lg); }
.inv-page .exec-panel-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.exec-panel-actions { display: flex; flex: 0 0 auto; align-items: center; gap: var(--space-sm); }
.exec-panel-actions:empty { display: none; }
.exec-panel-header .exec-panel-link, .exec-tab-stats .exec-panel-link { margin: 0; }
.exec-footnote { margin-left: 2px; color: var(--muted); font-weight: 600; text-decoration: none; }
.exec-tabs { display: flex; gap: 4px; margin: var(--space-xl) 0 var(--space-md); overflow-x: auto; border-bottom: 1px solid var(--border); scrollbar-width: none; }
.exec-tabs a { display: inline-flex; flex: 0 0 auto; align-items: center; min-height: var(--control-h); padding: 0 16px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--muted); font-weight: 600; text-decoration: none; }
.exec-tabs a[aria-selected="true"] { color: var(--primary-dark); border-bottom-color: var(--primary); }
.exec-tabs a:hover { color: var(--text); }
.exec-tab-panels { position: relative; }
.exec-tab + .exec-tab { margin-top: var(--space-xl); }
.has-exec-tabs .exec-tab + .exec-tab { margin-top: 0; }
.has-exec-tabs .exec-tab.is-off { position: absolute; top: 0; left: 0; right: 0; visibility: hidden; pointer-events: none; }
.exec-tab > .exec-panel + .exec-panel { margin-top: var(--space-lg); }
.exec-tab-stats { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-lg); margin: 0 0 var(--space-md); color: var(--muted); font-size: var(--text-sm); }
.exec-tab-stats strong { color: var(--text); font-size: var(--text-md); }
.exec-tab-note { margin: var(--space-md) 0 0; color: var(--muted); font-size: var(--text-sm); }
details.inv-definitions { padding: 0 var(--space-lg); }
details.inv-definitions > summary { display: flex; align-items: center; min-height: 44px; cursor: pointer; color: var(--text); font-size: var(--text-sm); font-weight: 600; }
details.inv-definitions[open] > ul { padding-bottom: var(--space-lg); }
@media print {
  .exec-tabs { display: none; }
  .has-exec-tabs .exec-tab.is-off { position: static; visibility: visible; }
}
@media (max-width: 575.98px) {
  .inv-page .exec-panel-header { flex-direction: row; flex-wrap: wrap; justify-content: space-between; }
}

/* sales/dashboard.php after the 2026-10-17 critique: one section rhythm, numbers as a strip, the two rankings side by side */
.p2s-dash-section { margin-bottom: var(--space-xl); }
.p2s-dash-section > .section-title, .p2s-dash-head .section-title { margin-top: 0; }
.p2s-dash-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px var(--space-md); }
.p2s-dash-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-lg); }
@media (max-width: 991.98px) { .p2s-dash-pair { grid-template-columns: 1fr; } }
a.exec-kpi { color: inherit; text-decoration: none; }
a.exec-kpi:hover, a.exec-kpi:focus-visible { background: var(--head-bg); }
.exec-kpi strong.is-alert { color: var(--color-danger); }
.exec-kpi-strip.is-4:has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@container kpi (max-width: 760px) {
  .exec-kpi-strip.is-4:has(> :nth-child(3):last-child) { grid-template-columns: 1fr; }
  .exec-kpi-strip.is-4:has(> :nth-child(3):last-child) > .exec-kpi:nth-child(n) { border-right: 0; border-bottom: 1px solid var(--border); }
  .exec-kpi-strip.is-4:has(> :nth-child(3):last-child) > .exec-kpi:last-child { border-bottom: 0; }
}

/* completeness: one line on a detail page, a folded list on a form */
.p2s-complete-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 0 0 var(--space-md); color: var(--muted); font-size: var(--text-sm); }
.p2s-complete-line .p2s-complete-go { font-weight: 600; }
.p2s-complete-line .p2s-complete-more { flex-basis: 100%; }
.p2s-complete-more > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; }
.p2s-complete-more .p2s-complete-list { margin-top: 4px; }

/* ── Compact list card (Lead / Co-Agent / central lists, 2026-10-17 critique: a 410px card showed two people per screen) ──
   <article class="p2s-contact-card is-compact"> → header: title (h3 > a = the whole card is the link) + one .p2s-contact-line of
   .p2s-fact spans (only facts that have a value — never "—") ; tools on the right (call + "⋯"). Nothing else on the card. */
.p2s-contact-card.is-compact { position: relative; padding: 12px var(--space-md); }
.p2s-contact-cards:has(> .p2s-contact-card.is-compact), .p2s-contact-cards.is-compact { gap: var(--space-sm); }
.p2s-contact-card.is-compact .p2s-contact-card-head { flex-wrap: nowrap; align-items: center; }
.p2s-contact-card.is-compact .p2s-contact-card-title { flex: 1 1 auto; }
.p2s-contact-card.is-compact .p2s-contact-card-title h3 { font-size: var(--text-md); line-height: 1.35; overflow-wrap: anywhere; }
.p2s-contact-card.is-compact .p2s-contact-card-title h3 a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.p2s-contact-card.is-compact:hover { border-color: var(--primary-soft-border, var(--border)); background: color-mix(in srgb, var(--primary) 3%, var(--surface)); }
.p2s-contact-card.is-compact .p2s-card-head-tools,
.p2s-contact-card.is-compact .p2s-contact-card-actions,
.p2s-contact-card.is-compact .p2s-contact-line a,
.p2s-contact-card.is-compact .p2s-contact-line button { position: relative; z-index: 1; }
/* these groups are z-index:1 (above the card's stretched link), which makes each one a stacking context — an open ⋯ panel is trapped inside it and the NEXT card's buttons paint over it (2026-10-17). The group holding the open menu goes above the others. */
.p2s-contact-card.is-compact .p2s-card-head-tools:has(details[open]),
.p2s-contact-card.is-compact .p2s-contact-card-actions:has(details[open]) { z-index: 25; }
.p2s-contact-card.is-compact .p2s-card-head-tools .p2s-quick-call,
.p2s-contact-card.is-compact .p2s-card-head-tools .p2s-card-menu > summary { width: auto; min-width: var(--control-h); padding: 0 10px; }
.p2s-contact-line { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 0; margin: 4px 0 0; color: var(--muted); font-size: var(--text-sm); }
.p2s-contact-line .p2s-fact + .p2s-fact::before { content: "·"; margin: 0 8px; color: var(--border-strong, var(--muted)); }
.p2s-contact-line .p2s-fact.is-alert { color: var(--color-danger); font-weight: 600; }
.p2s-contact-line .badge { margin-right: 8px; }
.p2s-contact-card.is-compact .p2s-contact-card-actions { margin-top: 8px; padding-top: 8px; }
@media (max-width: 575.98px) {
  .p2s-contact-card.is-compact .p2s-card-head-tools .p2s-quick-call span:not(.p2s-icon):not(svg) { display: none; }
}

/* ── โครงการ pages (critique 2026-10-17) ── */
/* project_detail.php: plain white cards — colour stays only where it means something (status badges, Verify ✗) */
.p2s-pd .p2s-card-accent { border-left: 1px solid var(--border); background: var(--surface); }
.p2s-pd .p2s-card-accent > .p2s-card-title { color: var(--text); }
.p2s-pd .p2s-card-accent.is-docs .project-doc-row > strong { color: var(--text); }
.p2s-pd .p2s-card-title .p2s-from-unit { margin-left: 8px; font-size: var(--text-sm); font-weight: 600; vertical-align: middle; }
.p2s-from-unit-mark { margin-left: 2px; color: var(--muted); font-weight: 700; cursor: help; }
.p2s-pd-empty { margin: 4px 0 0; color: var(--muted); font-size: var(--text-sm); }
.p2s-pd-more { margin: 8px 0 0; }
.p2s-pd-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0 0 var(--space-md); }
/* Sale / Support: what a seller opens a project for comes first (DOM order stays the BU order) */
.p2s-pd.is-seller { display: flex; flex-direction: column; }
.p2s-pd.is-seller > * { order: 20; }
.p2s-pd.is-seller > .p2s-project-jump { order: 0; }
.p2s-pd.is-seller > #numbers { order: 1; }
.p2s-pd.is-seller > #units { order: 2; }
.p2s-pd.is-seller > #section-info { order: 3; }
.p2s-pd.is-seller > #location { order: 4; }
.p2s-pd.is-seller > #landing { order: 5; }
.p2s-pd.is-seller > #docs { order: 6; }
/* project lists: a phone card drops the cells that hold nothing */
@media (max-width: 575.98px) {
  table.data-table-cards td.p2s-cell-empty { display: none; }
}
.p2s-projects-stats { margin-top: var(--space-sm); }

/* ── Co-Agent pages (critique 2026-10-17) ── */
/* list: the main "เพิ่ม Co-Agent" keeps its words on a phone (the daily job); เชิญ / ตัวกรอง / CSV stay icons */
@media (max-width: 767.98px) {
  .list-toolbar a.btn-primary.p2s-add-btn.is-labelled { width: auto; padding: 5px 12px; gap: 6px; }
  .list-toolbar a.btn-primary.p2s-add-btn.is-labelled .p2s-add-label { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
}
.p2s-contact-line .p2s-lastcontact { display: inline; }
.p2s-contact-line .p2s-lastcontact-out { margin-left: 6px; color: var(--muted); }
.p2s-contact-line .p2s-lastcontact-out::before { content: "· "; }
/* detail: the daily job beside the call button; the rest as a row of plain links */
.p2s-summary-card .p2s-call-row .p2s-log-btn { display: inline-flex; align-items: center; gap: 6px; }
.p2s-next-links { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; margin: 0 0 var(--space-lg); font-size: var(--text-sm); font-weight: 600; }
.p2s-next-links > a, .p2s-next-links .p2s-locked { display: inline-flex; align-items: center; min-height: var(--control-h); }
.p2s-summary-card .p2s-complete-line { margin: var(--space-sm) 0 0; }
.p2s-verify-mini { margin: var(--space-xs, 6px) 0 0; font-size: var(--text-sm); }
.p2s-verify-mini > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--muted); font-weight: 600; }
.p2s-verify-mini.is-on > summary { color: var(--color-info, var(--primary-dark)); }
.p2s-verify-mini[open] { padding-bottom: var(--space-sm); }
.p2s-verify-mini .p2s-verify-checks { margin: 4px 0 8px; }
.p2s-ca-numbers { margin-bottom: var(--space-lg); }
@media (max-width: 575.98px) {
  .p2s-summary-card .p2s-call-row .p2s-log-btn { width: 100%; justify-content: center; }
}
.p2s-contact-line .p2s-lastcontact-out { display: inline; margin-top: 0; padding: 0; background: none; border-radius: 0; }
/* project_form.php: the AI helper is one folded line; the completeness card is plain */
.p2s-ai-fold { padding: 0 16px; border-left-width: 1px; background: var(--surface); }
.p2s-ai-fold > summary { display: flex; align-items: center; min-height: 44px; cursor: pointer; color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; }
.p2s-ai-fold[open] { padding-bottom: 16px; }
.p2s-complete-formcard .p2s-card-title { color: var(--text); }
/* inline "?" help: one line of text, the detail one tap away */
.p2s-inline-help { display: inline; }
.p2s-inline-help > summary { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; margin-left: 4px; border-radius: 50%; background: var(--head-bg); color: var(--muted); font-weight: 700; cursor: pointer; list-style: none; vertical-align: middle; }
.p2s-inline-help > summary::-webkit-details-marker { display: none; }
.p2s-inline-help[open] > span { display: block; margin-top: 6px; }
/* project_units.php: the add form as wide as the table; the edit table fits 1440 */
.p2s-units-form { max-width: none; }
.p2s-units-edit input[type="text"] { width: 100%; min-width: 4.5em; }
.p2s-units-edit select { min-width: 6em; }
/* project_group.php: the three cards one width; the deactivate button sits with its note, left */
.p2s-group-card { max-width: none; }
.p2s-group-card .project-form { max-width: none; }
.p2s-group-card .p2s-group-delete { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; justify-content: flex-start; margin-top: var(--space-md); }
.p2s-companies-list { table-layout: auto; }
.p2s-companies-list .p2s-col-actions { width: 1%; white-space: nowrap; text-align: right; }
.p2s-companies-list .p2s-col-no { width: 1%; white-space: nowrap; }
/* phone: two lines per company — the name and the "⋯" on one, the count (and any odd status) muted under it */
@media (max-width: 575.98px) {
  .p2s-companies-list td.p2s-col-no, .p2s-companies-list td.is-blank { display: none; }
  .p2s-companies-list tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; align-items: center; }
  .p2s-companies-list td.card-title { grid-column: 1; grid-row: 1; padding-bottom: 0; }
  table.p2s-companies-list td.data-table-actions, table.p2s-companies-list td.p2s-col-actions { grid-column: 2; grid-row: 1 / span 3; width: auto; margin: 0; padding: 0; border: 0; }
  table.p2s-companies-list td:not(.card-title):not(.data-table-actions):not(.p2s-col-actions) { grid-column: 1; justify-content: flex-start; gap: 6px; padding: 2px 0 0; color: var(--muted); font-size: var(--text-sm); }
}
.dm-strip .exec-kpi.is-open { background: var(--primary-soft); }

/* ── Lead pages (critique 2026-10-17) ── */
/* a one-line page intro with its rules behind "?" */
.p2s-intro-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0 0 var(--space-md); }
.p2s-inline-help { display: inline; }
.p2s-inline-help > summary { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--border); cursor: pointer; list-style: none; color: var(--muted); font-weight: 700; font-size: var(--text-sm); }
.p2s-inline-help > summary::-webkit-details-marker { display: none; }
.p2s-inline-help[open] > span { display: block; flex-basis: 100%; margin-top: 6px; padding: 10px 12px; border-radius: var(--radius-sm, 6px); background: var(--head-bg); color: var(--text); }
.p2s-intro-line .p2s-inline-help[open] { flex-basis: 100%; }
/* the raw invite URL stays in the page for the copy / QR buttons but is not printed */
.p2s-link-raw { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); border: 0; }
.p2s-invite-list-title { margin-top: var(--space-xl); }
/* the masked chip and links sit inline in a card's fact line */
.p2s-contact-line .p2s-masked, .p2s-contact-line .p2s-masked-by { display: inline-flex; align-items: center; gap: 4px; margin: 0; font-size: inherit; }
.p2s-contact-line .p2s-masked-by { margin-left: 6px; }
/* leads.php on a phone: the main job (เพิ่ม Lead) keeps its words; the rest stay icons */
@media (max-width: 767.98px) {
  .list-toolbar .p2s-add-btn.btn-primary .p2s-add-label { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
  .list-toolbar .p2s-add-btn.btn-primary { width: auto; padding: 0 12px; gap: 6px; }
}
/* Lead detail: the next steps sit inside the summary card, right under the call button */
.p2s-next-inline { margin: var(--space-sm) 0 var(--space-md); }
.p2s-next-inline .p2s-next-actions-grid { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: flex-start; }
.p2s-next-inline .p2s-next-actions-grid > .btn-primary, .p2s-next-inline .p2s-next-actions-grid > .btn-secondary { flex: 1 1 140px; }
.p2s-next-inline .p2s-card-menu > summary { height: var(--control-h); }
.p2s-verify-line { margin: 0 0 var(--space-md); }
/* งานวันนี้: a card months overdue is no longer this week's alarm — grey, so red keeps its meaning */
.followup-card-late.is-stale { border-left-color: var(--border); }
.followup-card-late.is-stale .followup-when { color: var(--muted); }
/* compact card fact line: inline text, so a long line wraps mid-flow instead of one fact per row */
.p2s-contact-card.is-compact .p2s-contact-line { display: block; line-height: 1.6; }
.p2s-contact-card.is-compact .p2s-contact-line .p2s-fact { display: inline; }
.p2s-contact-card.is-compact .p2s-card-head-tools { flex-direction: row; flex-wrap: nowrap; align-self: flex-start; }
@media (max-width: 575.98px) {
  /* on a phone the card tools are icons (call + ⋯), side by side, so the card stays two lines tall */
  .p2s-contact-card.is-compact .p2s-card-menu-label { display: none; }
  .p2s-contact-card.is-compact .p2s-card-head-tools .p2s-quick-call:not(.p2s-quick-line) { width: var(--control-h); min-width: var(--control-h); padding: 0; font-size: 0; }
  .p2s-contact-card.is-compact .p2s-card-head-tools .p2s-quick-call svg { width: 18px; height: 18px; }
  .p2s-contact-card.is-compact .p2s-card-head-tools .p2s-card-menu > summary { width: var(--control-h); min-width: var(--control-h); padding: 0; }
}
/* Lead detail: the one-line completeness reads as one line inside the summary card */
.p2s-summary-card .p2s-complete-line { align-items: center; padding-top: var(--space-sm); border-top: 1px solid var(--border); }
.p2s-summary-card .p2s-complete-line .field-hint { margin: 0; }
.p2s-summary-card .p2s-complete-line .p2s-complete-track { width: 64px; }
.p2s-summary-card .p2s-complete-line .p2s-complete-more { flex-basis: auto; }
.p2s-summary-card .p2s-complete-line .p2s-complete-more[open] { flex-basis: 100%; }
.p2s-pd .p2s-complete-line .field-hint { white-space: nowrap; margin: 0; }
.p2s-pd-emptysections { margin: 0 0 var(--space-md); }
.p2s-pd.is-seller > .p2s-pd-emptysections { order: 20; }
/* งานวันนี้: "ทำวันนี้ก่อน" is a short list of links to the cards below, not a second set of cards */
.followup-top-links { display: grid; gap: 2px; margin: 0; padding: 0 0 0 1.4em; }
.followup-top-links li { padding: 6px 0; border-bottom: 1px solid var(--border); }
.followup-top-links li:last-child { border-bottom: 0; }
.followup-top-links a { text-decoration: none; }
.followup-top-why { display: block; color: var(--muted); font-size: var(--text-sm); }
.content h2.section-title.p2s-invite-list-title, h2.section-title.p2s-invite-list-title { margin-top: var(--space-xl); }   /* beats ".content h2.section-title:first-of-type" */
@media (max-width: 575.98px) { table.p2s-companies-list td.card-title { text-align: left; } }
@media (max-width: 575.98px) { table.p2s-companies-list td:nth-child(2) { display: block; grid-column: 1; grid-row: 1; padding: 0; text-align: left; font-weight: 700; } table.p2s-companies-list td:nth-child(2)::before { display: none; } }
@media (max-width: 575.98px) { table.p2s-companies-list td:nth-child(2) a { justify-content: flex-start; text-align: left; } }

/* ── round 3 (critique 2026-10-17 round 2) ── */
/* a box that blocks saving right now (e.g. commission once "ตกลงแล้ว") keeps the error red while empty; other empty boxes stay grey */
.project-form label.is-required-now.p2s-missing,
.project-form label.is-required-now:has(input:placeholder-shown),
.project-form label.is-required-now:has(input[value=""]) { border-left: 3px solid var(--color-red); padding-left: 7px; margin-left: -10px; }
/* a fact never breaks inside itself — "ตามต่อ 3/10" moves to the next line whole */
.p2s-contact-line .p2s-fact { white-space: nowrap; }
.p2s-contact-line .p2s-fact a { white-space: nowrap; }
/* an empty chart's title and hint (P2S.empty) sit apart, not run together */
.exec-chart-empty > div, .exec-chart-empty > span:not(:only-child) { display: flex; flex-wrap: wrap; gap: 2px 8px; }
.exec-chart-empty strong + span::before { content: none; }
/* the form's completeness card: no accent bar, title + bar + one-line hint in a row, the list folded under it */
.p2s-complete-formcard { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
.p2s-complete-formcard::before { content: none !important; }
.p2s-complete-formcard .p2s-card-title { margin: 0; font-size: var(--text-md); }
.p2s-complete-formcard #completeHint { margin: 0; }
.p2s-complete-formcard .p2s-complete-more { flex-basis: 100%; }
.p2s-complete-line .p2s-complete-go, .p2s-complete-more > summary { display: inline-flex; align-items: center; min-height: var(--control-h); }

/* ── Lead round 3 ── */
/* a notice with its action: text and button side by side, the button drops to its own line on a phone instead of sitting on the words */
.alert.p2s-alert-action { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.alert.p2s-alert-action > span { flex: 1 1 220px; min-width: 0; }
.alert.p2s-alert-action > a, .alert.p2s-alert-action > form { flex: none; }
@media (max-width: 575.98px) { .alert.p2s-alert-action > a { width: 100%; justify-content: center; } }
/* lead detail first screen: one solid button only — the call button is an outline, "บันทึกการติดต่อ" stays the solid one */
.p2s-summary-card .p2s-call-row > a.p2s-call-btn[href] { background: var(--surface); color: var(--primary-dark); border: 1px solid var(--primary); }
.p2s-summary-card .p2s-call-row > a.p2s-call-btn[href]:hover, .p2s-summary-card .p2s-call-row > a.p2s-call-btn[href]:focus-visible { background: var(--primary-soft); color: var(--primary-dark); }
/* "ยืนยันความต้องการวันนี้": one layer, not a grey box inside the white card */
.p2s-needs-card .p2s-needs-box { padding: 0; background: none; }

/* ── โครงการ round 3 ── */
/* the checkbox column has an empty header, so shell.js marks it as the actions cell — which carries a top rule; not here */
@media (max-width: 575.98px) {
  table.data-table-cards td.p2s-pb-cell { border-top: 0 !important; margin-top: 0 !important; }
  /* bu/projects.php main list on a phone: a compact card (design-system §3.6) — name, then status · start price · units */
  table.p2s-proj-main tr { flex-direction: row; flex-wrap: wrap; align-items: center; column-gap: 14px; row-gap: 4px; padding: 10px 12px; }
  table.p2s-proj-main td { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 4px; padding: 0; text-align: left; font-size: var(--text-sm); color: var(--muted); }
  table.p2s-proj-main td[data-label]::before { content: none; }
  table.p2s-proj-main td.card-title { display: block; flex: 1 1 100%; order: -1; padding: 0 0 2px 0; text-align: left; font-size: var(--text-md); color: var(--text); }
  table.p2s-proj-main td.p2s-thumb-cell { flex: 1 1 100%; order: -2; display: block; margin: -10px -12px 6px; width: auto; }
  table.p2s-proj-main td.p2s-thumb-cell:has(.p2s-thumb-empty) { display: none; }
  table.p2s-proj-main td.p2s-pc-start { order: 1; }
  table.p2s-proj-main td.p2s-pc-start::before { content: "เริ่ม"; }
  table.p2s-proj-main td.p2s-pc-units { order: 2; }
  table.p2s-proj-main td.p2s-pc-units::before { content: "ยูนิต"; }
  table.p2s-proj-main td.p2s-pc-complete, table.p2s-proj-main td.p2s-pc-gap { order: 3; }
  table.p2s-proj-main td.p2s-pc-dev, table.p2s-proj-main td.p2s-pc-pkg, table.p2s-proj-main td.p2s-pc-sqm,
  table.p2s-proj-main td.p2s-pc-gap:not(.is-meaningful), table.p2s-proj-main td.p2s-pc-complete:not(.is-meaningful),
  table.p2s-proj-main td.p2s-cell-empty, table.p2s-proj-main td.card-no { display: none; }
  table.p2s-proj-main td.p2s-pc-status .status-btn, table.p2s-proj-main td.p2s-pc-status .badge { font-size: var(--text-xs); }
}
/* bu/projects.php: the project name link carries the status colour (replaces the สถานะ column) */
.data-table a.p2s-proj-link { color: var(--ink); font-weight: 600; }
.data-table a.p2s-proj-link:hover, .data-table a.p2s-proj-link:focus-visible { filter: brightness(.85); }
/* งานวันนี้: a compact card — name with the call buttons on the right, then one flowing line (project · status · why · how late), then the actions */
.followup-card { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; padding: 10px 14px; }
.followup-card > .alert, .followup-card > .followup-main, .followup-card > .followup-actions { flex-basis: 100%; }
.followup-card > .followup-main { justify-content: space-between; align-items: center; flex-wrap: nowrap; }
.followup-card > .followup-main .followup-name { flex: 1 1 auto; }
.followup-card > .followup-main .p2s-quick-call, .followup-card > .followup-main .p2s-call-buttons { flex: none; }
.followup-card > .followup-meta, .followup-card > .followup-why, .followup-card > .followup-when { flex: 0 1 auto; margin: 0; }
.followup-card > .followup-why strong { display: inline; font-weight: 600; }
.followup-card > .followup-actions { margin-top: 2px; }
/* ทำวันนี้ก่อน: one line per entry, the reason trimmed rather than wrapped */
.followup-top-links li { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.followup-top-links li > a { flex: none; }
.followup-top-why { display: block; flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* group manage: the three cards one width (the .settings-card:has(> .project-form) 760px cap was winning over .p2s-group-card) */
.settings-card.p2s-group-card, .settings-card.p2s-group-card:has(> .project-form:not(.form-wide)) { max-width: none; }
.settings-card.p2s-group-card > .project-form { max-width: none; width: 100%; }
.p2s-group-member-name { gap: 2px; }
.p2s-group-member-name .field-hint { margin: 0; line-height: 1.45; }
.settings-card.p2s-group-card > .project-form { background: none; border: 0; box-shadow: none; padding: 0; }
.settings-card.p2s-group-card > .project-form + .project-form { margin-top: var(--space-lg); padding-top: var(--space-lg); border-top: 1px solid var(--border); }
/* the "?" sits at the end of its sentence (not on a line of its own) and is a full-size tap target with a small visible ring */
.p2s-intro-line { display: block; }
.p2s-intro-line .p2s-inline-help[open] > span { margin-top: 8px; }
.p2s-inline-help > summary { position: relative; width: var(--control-h); height: var(--control-h); margin: -6px -4px -6px 0; border: 0; background: none; vertical-align: middle; }
.p2s-inline-help > summary::before { content: ""; position: absolute; inset: 6px; border: 1px solid var(--border); border-radius: 50%; background: var(--surface); z-index: -1; }
.p2s-inline-help > summary { z-index: 0; }

/* ── Co-Agent round 3 ── */
/* a narrow-screen-only menu item (LINE moves from the card head into "⋯" on a phone) */
.p2s-only-narrow { display: none !important; }
@media (max-width: 575.98px) {
  .p2s-only-narrow { display: inline-flex !important; }
  .p2s-ca-card .p2s-card-head-tools .p2s-quick-line { display: none; }
  .p2s-ca-card .p2s-card-head-tools { flex-wrap: nowrap; }
  /* companies: one line per company, an icon-only "⋯" */
  .p2s-companies-list .p2s-card-menu-label { display: none; }
  .p2s-companies-list .p2s-card-menu > summary { width: var(--control-h); padding: 0; justify-content: center; }
  .p2s-companies-list tr { padding-top: 4px; padding-bottom: 4px; }
}
/* the detail page's "อื่น ๆ" sits beside the solid บันทึกการติดต่อ */
.p2s-summary-card .p2s-call-row .p2s-card-menu { margin-left: 0; }
/* visited projects as a plain list */
.p2s-plain-list { list-style: none; margin: 0 0 var(--space-lg); padding: 0; display: grid; gap: 2px; }
.p2s-plain-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.p2s-plain-list li:last-child { border-bottom: 0; }
.p2s-plain-meta { color: var(--muted); font-size: var(--text-sm); }
.p2s-ca-visits { margin-top: var(--space-lg); }
/* a fact never breaks inside itself (2fe033b), so one that is longer than the card is cut with "…" instead of widening the card */
.p2s-ca-card .p2s-contact-card-title { min-width: 0; }
.p2s-ca-card .p2s-contact-line .p2s-fact { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
/* the card grids size to the viewport, not to the longest unbreakable fact */
.p2s-contact-cards, .p2s-contact-group { grid-template-columns: minmax(0, 1fr); }
.p2s-contact-cards > *, .p2s-contact-group > * { min-width: 0; }
/* a fact keeps its words together (no mid-fact break) but never pushes the card wider than the screen: a long one is trimmed with "…" */
.p2s-contact-card.is-compact .p2s-contact-line .p2s-fact,
.p2s-contact-line .p2s-fact { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.p2s-contact-card.is-compact .p2s-contact-card-title { min-width: 0; }
/* units page: the add form folded under the list */
.p2s-units-add { margin-top: var(--space-lg); }
.p2s-units-add > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--primary-dark); }
.p2s-units-add[open] > summary { margin-bottom: var(--space-sm); }
/* group view: the month line above the one table */
.p2s-group-perfline { align-items: center; margin: var(--space-md) 0 var(--space-sm); }
.p2s-group-perfline .exec-tab-stats { margin: 0; align-items: center; }
.p2s-group-perfline .p2s-month-nav { display: inline-grid; vertical-align: middle; }
/* the Verify state as one line for everyone but the Admin who confirms */
.p2s-pd .p2s-verify-line { margin: 0 0 var(--space-md); }
/* the "·" between facts became the first thing on a wrapped line; space separates them instead */
.p2s-contact-line { column-gap: 14px; }
.p2s-contact-line .p2s-fact + .p2s-fact::before { content: none; }
.p2s-contact-line .badge { margin-right: 0; }
/* co-agent edit form on a phone: folded sections sit close together, the save bar is one row, the grey empty mark hugs its box */
@media (max-width: 575.98px) {
  .p2s-ca-form details.form-section-collapsible:not([open]),
  .p2s-ca-form .form-section:has(> details.form-section-collapsible:not([open]):only-child) { padding-top: 6px; margin-top: 0; }
  .p2s-ca-form details.form-section-collapsible:not([open]) > summary { min-height: 44px; }
  .p2s-ca-form .p2s-form-bar { display: flex; flex-direction: row; gap: 8px; }
  .p2s-ca-form .p2s-form-bar > * { flex: 1 1 0; margin: 0; }
}
.p2s-ca-form label.p2s-missing, .p2s-ca-form fieldset.p2s-missing, .p2s-ca-form .field-group.p2s-missing { margin-left: -8px; padding-left: 5px; border-left-width: 3px; }
.p2s-contact-line .p2s-fact, .p2s-contact-line > .badge { margin-right: 12px; }
.p2s-contact-line .p2s-fact:last-child { margin-right: 0; }
/* the tool links joined the project's top action row: lay them out as its own items */
.project-detail-actions .p2s-pd-tools { display: contents; }

/* งานวันนี้: one visible action, the rest in "⋯" that opens in place (owner 2026-10-17) */
.followup-menu { display: block; }
.followup-menu[open] { flex-basis: 100%; }
.followup-menu > summary.followup-menu-toggle { min-width: var(--control-h); justify-content: center; list-style: none; font-weight: 700; }
.followup-menu > summary.followup-menu-toggle::-webkit-details-marker { display: none; }
.followup-menu[open] > summary.followup-menu-toggle { background: var(--head-bg); }
.followup-menu-body { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border); }
.followup-menu-body > .followup-log, .followup-menu-body > .followup-postpone { flex-basis: 100%; }

/* ข้อมูลเก่า: one tab bar across its pages (src/legacy_tabs.php), same look as the exec dashboard tabs */
.p2s-legacy-tabs { display: flex; gap: 4px; margin: 0 0 var(--space-lg); overflow-x: auto; border-bottom: 1px solid var(--border); scrollbar-width: none; }
.p2s-legacy-tabs a { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 6px; min-height: var(--control-h); padding: 0 14px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--muted); font-weight: 600; text-decoration: none; }
.p2s-legacy-tabs a.is-active { color: var(--primary-dark); border-bottom-color: var(--primary); }
.p2s-legacy-tabs a:hover { color: var(--text); }

/* ── ตั้งค่าเว็บ B (2026-10-17) ── */
/* a plain section title on the admin pages (no purple bar — the bar was dropped app-wide) */
.p2s-import-title { margin: var(--space-lg) 0 var(--space-sm); font-size: var(--text-md); font-weight: 700; color: var(--text); }
.settings-card > .p2s-import-title:first-child { margin-top: 0; }
/* import: three equal cards, upload + template link in one row, the column list folded */
.p2s-import-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-md); }
.p2s-import-cards .settings-card { margin: 0; max-width: none; }
.p2s-import-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px var(--space-md); margin-top: var(--space-sm); }
.p2s-import-cols { margin-top: var(--space-sm); }
.p2s-import-cols > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; }
.p2s-import-cols p { overflow-wrap: anywhere; }
/* search?tab=legacy: one line of what waits where */
.p2s-legacy-counts { display: flex; flex-wrap: wrap; gap: 4px var(--space-lg); margin: 0 0 var(--space-md); font-size: var(--text-sm); }
.p2s-legacy-counts a { display: inline-flex; align-items: center; gap: 6px; min-height: var(--control-h); color: var(--muted); text-decoration: none; }
.p2s-legacy-counts a strong { color: var(--text); font-size: var(--text-md); }
.p2s-legacy-counts a:hover { color: var(--primary-dark); }
/* duplicate phones: on a phone the scope picker takes its own line so the search box is not clipped */
@media (max-width: 575.98px) {
  .p2s-dup-filters { flex-wrap: wrap; }
  .p2s-dup-filters select { flex: 1 1 100%; }
  .p2s-dup-filters input[type="search"] { flex: 1 1 0; min-width: 0; }
}
/* pool: the hint next to the hand-out button wraps instead of pushing the row wide */
.pool-controls [data-count] { flex-basis: 100%; }

/* ── ตั้งค่าเว็บ A (2026-10-17) ── */
/* settings: tabs instead of one long page; save buttons quiet until their form holds unsaved edits (settings-dirty.js marks .is-unsaved) */
.p2s-settings-page .section-title { max-width: 760px; }
.p2s-settings-page .settings-card form button[type="submit"]:not(.link-btn):not(.btn-secondary) { background: var(--surface); color: var(--primary-dark); border: 1px solid var(--border); }
.p2s-settings-page .settings-card form.is-unsaved button[type="submit"]:not(.link-btn):not(.btn-secondary) { background: var(--primary); color: #fff; border-color: var(--primary); }
.p2s-settings-status { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 var(--space-sm); font-size: var(--text-sm); }
.p2s-settings-status a { color: var(--text); text-decoration: none; }
.p2s-settings-status a:hover { text-decoration: underline; }
.p2s-setting-state { display: inline-block; margin-left: 4px; padding: 1px 8px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; vertical-align: middle; }
.p2s-setting-state.is-on { background: color-mix(in srgb, var(--color-success) 14%, var(--surface)); color: var(--color-success); }
.p2s-setting-state.is-off { background: var(--head-bg); color: var(--muted); }
.p2s-settings-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 var(--space-sm); }
/* approvals: one line of number links */
.p2s-appr-links { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 0 0 var(--space-md); }
.p2s-appr-links a { display: inline-flex; align-items: center; gap: 6px; min-height: var(--control-h); padding: 0 12px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
.p2s-appr-links.is-dq a { background: var(--head-bg); color: var(--text); }
.p2s-appr-links.is-dq > span { color: var(--muted); font-size: var(--text-sm); }
.p2s-dq-title { margin-top: var(--space-2xl); }
/* doc items: one table, a header row per group */
.p2s-doc-items-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
.p2s-doc-items-table tr.p2s-doc-group-row th { background: var(--head-bg); text-align: left; font-weight: 700; }
.p2s-doc-items-table tr.is-off td { color: var(--muted); }
.p2s-doc-item-tools { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
@media (max-width: 767.98px) {
  table.p2s-doc-items-table tr.p2s-doc-group-row { display: block; border: 0; background: none; box-shadow: none; padding: 0; margin: var(--space-md) 0 4px; }
  table.p2s-doc-items-table tr.p2s-doc-group-row th { display: block; background: none; padding: 0; }
  .p2s-users-table td.p2s-hide-sm { display: none; }
  .al-filters-fold:not([open]) { margin-bottom: var(--space-md); }
}
.al-filters-fold > summary { display: none; list-style: none; }
@media (max-width: 767.98px) { .al-filters-fold > summary { display: inline-flex; margin-bottom: 8px; } }
/* danger in a menu sits apart, last, red */
.p2s-more-panel .p2s-menu-danger { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 6px; }
.p2s-btn-danger { color: var(--color-danger) !important; border-color: color-mix(in srgb, var(--color-danger) 40%, var(--border)) !important; }
.p2s-health-more > summary { display: inline-flex; align-items: center; min-height: var(--control-h); cursor: pointer; color: var(--primary-dark); font-weight: 600; font-size: var(--text-sm); }
/* import card: the note sits under the upload row, not behind it */
.p2s-import-card .project-form > .field-hint { margin: 6px 0 0; position: static; }
.p2s-import-card .p2s-import-actions { margin-bottom: 0; }
/* pool on a phone: one compact row per person — checkbox, name, then the facts that have a value (§3.6) */
@media (max-width: 575.98px) {
  table.data-table-cards.p2s-pool-table tbody tr { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px 10px; padding: 10px 12px; min-height: 0; }
  table.data-table-cards.p2s-pool-table tbody td { display: block; padding: 0; border: 0; min-height: 0; width: auto; text-align: left; order: 0; }
  table.data-table-cards.p2s-pool-table tbody td::before { content: none; display: none; }
  table.data-table-cards.p2s-pool-table tbody td.p2s-pb-cell { position: static; flex: 0 0 auto; }
  table.data-table-cards.p2s-pool-table tbody td.card-title { flex: 1 1 0; min-width: 0; padding: 0 !important; font-weight: 600; text-align: left; }
  table.data-table-cards.p2s-pool-table tbody td:not(.p2s-pb-cell):not(.card-title) { flex-basis: 100%; padding-left: 30px; color: var(--muted); font-size: var(--text-sm); }
  table.data-table-cards.p2s-pool-table tbody td.p2s-cell-empty { display: none; }
}
.p2s-hint-line { margin: 0 0 var(--space-sm); }
.p2s-dq-title ~ h3.p2s-card-title { margin: var(--space-md) 0 4px; font-size: var(--text-md); font-weight: 700; }
/* the doc-items table is three columns now (the old 6-column widths are reset) */
@media (min-width: 768px) {
  .p2s-doc-items-table th:nth-child(2) { width: 26%; }
  .p2s-doc-items-table th:nth-child(3) { width: 220px; }
  .p2s-doc-items-table th:nth-child(4), .p2s-doc-items-table th:nth-child(5), .p2s-doc-items-table th:nth-child(6) { width: auto; }
  .p2s-doc-items-table tr.p2s-doc-group-row th { width: auto; }
}
