:root {
  /* ink & surface — violet-tinted neutrals, not flat gray */
  --ink: #221530;
  --ink-soft: #6b5c82;
  --paper: #fdfcfe;
  --white: #ffffff;
  --border: #e3d9f0;
  --border-strong: #cdb9ea;

  /* brand purple, depth scale */
  --volt-50: #f6f1fb;
  --volt-100: #ece0f8;
  --volt-300: #a877d6;
  --volt-500: #57298c;
  --volt-700: #38185c;
  --volt-900: #200f38;

  /* legacy aliases kept so every existing rule still resolves */
  --pea-purple: var(--volt-500);
  --pea-purple-dark: var(--volt-700);
  --pea-purple-light: var(--volt-300);
  --pea-purple-tint: var(--volt-50);

  --danger: #b23a3a;
  --danger-tint: #fbeaea;
  --success: #2f7d52;
  --success-tint: #eaf6ef;
  --warning: #9a6a12;
  --warning-tint: #fbf1e0;
  --info: #2f5f8a;
  --info-tint: #e8f1fb;

  /* ความสูงของ .topbar -- ใช้คำนวณตำแหน่ง sticky ของแถบซ้ายและแผงขวา
     ถ้าวันหนึ่งความสูงแถบบนเปลี่ยน แก้ที่นี่ที่เดียว */
  --topbar-h: 65px;

  /* ระยะขอบซ้าย/ขวาของ .requests-content -- เก็บเป็นตัวแปรเพราะกลุ่มคำร้อง
     ต้องหักลบระยะนี้ออกเพื่อกางเต็มความกว้าง ถ้าค่าสองที่ไม่ตรงกันจะเห็นเป็น
     ขอบเบี้ยวทันที */
  --content-pad-x: 36px;

  --shadow: 0 16px 36px rgba(32, 15, 56, 0.14);
  --shadow-sm: 0 2px 10px rgba(32, 15, 56, 0.08);
  --radius: 14px;

  --font-display: "Chakra Petch", "Segoe UI", sans-serif;
  --font-body: "Sarabun", "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  min-height: 100dvh;
}

body {
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

button, input, select, textarea {
  font-family: inherit;
}

h1, h2, .brand-mark, .topbar-title, .service-title,
.requests-content-heading h2, .placeholder-card h2 {
  font-family: var(--font-display);
}

a {
  color: var(--volt-500);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

:focus-visible {
  outline: 2px solid var(--volt-500);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.001ms !important;
    animation-duration: 0.001ms !important;
  }
}

/* ===== Signature: circuit-trace rule =====
   A short filled bar standing in for a plain hairline above section
   headings — a trace-line motif appropriate to a power utility, kept
   inside the white/purple palette rather than introducing a new hue. */
.home-heading::before,
.requests-content-heading::before {
  content: "";
  display: block;
  width: 32px;
  height: 3px;
  border-radius: 2px;
  margin-bottom: 14px;
  background: var(--volt-500);
}

.home-heading::before {
  margin-left: auto;
  margin-right: auto;
}

/* ===== Buttons ===== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: none;
  border-radius: 10px;
  padding: 12px 20px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease;
}

.btn[hidden] {
  display: none;
}

/* Press feedback: every button darkens a step and sinks slightly, so a tap
   is acknowledged even before whatever it triggered finishes. Applied to
   plain <button>s too, not just .btn -- the card actions, mode toggles and
   row controls are all real buttons and should answer a press the same way. */
.btn:active,
button:active {
  transform: translateY(1px);
}

.btn-primary {
  background: var(--volt-500);
  color: var(--white);
  box-shadow: var(--shadow-sm);
}

.btn-primary:hover {
  background: var(--volt-700);
  box-shadow: 0 0 0 4px var(--volt-100);
}

.btn-primary:active {
  background: var(--volt-900);
  box-shadow: none;
}

.btn-ghost:active,
.btn-icon:active {
  background: var(--volt-100);
}

/* A button disabled mid-action is working, not unavailable: keep it in brand
   colour at reduced strength with a wait cursor, so it reads as "busy" rather
   than "you cannot press this". setBusy() swaps the label to match. */
.btn:disabled {
  opacity: 0.72;
  cursor: progress;
  transform: none;
  box-shadow: none;
}

.btn:disabled:hover {
  background: var(--volt-500);
  box-shadow: none;
}

.btn-ghost:disabled:hover {
  background: transparent;
}

.btn-block {
  width: 100%;
}

.btn-ghost {
  background: transparent;
  color: var(--volt-500);
  border: 1px solid var(--border);
}

.btn-ghost:hover {
  background: var(--volt-50);
  border-color: var(--border-strong);
}

.btn-icon {
  padding: 10px;
  border-radius: 50%;
}

.btn-icon svg {
  width: 18px;
  height: 18px;
}

/* ===== Auth views ===== */
.auth-view {
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  position: relative;
  overflow: hidden;
  background: var(--paper);
}

.auth-view[hidden] {
  display: none;
}

/* Faint transmission-grid backdrop instead of a generic gradient blob */
.auth-view::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--volt-100) 1px, transparent 1px),
    linear-gradient(90deg, var(--volt-100) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(circle at 50% 20%, #000 0%, transparent 70%);
  mask-image: radial-gradient(circle at 50% 20%, #000 0%, transparent 70%);
  opacity: 0.55;
  pointer-events: none;
}

.auth-card {
  position: relative;
  width: 100%;
  max-width: 400px;
  background: var(--white);
  border: 1px solid var(--border);
  border-top: 3px solid var(--volt-500);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 36px 32px 28px;
}

.brand {
  text-align: center;
  margin-bottom: 28px;
}

.brand-mark {
  width: 56px;
  height: 56px;
  margin: 0 auto 14px;
  border-radius: 14px;
  background: linear-gradient(135deg, var(--volt-500) 0%, var(--volt-300) 100%);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 20px;
  letter-spacing: 0.5px;
  box-shadow: var(--shadow-sm);
}

.brand-mark.small {
  width: 36px;
  height: 36px;
  font-size: 14px;
  margin: 0;
}

.brand h1 {
  margin: 0 0 6px;
  font-size: 23px;
  font-weight: 600;
  color: var(--volt-700);
}

.brand-sub {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-soft);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* #requestForm carries this class and is hidden two ways -- for a request
   type with no form yet (so only the placeholder shows) and after a batch
   save (so only the result panel shows). Without this override neither
   worked: the form stayed on screen underneath both. */
.auth-form[hidden] {
  display: none;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field[hidden] {
  display: none;
}

.field-hint {
  margin: -2px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}

.field-row {
  display: flex;
  gap: 16px;
}

/* ตั้ง display ไว้ตายตัวด้านบน จึงต้องมีบรรทัดนี้ ไม่งั้น hidden ไม่มีผล --
   #extApprovalField (เลขที่/วันที่อนุมัติ) ใช้คลาสนี้และถูกซ่อน/แสดงตามสถานะ
   ดู Gotcha ใน CLAUDE.md */
.field-row[hidden] {
  display: none;
}

/* ช่องเบอร์โทร + ปุ่มโทรออก (tel: link ธรรมดา ใช้ได้จริงเฉพาะเปิดผ่านมือถือ) */
.field-input-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.field-input-row input,
/* สถานะงานของฟอร์มขอใช้ไฟฟ้าอยู่แถวเดียวกับปุ่มสร้างคำร้องขยายเขตฯ -- ดรอปดาวน์
   ต้องยืดกินที่ว่างเหมือน input ไม่งั้นมันจะหดเหลือเท่าข้อความที่ยาวที่สุดในลิสต์ */
.field-input-row select {
  flex: 1;
  min-width: 0;
}

/* ปุ่มที่อยู่ข้างช่องกรอกในแถวเดียวกัน -- ห้ามตัดบรรทัด ไม่งั้นความสูงของแถวจะ
   กระโดดจนดรอปดาวน์ข้าง ๆ ดูเบี้ยว */
.btn-inline {
  white-space: nowrap;
  flex: 0 0 auto;
}

.field-row > .field {
  flex: 1;
  min-width: 0;
}

.field label {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink);
}

.field input,
.field select,
.field textarea {
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14.5px;
  font-family: inherit;
  color: var(--ink);
  background: var(--white);
  outline: none;
  resize: vertical;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

.field input.invalid {
  border-color: var(--danger);
}

.field select:disabled,
.field input:disabled,
.field input[readonly] {
  background: var(--volt-50);
  color: var(--ink-soft);
  cursor: not-allowed;
}

.field-section {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-top: 16px;
  border-top: 1px dashed var(--border);
}

/* ตั้ง display ไว้ตายตัวด้านบน จึงต้องมีบรรทัดนี้ ไม่งั้น hidden ไม่มีผลเลย
   -- #extPlanSection (แนบแผนผัง) เคยโผล่ตั้งแต่หน้าเพิ่มคำร้องเพราะขาดตรงนี้
   ดู Gotcha ใน CLAUDE.md */
.field-section[hidden] {
  display: none;
}

.field-section-label {
  font-family: var(--font-display);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--volt-700);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.checkbox-field {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
  user-select: none;
}

.checkbox-field input {
  width: 16px;
  height: 16px;
  accent-color: var(--volt-500);
  cursor: pointer;
}

.form-error {
  margin: -4px 0 0;
  padding: 10px 12px;
  background: var(--danger-tint);
  color: var(--danger);
  border-radius: 8px;
  font-size: 13px;
}

.form-success {
  margin: -4px 0 0;
  padding: 10px 12px;
  background: var(--success-tint);
  color: var(--success);
  border-radius: 8px;
  font-size: 13px;
}

.auth-switch {
  text-align: center;
  margin: 22px 0 0;
  font-size: 13.5px;
  color: var(--ink-soft);
}

.track-result {
  margin-top: 20px;
  padding: 18px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--volt-50);
}

.track-result-details {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.track-result-details > div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13.5px;
}

.track-result-details dt {
  color: var(--ink-soft);
}

.track-result-details dd {
  margin: 0;
  font-weight: 500;
  color: var(--ink);
  text-align: right;
}

.track-result-details > div.plain {
  justify-content: flex-start;
}

.track-result-details > div.plain dd {
  text-align: left;
  font-weight: 400;
}

.track-payment {
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--white);
}

.track-payment-heading {
  margin: 0 0 12px;
  font-weight: 600;
  font-size: 14px;
  color: var(--volt-700);
}

.track-slip {
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--white);
}

.track-slip img {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 0 16px;
  border-radius: 8px;
  border: 1px solid var(--border);
}

/* Same [hidden]-vs-display gotcha as everywhere else that toggles hidden on
   an element whose class sets an unconditional display -- without this, a
   request with no slip yet still showed whatever slip image was loaded from
   the previous lookup, since setting .hidden = true had no visible effect. */
.track-slip img[hidden] {
  display: none;
}

.track-slip form {
  margin-top: 0;
}

/* ===== Boot overlay ===== */
.boot-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--paper);
}

.boot-overlay[hidden] {
  display: none;
}

.boot-card {
  text-align: center;
}

.boot-card .brand-mark {
  margin: 0 auto 16px;
}

.boot-text {
  margin: 0;
  font-size: 14px;
  color: var(--ink-soft);
}

.boot-error {
  margin-top: 18px;
  color: var(--danger);
}

.boot-error p {
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 600;
}

.boot-error-hint {
  font-weight: 400 !important;
  color: var(--ink-soft);
  margin-bottom: 16px !important;
}

/* ===== Popup modal ===== */
.modal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(32, 15, 56, 0.5);
  z-index: 100;
}

.modal-overlay[hidden] {
  display: none;
}

.modal-card {
  width: 100%;
  max-width: 340px;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px 28px 28px;
  text-align: center;
}

.modal-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin: 0 auto 16px;
  border-radius: 999px;
  background: var(--success-tint);
  color: var(--success);
}

.modal-icon svg {
  width: 26px;
  height: 26px;
}

.modal-card h3 {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  color: var(--volt-700);
}

.modal-card p {
  margin: 0 0 22px;
  font-size: 14px;
  color: var(--ink-soft);
}

/* ===== App shell ===== */
.app-view {
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  background: var(--paper);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 28px;
  border-bottom: 1px solid var(--border);
  background: var(--white);
  position: sticky;
  top: 0;
  z-index: 10;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.topbar-title {
  font-weight: 600;
  font-size: 17px;
  letter-spacing: 0.2px;
  color: var(--volt-700);
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 14px;
}

.user-greeting {
  font-size: 13.5px;
  color: var(--ink-soft);
}

/* ===== Home ===== */
.home-main {
  max-width: 1040px;
  margin: 0 auto;
  padding: 48px 28px 64px;
}

.home-heading {
  text-align: center;
  margin-bottom: 40px;
}

.home-heading h2 {
  margin: 0 0 10px;
  font-size: 27px;
  font-weight: 600;
  color: var(--volt-700);
}

.home-heading p {
  margin: 0;
  color: var(--ink-soft);
  font-size: 14.5px;
}

.service-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.service-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  text-align: left;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  border-top: 3px solid var(--volt-100);
  padding: 28px 24px;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.service-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
  border-color: var(--border);
  border-top-color: var(--volt-500);
}

.service-icon {
  width: 52px;
  height: 52px;
  border-radius: 12px;
  background: var(--volt-50);
  color: var(--volt-500);
  display: flex;
  align-items: center;
  justify-content: center;
}

.service-icon svg {
  width: 26px;
  height: 26px;
}

.service-title {
  font-size: 16.5px;
  font-weight: 600;
  color: var(--ink);
}

.service-desc {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.5;
}

/* ===== Placeholder service page ===== */
.placeholder-main {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 24px;
  min-height: calc(100vh - var(--topbar-h));
  min-height: calc(100dvh - var(--topbar-h));
}

.placeholder-card {
  max-width: 460px;
  width: 100%;
  text-align: center;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 44px 36px;
}

.placeholder-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin: 0 auto 18px;
  border-radius: 16px;
  background: var(--volt-50);
  color: var(--volt-500);
}

.placeholder-icon svg {
  width: 32px;
  height: 32px;
}

.placeholder-card h2 {
  margin: 0 0 8px;
  font-weight: 600;
  color: var(--volt-700);
  font-size: 21px;
}

.placeholder-desc {
  margin: 0 0 18px;
  color: var(--ink);
  font-size: 14.5px;
}

.placeholder-note {
  margin: 0 0 24px;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1.6;
}

/* ===== Requests workspace ===== */
.requests-layout {
  display: flex;
  align-items: flex-start;
  min-height: calc(100vh - var(--topbar-h));
  min-height: calc(100dvh - var(--topbar-h));
}

.requests-sidebar {
  width: 240px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 20px 12px;
  border-right: 1px solid var(--border);
  background: var(--white);

  /* ล็อกแถบแท็บไว้ ไม่ให้ไหลหายไปตอนเลื่อนอ่านรายการหรือกรอกฟอร์มยาว ๆ
     ต้องเป็น flex-start ไม่ใช่ stretch: ถ้ายืดเต็มความสูงของ layout อยู่แล้ว
     มันจะไม่เหลือระยะให้ "ติด" เลย sticky ก็ไม่มีผลอะไร
     ความสูงเท่าจอพอดี พร้อม overflow ของตัวเอง เผื่อวันหน้ามีแท็บมากจนเกินจอ */
  align-self: flex-start;
  position: sticky;
  top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
  height: calc(100dvh - var(--topbar-h));
  overflow-y: auto;
}

.requests-nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  border-radius: 0 10px 10px 0;
  padding: 11px 14px;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

/* ต้องมี -- .requests-nav-item ตั้ง display: flex ไว้ ซึ่งชนะกฎ [hidden] ของ
   เบราว์เซอร์ (ดู Gotcha ใน CLAUDE.md) หน้างานขอใช้ไฟฟ้าซ่อนแท็บแนบแผนผัง/
   ภาพหน้างาน/ประมาณการ ด้วย hidden ถ้าไม่มีกฎนี้แท็บจะยังโผล่อยู่ครบ */
.requests-nav-item[hidden] {
  display: none;
}

.requests-nav-item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--volt-500);
  transition: color 0.15s ease;
}

.nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  margin-left: auto;
  border-radius: 999px;
  background: var(--danger);
  color: var(--white);
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
}

/* updateTabBadges() hides a badge at zero; without this it stayed on screen
   showing "0" instead of disappearing. */
.nav-badge[hidden] {
  display: none;
}

.requests-nav-item:hover {
  background: var(--volt-50);
}

.requests-nav-item.active {
  background: var(--volt-50);
  border-left-color: var(--volt-500);
  color: var(--volt-700);
  font-weight: 500;
}

.requests-nav-item.active svg {
  color: var(--volt-500);
}

.requests-content {
  flex: 1;
  min-width: 0;
  padding: 32px 36px 48px;
}

.requests-content-heading {
  margin-bottom: 20px;
  padding-bottom: 14px;
}

.requests-content-heading h2 {
  margin: 0 0 4px;
  font-weight: 600;
  font-size: 21px;
  color: var(--volt-700);
}

.requests-content-heading p {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-soft);
}

.requests-content-heading-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.requests-content-heading-row .btn {
  flex-shrink: 0;
}

/* Edit form: fields on the left, audit panel as its own right-hand column
   so a growing status trail never pushes the form down */
.form-layout {
  display: flex;
  align-items: flex-start;
  gap: 32px;
}

.form-main {
  flex: 1;
  min-width: 0;
}

.form-side {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 400px;
  flex-shrink: 0;
  padding: 18px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--white);

  /* แผงยาวเท่าคอลัมน์ฟอร์ม คือลงไปสุดที่ปุ่มบันทึกพอดี แล้วค่อยเลื่อนในแผงเอง
     ถ้าประวัติสถานะกับคอมเมนต์ยาวเกินกว่านั้น (ตามที่เจ้าของงานสั่ง)

     เดิมแผงนี้ถูกล็อกให้ลอยตามจอ (position: sticky) ซึ่งบังคับให้ความสูงถูกจำกัด
     ไว้ที่ "เท่าหน้าจอ" ไม่ใช่ "เท่าฟอร์ม" -- เนื้อหาจึงถูกตัดให้เลื่อนตั้งแต่ยัง
     ไม่ถึงครึ่งฟอร์ม สองอย่างนี้เลือกได้อย่างเดียว: กล่องที่สูงเท่าคอนเทนเนอร์
     ไม่เหลือระยะให้เลื่อนตัวเอง sticky จึงไม่ทำงาน (กับดักเดียวกับที่
     .requests-sidebar ต้องเลิกใช้ align-self: stretch เพื่อให้ sticky ทำงาน)

     ผลที่แลกไป: เลื่อนหน้าลงไปกรอกช่องล่าง ๆ แล้วแผงจะเลื่อนหายขึ้นไปด้วย
     ไม่ลอยค้างให้เห็นสถานะตลอดเหมือนเดิม */
  align-self: stretch;
  overflow-y: auto;
}

/* กล่องรับแผงในหน้ารายละเอียดขอขยายเขตฯ -- ต้องสูงเท่าคอลัมน์ฟอร์มเหมือนกัน
   ไม่งั้น align-self: stretch ของ .form-side จะยืดได้แค่เท่ากล่องนี้ ซึ่งสูงเท่า
   เนื้อหาตัวเอง เพราะสิ่งที่ flex container เห็นคือกล่องห่อ ไม่ใช่ตัวแผง --
   กับดักเดียวกับที่ทำให้ order: -1 บนจอแคบไม่มีผลจนต้องเพิ่มคลาสนี้ขึ้นมา */
.form-meta-slot {
  align-self: stretch;
  display: flex;
}

.form-side[hidden] {
  display: none;
}

.form-side .form-meta-line {
  margin: 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* การ์ดสรุปคำร้อง -- ออกแบบมาเพื่อ "แคปหน้าจอแล้วส่งต่อได้เลย" เวลาลูกค้าโทรมา
   ตามงาน จึงยกเป็นกล่องพื้นสีอ่อนมีขอบชัด ครอปภาพได้ตรงกรอบโดยไม่ติดของรอบข้าง
   และมีชื่อประเภทคำร้องกำกับอยู่ในกล่อง เพราะพอภาพหลุดไปอยู่ในแชทแล้ว
   จะไม่เหลืออะไรบอกว่านี่คือคำร้องประเภทไหน

   เป็นคอลัมน์กลางของ .form-layout ระหว่างฟอร์มกับแผงประวัติ -- ที่ว่างตรงนี้มีอยู่แล้ว
   บนจอกว้าง เพราะ .request-form ถูกจำกัดไว้ที่ 480px ส่วน .form-main เป็น flex: 1
   ที่เหลือจึงว่างเปล่ามาแต่ไหนแต่ไร

   sticky เหมือนแผงขวา ไม่ใช่เพื่อความสวย -- ฟอร์มขอขยายเขตฯ ยาวมาก เลื่อนไปสักพัก
   จะจำไม่ได้ว่ากำลังแก้คำร้องใบไหน การ์ดที่ติดตาไปด้วยจึงทำหน้าที่เป็นป้ายบอกใบงาน */
.req-summary {
  width: 340px;
  flex-shrink: 0;
  padding: 14px 16px 16px;
  border: 1px solid var(--volt-100);
  border-radius: 12px;
  background: var(--volt-50);
  position: sticky;
  top: calc(var(--topbar-h) + 16px);
}

/* สถานะที่สอง: จอไม่กว้างพอจะมีคอลัมน์กลาง การ์ดถูกย้ายเข้าไปอยู่บนสุดของแผงขวา
   เหนือประวัติสถานะ (placeSummaryCard ใน app.js เป็นคนย้าย) แยกสองสถานะด้วย
   "พ่อแม่เป็นใคร" ไม่ต้องสลับคลาสให้มีสองที่ที่ต้องตรงกัน -- ในแผงขวาการ์ดกว้าง
   เต็มแผงและไม่ sticky เพราะมันเลื่อนอยู่ในแผงที่ยาวเท่าฟอร์มอยู่แล้ว ไม่ใช่
   สิ่งที่ควรลอยค้างทับเนื้อหาอื่นในแผงเดียวกัน */
.form-side > .req-summary {
  width: auto;
  position: static;
}

.req-summary[hidden] {
  display: none;
}

.req-summary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.req-summary-head[hidden] {
  display: none;
}

.req-summary-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--volt-700);
}

/* ปุ่มคัดลอกเป็นเครื่องมือของเจ้าหน้าที่ ไม่ใช่เนื้อหาที่ต้องการให้ติดไปในภาพ
   จึงทำให้เล็กและจางกว่าปุ่มปกติ ไม่ให้แย่งสายตาในภาพที่แคปไป */
.req-summary-copy {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--white);
  font-size: 11.5px;
  color: var(--ink-soft);
}

.req-summary-type {
  margin: 3px 0 12px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--volt-900);
}

/* กริดสองคอลัมน์ ไม่ใช่ flex ทีละแถว -- ค่าทุกบรรทัดจะได้เริ่มตรงกันเป็นแนวเดียว
   ซึ่งอ่านง่ายกว่ามากในภาพนิ่งที่ผู้อ่านเลื่อนดูหรือกดขยายทีละส่วนไม่ได้ */
.req-summary-list {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
  gap: 9px 12px;
  margin: 0;
  align-items: baseline;
}

.req-summary-list[hidden] {
  display: none;
}

.req-summary-list dt {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}

.req-summary-list dd {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
  /* ที่อยู่และชื่อลูกค้ายาวได้ไม่จำกัด -- ให้ตัดขึ้นบรรทัดใหม่แทนที่จะดันกล่องกว้างออก */
  overflow-wrap: anywhere;
}

.req-summary-list dd .request-badge {
  font-size: 12.5px;
  padding: 3px 10px;
}

/* เลขที่คำร้องและวันที่เป็นค่าที่คนอ่านออกเสียงตามหรือพิมพ์ตาม ฟอนต์โมโนช่วยให้
   ตัวเลขที่หน้าตาคล้ายกันไม่กลืนกัน -- เหตุผลเดียวกับที่ใช้ที่อื่นในระบบ */
.req-summary-mono {
  font-family: var(--font-mono);
  font-size: 13px;
}

/* เบอร์โทรกับพิกัดกดได้จริง (tel: / นำทางบน Google Maps) -- ผู้รับผิดชอบมักเปิดการ์ดนี้
   ตอนอยู่หน้างานบนมือถือ ทำเป็นเส้นใต้จาง ๆ ไม่ใช่ลิงก์สีน้ำเงินเต็มตัว เพราะในภาพที่
   แคปไปมันควรอ่านเป็นข้อมูลบรรทัดหนึ่งเหมือนบรรทัดอื่น ไม่ใช่ปุ่มที่กดในภาพไม่ได้ */
.req-summary-link {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}

.req-summary-link:hover {
  color: var(--volt-500);
  text-decoration-color: var(--volt-300);
}

/* Status trail: oldest at the top, most recent change at the bottom */
.status-history {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Status, name and time always share one line -- the name is the only part
   allowed to truncate, so the pill and timestamp stay fully readable. */
.status-stamp {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}

.status-stamp .request-badge {
  flex-shrink: 0;
  font-size: 12.5px;
  padding: 3px 10px;
}

.status-stamp-by {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 11.5px;
  color: var(--ink-soft);
}

/* Work-log comments -- their own stream under the status trail, not mixed
   into it: each carries its own author and timestamp. */
.side-comments {
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.side-comments-heading {
  margin: 0 0 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--volt-700);
}

.comment-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.comment-list:not(:empty) {
  margin-bottom: 14px;
}

.comment-item {
  padding-left: 10px;
  border-left: 2px solid var(--volt-100);
}

.comment-text {
  font-size: 13px;
  color: var(--ink);
  line-height: 1.55;
  white-space: pre-wrap;
}

.comment-by {
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--ink-soft);
}

.side-comments textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--ink);
  background: var(--white);
  outline: none;
  resize: vertical;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.side-comments textarea:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

.side-comments .btn {
  margin-top: 10px;
  padding: 9px 16px;
  font-size: 13.5px;
}

.side-comments .form-error {
  margin: 8px 0 0;
}

.requests-search {
  margin-top: 16px;
}

.requests-search input {
  width: 100%;
  max-width: 360px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  background: var(--white);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.requests-search input:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

.requests-count {
  font-family: var(--font-mono);
  color: var(--ink-soft);
}

.request-form {
  max-width: 480px;
}

.request-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.request-card {
  position: relative;
  border: 1px solid var(--border);
  border-left: 3px solid var(--volt-500);
  border-radius: 0 12px 12px 0;
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--white);
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

.request-card.has-card-actions {
  padding-bottom: 56px;
}

.request-card:hover {
  box-shadow: var(--shadow-sm);
  border-color: var(--border-strong);
}

.request-card-clickable {
  cursor: pointer;
}

.request-card-clickable:hover {
  box-shadow: var(--shadow);
  border-color: var(--volt-300);
}

/* Type reads as a depth of the same brand purple, not a new hue */
.request-card[data-type="power"] {
  border-left-color: var(--volt-500);
}

.request-card[data-type="payment"] {
  border-left-color: var(--volt-100);
}

.request-card[data-type="general"] {
  border-left-color: var(--volt-300);
}

.request-card[data-type="extend"] {
  border-left-color: var(--volt-700);
}

.request-card[data-type="deposit"] {
  border-left-color: var(--volt-900);
}

.request-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.request-badges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.request-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 14.5px;
  font-weight: 500;
  white-space: nowrap;
}

.request-badge-id {
  background: var(--volt-50);
  color: var(--volt-700);
}

.request-badge-purpose {
  background: var(--info-tint);
  color: var(--info);
}

.request-badge-status.tone-warning {
  background: var(--warning-tint);
  color: var(--warning);
}

.request-badge-status.tone-success {
  background: var(--success-tint);
  color: var(--success);
}

.request-badge-status.tone-danger {
  background: var(--danger-tint);
  color: var(--danger);
}

.request-badge-status.tone-info {
  background: var(--info-tint);
  color: var(--info);
}

/* ===== งานขอขยายเขตระบบจำหน่ายไฟฟ้า (โมดูลปฏิบัติงาน) ===== */
.work-main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 28px 28px 64px;
}

/* ชิปกรองสถานะ -- ทุกสถานะอยู่ครบเสมอและอยู่ตำแหน่งเดิมเสมอ ถึงจะเป็นศูนย์
   (ชิปที่หายไปเวลาเป็นศูนย์ทำให้ชิปอื่นขยับ ปุ่มที่จำตำแหน่งไว้เลยกดผิด) */
.status-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--white);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 14px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.status-chip:hover {
  border-color: var(--volt-300);
  background: var(--volt-50);
}

.status-chip.active {
  background: var(--volt-500);
  border-color: var(--volt-500);
  color: var(--white);
}

/* สถานะที่ยังไม่มีงานอยู่เลย -- จางลงแต่ยังกดได้ เพื่อยืนยันด้วยตาว่าว่างจริง */
.status-chip.is-empty {
  opacity: 0.5;
}

.status-chip-count {
  font-family: var(--font-mono);
  font-size: 12.5px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--volt-50);
  color: var(--volt-700);
}

.status-chip.active .status-chip-count {
  background: rgba(255, 255, 255, 0.22);
  color: var(--white);
}

/* แถบจ่ายงาน -- ติดขอบล่างจอไว้ เพราะเลือกงานเสร็จแล้วมักอยู่ท้ายรายการยาว ๆ
   พอดี ไม่ควรต้องเลื่อนกลับขึ้นไปกดปุ่ม */
.assign-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 16px;
  padding: 14px 18px;
  border: 1px solid var(--volt-300);
  border-radius: 12px;
  background: var(--white);
  box-shadow: var(--shadow);
}

/* .assign-bar ตั้ง display: flex ไว้ตายตัว จึงต้องมีบรรทัดนี้ ไม่งั้น hidden
   ไม่มีผลเลย (ดู Gotcha ใน CLAUDE.md) */
.assign-bar[hidden] {
  display: none;
}

.assign-bar-count {
  font-weight: 600;
  color: var(--volt-700);
}

.assign-bar-field {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink-soft);
}

/* ดรอปดาวน์เลือกผู้รับผิดชอบ -- ใช้ลูกศรที่วาดเองแบบเดียวกับตารางประมาณการ
   แทนลูกศรดีฟอลต์ของเบราว์เซอร์ ซึ่งหน้าตาต่างกันไปทุกเครื่องและดูเป็นคนละหน้า
   กับที่เหลือ นี่คือช่องที่หัวหน้าใช้บ่อยที่สุดในหน้านี้ จึงให้ที่ยืนเต็มที่ */
.assign-bar-field select {
  min-width: 260px;
  padding: 9px 34px 9px 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  font-family: inherit;
  font-size: 14px;
  color: var(--ink);
  background-color: var(--white);
  background-image: linear-gradient(45deg, transparent 50%, var(--volt-500) 50%),
                    linear-gradient(135deg, var(--volt-500) 50%, transparent 50%);
  background-position: right 15px center, right 10px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.assign-bar-field select:hover {
  border-color: var(--border-strong);
}

.assign-bar-field select:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

/* แผนผังที่แนบไว้ -- หลายไฟล์ได้ แต่ละใบมีภาพตัวอย่างของตัวเอง */
.plan-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}

.plan-list:empty {
  display: none;
}

.plan-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--paper);
}

/* ตัวอย่างแผนผัง (ไฟล์ภาพ หรือหน้าแรกของ PDF) */
.plan-preview {
  width: 100%;
  height: 200px;
  object-fit: contain;
  border-radius: 8px;
  background: var(--white);
}

/* ตั้ง display ไว้ในกฎด้านบน จึงต้องกันไว้ (ดู Gotcha ใน CLAUDE.md) */
.plan-preview[hidden] {
  display: none;
}

.plan-item-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12.5px;
}

.plan-item-when {
  color: var(--ink-soft);
}

/* ===== ภาพหน้างาน ===== */
.photo-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.photo-slot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--paper);
}

.photo-slot-label {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--volt-700);
}

.photo-slot img {
  width: 100%;
  max-height: 220px;
  object-fit: contain;
  border-radius: 8px;
  background: var(--white);
}

/* ตั้ง display ไว้ตายตัวด้านบน จึงต้องมีบรรทัดนี้ (ดู Gotcha ใน CLAUDE.md) */
.photo-slot img[hidden] {
  display: none;
}

@media (max-width: 700px) {
  .photo-grid {
    grid-template-columns: 1fr;
  }
}

/* ===== หน้าประมาณการ ===== */
.estimate-context {
  margin-bottom: 6px;
}

.estimate-context-wbs {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--volt-700);
}

.estimate-context-sub {
  margin: 2px 0 0;
  font-size: 13.5px;
  color: var(--ink-soft);
}

/* เตือนว่ายังไม่ได้บันทึก -- อยู่ข้างปุ่มบันทึกบนแถบบน จะได้เห็นตลอดทุกชั้น */
.estimate-dirty {
  font-size: 13px;
  color: var(--warning);
  font-weight: 600;
}

.estimate-crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 14px 0 18px;
  font-size: 13.5px;
}

.estimate-crumb {
  border: none;
  background: transparent;
  padding: 2px 4px;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--volt-500);
  cursor: pointer;
  border-radius: 6px;
}

.estimate-crumb:hover {
  background: var(--volt-50);
}

/* ชั้นสุดท้ายเป็น <span> ไม่ใช่ปุ่ม -- กดกลับไปที่ตัวเองไม่ได้ */
span.estimate-crumb {
  color: var(--ink);
  font-weight: 600;
  cursor: default;
}

.estimate-crumb-sep {
  color: var(--ink-soft);
}

.estimate-pane-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--volt-700);
}

.estimate-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 14px 0 18px;
}

.estimate-card {
  display: flex;
  align-items: stretch;
  gap: 8px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--volt-500);
  border-radius: 0 12px 12px 0;
  background: var(--white);
}

.estimate-card-open {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 16px;
  border: none;
  background: transparent;
  text-align: left;
  cursor: pointer;
  border-radius: 0 12px 12px 0;
}

.estimate-card-open:hover {
  background: var(--volt-50);
}

.estimate-card-title {
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
}

.estimate-card-meta {
  font-size: 13px;
  color: var(--ink-soft);
}

/* แถวเพิ่มแผนก/งานย่อย -- ช่องกรอกกับปุ่มอยู่บรรทัดเดียวกัน */
.estimate-add-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 1px dashed var(--border-strong);
  border-radius: 12px;
  background: var(--paper);
}

.estimate-add-row input,
.estimate-add-row select {
  flex: 1;
  min-width: 160px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-family: inherit;
  font-size: 14.5px;
  color: var(--ink);
  background: var(--white);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.estimate-add-row input:focus,
.estimate-add-row select:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

/* ===== ใบประมาณการ ===== */
.estimate-head {
  display: grid;
  grid-template-columns: 2fr 1.4fr 1fr;
  gap: 16px;
  margin-bottom: 14px;
}

/* ตารางกว้างกว่าคอลัมน์ฟอร์มแน่นอน -- ให้เลื่อนแนวนอนในกรอบของตัวเอง ไม่ใช่
   ทำให้ทั้งหน้าเลื่อนได้ (กติกาเดียวกับตารางกว้างที่อื่นในโปรเจกต์นี้) */
.estimate-scroll {
  overflow-x: auto;
}

.estimate-table {
  min-width: 880px;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

.estimate-row {
  display: grid;
  grid-template-columns: 44px 108px minmax(240px, 1fr) 150px 72px 72px 72px 40px;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid var(--border);
}

.estimate-row:first-child {
  border-top: none;
}

/* สลับสีแถวอ่อน ๆ -- ตารางกว้าง ตาต้องกวาดตามแถวให้ไม่หลุด */
#estRows .estimate-row:nth-child(even) {
  background: var(--paper);
}

#estRows .estimate-row:focus-within {
  background: var(--volt-50);
}

.estimate-head-row {
  background: var(--volt-50);
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: 0.3px;
  color: var(--volt-700);
  text-align: center;
  border-bottom: 1px solid var(--border);
}

/* หัวคอลัมน์ Description ชิดซ้ายให้ตรงกับข้อความในช่อง */
.estimate-head-row span:nth-child(2) {
  text-align: left;
}

/* ช่องกรอกในตารางใช้หน้าตาเดียวกับช่องกรอกทั้งเว็บ แค่ย่อระยะขอบลงให้พอดีแถว
   -- ตารางที่ใช้ช่องกรอกหน้าตาดีฟอลต์ของเบราว์เซอร์จะดูเป็นคนละหน้ากับที่เหลือ */
.estimate-row input,
.estimate-row select {
  width: 100%;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-family: inherit;
  font-size: 13.5px;
  color: var(--ink);
  background: var(--white);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.estimate-row input:hover:not([readonly]),
.estimate-row select:hover {
  border-color: var(--border-strong);
}

.estimate-row input:focus,
.estimate-row select:focus {
  border-color: var(--volt-300);
  box-shadow: 0 0 0 3px var(--volt-100);
}

/* ลูกศรของ <select> วาดเอง เพื่อให้เหมือนกันทุกเบราว์เซอร์และไม่กินที่ */
.estimate-row select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--volt-500) 50%),
                    linear-gradient(135deg, var(--volt-500) 50%, transparent 50%);
  background-position: right 12px center, right 7px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}


/* ---------- แผงเลือกพัสดุของใบประมาณการ ---------- */

/* แผงชุดเซ็ตพัสดุ -- วางเหนือแผงเลือกทีละตัว และทำให้ดูเป็นคนละก้อนกันชัด ๆ
   ด้วยเส้นคั่นด้านล่าง ไม่งั้นดรอปดาวน์สองอันที่อยู่ติดกันจะดูเหมือนแผงเดียวกัน
   แล้วคนจะสับสนว่า "ชุดเซ็ต" กับ "กลุ่ม" ต่างกันตรงไหน */
.est-kits {
  padding-bottom: 18px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--border);
}

.est-kits-row {
  display: flex;
  align-items: end;
  flex-wrap: wrap;
  gap: 12px;
}

.est-kits-row[hidden] {
  display: none;
}

.est-kits-pick {
  flex: 1 1 320px;
  min-width: 0;
}

.est-kits-pick select {
  width: 100%;
}

.est-kits .field-hint,
.est-kits .form-error {
  margin: 10px 0 0;
}

.est-kit-save {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 12px;
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--volt-50);
}

.est-kit-save[hidden] {
  display: none;
}

.est-kit-save .batch-toolbar {
  grid-column: 1 / -1;
}

@media (max-width: 700px) {
  .est-kit-save {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ไฮไลต์แถวที่เพิ่งเปลี่ยนจากการเพิ่มชุดเซ็ต -- เพิ่มทีเดียวสิบสี่รายการแล้วบางตัว
   ถูกรวมเข้ากับแถวเดิมเงียบ ๆ คือกรณีที่คนกดไม่รู้ว่าเกิดอะไรขึ้น แล้วกดซ้ำ
   สีคนละสีระหว่าง "แถวใหม่" กับ "ถูกบวกเพิ่ม" เพราะสองอย่างนี้ต้องแยกออกจากกันให้ได้ */
.estimate-row.is-kit-new {
  background: var(--success-tint);
}

.estimate-row.is-kit-merged {
  background: var(--warning-tint);
}

/* การเลือกพัสดุเกิดที่นี่ที่เดียว ตารางด้านล่างมีแต่ของที่เพิ่มแล้ว -- แผงจึงถูก
   ยกออกมาเป็นกล่องของตัวเองให้ต่างจากตาราง ไม่ใช่แถวหนึ่งในตารางเดียวกัน */
.est-picker {
  margin: 18px 0 14px;
  padding: 16px 18px 18px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--paper);
}

.est-picker-title {
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--volt-700);
}

.est-picker-grid {
  display: grid;
  grid-template-columns: minmax(150px, 1fr) 150px minmax(260px, 2.2fr) repeat(3, 92px);
  gap: 12px;
  align-items: end;
}

.est-picker-item input,
.est-picker-key input {
  width: 100%;
}

/* ---------- กล่องรายการแนะนำของช่องรหัสพัสดุ/รายการพัสดุ ----------
   เขียนเองแทน <datalist> ของเบราว์เซอร์ ซึ่งตัดชื่อพัสดุยาวทิ้งและปรับหน้าตาไม่ได้
   ชื่อที่ต่างกันแค่ท้ายชื่อ (ขนาดสาย ขนาดหม้อแปลง) จึงเลือกผิดตัวได้ง่าย */
.est-picker-key,
.est-picker-item {
  position: relative;
}

.est-suggest {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 40;
  width: 100%;
  /* ช่องรหัสพัสดุแคบแค่ 150px -- กล่องต้องกว้างพอให้อ่านชื่อเต็มได้ จึงลอยทับ
     คอลัมน์ข้าง ๆ ได้ แต่ห้ามกว้างเกินจอบนมือถือ */
  min-width: min(480px, calc(100vw - 32px));
  max-height: 340px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgba(34, 21, 48, 0.16);
}

/* ใส่ไว้ตามกติกา [hidden] ของโปรเจกต์ (ดู Gotcha ใน CLAUDE.md) -- กล่องนี้สลับ
   ด้วย hidden ตลอดเวลา ถ้าวันหนึ่งมีคนเติม display ให้มัน จะได้ไม่ค้างเปิด */
.est-suggest[hidden] {
  display: none;
}

.est-suggest-option {
  display: grid;
  grid-template-columns: minmax(84px, auto) 1fr;
  column-gap: 12px;
  row-gap: 2px;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
}

.est-suggest-option.is-active {
  background: var(--volt-50);
}

/* ตัวที่เลือกค้างไว้อยู่แล้ว -- เปิดกล่องใหม่เพื่อเปลี่ยนตัว ต้องเห็นว่าตอนนี้เลือกตัวไหน */
.est-suggest-option.is-picked .est-suggest-desc {
  font-weight: 600;
  color: var(--volt-700);
}

.est-suggest-key {
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--volt-700);
  white-space: nowrap;
}

/* ชื่อเต็มเสมอ -- ขึ้นบรรทัดใหม่ได้ ไม่ตัดท้าย ซึ่งคือเหตุผลเดียวที่กล่องนี้มีอยู่ */
.est-suggest-desc {
  min-width: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.est-suggest-group {
  grid-column: 2;
  font-size: 12px;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

.est-suggest-empty,
.est-suggest-more {
  padding: 8px 10px;
  font-size: 12.5px;
  color: var(--ink-soft);
  cursor: default;
}

.est-suggest-more {
  border-top: 1px solid var(--border);
}

/* ฟอนต์โมโนไว้เพราะเป็นรหัส -- แต่สีต้องเหมือนช่องกรอกอื่นทุกช่อง
   พื้นม่วงอ่อน + cursor: default เป็นหน้าตาของช่องอ่านอย่างเดียว (ตอนที่ช่องนี้
   ยังเป็น readonly) พอพิมพ์ค้นได้แล้วยังคงสีนั้นไว้ คนจะเข้าใจว่ากรอกไม่ได้ */
.est-picker-key input {
  font-family: var(--font-mono);
  font-size: 13px;
}

.est-picker-qty input {
  text-align: right;
}

/* เลขจำนวนพิมพ์เองเสมอ ลูกศรขึ้นลงของเบราว์เซอร์กินที่และกดโดนโดยไม่ตั้งใจ */
.est-picker-qty input::-webkit-outer-spin-button,
.est-picker-qty input::-webkit-inner-spin-button {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
}

.est-picker-qty input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.est-picker-add {
  width: 100%;
  margin-top: 14px;
}

/* ข้อความ "รวมจำนวนเข้ากับรายการเดิมแล้ว" ใช้กล่องเดียวกับ error แต่ไม่ใช่ error
   -- เป็นการยืนยันว่าเกิดอะไรขึ้น จึงต้องอ่านออกว่าเป็นคนละเรื่องกับความผิดพลาด */
.est-picker .form-error.is-notice {
  color: var(--volt-700);
  background: var(--volt-50);
  border-color: var(--volt-100);
}

/* ---------- เซลล์ที่เลือกมาแล้ว แสดงเป็นข้อความ ไม่ใช่ช่องกรอก ---------- */
.estimate-cell-key {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--volt-700);
}

/* ชื่อพัสดุต้องขึ้นครบเสมอ -- ตัดขึ้นบรรทัดใหม่ในช่องของตัวเอง min-width: 0 กัน
   ไม่ให้ช่องกริดขยายตามข้อความยาวจนดันคอลัมน์ข้าง ๆ หรือโดน .estimate-table
   (overflow: hidden) ตัดทิ้ง และ overflow-wrap ใช้กับข้อความยาวที่ไม่มีเว้นวรรค */
.estimate-cell-desc {
  min-width: 0;
  font-size: 13.5px;
  color: var(--ink);
  line-height: 1.4;
  overflow-wrap: anywhere;
}

/* กลุ่มเคยตัดท้ายเป็น "…" -- ชื่อกลุ่มยาวจึงอ่านไม่ครบ ให้ขึ้นบรรทัดใหม่แทน */
.estimate-cell-group {
  min-width: 0;
  font-size: 12.5px;
  color: var(--ink-soft);
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.est-picker-full {
  margin: 12px 0 0;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--volt-50);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--volt-700);
  overflow-wrap: anywhere;
}

.estimate-empty {
  margin: 0;
  padding: 26px 16px;
  text-align: center;
  font-size: 13.5px;
  color: var(--ink-soft);
}

/* .estimate-empty ไม่ได้ตั้ง display ตายตัว จึงไม่ต้องมี [hidden] override
   (ดูหมายเหตุเรื่อง hidden vs display ใน CLAUDE.md) */

@media (max-width: 900px) {
  .est-picker-grid {
    grid-template-columns: 1fr 1fr 1fr;
  }

  .est-picker-item {
    grid-column: 1 / -1;
  }
}

.estimate-no {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-soft);
  text-align: center;
}

/* KeyCode ไม่ได้กรอกเอง -- มาจากรายการที่เลือกเสมอ จึงทำให้อ่านออกว่าเป็นค่าที่
   ระบบเติมให้ ไม่ใช่ช่องว่างที่รอให้พิมพ์ */
.estimate-keycode {
  font-family: var(--font-mono);
  font-size: 12.5px;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: default;
}

/* ปุ่มเพิ่ม/ลดของช่องตัวเลขกินที่ในแถวแคบ และไม่มีใครใช้กับจำนวนพัสดุ */
.estimate-qty {
  font-family: var(--font-mono);
  text-align: center;
  -moz-appearance: textfield;
  appearance: textfield;
}

.estimate-qty::-webkit-outer-spin-button,
.estimate-qty::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.estimate-remove {
  border: none;
  background: transparent;
  color: var(--ink-soft);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  border-radius: 8px;
  padding: 4px;
}

.estimate-remove:hover {
  background: var(--danger-tint);
  color: var(--danger);
}

@media (max-width: 700px) {
  .estimate-head {
    grid-template-columns: 1fr;
  }
}

/* ===== จัดกลุ่มงานตามสถานะ (ย่อ/ขยายได้) ===== */
.status-group {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--white);
  overflow: hidden;
}

.status-group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  border: none;
  background: var(--volt-50);
  text-align: left;
  cursor: pointer;
}

.status-group-head:hover {
  background: var(--volt-100);
}

/* ลูกศรหมุนลงเมื่อกางอยู่ -- บอกสถานะย่อ/ขยายโดยไม่ต้องมีข้อความ */
.status-group-caret {
  flex: none;
  width: 0;
  height: 0;
  border-left: 6px solid var(--volt-500);
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  transform: rotate(90deg);
  transition: transform 0.15s ease;
}

.status-group.is-collapsed .status-group-caret {
  transform: rotate(0deg);
}

.status-group-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-soft);
}

.status-group-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
}

.status-group-body[hidden] {
  display: none;
}

/* ===== งานในมือของแต่ละคน (หัวหน้างาน) ===== */
.person-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--volt-500);
  border-radius: 0 12px 12px 0;
  background: var(--white);
  text-align: left;
  cursor: pointer;
}

.person-card:hover {
  background: var(--volt-50);
}

.person-card.is-mine {
  border-left-color: var(--warning);
}

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

.person-card-name {
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
}

.person-card-total {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--volt-700);
}

/* สรุปว่ากองงานของคนนี้ค้างอยู่ขั้นไหนบ้าง โดยไม่ต้องกดเข้าไปดู */
.person-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.person-card-chips .request-badge {
  font-size: 12.5px;
  padding: 2px 10px;
}

/* ===== คิวรอสำรวจ แยกเป็นของแต่ละคน ===== */
.queue-group {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--white);
  overflow: hidden;
}

/* คิวของตัวเองอยู่บนสุดอยู่แล้ว ขีดซ้ายหนาเป็นตัวย้ำอีกชั้นว่าอันไหนของเรา */
.queue-group.is-mine {
  border-left: 3px solid var(--volt-500);
}

.queue-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  background: var(--volt-50);
  border-bottom: 1px solid var(--border);
}

.queue-group-name {
  font-weight: 600;
  color: var(--volt-700);
}

.queue-group-count {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-soft);
}

.queue-item {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 12px 16px;
  border: none;
  border-top: 1px solid var(--border);
  background: var(--white);
  text-align: left;
  cursor: pointer;
}

.queue-item:first-of-type {
  border-top: none;
}

.queue-item:hover {
  background: var(--volt-50);
}

/* เลขคิวเป็นตัวเลขเดี่ยว ๆ ตัวใหญ่ อ่านได้จากระยะไกลว่าใครคิวไหน */
.queue-pos {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--volt-500);
  color: var(--white);
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* คิวที่ 1 ของทุกคน = งานที่ต้องลงพื้นที่ก่อน ทำให้เด่นกว่าคิวถัด ๆ ไป */
.queue-item:first-of-type .queue-pos {
  background: var(--warning);
}

.queue-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.queue-title {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}

.queue-meta {
  font-size: 13px;
  color: var(--ink-soft);
}

/* บรรทัดผู้รับผิดชอบบนการ์ดงาน */
.work-assignee {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--volt-700);
}

.work-assignee.is-unassigned {
  color: var(--warning);
}

.request-date {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-soft);
}

.request-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.request-meta {
  font-size: 13.5px;
  color: var(--ink-soft);
  line-height: 1.65;
}

.request-empty {
  text-align: center;
  padding: 48px 24px;
  color: var(--ink-soft);
  font-size: 14px;
  border: 1px dashed var(--border-strong);
  border-radius: 12px;
}

.request-card-actions {
  position: absolute;
  right: 20px;
  bottom: 16px;
  display: flex;
  gap: 8px;
}

.requests-heading-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* ===== Batch request entry ===== */
.form-divider {
  height: 1px;
  background: var(--border);
  border: none;
  margin: 8px 0 0;
}

.batch-heading {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--volt-700);
  margin: 0;
}

/* เดิมเป็นย่อหน้าเดียวยาวติดกันจนอ่านไม่ออกว่ามีกี่เรื่อง -- แยกเป็นข้อ ๆ
   เรื่องละบรรทัด */
.batch-hint {
  margin: -8px 0 0;
  padding-left: 20px;
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.85;
}

.batch-hint li {
  margin-bottom: 2px;
}

.batch-hint strong {
  color: var(--ink);
  font-weight: 600;
}

.batch-toolbar {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.batch-toolbar[hidden] {
  display: none;
}

/* กลุ่มคำร้องกางเต็มความกว้างของพื้นที่เนื้อหา โดยหักระยะขอบของ
   .requests-content ออกทั้งสองข้าง -- แต่ละใบมีช่องกรอกครบทุกช่อง จึงต้องการ
   ความกว้างมากกว่าฟอร์มตั้งต้นด้านบนที่เป็นสองคอลัมน์ */
.batch-rows {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-left: calc(var(--content-pad-x) * -1);
  margin-right: calc(var(--content-pad-x) * -1);
  padding-left: var(--content-pad-x);
  padding-right: var(--content-pad-x);
}

.batch-row {
  border: 1px solid var(--border);
  border-left: 3px solid var(--volt-300);
  border-radius: var(--radius);
  background: var(--white);
  padding: 16px 18px;
}

.batch-row-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.batch-row-title {
  font-family: var(--font-display);
  font-size: 14.5px;
  font-weight: 600;
  color: var(--volt-700);
}

/* ทุกช่องของคำร้องหนึ่งใบเรียงเป็นกริดที่ยืดตามความกว้างจริง -- auto-fill ทำให้
   จอกว้างได้หลายคอลัมน์ จอแคบลดเองโดยไม่ต้องมี breakpoint ของตัวเอง */
.batch-row-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px 14px;
  align-items: start;
}

.batch-row-grid .field[hidden] {
  display: none;
}

/* ป้ายกำกับในแถวกลุ่มเล็กกว่าฟอร์มหลัก เพราะมีจำนวนมากกว่ามากในพื้นที่เท่ากัน */
.batch-row-grid label {
  font-size: 12px;
}

.batch-row-grid input,
.batch-row-grid select,
.batch-row-grid textarea {
  padding: 8px 10px;
  font-size: 13.5px;
}

.batch-remove-btn {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 12px;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}

.batch-remove-btn:hover {
  background: var(--danger-tint);
  border-color: var(--danger);
  color: var(--danger);
}

.batch-result {
  padding: 28px 26px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--success);
  border-radius: var(--radius);
  background: var(--white);
}

.batch-result[hidden] {
  display: none;
}

.batch-result h3 {
  font-family: var(--font-display);
  margin: 0 0 8px;
  color: var(--ink);
}

.batch-result p {
  margin: 0 0 20px;
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.7;
}

.batch-result-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* ===== Meter tab: รอส่ง / ประวัติการส่ง toggle ===== */
.requests-modes {
  display: flex;
  gap: 6px;
  margin-top: 14px;
  padding: 4px;
  background: var(--volt-50);
  border: 1px solid var(--border);
  border-radius: 10px;
  width: fit-content;
}

.requests-modes[hidden] {
  display: none;
}

.requests-mode-btn {
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  padding: 7px 16px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}

.requests-mode-btn:hover {
  color: var(--volt-700);
}

.requests-mode-btn.active {
  background: var(--white);
  color: var(--volt-700);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

.dispatch-meta-people {
  font-family: var(--font-mono);
  font-size: 12.5px;
}

/* Inline action that reads as a link but is a real button, since it acts on
   the page rather than navigating anywhere. */
.link-btn {
  font: inherit;
  padding: 0;
  border: none;
  background: none;
  color: var(--volt-500);
  text-decoration: underline;
  cursor: pointer;
}

.link-btn:hover {
  color: var(--volt-700);
}

.request-card.has-card-select {
  padding-left: 46px;
}

.request-card-select {
  position: absolute;
  left: 16px;
  top: 18px;
  display: flex;
  cursor: pointer;
}

.request-card-select input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--volt-500);
  cursor: pointer;
}

.request-card-action-btn {
  padding: 6px 12px;
  font-size: 12.5px;
}

/* ===== Responsive =====
   Breakpoints, widest first:
   1100px — sidebar + 480px form + 400px audit panel stop fitting side by side
   1024px — iPad landscape: narrower sidebar buys back content width
    860px — iPad portrait / large phones: sidebar becomes a scrolling tab bar
    640px — phones: single column everywhere, tighter gutters */

@media (max-width: 1100px) {
  .form-layout {
    flex-direction: column;
  }

  .form-side {
    width: 100%;
    order: -1;

    /* จอแคบวางแผงไว้เหนือฟอร์ม ปล่อยให้ไหลตามเนื้อหาไปเลย ไม่ต้องยืดให้เท่า
       ฟอร์มและไม่ต้องมีแถบเลื่อนของตัวเอง */
    align-self: auto;
    overflow-y: visible;
  }

  /* หน้ารายละเอียดขอขยายเขตฯ ห่อแผงไว้อีกชั้น (#extendPaneMeta) เพราะแผงเป็นโหนด
     ที่ถูกย้ายไปมาระหว่างสองหน้า -- order ที่ให้ .form-side จึงไม่มีผลกับการเรียงใน
     .form-layout เพราะลูกที่แท้จริงคือกล่องห่อ ไม่ใช่ตัวแผง ผลคือบนมือถือ สรุปคำร้อง
     ประวัติสถานะ และคอมเมนต์ไปกองอยู่ "ใต้" ฟอร์มที่ยาวมาก ต่างจากคำร้องขอใช้ไฟฟ้า
     ที่แผงเป็นลูกโดยตรงและขึ้นก่อนฟอร์มอยู่แล้ว ต้องสั่งที่กล่องห่อด้วยจึงจะตรงกัน

     การ์ดสรุปก็มาด้วยกันเอง เพราะบนจอแคบ placeSummaryCard() ย้ายมันเข้าไปอยู่
     บนสุดของแผงนี้อยู่แล้ว (ดู min-width: 1640px ใน app.js) */
  .form-meta-slot {
    width: 100%;
    order: -1;
  }
}

@media (max-width: 1024px) {
  .requests-sidebar {
    width: 200px;
    padding: 20px 8px;
  }

  .requests-nav-item {
    font-size: 13.5px;
    padding: 10px 12px;
  }

  .requests-content {
    --content-pad-x: 24px;
    padding: 28px 24px 40px;
  }
}

@media (max-width: 860px) {
  .service-grid {
    grid-template-columns: 1fr;
  }

  .requests-layout {
    flex-direction: column;
  }

  /* Six tabs won't stack on a phone, so scroll them sideways instead of
     wrapping into a tall block that pushes the list off-screen. */
  .requests-sidebar {
    width: 100%;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 4px;
    padding: 8px 12px;
    border-right: none;
    border-bottom: 1px solid var(--border);
    -webkit-overflow-scrolling: touch;

    /* ยังล็อกอยู่ แต่เป็นแถบแนวนอนใต้แถบบน ไม่ใช่คอลัมน์สูงเต็มจอ
       ความสูงต้องกลับเป็น auto ไม่งั้นแถบแท็บจะสูงเท่าจอทั้งจอ */
    height: auto;
    align-self: stretch;
    z-index: 5;
    overflow-x: auto;
    overflow-y: hidden;
  }

  .requests-nav-item {
    flex-shrink: 0;
    white-space: nowrap;
    border-left: none;
    border-bottom: 2px solid transparent;
    border-radius: 8px;
  }

  .requests-nav-item.active {
    border-left-color: transparent;
    border-bottom-color: var(--volt-500);
  }

  .nav-badge {
    margin-left: 2px;
  }

  /* iOS zooms the whole page when a focused input is under 16px. */
  .field input,
  .field select,
  .field textarea,
  .requests-search input,
  .side-comments textarea {
    font-size: 16px;
  }
}

@media (max-width: 640px) {
  .field-row {
    flex-direction: column;
  }

  .auth-view {
    padding: 24px 16px;
  }

  .auth-card {
    padding: 28px 22px 22px;
  }

  .topbar {
    padding: 12px 16px;
  }

  .user-greeting {
    max-width: 38vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .home-main {
    padding: 32px 18px 48px;
  }

  .requests-content {
    --content-pad-x: 16px;
    padding: 24px 16px 40px;
  }

  .requests-content-heading-row .btn {
    width: 100%;
  }

  .request-card {
    padding: 16px 16px;
  }

  .request-card-actions {
    right: 16px;
  }

  .form-side {
    padding: 16px;
  }

  /* The one-line status stamp can't hold a pill, a name and a timestamp on a
     phone-width panel -- let it wrap rather than overflow. */
  .status-stamp {
    flex-wrap: wrap;
    white-space: normal;
  }
}

/* ===== qr.html — staff QR-label printing tool ===== */

.qr-page {
  max-width: 920px;
  margin: 0 auto;
  padding: 40px 20px;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 24px;
  align-items: start;
}

/* Without this the single card stays on screen (and on the printed page)
   behind the batch label sheet, because the unconditional `display: grid`
   above outranks the UA's [hidden] rule. Same gotcha as .auth-view/.field/
   .btn/.track-slip img -- see CLAUDE.md. */
.qr-page[hidden] {
  display: none;
}

@media (max-width: 720px) {
  .qr-page {
    grid-template-columns: 1fr;
  }
}

.qr-card,
.qr-side {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

.qr-card {
  padding: 36px 32px 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 16px;
  border-top: 3px solid var(--volt-500);
}

.qr-brand {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  color: var(--volt-700);
  text-transform: uppercase;
}

.qr-card h1 {
  font-size: 1.5rem;
  line-height: 1.3;
  margin: 0;
  color: var(--ink);
}

.qr-lede {
  margin: -6px 0 4px;
  color: var(--ink-soft);
  font-size: 0.95rem;
  max-width: 34ch;
}

.qr-frame {
  background: #ffffff;
  padding: 16px;
  border-radius: 12px;
  border: 1px solid var(--border);
}

.qr-frame canvas,
.qr-frame img {
  display: block;
  image-rendering: pixelated;
}

.qr-tracking-line {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--ink);
  margin: 2px 0 0;
}

.qr-blank {
  display: inline-block;
  min-width: 11ch;
  border-bottom: 1.5px solid var(--ink-soft);
  padding: 0 2px 1px;
  font-weight: 600;
  color: var(--volt-700);
}

.qr-side {
  padding: 28px 26px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.qr-scan-hint {
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin: -4px 0 0;
}

.qr-hint {
  font-size: 0.78rem;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 4px 0 0;
}

.qr-note {
  font-size: 0.78rem;
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0;
}

.copy-feedback {
  font-size: 0.8rem;
  color: var(--volt-700);
  text-align: center;
  min-height: 1.2em;
  margin: -8px 0 0;
}

@media print {
  body {
    background: #fff;
  }
  .qr-side {
    display: none;
  }
  .qr-page {
    display: block;
    max-width: none;
    padding: 0;
  }
  .qr-card {
    box-shadow: none;
    border: none;
    padding: 10mm;
  }
}


/* ปุ่มคำสั่งฉุกเฉินของผู้ดูแลระบบบน topbar -- ยังเป็นปุ่ม ghost เหมือนปุ่มข้างกัน
   (ไม่ใช่ปุ่มทึบ) เพราะไม่ควรเด่นกว่างานประจำวัน แต่ย้อมเป็นโทนอันตรายเพื่อไม่ให้
   กดสลับกับ "ออกจากระบบ" ที่อยู่ติดกัน ซึ่งผลลัพธ์ต่างกันคนละเรื่อง */
.btn-danger-ghost {
  color: var(--danger);
  border-color: var(--danger);
}

.btn-danger-ghost:hover {
  background: var(--danger-tint);
  color: var(--danger);
}

/* ==========================================================================
   Account view (เปลี่ยนรหัสผ่าน / ตั้งรหัสผ่านชั่วคราว)
   ========================================================================== */

/* แยกส่วนของผู้ดูแลระบบออกจากฟอร์มด้านบนให้ชัด -- สองฟอร์มนี้ทำคนละเรื่องกัน
   (รหัสของตัวเอง กับรหัสของคนอื่น) การเผลอกรอกผิดฟอร์มมีราคาแพง */
.admin-panel {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px dashed var(--border-strong);
}

/* ไม่ได้ตั้ง display ไว้ จึงยังไม่จำเป็นตอนนี้ แต่ใส่กันไว้ตามกฎ [hidden] ของ
   โปรเจกต์ เผื่อวันหลังมีคนเติม display: flex/grid ให้คลาสนี้แล้วซ่อนไม่ลง */
.admin-panel[hidden],
.temp-password[hidden] {
  display: none;
}

.admin-panel-title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  color: var(--danger);
}

.admin-panel-hint {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--ink-soft);
}

.temp-password {
  margin-top: 18px;
  padding: 16px 18px;
  background: var(--warning-tint);
  border: 1px solid var(--warning);
  border-radius: 10px;
  text-align: center;
}

.temp-password-label {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--ink-soft);
}

.temp-password-label span {
  font-weight: 600;
  color: var(--ink);
}

/* mono + user-select: all -- รหัสนี้ต้องถูกอ่านออกเสียงหรือคัดลอกให้ถูกทุกตัว
   ฟอนต์ที่แยก 0/O และ 1/l ได้จึงสำคัญพอ ๆ กับการคัดลอกได้ในคลิกเดียว */
.temp-password-value {
  margin: 0 0 12px;
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 2px;
  color: var(--ink);
  user-select: all;
  word-break: break-all;
}

.temp-password-warn {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--warning);
}

/* คำอธิบายกลาง ๆ ที่ไม่ใช่ทั้ง error และ success -- ใช้กับข้อความบังคับเปลี่ยน
   รหัสผ่านชั่วคราว ซึ่งไม่ใช่ความผิดพลาดของผู้ใช้ แต่ก็ผ่านไปเฉย ๆ ไม่ได้ */
.form-notice {
  margin: 0 0 18px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--warning-tint);
  border-left: 3px solid var(--warning);
  color: var(--warning);
  font-size: 13.5px;
  line-height: 1.75;
}

/* ทั้งสองคลาสนี้ถูกสลับด้วย hidden -- ยังไม่ได้ตั้ง display จึงยังไม่จำเป็น
   แต่ใส่ตามกฎของโปรเจกต์ เผื่อวันหลังมีคนเติม display ให้แล้วซ่อนไม่ลง */
.form-notice[hidden],
.auth-switch[hidden] {
  display: none;
}

/* ปุ่มที่ "กำลังทำงานอยู่" ต้องดูต่างจากปุ่ม "กดไม่ได้" -- .btn:disabled ทำให้จาง
   ลงอย่างเดียว ซึ่งสื่อว่าใช้ไม่ได้ ไม่ได้สื่อว่ากำลังรออยู่ คนละความหมายกัน
   สำหรับคนที่เพิ่งกดบันทึกแล้วกำลังรอผล */
.btn.is-busy,
.btn.is-busy:hover {
  opacity: 1;
  background: var(--volt-300);
  border-color: var(--volt-300);
  color: var(--white);
  cursor: progress;
  box-shadow: none;
  animation: btn-busy-pulse 1.15s ease-in-out infinite;
}

@keyframes btn-busy-pulse {
  0%, 100% { background: var(--volt-300); }
  50%      { background: var(--volt-500); }
}

/* เคารพการตั้งค่าของผู้ใช้ที่ขอให้ลดการเคลื่อนไหว -- ยังเปลี่ยนโทนสีให้เห็นอยู่
   แค่ไม่กะพริบ */
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy,
  .btn.is-busy:hover {
    animation: none;
  }
}

/* ==========================================================================
   ย่อ/ขยายคำร้องในกลุ่ม -- เวลามีหลายใบ การกางทุกใบพร้อมกันทำให้หน้ายาวจน
   เลื่อนหาไม่เจอ
   ========================================================================== */

/* กฎ [hidden] ที่ขาดไม่ได้: .batch-row-grid ตั้ง display: grid แบบไม่มีเงื่อนไข
   ซึ่งชนะกฎ [hidden] { display: none } ของเบราว์เซอร์เพราะ specificity เท่ากัน
   ถ้าไม่มีบรรทัดนี้ การตั้ง .hidden = true จะไม่เกิดอะไรขึ้นเลย และเป็นความผิด
   พลาดที่เงียบสนิท (เกิดมาแล้วห้าครั้งในโปรเจกต์นี้ ดูหัวข้อ Gotcha) */
.batch-row-grid[hidden] {
  display: none;
}

.batch-toggle-btn {
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px 12px;
  background: var(--white);
  color: var(--volt-500);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}

.batch-toggle-btn:hover {
  background: var(--volt-50);
  border-color: var(--volt-300);
}

/* ชื่อใบกดได้ด้วย -- เป้าที่ใหญ่กว่าปุ่มเล็ก ๆ สำหรับการกระทำที่ทำบ่อย */
.batch-row-title {
  cursor: pointer;
  user-select: none;
}

/* สรุปย่อ ๆ ตอนย่อ: ถ้าเหลือแต่ "คำร้องที่ 3" ก็ไม่รู้ว่าใบไหนเป็นใบไหน
   กินพื้นที่ที่เหลือทั้งหมด และตัดด้วย ... เมื่อยาวเกิน แทนที่จะดันปุ่มลบตกบรรทัด */
.batch-row-summary {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.batch-row.is-collapsed {
  padding: 10px 18px;
  background: var(--volt-50);
}

.batch-row.is-collapsed .batch-row-head {
  margin-bottom: 0;
}

/* ===== qr.html: เลขที่คำร้องหลายใบบนการ์ดเดียว (คำร้องกลุ่ม) ===== */

/* auto-fit เพื่อให้กลุ่มเล็กเรียงแถวเดียว กลุ่มใหญ่พับเป็นหลายคอลัมน์เอง
   โดยไม่ต้องมี breakpoint ของตัวเอง */
.qr-tracking-many {
  align-self: stretch;
  margin-top: 2px;
}

.qr-tracking-many[hidden] {
  display: none;
}

.qr-tracking-many-label {
  margin: 0 0 6px;
  font-size: 0.9rem;
  color: var(--ink);
}

.qr-tracking-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 6px 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.qr-tracking-list[hidden] {
  display: none;
}

.qr-tracking-list li {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--volt-700);
  text-align: center;
  padding: 4px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
}

/* เบอร์ติดต่อของเคาน์เตอร์ -- อยู่ใน .qr-card จึงติดไปกับกระดาษที่พิมพ์ออกมา
   คนที่สแกนแล้วติดขัดต้องมีที่ให้โทรถามบนแผ่นเดียวกัน */
.qr-contact {
  align-self: stretch;
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  text-align: center;
  color: var(--ink);
}

/* ===== track.html: ค่าธรรมเนียมตอนรอชำระเงิน ===== */

/* กฎ [hidden] ที่ขาดไม่ได้: .track-result-details > div ตั้ง display: flex ไว้
   ซึ่งชนะกฎ [hidden] ของเบราว์เซอร์เพราะ specificity เท่ากัน ถ้าไม่มีบรรทัดนี้
   แถวค่าธรรมเนียมจะโผล่ตลอดแม้ตอนที่ไม่มีค่าให้แสดง (ดูหัวข้อ Gotcha) */
.track-result-details > div[hidden] {
  display: none;
}

/* ยอดเงินคือสิ่งที่คนอ่านหน้านี้กำลังมองหาก่อนไปโอน จึงเน้นให้เห็นชัดกว่าแถวอื่น */
.track-fee {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--volt-700);
}

/* ข้อความบอกว่าต้องติดต่อใครต่อ เมื่อคำร้องอยู่ในสถานะที่งานไปอยู่กับแผนกอื่นแล้ว
   ใช้โทน info เพราะเป็นข้อมูลที่ช่วยได้ ไม่ใช่คำเตือนหรือข้อผิดพลาด */
.track-status-note {
  margin: 14px 0 0;
  padding: 11px 14px;
  border-radius: 10px;
  border-left: 3px solid var(--info);
  background: var(--info-tint);
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--ink);
}

/* ข้อความติดต่อตามสถานะแยกเป็นสองบรรทัดเสมอ: บรรทัดแรกบอกว่าจะติดตามเรื่องอะไร
   บรรทัดที่สองคือเบอร์และแผนก -- แยกด้วย element ไม่ใช่ปล่อยให้ตัดบรรทัดตาม
   ความกว้างจอ เบอร์โทรจึงไม่มีทางถูกตัดคาบรรทัด */
.track-status-note-line {
  display: block;
}

.track-status-note-contact {
  margin-top: 2px;
  font-weight: 600;
}

/* ===== รหัสเชิญสำหรับสมัครสมาชิก (ผู้ดูแลระบบ) ===== */

.invite-status {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-soft);
}

.invite-code-box {
  margin: 0 0 14px;
  padding: 14px 16px;
  border-radius: 10px;
  background: var(--volt-50);
  border: 1px solid var(--volt-300);
  text-align: center;
}

/* .invite-code-box ไม่ได้ตั้ง display จึงยังไม่จำเป็น แต่ใส่ตามกฎ [hidden] ของ
   โปรเจกต์ เผื่อวันหลังมีคนเติม display ให้แล้วซ่อนไม่ลง (เคยพลาดมาห้าครั้ง) */
.invite-code-box[hidden] {
  display: none;
}

/* mono + user-select: all -- รหัสนี้ต้องอ่านออกเสียงหรือคัดลอกให้ถูกทุกตัว
   ฟอนต์ที่แยก 0/O และ 1/l ได้จึงสำคัญพอกับการเลือกได้ในคลิกเดียว */
.invite-code-value {
  margin: 0 0 6px;
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: 4px;
  color: var(--volt-700);
  user-select: all;
}

.invite-countdown {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--warning);
}

/* หน้าติดตามสถานะกว้างกว่าการ์ดเข้าสู่ระบบเล็กน้อย -- มีทั้งรายการผลลัพธ์
   บัญชีโอนเงิน และข้อความติดต่อ ซึ่งยาวกว่าฟอร์มสองช่องของหน้าเข้าสู่ระบบมาก */
.track-card {
  max-width: 480px;
}

/* เบอร์โทรและชื่อแผนกในวงเล็บห้ามถูกตัดกลางก้อน -- ที่จอแคบให้ขึ้นบรรทัดใหม่
   ทั้งก้อนแทน จะได้ไม่มีวงเล็บปิดห้อยอยู่บรรทัดเดียวโดด ๆ */
.track-status-note-part {
  white-space: nowrap;
}

/* ===== ปุ่มย้อนกลับมุมซ้ายบน (หน้าที่ไม่มีแถบบน) ===== */

/* หน้าแบบการ์ด (สมัครสมาชิก / บัญชีของฉัน / พิมพ์ QR) ไม่มี .topbar ให้วางปุ่ม
   จึงลอยไว้มุมเดียวกับปุ่มย้อนกลับของหน้าที่มีแถบบน เพื่อให้เป็นตำแหน่งเดียวกัน
   ทั้งระบบ ไม่ใช่สองความเคยชิน

   จุดยึดต่างกันสองแบบ และทั้งคู่ได้ผลเหมือนกัน: ใน index.html ปุ่มอยู่ใน
   .auth-view ซึ่งเป็น position: relative อยู่แล้ว จึงยึดกับ view นั้น
   ส่วนใน qr.html ปุ่มเป็นลูกของ body ตรง ๆ (.qr-page ไม่ได้เป็น relative)
   จึงยึดกับตัวหน้าเอง ซึ่งเป็นมุมเดียวกันพอดี */
.view-back-btn {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 2;
  background: var(--white);
  text-decoration: none;
}

/* ปุ่มนี้เป็นของหน้าจอเท่านั้น ไม่ใช่ของกระดาษที่ลูกค้าถือกลับ */
@media print {
  .view-back-btn {
    display: none;
  }
}

@media (max-width: 640px) {
  .view-back-btn {
    top: 12px;
    left: 12px;
  }
}

/* ปุ่มย้อนกลับเหนือหัวข้อฟอร์มคำร้อง -- ชิดซ้ายบนของพื้นที่เนื้อหา ตำแหน่งเดียว
   กับปุ่มย้อนกลับของหน้าอื่น ดึงเข้าซ้ายและขึ้นบนเล็กน้อยให้เกาะขอบ ไม่ลอยกลาง */
.form-back-btn {
  /* .btn เป็น inline-flex และ <h2> ที่ตามมาเป็น block ปุ่มจึงอยู่บรรทัดของตัวเอง
     ชิดซ้ายอยู่แล้ว ไม่ต้องใช้ align-self (หัวข้อนี้ไม่ใช่ flex container) */
  margin: -4px 0 10px -10px;
  padding: 6px 10px;
  font-size: 13.5px;
}
