/* ---- Self-hosted fonts ----
   Previously loaded from fonts.googleapis.com; moved local so the site works
   fully offline / behind restrictive school networks, and to fix a real bug:
   Google Fonts silently drops "IBM Plex Mono:wght@100..900" from a combined
   request (that variable weight range isn't published for this family — it
   only exists as static weights), so IBM Plex Mono never actually loaded on
   any page that requested it this way. */
@font-face {
  font-family: 'Noto Sans Thai';
  font-style: normal;
  font-weight: 100 900;
  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-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-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;
}
/* IBM Plex Sans Thai only ships static weights (no variable range — same
   "wght@100..900 silently dropped" issue as IBM Plex Mono above), so each
   weight/subset pair is its own @font-face like the Mono block below.
   Noto Sans Thai above is kept loaded as the fallback for weight 800
   (.text-headline-xl), which this family doesn't have a static file for. */
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-400-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-400-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: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-400-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;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-500-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-500-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: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-500-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;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-600-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-600-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: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-600-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;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-700-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-700-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: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-thai-700-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;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-500.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Material Symbols Outlined';
  font-style: normal;
  font-weight: 100 700;
  font-display: block;
  src: url('/assets/fonts/material-symbols-outlined.woff2') format('woff2');
}

*, ::before, ::after {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}

::backdrop {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}

/*
! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
*/

/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/

/* =====================================================================
   โทเคนสีกลางของทั้งระบบ

   คลาสยูทิลิตี้ทั้งหมดในไฟล์นี้ถูก build มาพร้อมค่าสีฝังอยู่ข้างใน และโปรเจค
   ไม่มีขั้นตอน build ตอนรัน จึงสร้างใหม่ไม่ได้ — แต่แก้ค่าข้างในได้ ทุกคลาส
   จึงถูกเปลี่ยนให้ชี้มาที่ตัวแปรชุดนี้ เปลี่ยนธีมทั้งเว็บได้จากบล็อกเดียว

   เก็บเป็นสามค่าคั่นช่องว่าง ไม่ใช่ #hex เพราะยูทิลิตี้เขียนว่า
   rgb(R G B / var(--tw-bg-opacity)) — รูปแบบนี้ทำให้ทับความโปร่งใสด้วย /
   ได้เหมือนเดิม ซึ่ง #hex ทำไม่ได้
   ===================================================================== */
:root {
  --c-primary         : 26 41 71;  /* #1a2947 กรมโมเดิร์น — แถบหัวเว็บ แบรนด์ (ขาวบนพื้นนี้ 13.6:1) */
  --c-primary-strong  : 36 54 92;  /* #24365c กรมอ่อนกว่าหนึ่งขั้น */
  --c-primary-soft    : 127 140 168;  /* #7f8ca8 ตัวหนังสือรองบนพื้นกรม */
  --c-primary-dim     : 157 184 232;  /* #9db8e8 ฟ้าอ่อน — ใช้บนพื้นเข้มและโหมดมืด */
  --c-accent          : 10 111 184;  /* #0a6fb8 ฟ้าสด — ลิงก์ โฟกัส การกระทำรอง (ขาว 5.3:1) */
  --c-accent-soft     : 220 238 251;  /* #dceefb ฟ้าจาง — พื้นป้าย ห้ามใช้เป็นสีตัวหนังสือ */
  --c-ink             : 33 36 48;  /* #212430 ตัวหนังสือหลัก (บนพื้นหลัง 14.6:1) */
  --c-ink-2           : 92 100 112;  /* #5c6470 ตัวหนังสือรอง (5.5:1) */
  --c-ink-3           : 102 110 124;  /* #666e7c ตัวหนังสือจาง/ไอคอน (4.7:1) */
  --c-line            : 231 227 219;  /* #e7e3db เส้นขอบ โทนอุ่น */
  --c-line-strong     : 213 208 198;  /* #d5d0c6 เส้นขอบที่ต้องเห็นชัด */
  --c-bg              : 250 249 246;  /* #faf9f6 พื้นหลังงาช้างอุ่น — สว่าง ไม่ขาวจ้า */
  --c-bg-2            : 244 242 236;  /* #f4f2ec พื้นชั้นรอง */
  --c-success         : 13 130 102;  /* #0d8266 มรกตสด — เช็คอิน/สำเร็จ (4.8:1) */
  --c-warning         : 180 83 9;  /* #b45309 อำพัน — ป้ายเตือน ใช้น้อยๆ (5.0:1) */
  --c-danger          : 192 52 43;  /* #c0342b แดง — ผิดพลาด/ลบ (5.6:1) */
  --c-dm-surface      : 24 32 50;  /* #182032 พื้นการ์ดโหมดมืด — กรมเข้ม ไม่ใช่ดำ */
  --c-dm-surface-alt  : 33 43 65;  /* #212b41 พื้นชั้นรองโหมดมืด */
  --c-card            : 255 255 255;  /* #ffffff การ์ดขาว ตัดกับพื้นงาช้าง */
  --c-dm-bg           : 15 22 38;  /* #0f1626 พื้นหลังโหมดมืด — กรมเข้ม */
  --c-dm-text-2       : 195 203 217;  /* #c3cbd9 ตัวหนังสือรองโหมดมืด */
  --c-dm-border       : 47 59 82;  /* #2f3b52 เส้นขอบโหมดมืด */
}

*,
::before,
::after {
  box-sizing: border-box;
  /* 1 */
  border-width: 0;
  /* 2 */
  border-style: solid;
  /* 2 */
  border-color: #e5e7eb;
  /* 2 */
}

::before,
::after {
  --tw-content: '';
}

/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/

html,
:host {
  line-height: 1.5;
  /* 1 */
  -webkit-text-size-adjust: 100%;
  /* 2 */
  -moz-tab-size: 4;
  /* 3 */
  -o-tab-size: 4;
     tab-size: 4;
  /* 3 */
  /* IBM Plex Sans Thai is the document-wide default so every element uses it
     even when it carries no explicit .font-* class (e.g. modal buttons, form
     inputs, dynamically-built markup). Previously this was the Tailwind
     system-ui default, so anything relying on inheritance could quietly fall
     back to the OS font. IBM/Noto lead; the system stack stays as the final
     fallback for glyphs neither Thai font provides (emoji, rare symbols). */
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  /* 4 */
  font-feature-settings: normal;
  /* 5 */
  font-variation-settings: normal;
  /* 6 */
  -webkit-tap-highlight-color: transparent;
  /* 7 */
}

/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/

body {
  margin: 0;
  /* 1 */
  line-height: inherit;
  /* 2 */
}

/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/

hr {
  height: 0;
  /* 1 */
  color: inherit;
  /* 2 */
  border-top-width: 1px;
  /* 3 */
}

/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/

abbr:where([title]) {
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
}

/*
Remove the default font size and weight for headings.
*/

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: inherit;
}

/*
Reset links to optimize for opt-in styling instead of opt-out.
*/

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

/*
Add the correct font weight in Edge and Safari.
*/

b,
strong {
  font-weight: bolder;
}

/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/

code,
kbd,
samp,
pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  /* 1 */
  font-feature-settings: normal;
  /* 2 */
  font-variation-settings: normal;
  /* 3 */
  font-size: 1em;
  /* 4 */
}

/*
Add the correct font size in all browsers.
*/

small {
  font-size: 80%;
}

/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/

table {
  text-indent: 0;
  /* 1 */
  border-color: inherit;
  /* 2 */
  border-collapse: collapse;
  /* 3 */
}

/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/

button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  /* 1 */
  font-feature-settings: inherit;
  /* 1 */
  font-variation-settings: inherit;
  /* 1 */
  font-size: 100%;
  /* 1 */
  font-weight: inherit;
  /* 1 */
  line-height: inherit;
  /* 1 */
  letter-spacing: inherit;
  /* 1 */
  color: inherit;
  /* 1 */
  margin: 0;
  /* 2 */
  padding: 0;
  /* 3 */
}

/*
Remove the inheritance of text transform in Edge and Firefox.
*/

button,
select {
  text-transform: none;
}

/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/

button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
  -webkit-appearance: button;
  /* 1 */
  background-color: transparent;
  /* 2 */
  background-image: none;
  /* 2 */
}

/*
Use the modern Firefox focus style for all focusable elements.
*/

:-moz-focusring {
  outline: auto;
}

/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/

:-moz-ui-invalid {
  box-shadow: none;
}

/*
Add the correct vertical alignment in Chrome and Firefox.
*/

progress {
  vertical-align: baseline;
}

/*
Correct the cursor style of increment and decrement buttons in Safari.
*/

::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
  height: auto;
}

/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/

[type='search'] {
  -webkit-appearance: textfield;
  /* 1 */
  outline-offset: -2px;
  /* 2 */
}

/*
Remove the inner padding in Chrome and Safari on macOS.
*/

::-webkit-search-decoration {
  -webkit-appearance: none;
}

/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/

::-webkit-file-upload-button {
  -webkit-appearance: button;
  /* 1 */
  font: inherit;
  /* 2 */
}

/*
Add the correct display in Chrome and Safari.
*/

summary {
  display: list-item;
}

/*
Removes the default spacing and border for appropriate elements.
*/

blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
  margin: 0;
}

fieldset {
  margin: 0;
  padding: 0;
}

legend {
  padding: 0;
}

ol,
ul,
menu {
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
Reset default styling for dialogs.
*/

dialog {
  padding: 0;
}

/*
Prevent resizing textareas horizontally by default.
*/

textarea {
  resize: vertical;
}

/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/

input::-moz-placeholder, textarea::-moz-placeholder {
  opacity: 1;
  /* 1 */
  color: #9ca3af;
  /* 2 */
}

input::placeholder,
textarea::placeholder {
  opacity: 1;
  /* 1 */
  color: #9ca3af;
  /* 2 */
}

/*
Set the default cursor for buttons.
*/

button,
[role="button"] {
  cursor: pointer;
}

/*
Make sure disabled buttons don't get the pointer cursor.
*/

:disabled {
  cursor: default;
}

/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
   This can trigger a poorly considered lint error in some tools but is included by design.
*/

img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
  display: block;
  /* 1 */
  vertical-align: middle;
  /* 2 */
}

/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/

img,
video {
  max-width: 100%;
  height: auto;
}

/* Make elements with the HTML hidden attribute stay hidden by default */

[hidden]:where(:not([hidden="until-found"])) {
  display: none;
}

.container {
  width: 100%;
}

@media (min-width: 640px) {
  .container {
    max-width: 640px;
  }
}

@media (min-width: 768px) {
  .container {
    max-width: 768px;
  }
}

@media (min-width: 1024px) {
  .container {
    max-width: 1024px;
  }
}

@media (min-width: 1280px) {
  .container {
    max-width: 1280px;
  }
}

@media (min-width: 1536px) {
  .container {
    max-width: 1536px;
  }
}

.pointer-events-none {
  pointer-events: none;
}

.pointer-events-auto {
  pointer-events: auto;
}

/* Not in the prebuilt Tailwind bundle (no live build step) — added for the
   reset-password temp code so a single click/tap selects the whole password
   for copying, alongside the explicit copy button. */
.select-all {
  -webkit-user-select: all;
     -moz-user-select: all;
          user-select: all;
}

.visible {
  visibility: visible;
}

.invisible {
  visibility: hidden;
}

.static {
  position: static;
}

.fixed {
  position: fixed;
}

.absolute {
  position: absolute;
}

.relative {
  position: relative;
}

.-inset-4 {
  inset: -1rem;
}

.inset-0 {
  inset: 0px;
}

.bottom-4 {
  bottom: 1rem;
}

.left-0 {
  left: 0px;
}

.left-1\/2 {
  left: 50%;
}

.left-3 {
  left: 0.75rem;
}

.left-4 {
  left: 1rem;
}

.right-0 {
  right: 0px;
}

.right-4 {
  right: 1rem;
}

.top-0 {
  top: 0px;
}

.top-1\/2 {
  top: 50%;
}

.top-16 {
  top: 4rem;
}

.top-3 {
  top: 0.75rem;
}

.top-4 {
  top: 1rem;
}

.z-10 {
  z-index: 10;
}

.z-20 {
  z-index: 20;
}

.z-40 {
  z-index: 40;
}

.z-50 {
  z-index: 50;
}

.z-\[200\] {
  z-index: 200;
}

.z-\[60\] {
  z-index: 60;
}

.z-\[80\] {
  z-index: 80;
}

.z-\[90\] {
  z-index: 90;
}

.z-\[95\] {
  z-index: 95;
}

.z-\[100\] {
  z-index: 100;
}

.m-auto {
  margin: auto;
}

.mx-auto {
  margin-left: auto;
  margin-right: auto;
}

.-mt-12 {
  margin-top: -3rem;
}

.-mt-2 {
  margin-top: -0.5rem;
}

.-mt-24 {
  margin-top: -6rem;
}

.-mt-6 {
  margin-top: -1.5rem;
}

.-mt-8 {
  margin-top: -2rem;
}

.mb-0\.5 {
  margin-bottom: 0.125rem;
}

.mb-1 {
  margin-bottom: 0.25rem;
}

.mb-10 {
  margin-bottom: 2.5rem;
}

.mb-12 {
  margin-bottom: 3rem;
}

.mb-16 {
  margin-bottom: 4rem;
}

.mb-2 {
  margin-bottom: 0.5rem;
}

.mb-3 {
  margin-bottom: 0.75rem;
}

.mb-4 {
  margin-bottom: 1rem;
}

.mb-6 {
  margin-bottom: 1.5rem;
}

.mb-8 {
  margin-bottom: 2rem;
}

.ml-1 {
  margin-left: 0.25rem;
}

.ml-auto {
  margin-left: auto;
}

.mt-10 {
  margin-top: 2.5rem;
}

.mt-12 {
  margin-top: 3rem;
}

.mt-2 {
  margin-top: 0.5rem;
}

.mt-4 {
  margin-top: 1rem;
}

.mt-5 {
  margin-top: 1.25rem;
}

.mt-8 {
  margin-top: 2rem;
}

.mt-auto {
  margin-top: auto;
}

.block {
  display: block;
}

.inline {
  display: inline;
}

.flex {
  display: flex;
}

.inline-flex {
  display: inline-flex;
}

.table {
  display: table;
}

.grid {
  display: grid;
}

.hidden {
  display: none;
}

.h-1\.5 {
  height: 0.375rem;
}

.h-10 {
  height: 2.5rem;
}

.h-11 {
  height: 2.75rem;
}

.h-12 {
  height: 3rem;
}

.h-14 {
  height: 3.5rem;
}

.h-16 {
  height: 4rem;
}

.h-2 {
  height: 0.5rem;
}

.h-2\.5 {
  height: 0.625rem;
}

.h-3 {
  height: 0.75rem;
}

.h-40 {
  height: 10rem;
}

.h-48 {
  height: 12rem;
}

.h-64 {
  height: 16rem;
}

.h-8 {
  height: 2rem;
}

.h-80 {
  height: 20rem;
}

.h-9 {
  height: 2.25rem;
}

.h-\[240px\] {
  height: 240px;
}

.h-\[46px\] {
  height: 46px;
}

.h-\[calc\(100vh-4rem\)\] {
  height: calc(100vh - 4rem);
}

/* Dead-center popup position + sizing for toast-container (see
   assets/js/toast.js). One plain-CSS class instead of stacking top-1/2 /
   left-1/2 / -translate-x-1/2 / -translate-y-1/2 / w-[calc(...)] / max-w-sm
   utilities — same reasoning as .heatmap-tooltip below: this project serves
   a prebuilt styles.css with no live Tailwind build step, so untested
   combinations of arbitrary-value classes are a trap. */
.toast-container {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 2rem);
  max-width: 24rem;
}

.h-screen {
  height: 100vh;
}

.max-h-\[80vh\] {
  max-height: 80vh;
}

.min-h-screen {
  min-height: 100vh;
}

.w-1\.5 {
  width: 0.375rem;
}

.w-10 {
  width: 2.5rem;
}

.w-11 {
  width: 2.75rem;
}

.w-12 {
  width: 3rem;
}

.w-2\.5 {
  width: 0.625rem;
}

.w-20 {
  width: 5rem;
}

.w-3 {
  width: 0.75rem;
}

.w-48 {
  width: 12rem;
}

.w-52 {
  width: 13rem;
}

.w-60 {
  width: 15rem;
}

.w-64 {
  width: 16rem;
}

.w-72 {
  width: 18rem;
}

.w-8 {
  width: 2rem;
}

.w-9 {
  width: 2.25rem;
}

.w-\[1px\] {
  width: 1px;
}

.w-\[calc\(100\%-2rem\)\] {
  width: calc(100% - 2rem);
}

.w-\[calc\(100\%-32px\)\] {
  width: calc(100% - 32px);
}

.w-fit {
  width: -moz-fit-content;
  width: fit-content;
}

.w-full {
  width: 100%;
}

.min-w-0 {
  min-width: 0px;
}

.min-w-\[260px\] {
  min-width: 260px;
}

.min-w-\[900px\] {
  min-width: 900px;
}

.max-w-2xl {
  max-width: 42rem;
}

.max-w-3xl {
  max-width: 48rem;
}

.max-w-4xl {
  max-width: 56rem;
}

.max-w-7xl {
  max-width: 80rem;
}

.max-w-\[140px\] {
  max-width: 140px;
}

.max-w-\[80vw\] {
  max-width: 80vw;
}

.max-w-lg {
  max-width: 32rem;
}

.max-w-md {
  max-width: 28rem;
}

.max-w-sm {
  max-width: 24rem;
}

.max-w-xl {
  max-width: 36rem;
}

.flex-1 {
  flex: 1 1 0%;
}

.flex-shrink-0 {
  flex-shrink: 0;
}

.flex-grow {
  flex-grow: 1;
}

.grow {
  flex-grow: 1;
}

.border-collapse {
  border-collapse: collapse;
}

.-translate-x-1\/2 {
  --tw-translate-x: -50%;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.-translate-y-1\/2 {
  --tw-translate-y: -50%;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.translate-y-16 {
  --tw-translate-y: 4rem;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.transform {
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

@keyframes ping {
  75%, 100% {
    transform: scale(2);
    opacity: 0;
  }
}

.animate-ping {
  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}

.cursor-help {
  cursor: help;
}

.cursor-not-allowed {
  cursor: not-allowed;
}

.cursor-pointer {
  cursor: pointer;
}

.grid-flow-col {
  grid-auto-flow: column;
}

.grid-cols-1 {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}

.grid-cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.grid-cols-\[auto_1fr\] {
  grid-template-columns: auto 1fr;
}

.grid-rows-7 {
  grid-template-rows: repeat(7, minmax(0, 1fr));
}

.flex-col {
  flex-direction: column;
}

.flex-wrap {
  flex-wrap: wrap;
}

.items-start {
  align-items: flex-start;
}

.items-end {
  align-items: flex-end;
}

.items-center {
  align-items: center;
}

.items-stretch {
  align-items: stretch;
}

.justify-end {
  justify-content: flex-end;
}

.justify-center {
  justify-content: center;
}

.justify-between {
  justify-content: space-between;
}

.gap-1 {
  gap: 0.25rem;
}

.gap-1\.5 {
  gap: 0.375rem;
}

.gap-2 {
  gap: 0.5rem;
}

.gap-3 {
  gap: 0.75rem;
}

.gap-4 {
  gap: 1rem;
}

.gap-6 {
  gap: 1.5rem;
}

.gap-8 {
  gap: 2rem;
}

.space-y-10 > :not([hidden]) ~ :not([hidden]) {
  --tw-space-y-reverse: 0;
  margin-top: calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));
  margin-bottom: calc(2.5rem * var(--tw-space-y-reverse));
}

.space-y-2 > :not([hidden]) ~ :not([hidden]) {
  --tw-space-y-reverse: 0;
  margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
  margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}

.space-y-3 > :not([hidden]) ~ :not([hidden]) {
  --tw-space-y-reverse: 0;
  margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
  margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
}

.space-y-6 > :not([hidden]) ~ :not([hidden]) {
  --tw-space-y-reverse: 0;
  margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
  margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}

.space-y-8 > :not([hidden]) ~ :not([hidden]) {
  --tw-space-y-reverse: 0;
  margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
  margin-bottom: calc(2rem * var(--tw-space-y-reverse));
}

.divide-y > :not([hidden]) ~ :not([hidden]) {
  --tw-divide-y-reverse: 0;
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
  border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}

.divide-outline-variant\/30 > :not([hidden]) ~ :not([hidden]) {
  border-color: rgb(var(--c-line-strong) / 0.3);
}

.self-end {
  align-self: flex-end;
}

.overflow-hidden {
  overflow: hidden;
}

.overflow-x-auto {
  overflow-x: auto;
}

.overflow-y-auto {
  overflow-y: auto;
}

.overflow-x-hidden {
  overflow-x: hidden;
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.whitespace-nowrap {
  white-space: nowrap;
}

.rounded-2xl {
  border-radius: 1rem;
}

.rounded-full {
  border-radius: 9999px;
}

.rounded-lg {
  border-radius: 0.5rem;
}

.rounded-md {
  border-radius: 0.375rem;
}

.rounded-sm {
  border-radius: 0.125rem;
}

.rounded-xl {
  border-radius: 0.75rem;
}

.rounded-t {
  border-top-left-radius: 0.25rem;
  border-top-right-radius: 0.25rem;
}

.border {
  border-width: 1px;
}

.border-b {
  border-bottom-width: 1px;
}

.border-l-4 {
  border-left-width: 4px;
}

.border-r {
  border-right-width: 1px;
}

.border-t {
  border-top-width: 1px;
}

.border-dashed {
  border-style: dashed;
}

.border-none {
  border-style: none;
}

.border-error\/50 {
  border-color: rgb(var(--c-danger) / 0.5);
}

.border-on-primary\/20 {
  border-color: rgb(255 255 255 / 0.2);
}

.border-outline {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-ink-2) / var(--tw-border-opacity, 1));
}

.border-outline-variant {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-line-strong) / var(--tw-border-opacity, 1));
}

.border-outline-variant\/30 {
  border-color: rgb(var(--c-line-strong) / 0.3);
}

.border-primary {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-primary) / var(--tw-border-opacity, 1));
}

.border-primary\/50 {
  border-color: rgb(var(--c-primary) / 0.5);
}

.border-status-success {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-success) / var(--tw-border-opacity, 1));
}

.border-status-success\/50 {
  border-color: rgb(var(--c-success) / 0.5);
}

.border-tertiary-container\/10 {
  border-color: rgb(var(--c-success) / 0.1);
}

.border-warning {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-warning) / var(--tw-border-opacity, 1));
}

.border-white\/10 {
  border-color: rgb(255 255 255 / 0.1);
}

.border-white\/20 {
  border-color: rgb(255 255 255 / 0.2);
}

.bg-accent-stats {
  --tw-bg-opacity: 1;
  background-color: rgb(139 95 163 / var(--tw-bg-opacity, 1));
}

.bg-accent-stats\/10 {
  background-color: rgb(139 95 163 / 0.1);
}

.bg-background {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-bg) / var(--tw-bg-opacity, 1));
}

.bg-black\/50 {
  background-color: rgb(0 0 0 / 0.5);
}

.bg-error {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-danger) / var(--tw-bg-opacity, 1));
}

/* Faint error tint — not in the prebuilt bundle (no live Tailwind build).
   Used for the confirm dialog's destructive-action icon so it matches the
   red confirm button and the app's error palette instead of the off-theme
   amber warning color. Mirrors the existing .bg-primary\/10 / .bg-warning\/10
   opacity utilities. */
.bg-error\/10 {
  background-color: rgb(var(--c-danger) / 0.1);
}

.bg-error-container {
  --tw-bg-opacity: 1;
  background-color: rgb(255 218 214 / var(--tw-bg-opacity, 1));
}

.bg-outline {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-ink-2) / var(--tw-bg-opacity, 1));
}

.bg-primary {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary) / var(--tw-bg-opacity, 1));
}

.bg-primary-container {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary-strong) / var(--tw-bg-opacity, 1));
}

.bg-primary-container\/40 {
  background-color: rgb(var(--c-primary-strong) / 0.4);
}

.bg-primary\/10 {
  background-color: rgb(var(--c-primary) / 0.1);
}

.bg-primary\/20 {
  background-color: rgb(var(--c-primary) / 0.2);
}

.bg-primary\/40 {
  background-color: rgb(var(--c-primary) / 0.4);
}

.bg-primary\/70 {
  background-color: rgb(var(--c-primary) / 0.7);
}

.bg-secondary {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-accent) / var(--tw-bg-opacity, 1));
}

.bg-secondary-container {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-accent-soft) / var(--tw-bg-opacity, 1));
}

.bg-secondary-container\/10 {
  background-color: rgb(var(--c-accent-soft) / 0.1);
}

.bg-secondary\/10 {
  background-color: rgb(var(--c-accent) / 0.1);
}

.bg-status-success {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-success) / var(--tw-bg-opacity, 1));
}

.bg-status-success\/10 {
  background-color: rgb(var(--c-success) / 0.1);
}

.bg-status-success\/20 {
  background-color: rgb(var(--c-success) / 0.2);
}

.bg-surface {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-bg) / var(--tw-bg-opacity, 1));
}

.bg-surface-container {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-bg-2) / var(--tw-bg-opacity, 1));
}

.bg-surface-container-high {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-line) / var(--tw-bg-opacity, 1));
}

.bg-surface-container-highest {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-line-strong) / var(--tw-bg-opacity, 1));
}

.bg-surface-container-highest\/20 {
  background-color: rgb(var(--c-line-strong) / 0.2);
}

.bg-surface-container-low {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-bg) / var(--tw-bg-opacity, 1));
}

.bg-surface-white {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-card) / var(--tw-bg-opacity, 1));
}

.bg-surface-white\/90 {
  background-color: rgb(var(--c-card) / 0.9);
}

.bg-tertiary-fixed {
  --tw-bg-opacity: 1;
  background-color: rgb(167 243 208 / var(--tw-bg-opacity, 1));
}

.bg-transparent {
  background-color: transparent;
}

.bg-warning {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-warning) / var(--tw-bg-opacity, 1));
}

.bg-warning\/10 {
  background-color: rgb(var(--c-warning) / 0.1);
}

.bg-white {
  --tw-bg-opacity: 1;
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
}

.bg-white\/10 {
  background-color: rgb(255 255 255 / 0.1);
}

.bg-white\/20 {
  background-color: rgb(255 255 255 / 0.2);
}

.bg-white\/90 {
  background-color: rgb(255 255 255 / 0.9);
}

.bg-gradient-to-br {
  background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}

.from-primary {
  --tw-gradient-from: #1a2947 var(--tw-gradient-from-position);
  --tw-gradient-to: rgb(var(--c-primary) / 0) var(--tw-gradient-to-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}

.from-warning {
  --tw-gradient-from: #d97706 var(--tw-gradient-from-position);
  --tw-gradient-to: rgb(var(--c-warning) / 0) var(--tw-gradient-to-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}

.to-primary-container {
  --tw-gradient-to: #24365c var(--tw-gradient-to-position);
}

.to-warning\/80 {
  --tw-gradient-to: rgb(var(--c-warning) / 0.8) var(--tw-gradient-to-position);
}

.p-0 {
  padding: 0px;
}

.p-1 {
  padding: 0.25rem;
}

.p-2 {
  padding: 0.5rem;
}

.p-4 {
  padding: 1rem;
}

.p-6 {
  padding: 1.5rem;
}

.p-8 {
  padding: 2rem;
}

.px-1 {
  padding-left: 0.25rem;
  padding-right: 0.25rem;
}

.px-10 {
  padding-left: 2.5rem;
  padding-right: 2.5rem;
}

.px-2 {
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

.px-2\.5 {
  padding-left: 0.625rem;
  padding-right: 0.625rem;
}

.px-3 {
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

.px-4 {
  padding-left: 1rem;
  padding-right: 1rem;
}

.px-6 {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

.px-gutter {
  padding-left: 24px;
  padding-right: 24px;
}

.py-1 {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

.py-10 {
  padding-top: 2.5rem;
  padding-bottom: 2.5rem;
}

.py-12 {
  padding-top: 3rem;
  padding-bottom: 3rem;
}

.py-2 {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.py-2\.5 {
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
}

.py-3 {
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
}

.py-4 {
  padding-top: 1rem;
  padding-bottom: 1rem;
}

.py-6 {
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}

.py-8 {
  padding-top: 2rem;
  padding-bottom: 2rem;
}

.pb-12 {
  padding-bottom: 3rem;
}

.pb-16 {
  padding-bottom: 4rem;
}

.pb-20 {
  padding-bottom: 5rem;
}

.pb-24 {
  padding-bottom: 6rem;
}

.pb-32 {
  padding-bottom: 8rem;
}

.pb-4 {
  padding-bottom: 1rem;
}

.pb-6 {
  padding-bottom: 1.5rem;
}

.pb-8 {
  padding-bottom: 2rem;
}

.pl-1 {
  padding-left: 0.25rem;
}

.pl-10 {
  padding-left: 2.5rem;
}

.pl-12 {
  padding-left: 3rem;
}

.pr-2 {
  padding-right: 0.5rem;
}

.pr-4 {
  padding-right: 1rem;
}

.pt-0\.5 {
  padding-top: 0.125rem;
}

.pt-12 {
  padding-top: 3rem;
}

.pt-16 {
  padding-top: 4rem;
}

.pt-20 {
  padding-top: 5rem;
}

.pt-24 {
  padding-top: 6rem;
}

.pt-28 {
  padding-top: 7rem;
}

.pt-4 {
  padding-top: 1rem;
}

.pt-6 {
  padding-top: 1.5rem;
}

.pt-8 {
  padding-top: 2rem;
}

.text-left {
  text-align: left;
}

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

.text-right {
  text-align: right;
}

.font-body-lg {
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', sans-serif;
}

.font-body-md {
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', sans-serif;
}

.font-headline-lg {
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', sans-serif;
}

.font-headline-md {
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', sans-serif;
}

.font-headline-xl {
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', sans-serif;
}

.font-label-caps {
  /* IBM Plex Sans Thai มาก่อน เพราะคลาสนี้ใช้กับป้ายภาษาไทยเป็นหลัก
     IBM Plex Mono ไม่มีอักขระไทย ถ้าวางไว้หน้าสุดตัวไทยจะหลุดไปใช้ฟอนต์
     ปริยายของเครื่อง (Tahoma บน Windows) ซึ่งเป็นคนละตระกูลกับทั้งเว็บ
     คงตัวเลข/ตัวละตินไว้ที่ Mono เป็นทางเลือกถัดไป */
  font-family: 'IBM Plex Sans Thai', 'IBM Plex Mono', monospace;
}

.font-label-code {
  /* ใช้กับรหัสนักศึกษาและตัวเลข ซึ่ง IBM Plex Mono ครอบคลุมอยู่แล้ว
     ต่อท้ายด้วย IBM Plex Sans Thai เผื่อมีตัวไทยปนมา จะได้ไม่ตกไป Tahoma */
  font-family: 'IBM Plex Mono', 'IBM Plex Sans Thai', monospace;
}

.text-2xl {
  font-size: 1.5rem;
  line-height: 2rem;
}

.text-3xl {
  font-size: 1.875rem;
  line-height: 2.25rem;
}

.text-4xl {
  font-size: 2.25rem;
  line-height: 2.5rem;
}

.text-6xl {
  font-size: 3.75rem;
  line-height: 1;
}

.text-9xl {
  font-size: 8rem;
  line-height: 1;
}

.text-\[10px\] {
  font-size: 10px;
}

.text-\[16px\] {
  font-size: 16px;
}

.text-\[34px\] {
  font-size: 34px;
}

.text-base {
  font-size: 1rem;
  line-height: 1.5rem;
}

.text-body-lg {
  font-size: 18px;
  line-height: 1.6;
  font-weight: 400;
}

.text-body-md {
  font-size: 16px;
  line-height: 1.5;
  font-weight: 400;
}

.text-headline-lg {
  font-size: 32px;
  line-height: 1.3;
  font-weight: 700;
}

.text-headline-md {
  font-size: 24px;
  line-height: 1.4;
  font-weight: 700;
}

.text-headline-xl {
  font-size: 48px;
  line-height: 1.2;
  /* 700, not 800 — IBM Plex Sans Thai has no 800/ExtraBold static weight
     (see the self-hosted-fonts note at the top of this file), so weight 800
     fell through the font-family stack to the Noto Sans Thai fallback for
     every headline-xl element. 700 is the heaviest weight IBM Plex Sans Thai
     actually ships, so this keeps IBM Plex Sans Thai rendering everywhere. */
  font-weight: 700;
}
/* 48px is a desktop hero size. Every page title in the app carries this
   class, so on a 390px phone the four admin headings ("ภาพรวมการใช้งาน
   ห้องสมุด", "ประวัติการเช็คชื่อทั้งหมด", …) each took two full lines and
   most of the first screen before any content. Thai sets no smaller than
   Latin at the same px, and these strings are long, so the drop is steep.

   Placed ABOVE the signup rules below deliberately: that page tuned its own
   title to 32px to hold one line inside a fixed-height hero, and being later
   in the file it still wins at the width it cares about.
   --------------------------------------------------------------------- */
@media (max-width: 640px) {
  .text-headline-xl { font-size: 28px; line-height: 1.25; }
}
/* "สร้างบัญชีของคุณ" at 48px needs ~369px but a phone only gives it ~310-340px,
   so it wraps to 2 lines — inside signup.php's hero section (fixed h-80,
   overflow:hidden) that pushes the paragraph below it past the section's
   bottom edge and clips it. Shrinking just this heading on narrow screens
   keeps it on one line and everything within the fixed-height hero. */
@media (max-width: 480px) { .signup-hero-title { font-size: 32px; } }
/* .signup-hero-section is a fixed h-80 (320px), sized for the original
   2-line heading. Now that .signup-hero-title fits one line on mobile, the
   same fixed height leaves a large empty gap below the text before the
   form card. Shrinking the height alone isn't enough though: the form card
   overlaps the bottom 96px of this section (-mt-24 on the section after
   it), and `flex items-center` pushes content toward that overlapped zone
   as the section shrinks — the card ends up covering the paragraph's
   second line. Pinning content near the top with padding instead of
   centering it keeps it clear of the overlap regardless of height. */
@media (max-width: 480px) {
  .signup-hero-section { height: 240px; align-items: flex-start; padding-top: 28px; }
}

.text-label-caps {
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 0.05em;
  font-weight: 600;
}

.text-label-code {
  font-size: 14px;
  line-height: 1.2;
  font-weight: 500;
}

.text-lg {
  font-size: 1.125rem;
  line-height: 1.75rem;
}

.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem;
}

.text-xl {
  font-size: 1.25rem;
  line-height: 1.75rem;
}

.text-xs {
  font-size: 0.75rem;
  line-height: 1rem;
}

.font-bold {
  font-weight: 700;
}

.font-medium {
  font-weight: 500;
}

.uppercase {
  text-transform: uppercase;
}

.italic {
  font-style: italic;
}

.leading-none {
  line-height: 1;
}

.leading-snug {
  line-height: 1.375;
}

.leading-tight {
  line-height: 1.25;
}

.tracking-wider {
  letter-spacing: 0.05em;
}

.tracking-widest {
  letter-spacing: 0.1em;
}

.text-accent-stats {
  --tw-text-opacity: 1;
  color: rgb(139 95 163 / var(--tw-text-opacity, 1));
}

.text-error {
  --tw-text-opacity: 1;
  color: rgb(var(--c-danger) / var(--tw-text-opacity, 1));
}

.text-on-background {
  --tw-text-opacity: 1;
  color: rgb(var(--c-ink) / var(--tw-text-opacity, 1));
}

.text-on-error {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}

.text-on-error-container {
  --tw-text-opacity: 1;
  color: rgb(147 0 10 / var(--tw-text-opacity, 1));
}

.text-on-primary {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}

.text-on-primary-container {
  --tw-text-opacity: 1;
  color: rgb(var(--c-accent-soft) / var(--tw-text-opacity, 1));
}

.text-on-primary\/70 {
  color: rgb(255 255 255 / 0.7);
}

.text-on-primary\/80 {
  color: rgb(255 255 255 / 0.8);
}

.text-on-secondary {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}

.text-on-secondary-container {
  --tw-text-opacity: 1;
  color: rgb(var(--c-primary) / var(--tw-text-opacity, 1));
}

.text-on-surface {
  --tw-text-opacity: 1;
  color: rgb(var(--c-ink) / var(--tw-text-opacity, 1));
}

.text-on-surface-variant {
  --tw-text-opacity: 1;
  color: rgb(71 85 105 / var(--tw-text-opacity, 1));
}

.text-on-tertiary-fixed {
  --tw-text-opacity: 1;
  color: rgb(6 78 59 / var(--tw-text-opacity, 1));
}

.text-on-tertiary-fixed-variant {
  --tw-text-opacity: 1;
  color: rgb(var(--c-success) / var(--tw-text-opacity, 1));
}

.text-outline {
  --tw-text-opacity: 1;
  color: rgb(var(--c-ink-2) / var(--tw-text-opacity, 1));
}

.text-primary {
  --tw-text-opacity: 1;
  color: rgb(var(--c-primary) / var(--tw-text-opacity, 1));
}

.text-secondary {
  --tw-text-opacity: 1;
  color: rgb(var(--c-accent) / var(--tw-text-opacity, 1));
}

.text-status-success {
  --tw-text-opacity: 1;
  color: rgb(var(--c-success) / var(--tw-text-opacity, 1));
}

.text-tertiary-container {
  --tw-text-opacity: 1;
  color: rgb(var(--c-success) / var(--tw-text-opacity, 1));
}

.text-text-primary {
  --tw-text-opacity: 1;
  color: rgb(var(--c-ink) / var(--tw-text-opacity, 1));
}

.text-text-secondary {
  --tw-text-opacity: 1;
  color: rgb(71 85 105 / var(--tw-text-opacity, 1));
}

.text-warning {
  --tw-text-opacity: 1;
  color: rgb(var(--c-warning) / var(--tw-text-opacity, 1));
}

.text-white {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}

.text-white\/60 {
  color: rgb(255 255 255 / 0.6);
}

.opacity-20 {
  opacity: 0.2;
}

.opacity-5 {
  opacity: 0.05;
}

.opacity-60 {
  opacity: 0.6;
}

.opacity-70 {
  opacity: 0.7;
}

.opacity-80 {
  opacity: 0.8;
}

.shadow-2xl {
  --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
  --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-\[0px_8px_24px_rgba\(37\2c 99\2c 235\2c 0\.3\)\] {
  --tw-shadow: 0px 8px 24px rgba(37,99,235,0.3);
  --tw-shadow-colored: 0px 8px 24px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-lg {
  --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-md {
  --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-sm {
  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-xl {
  --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.outline-none {
  outline: 2px solid transparent;
  outline-offset: 2px;
}

.ring-2 {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.ring-primary\/30 {
  --tw-ring-color: rgb(var(--c-primary) / 0.3);
}

.filter {
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.backdrop-blur {
  --tw-backdrop-blur: blur(8px);
  -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
  backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}

.backdrop-blur-md {
  --tw-backdrop-blur: blur(12px);
  -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
  backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}

.backdrop-blur-sm {
  --tw-backdrop-blur: blur(4px);
  -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
  backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}

.transition-all {
  transition-property: all;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.transition-colors {
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.transition-opacity {
  transition-property: opacity;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.transition-transform {
  transition-property: transform;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.duration-200 {
  transition-duration: 200ms;
}

.duration-300 {
  transition-duration: 300ms;
}

.\[color-scheme\:light\] {
  color-scheme: light;
}

.hover\:scale-105:hover {
  --tw-scale-x: 1.05;
  --tw-scale-y: 1.05;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.hover\:scale-\[1\.02\]:hover {
  --tw-scale-x: 1.02;
  --tw-scale-y: 1.02;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.hover\:bg-error-container\/30:hover {
  background-color: rgb(255 218 214 / 0.3);
}

.hover\:bg-primary-container:hover {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary-strong) / var(--tw-bg-opacity, 1));
}

.hover\:bg-primary\/40:hover {
  background-color: rgb(var(--c-primary) / 0.4);
}

.hover\:bg-surface-container-high:hover {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-line) / var(--tw-bg-opacity, 1));
}

.hover\:bg-surface-container-highest:hover {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-line-strong) / var(--tw-bg-opacity, 1));
}

.hover\:bg-surface-container-low:hover {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-bg) / var(--tw-bg-opacity, 1));
}

.hover\:bg-surface-container-low\/50:hover {
  background-color: rgb(var(--c-bg) / 0.5);
}

.hover\:bg-white\/10:hover {
  background-color: rgb(255 255 255 / 0.1);
}

.hover\:bg-white\/20:hover {
  background-color: rgb(255 255 255 / 0.2);
}

.hover\:text-on-primary:hover {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}

.hover\:underline:hover {
  text-decoration-line: underline;
}

.hover\:opacity-100:hover {
  opacity: 1;
}

.hover\:opacity-90:hover {
  opacity: 0.9;
}

.hover\:ring-2:hover {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.hover\:ring-primary\/50:hover {
  --tw-ring-color: rgb(var(--c-primary) / 0.5);
}

.hover\:brightness-110:hover {
  --tw-brightness: brightness(1.1);
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.hover\:brightness-95:hover {
  --tw-brightness: brightness(.95);
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.focus\:border-primary:focus {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-primary) / var(--tw-border-opacity, 1));
}

.focus\:border-transparent:focus {
  border-color: transparent;
}

.focus\:outline-none:focus {
  outline: 2px solid transparent;
  outline-offset: 2px;
}

.focus\:ring-0:focus {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.focus\:ring-1:focus {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.focus\:ring-2:focus {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.focus\:ring-primary:focus {
  --tw-ring-opacity: 1;
  --tw-ring-color: rgb(var(--c-primary) / var(--tw-ring-opacity, 1));
}

.active\:scale-95:active {
  --tw-scale-x: .95;
  --tw-scale-y: .95;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.active\:scale-\[0\.98\]:active {
  --tw-scale-x: 0.98;
  --tw-scale-y: 0.98;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.disabled\:cursor-not-allowed:disabled {
  cursor: not-allowed;
}

.disabled\:opacity-40:disabled {
  opacity: 0.4;
}

.disabled\:opacity-60:disabled {
  opacity: 0.6;
}

.group:hover .group-hover\:translate-x-1 {
  --tw-translate-x: 0.25rem;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.group:hover .group-hover\:rotate-12 {
  --tw-rotate: 12deg;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.dark\:divide-dm-border:is(.dark *) > :not([hidden]) ~ :not([hidden]) {
  --tw-divide-opacity: 1;
  border-color: rgb(var(--c-primary-soft) / var(--tw-divide-opacity, 1));
}

.dark\:border:is(.dark *) {
  border-width: 1px;
}

.dark\:border-dm-border:is(.dark *) {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-primary-soft) / var(--tw-border-opacity, 1));
}

.dark\:border-primary-fixed-dim:is(.dark *) {
  --tw-border-opacity: 1;
  border-color: rgb(var(--c-primary-dim) / var(--tw-border-opacity, 1));
}

.dark\:bg-accent-stats\/25:is(.dark *) {
  background-color: rgb(139 95 163 / 0.25);
}

.dark\:bg-dm-bg:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-dm-bg) / var(--tw-bg-opacity, 1));
}

.dark\:bg-dm-border:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary-soft) / var(--tw-bg-opacity, 1));
}

.dark\:bg-dm-surface:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-dm-surface) / var(--tw-bg-opacity, 1));
}

.dark\:bg-dm-surface-alt:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-dm-surface-alt) / var(--tw-bg-opacity, 1));
}

.dark\:bg-dm-surface\/90:is(.dark *) {
  background-color: rgb(var(--c-dm-surface) / 0.9);
}

.dark\:bg-primary-fixed-dim:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary-dim) / var(--tw-bg-opacity, 1));
}

.dark\:bg-primary-fixed-dim\/15:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.15);
}

.dark\:bg-primary-fixed-dim\/35:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.35);
}

.dark\:bg-primary-fixed-dim\/40:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.4);
}

.dark\:bg-primary-fixed-dim\/50:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.5);
}

.dark\:bg-primary-fixed-dim\/70:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.7);
}

.dark\:bg-secondary-fixed-dim\/15:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.15);
}

.dark\:text-dm-text-secondary:is(.dark *) {
  --tw-text-opacity: 1;
  color: rgb(var(--c-dm-text-2) / var(--tw-text-opacity, 1));
}

.dark\:text-inverse-on-surface:is(.dark *) {
  --tw-text-opacity: 1;
  color: rgb(var(--c-bg-2) / var(--tw-text-opacity, 1));
}

.dark\:text-primary-fixed-dim:is(.dark *) {
  --tw-text-opacity: 1;
  color: rgb(var(--c-primary-dim) / var(--tw-text-opacity, 1));
}

.dark\:text-secondary-fixed-dim:is(.dark *) {
  --tw-text-opacity: 1;
  color: rgb(var(--c-primary-dim) / var(--tw-text-opacity, 1));
}

.dark\:text-tertiary-fixed-dim:is(.dark *) {
  --tw-text-opacity: 1;
  color: rgb(110 231 183 / var(--tw-text-opacity, 1));
}

.dark\:opacity-100:is(.dark *) {
  opacity: 1;
}

.dark\:\[color-scheme\:dark\]:is(.dark *) {
  color-scheme: dark;
}

.dark\:hover\:bg-dm-bg:hover:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-dm-bg) / var(--tw-bg-opacity, 1));
}

.dark\:hover\:bg-dm-border:hover:is(.dark *) {
  --tw-bg-opacity: 1;
  background-color: rgb(var(--c-primary-soft) / var(--tw-bg-opacity, 1));
}

.dark\:hover\:bg-primary-fixed-dim\/60:hover:is(.dark *) {
  background-color: rgb(var(--c-primary-dim) / 0.6);
}

@media (min-width: 640px) {
  .sm\:w-40 {
    width: 10rem;
  }

  .sm\:shrink-0 {
    flex-shrink: 0;
  }

  .sm\:grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .sm\:flex-row {
    flex-direction: row;
  }

  .sm\:items-center {
    align-items: center;
  }

  .sm\:gap-3 {
    gap: 0.75rem;
  }
}

@media (min-width: 768px) {
  .md\:-mt-10 {
    margin-top: -2.5rem;
  }

  .md\:-mt-16 {
    margin-top: -4rem;
  }

  .md\:ml-64 {
    margin-left: 16rem;
  }

  .md\:block {
    display: block;
  }

  .md\:flex {
    display: flex;
  }

  .md\:hidden {
    display: none;
  }

  .md\:h-\[240px\] {
    height: 240px;
  }

  .md\:grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .md\:grid-cols-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .md\:grid-cols-\[140px_1fr_1fr\] {
    grid-template-columns: 140px 1fr 1fr;
  }

  .md\:flex-row {
    flex-direction: row;
  }

  .md\:items-start {
    align-items: flex-start;
  }

  .md\:items-end {
    align-items: flex-end;
  }

  .md\:items-center {
    align-items: center;
  }

  .md\:p-12 {
    padding: 3rem;
  }

  .md\:py-0 {
    padding-top: 0px;
    padding-bottom: 0px;
  }

  .md\:pb-20 {
    padding-bottom: 5rem;
  }

  .md\:pt-16 {
    padding-top: 4rem;
  }

  .md\:text-left {
    text-align: left;
  }
}

@media (min-width: 1024px) {
  .lg\:col-span-2 {
    grid-column: span 2 / span 2;
  }

  .lg\:col-span-3 {
    grid-column: span 3 / span 3;
  }

  .lg\:ml-64 {
    margin-left: 16rem;
  }

  .lg\:flex {
    display: flex;
  }

  .lg\:hidden {
    display: none;
  }

  .lg\:w-auto {
    width: auto;
  }

  .lg\:flex-1 {
    flex: 1 1 0%;
  }

  .lg\:grid-cols-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .lg\:grid-cols-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .lg\:flex-row {
    flex-direction: row;
  }

  .lg\:items-end {
    align-items: flex-end;
  }

  .lg\:pt-16 {
    padding-top: 4rem;
  }
}
/* ---- Custom, non-utility CSS below — ported verbatim from
   frontend-react/src/index.css (minus @tailwind directives and the one
   @apply on body, replaced with its resolved literal colors) plus
   frontend-react/src/components/LibBanner.css. ---- */

html.dark,
html.light {
  color-scheme: light dark;
}

body {
  background-color: rgb(var(--c-bg));
  color: rgb(var(--c-ink));
  transition: color 0.2s, background-color 0.2s;
}
html.dark body {
  background-color: #17140f;
  color: #f2ede4;
}

.material-symbols-outlined {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: 24px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
/* This static bundle doesn't preserve Tailwind's utilities-last @layer
   ordering, so .hidden (display:none, defined earlier) loses the cascade
   to this rule's display:inline-block on any icon carrying both classes
   (e.g. the theme-menu checkmarks) — equal specificity, later source wins.
   Two classes beats .material-symbols-outlined's one regardless of source
   order, so this fixes it without an !important that would also block
   .hidden from ever being overridden by md:flex/md:block elsewhere. */
.tm-check.hidden { display: none; }

.book-spine-pattern {
  background-image: repeating-linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.05) 0px,
    rgba(255, 255, 255, 0.05) 1px,
    transparent 1px,
    transparent 12px
  );
}

.hero-gradient {
  background: linear-gradient(135deg, #1a2947 0%, #212430 100%);
}

.stamp-shadow {
  box-shadow: 0px 8px 24px rgba(30, 58, 138, 0.2);
}

.transition-all-200 {
  transition: all 0.2s ease-in-out;
}

/* Per-page hero background patterns — distinct names since each ported page
   originally had its own self-contained .hero-pattern with different CSS. */
.signup-hero-pattern {
  background-image: linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 24px 100%;
}

.hero-pattern {
  background-image: linear-gradient(to bottom, #24365c 0%, #16223b 100%);
  position: relative;
  overflow: hidden;
}
.hero-pattern::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0px, rgba(255, 255, 255, 0.05) 1px, transparent 1px, transparent 40px);
  mask-image: linear-gradient(to bottom, black, transparent);
}

/* Collapsible desktop admin sidebar. The toggle button (partials/header.php,
   admin variant) flips `.sidebar-collapsed` on <html>; the state is persisted
   and pre-applied before paint in partials/admin-sidebar.php so nothing
   animates on load. Only affects md+ where the fixed sidebar exists. */
.admin-sidebar-desktop { transition: transform 0.25s ease; }
@media (min-width: 768px) {
  .admin-main { transition: margin-left 0.25s ease; }
  html.sidebar-collapsed .admin-sidebar-desktop { transform: translateX(-100%); }
  html.sidebar-collapsed .admin-main { margin-left: 0 !important; }
}

/* แถบเมนูฝั่งนักศึกษาใช้กลไกเดียวกัน ต่างที่เบรกพอยต์: ของนักศึกษาโผล่ที่
   1024px ไม่ใช่ 768px ถ้าใช้ 768px ร่วมกัน ช่วง 768-1023px จะไปดึง margin
   ของเนื้อหาออกทั้งที่แถบเมนูยังซ่อนอยู่และไม่มี margin ให้ดึงอยู่แล้ว */
.student-sidebar-desktop { transition: transform 0.25s ease; }
@media (min-width: 1024px) {
  .student-main { transition: margin-left 0.25s ease; }
  html.sidebar-collapsed .student-sidebar-desktop { transform: translateX(-100%); }
  html.sidebar-collapsed .student-main { margin-left: 0 !important; }
}

@media (prefers-reduced-motion: reduce) {
  .admin-sidebar-desktop, .admin-main,
  .student-sidebar-desktop, .student-main { transition: none; }
}

.admin-hero-pattern {
  background-image: linear-gradient(180deg, #2b4070 0%, #17243d 100%);
  position: relative;
  overflow: hidden;
}
.admin-hero-pattern::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0px, rgba(255, 255, 255, 0.05) 1px, transparent 1px, transparent 24px);
  opacity: 0.4;
}

.heatmap-cell {
  aspect-ratio: 1/1;
  border-radius: 2px;
}

/* Positioned in JS via left/top (set from the hovered cell's own
   getBoundingClientRect, relative to #heatmap-grid's positioned parent) —
   these two properties only center/lift it on top of that point. Plain CSS
   rather than Tailwind's translate-x-1/2 / arbitrary-value utilities: this
   project serves a prebuilt styles.css with no live Tailwind build step, so
   classes unused elsewhere in the codebase are silently missing from it. */
.heatmap-tooltip {
  position: absolute;
  transform: translate(-50%, calc(-100% - 8px));
  /* py-1.5 (0.375rem) — missing from the compiled bundle like the other
     classes noted below; px-3 was already present. */
  padding-top: 6px;
  padding-bottom: 6px;
}

.heatmap-cell-value {
  font-size: 9px;
}

/* mt-0.5 (0.125rem) missing the same way. */
#day-detail-subtitle {
  margin-top: 2px;
}

/* p-3 (0.75rem) missing on .day-detail-dept and the student-list rows in
   the day-detail modal (admin-dashboard.js); ml-3 (0.75rem) missing on the
   count badge — same silently-absent-utility pattern. */
.day-detail-dept,
.student-row {
  padding: 12px;
}
.student-row-count {
  margin-left: 12px;
}

/* Arbitrary-value shadow-[...] utility missing the same way (profile.php's
   change-password submit button). */
.profile-submit-shadow {
  box-shadow: 0px 8px 24px rgba(37, 99, 235, 0.3);
}

.vertical-pattern {
  background-image: linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 12px 100%;
}

.stamp-pulse {
  animation: pulse-ring 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-ring {
  0% {
    transform: scale(0.95);
    box-shadow: 0 0 0 0 rgba(30, 58, 138, 0.7);
  }
  70% {
    transform: scale(1);
    box-shadow: 0 0 0 15px rgba(30, 58, 138, 0);
  }
  100% {
    transform: scale(0.95);
    box-shadow: 0 0 0 0 rgba(30, 58, 138, 0);
  }
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.rise-in {
  animation: rise-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.rise-in-group > * {
  animation: rise-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.rise-in-group > *:nth-child(1) { animation-delay: 0.03s; }
.rise-in-group > *:nth-child(2) { animation-delay: 0.09s; }
.rise-in-group > *:nth-child(3) { animation-delay: 0.15s; }
.rise-in-group > *:nth-child(4) { animation-delay: 0.21s; }
.rise-in-group > *:nth-child(5) { animation-delay: 0.27s; }
.rise-in-group > *:nth-child(6) { animation-delay: 0.33s; }
.rise-in-group > *:nth-child(7) { animation-delay: 0.39s; }
.rise-in-group > *:nth-child(8) { animation-delay: 0.45s; }

@keyframes scale-in {
  from {
    opacity: 0;
    transform: scale(0.92);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
.scale-in {
  animation: scale-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.lift-on-hover {
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.25s ease;
}
.lift-on-hover:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 32px -12px rgba(15, 23, 42, 0.25);
}
html.dark .lift-on-hover:hover {
  box-shadow: 0 16px 32px -12px rgba(0, 0, 0, 0.5);
}

/* shadow-card/shadow-stamp: named box-shadow utilities this bundle's build
   never generated (nothing else in the codebase happened to reference them
   before now), so every card using them — the 3 KPI tiles + the "currently
   inside" tile on admin-dashboard.php, plus the profile and signup cards —
   was rendering with no resting shadow at all. */
.shadow-card {
  box-shadow: 0 4px 16px -4px rgba(15, 23, 42, 0.12);
}
html.dark .shadow-card {
  box-shadow: 0 4px 16px -4px rgba(0, 0, 0, 0.35);
}
.shadow-stamp {
  box-shadow: 0 6px 20px -4px rgba(30, 58, 138, 0.35);
}
html.dark .shadow-stamp {
  box-shadow: 0 6px 20px -4px rgba(0, 0, 0, 0.45);
}

/* dept-bar-entry (admin-dashboard.js department list): the row is a real
   button (click drills into that department's students) but nothing
   signaled that on hover/keyboard-focus — group-hover:underline was
   missing the same way. */
.dept-bar-entry:hover span:first-child,
.dept-bar-entry:focus-visible span:first-child {
  text-decoration: underline;
}

.underline-grow {
  position: relative;
}
.underline-grow::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.underline-grow:hover::after,
.underline-grow.is-active::after {
  transform: scaleX(1);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateX(24px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateX(0) scale(1);
  }
}
.toast-in {
  animation: toast-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.skeleton {
  background: linear-gradient(90deg, rgba(148, 163, 184, 0.15) 25%, rgba(148, 163, 184, 0.3) 37%, rgba(148, 163, 184, 0.15) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.6s ease-in-out infinite;
  border-radius: 0.5rem;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: rgb(var(--c-ink-3) / 0.5);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background: rgb(var(--c-ink-2) / 0.7);
  background-clip: content-box;
}

@media (prefers-reduced-motion: reduce) {
  .rise-in,
  .rise-in-group > *,
  .scale-in,
  .lift-on-hover,
  .stamp-pulse {
    animation: none !important;
    transition: none !important;
  }
}

/* ---- LibBanner.css (ported verbatim) ---- */
.lib-banner, .lib-banner * { box-sizing: border-box; margin: 0; padding: 0; }
.lib-banner {
  position: relative; width: 100%; overflow: hidden;
  font-family: 'IBM Plex Sans Thai', 'Noto Sans Thai', 'Segoe UI', sans-serif;
  background:
    radial-gradient(circle at 82% 20%, rgba(255,255,255,0.08), transparent 45%),
    radial-gradient(circle at 6% 90%, rgba(0,0,0,0.2), transparent 45%),
    linear-gradient(135deg, #24365c 0%, #1a2947 55%, #212430 100%);
}
html.dark .lib-banner { filter: brightness(.88); }
.lib-banner::before {
  content: ""; position: absolute; inset: 0;
  background-image: repeating-linear-gradient(90deg, rgba(255,255,255,0.05) 0 2px, transparent 2px 34px);
  animation: lib-banner-driftLines 42s linear infinite;
}
@keyframes lib-banner-driftLines { from { background-position-x: 0; } to { background-position-x: 680px; } }

.lib-banner .top-stripe {
  position: absolute; top: 0; left: 0; width: 100%; height: 5px;
  background: linear-gradient(90deg,#7fb2ea,#0a6fb8,#7fb2ea); background-size: 200% 100%;
  animation: lib-banner-shimmerBar 6s linear infinite;
}
@keyframes lib-banner-shimmerBar { from { background-position-x: 0%; } to { background-position-x: -200%; } }

/* Retired outright, at every width. These used to appear above 1320px, which
   is exactly where there was room to notice how busy they were. The markup
   still emits them (partials/lib-banner.php) so the decision is reversible in
   one rule; nothing here is load-bearing. */
.lib-banner .dotgrid, .lib-banner .divider, .lib-banner .illust, .lib-banner .spark { display: none; }

.lib-banner .dotgrid { position: absolute; top: 34px; right: 70px; grid-template-columns: repeat(7,10px); gap: 6px; }
.lib-banner .dotgrid span { width: 3px; height: 3px; border-radius: 50%; background: #fff; opacity: .35; animation: lib-banner-dotPulse 3s ease-in-out infinite; }
@keyframes lib-banner-dotPulse { 0%,100% { opacity: .15; } 50% { opacity: .55; } }

.lib-banner .divider { position: absolute; left: 52%; top: 70px; bottom: 70px; width: 1px; background: rgba(255,255,255,.08); }

.lib-banner .content { position: relative; z-index: 2; padding: 14px 24px 16px; max-width: 640px; margin: 0 auto; text-align: center; }
@media (min-width: 900px) { .lib-banner .content { padding: 16px 40px 18px; } }
/* Clears the theme-toggle button (absolute, top-3 right-3 in the banner box)
   on narrow screens, where .content spans nearly the full width and would
   otherwise sit directly under it. */
@media (max-width: 480px) { .lib-banner .content { padding-top: 64px; } }
.lib-banner .logo-row { display: flex; align-items: center; justify-content: center; gap: 14px; opacity: 0; animation: lib-banner-fadeUp .7s ease .1s forwards; }
.lib-banner .logo-badge {
  width: 42px; height: 42px; border-radius: 9999px; display: flex; align-items: center;
  justify-content: center; flex-shrink: 0; box-shadow: 0 8px 24px -6px rgba(15,23,42,.5);
  /* Gradient border via layered backgrounds, not border-image: border-image
     ignores border-radius in every browser (confirmed via real render —
     even overflow:hidden doesn't clip it), so it painted a square around
     this circular badge. padding-box/border-box backgrounds respect
     border-radius natively. */
  border: 2px solid transparent;
  background: linear-gradient(#fff, #fff) padding-box, linear-gradient(135deg,#7fb2ea,#0a6fb8) border-box;
  animation: lib-banner-logoGlow 3.4s ease-in-out infinite;
}
@keyframes lib-banner-logoGlow { 0%,100% { box-shadow: 0 8px 24px -6px rgba(15,23,42,.5); } 50% { box-shadow: 0 8px 30px -4px rgba(96,165,250,.55); } }
.lib-banner .logo-badge img { width: 36px; height: 36px; border-radius: 9999px; object-fit: cover; }

.lib-banner .eyebrow { font-size: 10px; font-weight: 700; letter-spacing: .08em; color: #9db8e8; text-transform: uppercase; }
/* The <h2> in .title-frame says the same words two lines down. */
.lib-banner .brand { display: none; }

.lib-banner .title-frame {
  display: inline-block; margin-top: 10px; padding: 8px 18px 10px;
  background: rgba(0,0,0,.22); border: 1.5px solid rgba(147,197,253,.5); border-radius: 14px;
  backdrop-filter: blur(1px);
  opacity: 0; animation: lib-banner-frameIn .6s ease .18s forwards;
  box-shadow: 0 10px 26px -10px rgba(2,6,23,.5);
}
@keyframes lib-banner-frameIn { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
.lib-banner h1 { font-size: 20px; font-weight: 700; margin-top: 0; opacity: 0; animation: lib-banner-fadeUp .7s ease .32s forwards; color: #fff; line-height: 1.2; }
.lib-banner h2 {
  font-size: 13px; font-weight: 700; margin-top: 2px; opacity: 0;
  background: linear-gradient(90deg,#ffffff,#bfdbfe,#ffffff); background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: lib-banner-fadeUp .7s ease .38s forwards, lib-banner-goldShimmer 5s linear infinite 1.1s;
}
@keyframes lib-banner-goldShimmer { from { background-position-x: 0%; } to { background-position-x: -200%; } }

.lib-banner .sub { display: none; }

@keyframes lib-banner-fadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }

.lib-banner .footer-line { display: none; }
/* Thai has no spaces between words, so the browser's automatic line-break
   dictionary can split mid-word on narrow screens (e.g. cutting
   "เช็คเอาท์" in half). Keeping each phrase in one nowrap span means a
   mobile wrap can only happen at the " | " separators, never inside a
   phrase. */
.lib-banner .footer-line span { white-space: nowrap; }
/* The full three-phrase line needs ~520px and never fits a phone's ~327px
   content width even at this font-size — shrinking the font enough to fit
   would be unreadably small. Drop the least-essential middle phrase below
   480px instead, so the remaining two phrases fit on one line with no wrap
   at all. */
@media (max-width: 480px) { .lib-banner .footer-line .footer-line-mid { display: none; } }

.lib-banner .illust { position: absolute; right: 36px; top: 34px; width: 300px; height: 190px; }
.lib-banner .float1 { position: absolute; left: 20px; top: 10px; animation: lib-banner-floatY 5.2s ease-in-out infinite; }
@keyframes lib-banner-floatY { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
.lib-banner .kiosk-check { animation: lib-banner-blinkCheck 2.4s ease-in-out infinite; }
@keyframes lib-banner-blinkCheck { 0%,70%,100% { opacity: 1; } 85% { opacity: .15; } }
.lib-banner .person { opacity: 0; animation: lib-banner-popIn .5s ease forwards; }
.lib-banner .person:nth-child(1) { animation-delay: .95s; }
.lib-banner .person:nth-child(2) { animation-delay: 1.05s; }
.lib-banner .person:nth-child(3) { animation-delay: 1.15s; }
@keyframes lib-banner-popIn { from { opacity: 0; transform: translateY(10px) scale(.9); } to { opacity: 1; transform: translateY(0) scale(1); } }

.lib-banner .gear-spin { animation: lib-banner-spinCW 14s linear infinite; transform-origin: 495px 150px; }
.lib-banner .gear-spin-rev { animation: lib-banner-spinCCW 10s linear infinite; transform-origin: 540px 190px; }
@keyframes lib-banner-spinCW { from { transform: translate(495px,150px) rotate(0deg); } to { transform: translate(495px,150px) rotate(360deg); } }
@keyframes lib-banner-spinCCW { from { transform: translate(540px,190px) rotate(0deg); } to { transform: translate(540px,190px) rotate(-360deg); } }

.lib-banner .wave { position: absolute; left: 0; right: 0; bottom: -2px; height: 36px; animation: lib-banner-waveDrift 10s ease-in-out infinite; }
@keyframes lib-banner-waveDrift { 0%,100% { transform: translateX(0); } 50% { transform: translateX(-18px); } }

.lib-banner .spark { position: absolute; border-radius: 9999px; background: #7fb2ea; opacity: 0; animation: lib-banner-twinkle 3s ease-in-out infinite; }
@keyframes lib-banner-twinkle { 0%,100% { opacity: 0; transform: scale(.6); } 50% { opacity: .9; transform: scale(1.2); } }

/* Retired with the rest of the scene above. */
.lib-banner .corner-deco { display: none; position: absolute; top: 50%; opacity: .4; }
.lib-banner .corner-deco.left { left: 32px; }
.lib-banner .corner-deco.right { right: 32px; animation-delay: 1.4s; }
@keyframes lib-banner-cornerFloat { 0%,100% { transform: translateY(-50%); } 50% { transform: translateY(calc(-50% - 8px)); } }


/* ---------------------------------------------------------------------
   Table pagination (admin-active / admin-members / admin-logs).
   Plain CSS on purpose: styles.css is a prebuilt bundle with no live
   build step, so any Tailwind utility not already used elsewhere in the
   codebase would be silently missing from the compiled output.
   --------------------------------------------------------------------- */
.pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; }
.pager:empty { margin-top: 0; }
.pager-info { font-size: 12px; color: rgb(var(--c-ink-2)); margin-right: auto; }
.dark .pager-info { color: rgb(var(--c-dm-text-2)); }
.pager-gap { color: rgb(var(--c-ink-2)); padding: 0 2px; }
.dark .pager-gap { color: rgb(var(--c-dm-text-2)); }
.pager-btn {
  min-width: 34px; height: 34px; padding: 0 10px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgb(var(--c-ink-2) / .5); border-radius: 8px;
  background: #fff; color: rgb(30 58 138);
  font-size: 13px; font-weight: 700; cursor: pointer;
  transition: background-color .15s, border-color .15s, color .15s;
}
.pager-btn:hover:not(:disabled) { background: rgb(var(--c-primary) / .08); border-color: rgb(30 58 138); }
.pager-btn:disabled { opacity: .4; cursor: not-allowed; }
.pager-btn.is-current { background: rgb(30 58 138); border-color: rgb(30 58 138); color: #fff; }
.pager-btn:focus-visible { outline: 2px solid rgb(30 58 138); outline-offset: 2px; }
.dark .pager-btn { background: rgb(21 31 54); border-color: rgb(61 80 121); color: rgb(147 197 253); }
.dark .pager-btn:hover:not(:disabled) { background: rgb(var(--c-primary-soft) / .5); border-color: rgb(147 197 253); }
.dark .pager-btn.is-current { background: rgb(147 197 253); border-color: rgb(147 197 253); color: rgb(21 31 54); }
.dark .pager-btn:focus-visible { outline-color: rgb(147 197 253); }

/* ---------------------------------------------------------------------
   Trend chart (admin dashboard) — one bar per day. The question the card
   answers is "how busy was this day, and how does it compare to the one
   beside it", a comparison of discrete magnitudes and so a bar's job.
   Drawn as inline SVG sized in JS, so plain CSS here (styles.css is
   a prebuilt bundle — unused Tailwind utilities are absent from it).
   --------------------------------------------------------------------- */
.trend-chart { position: relative; width: 100%; height: 256px; }
.trend-chart svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* Only the hit bands take the pointer, so hovering anywhere in a day's column
   registers and the bar itself never swallows the click that opens its detail. */
.trend-chart svg > * { pointer-events: none; }
.trend-chart svg > .trend-hit { pointer-events: all; }

/* Recessive: one step off the surface, hairline, solid — never dashed. */
.trend-grid { stroke: rgb(var(--c-ink-2) / .18); stroke-width: 1; }
.dark .trend-grid { stroke: rgb(var(--c-ink-3) / .22); }
.trend-axis-text { fill: rgb(100 116 139); font-size: 10px; letter-spacing: .04em; }
.dark .trend-axis-text { fill: rgb(148 163 184); }

.trend-bar { fill: rgb(30 58 138); transition: fill .12s ease; }
.dark .trend-bar { fill: rgb(147 197 253); }
/* A day nobody came is a 2px stub, muted so it reads as "counted, and the
   answer was zero" instead of as one more short-but-real day. */
.trend-bar.is-zero { fill: rgb(var(--c-ink-2) / .3); }
.dark .trend-bar.is-zero { fill: rgb(var(--c-ink-3) / .3); }
.trend-bar.is-active { fill: rgb(37 99 235); }
.dark .trend-bar.is-active { fill: rgb(191 219 254); }

.trend-peak-label { fill: rgb(30 58 138); font-size: 11px; font-weight: 700; }
.dark .trend-peak-label { fill: rgb(147 197 253); }
/* Invisible per-day column: the hit target is the whole band, not just the
   drawn bar, so even a zero day's 2px stub stays comfortably hoverable. */
.trend-hit { fill: transparent; cursor: pointer; }
.trend-hit:focus-visible { fill: rgb(var(--c-primary) / .08); outline: none; }
.dark .trend-hit:focus-visible { fill: rgb(var(--c-primary-dim) / .12); }

.trend-empty { display: flex; align-items: center; justify-content: center; height: 100%; color: rgb(100 116 139); font-size: 14px; }
.dark .trend-empty { color: rgb(148 163 184); }

/* ---------------------------------------------------------------------
   KPI card explanations (admin-dashboard.php).

   The four numbers on the dashboard read as bare figures — 98, 6, 4, 0 —
   and the labels above them said what was counted only if you already knew
   the system. The labels now say it outright; this is the second half: an
   ⓘ per card that unfolds one sentence of detail.

   Unfolded rather than always shown, because the cards are two-up on a
   phone and a permanent second paragraph in each would make the strip taller
   than the screen. The note is display:none until asked for, so the card
   keeps exactly the height it has today.
   --------------------------------------------------------------------- */
.kpi-info {
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: -3px; margin-left: 3px;
  width: 18px; height: 18px; border-radius: 999px; border: 0;
  background: none; color: rgb(100 116 139); cursor: pointer; padding: 0;
  transition: background-color .15s, color .15s;
}
.kpi-info .material-symbols-outlined { font-size: 15px; }
.kpi-info:hover { background: rgb(var(--c-primary) / .1); color: rgb(30 58 138); }
.kpi-info[aria-expanded="true"] { background: rgb(var(--c-primary) / .12); color: rgb(30 58 138); }
.dark .kpi-info { color: rgb(148 163 184); }
.dark .kpi-info:hover,
.dark .kpi-info[aria-expanded="true"] { background: rgb(var(--c-primary-dim) / .16); color: rgb(147 197 253); }
/* The fourth card sits on the filled primary-container, where the slate greys
   above have almost no contrast. */
.kpi-info.on-blue { color: rgb(255 255 255 / .75); }
.kpi-info.on-blue:hover,
.kpi-info.on-blue[aria-expanded="true"] { background: rgb(255 255 255 / .2); color: #fff; }
.dark .kpi-info.on-blue { color: rgb(255 255 255 / .75); }
.kpi-info:focus-visible { outline: 2px solid rgb(30 58 138); outline-offset: 2px; }
.dark .kpi-info:focus-visible { outline-color: rgb(147 197 253); }
.kpi-info.on-blue:focus-visible { outline-color: #fff; }

.kpi-note {
  margin-top: 6px; font-size: 11.5px; line-height: 1.45;
  color: rgb(100 116 139); text-transform: none; letter-spacing: 0;
}
.dark .kpi-note { color: rgb(148 163 184); }
.kpi-note.on-blue { color: rgb(255 255 255 / .85); }

/* ---------------------------------------------------------------------
   Visit history panel (partials/history-modal.php).

   Replaces a three-column table of dates and times. Ten rows of that was
   thirty cells of near-identical text with nothing to navigate by, and the
   only route back to a day in June was pressing Next until you got there.

   A visit is really three things — a day, a span between two times, and how
   long it lasted — so the row is built out of those: the day as a block on
   the left, the two times on a track between them, the length closing the
   row. Each run of rows carries its month as a sticky heading, so scrolling
   has landmarks instead of forty identical stripes. Above the list sit the
   range filter that made paging unnecessary and a summary of whatever range
   is currently showing.

   Plain CSS for the usual reason: styles.css is a prebuilt bundle with no
   live Tailwind step, so utilities that aren't already used elsewhere are
   simply absent from it.

   Dark mode note: the panel itself is dm-surface rgb(21 31 54), so rows have
   to sit ABOVE it (dm-surface-alt rgb(28 42 71)) and the strips that frame it
   below it (rgb(15 23 42)). Reusing the panel's own value for either makes
   the element disappear into its container.
   --------------------------------------------------------------------- */

/* --- shell ------------------------------------------------------------- */
.visit-panel { min-width: 0; }
.visit-panel-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  flex-shrink: 0; padding: 18px 24px 15px;
  border-bottom: 1px solid rgb(226 232 240);
}
.dark .visit-panel-head { border-color: rgb(61 80 121); }
.visit-panel-heading { display: flex; align-items: center; gap: 12px; min-width: 0; }
.visit-panel-heading > div { min-width: 0; }
.visit-panel-icon {
  width: 40px; height: 40px; border-radius: 12px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; background: rgb(var(--c-primary) / .1); color: rgb(30 58 138);
}
.dark .visit-panel-icon { background: rgb(var(--c-primary-dim) / .14); color: rgb(147 197 253); }
.visit-panel-head h3 { font-size: 19px; font-weight: 700; line-height: 1.3; color: rgb(30 58 138); }
/* 'anywhere' rather than 'break-word': only the former lowers the element's
   min-content width, which is the number the flex layout above reads. */
.visit-panel-head h3, .visit-panel-head p { overflow-wrap: anywhere; }
.dark .visit-panel-head h3 { color: rgb(147 197 253); }
.visit-panel-head p { font-size: 12.5px; margin-top: 1px; color: rgb(100 116 139); }
.dark .visit-panel-head p { color: rgb(148 163 184); }
.visit-panel-close {
  width: 36px; height: 36px; border-radius: 999px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: rgb(100 116 139); transition: background-color .15s, color .15s;
}
.visit-panel-close:hover { background: rgb(241 245 249); color: rgb(15 23 42); }
.dark .visit-panel-close { color: rgb(148 163 184); }
.dark .visit-panel-close:hover { background: rgb(15 23 42); color: rgb(226 232 240); }
.visit-panel-close:focus-visible { outline: 2px solid rgb(30 58 138); outline-offset: 2px; }
.dark .visit-panel-close:focus-visible { outline-color: rgb(147 197 253); }

/* --- range filter ------------------------------------------------------ */
/* One control on one line. The four preset chips plus two always-open date
   pickers plus a clear button came to three stacked rows and ~130px of a
   390px phone, spent before the first visit appeared. The select says what
   the range is in the space the chips used just to list the options, and the
   pickers only unfold when someone actually asks for a custom range. */
.visit-filter {
  flex-shrink: 0; padding: 12px 24px 13px;
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px;
  background: rgb(248 250 252); border-bottom: 1px solid rgb(226 232 240);
}
.dark .visit-filter { background: rgb(15 23 42); border-color: rgb(61 80 121); }
.visit-select-label {
  align-self: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: rgb(100 116 139);
}
.dark .visit-select-label { color: rgb(148 163 184); }
.visit-select {
  flex: 0 1 auto; min-width: 0;
  border: 1px solid rgb(226 232 240); background: #fff; color: rgb(15 23 42);
  border-radius: 10px; padding: 8px 12px; min-height: 38px;
  font-size: 13px; font-weight: 700; font-family: inherit; cursor: pointer;
}
.dark .visit-select { background: rgb(28 42 71); border-color: rgb(61 80 121); color: rgb(226 232 240); }
.visit-select:focus { outline: 2px solid rgb(30 58 138); outline-offset: -1px; }
.dark .visit-select:focus { outline-color: rgb(147 197 253); }

.visit-range { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
/* Tailwind's .hidden is a bare class like this one and is defined far earlier
   in the bundle, so with equal specificity its display:none loses to the
   display:flex above and the pickers stay on screen. Anything in this file
   that sets `display` and is also toggled with .hidden needs this pair. */
.visit-range.hidden { display: none; }
.visit-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.visit-field label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: rgb(100 116 139);
}
.dark .visit-field label { color: rgb(148 163 184); }
.visit-field input {
  border: 1px solid rgb(226 232 240); background: #fff; color: rgb(15 23 42);
  border-radius: 10px; padding: 7px 10px; min-height: 38px; min-width: 0;
  font-size: 13px; font-family: inherit;
}
.dark .visit-field input { background: rgb(28 42 71); border-color: rgb(61 80 121); color: rgb(226 232 240); }
.visit-field input:focus { outline: 2px solid rgb(30 58 138); outline-offset: -1px; }
.dark .visit-field input:focus { outline-color: rgb(147 197 253); }
/* The native calendar glyph ships near-black, which vanishes on the dark
   field; nothing else in the control needs recolouring. */
.dark .visit-field input::-webkit-calendar-picker-indicator { filter: invert(1) brightness(1.7); }
.visit-range-dash { color: rgb(148 163 184); padding-bottom: 9px; }

/* --- summary of the range on screen ------------------------------------ */
.visit-summary {
  flex-shrink: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-bottom: 1px solid rgb(226 232 240);
}
.dark .visit-summary { border-color: rgb(61 80 121); }
.visit-stat {
  display: flex; flex-direction: column; gap: 2px;
  padding: 11px 18px; border-left: 1px solid rgb(226 232 240);
}
.visit-stat:first-child { border-left: 0; padding-left: 24px; }
.dark .visit-stat { border-color: rgb(61 80 121); }
.visit-stat-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: rgb(100 116 139);
}
.dark .visit-stat-label { color: rgb(148 163 184); }
/* Tabular figures so the three values don't jitter sideways as the range
   changes under them. */
.visit-stat-value {
  font-size: 15px; font-weight: 700; color: rgb(15 23 42);
  font-variant-numeric: tabular-nums;
}
.dark .visit-stat-value { color: rgb(226 232 240); }

/* --- the list ---------------------------------------------------------- */
.visit-list { flex: 1 1 auto; overflow-y: auto; min-height: 150px; padding: 0 24px 18px; }
.visit-rows { display: flex; flex-direction: column; gap: 6px; list-style: none; margin: 0; padding: 0; }
/* Sticky: grouping by month is pointless if the heading scrolls away and
   leaves you looking at a bare "14" with no idea which month it is in. The
   opaque background is the panel's own, so rows pass underneath it cleanly. */
.visit-group-head {
  position: sticky; top: 0; z-index: 1;
  padding: 13px 0 7px; background: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: rgb(100 116 139);
}
.dark .visit-group-head { background: rgb(21 31 54); color: rgb(148 163 184); }

.visit-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 14px;
  padding: 9px 12px; border-radius: 12px;
  background: rgb(248 250 252); border: 1px solid rgb(226 232 240);
  transition: background-color .15s, border-color .15s;
}
.visit-row:hover { background: #fff; border-color: rgb(var(--c-primary) / .35); }
.dark .visit-row { background: rgb(28 42 71); border-color: rgb(61 80 121); }
.dark .visit-row:hover { border-color: rgb(var(--c-primary-dim) / .5); }

.visit-day {
  width: 46px; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 5px 0; border-radius: 10px;
  background: #fff; border: 1px solid rgb(226 232 240);
}
.dark .visit-day { background: rgb(21 31 54); border-color: rgb(61 80 121); }
.visit-day-num {
  font-size: 17px; font-weight: 700; line-height: 1.1; color: rgb(15 23 42);
  font-variant-numeric: tabular-nums;
}
.dark .visit-day-num { color: rgb(226 232 240); }
.visit-day-dow { font-size: 10.5px; line-height: 1.3; color: rgb(100 116 139); }
.dark .visit-day-dow { color: rgb(148 163 184); }

/* flex-start, not center: the "ออก" column carries a third line on rows the
   system closed ("ระบบบันทึกให้") while "เข้า" only ever has two. Centering
   made the taller column overhang top and bottom equally, so its label and
   time sat ~7px above their counterparts and the pair read as tilted — but
   only on auto-checkout rows, which is why it looked intermittent. Aligning
   to the top lines the two times up whatever hangs underneath them. */
.visit-track { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.visit-stop { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.visit-stop-label {
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  /* Pinned so .visit-line's offset below stays predictable rather than
     inheriting whatever line-height the cascade happens to supply. */
  line-height: 1.35;
  color: rgb(148 163 184);
}
.visit-stop-time {
  font-size: 14px; font-weight: 700; line-height: 1.35;
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.visit-stop.in .visit-stop-time { color: rgb(5 150 105); }
.dark .visit-stop.in .visit-stop-time { color: rgb(52 211 153); }
.visit-stop.out .visit-stop-time { color: rgb(71 85 105); }
.dark .visit-stop.out .visit-stop-time { color: rgb(203 213 225); }
.visit-stop-time.is-open { font-size: 12.5px; color: rgb(217 119 6); }
/* Green at the check-in end fading to grey at the other: the line reads as
   time passing rather than as a divider between two unrelated cells. */
.visit-line {
  flex: 1 1 auto; min-width: 14px; max-width: 96px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, rgb(5 150 105 / .55), rgb(var(--c-ink-3) / .35));
  /* The track no longer centres its children, so the line has to be placed on
     the times by hand: label (9.5 x 1.35) + the 1px column gap + half a time
     line (14 x 1.35 / 2), less half the line's own 2px height. */
  margin-top: 22px;
}
/* A visit still running has no end for the line to reach. */
.visit-row.is-open .visit-line {
  height: 0; background: none; border-top: 2px dashed rgb(var(--c-warning) / .5);
}
.visit-note { font-size: 10px; color: rgb(100 116 139); }
.dark .visit-note { color: rgb(148 163 184); }

.visit-dur { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
.visit-dur-label {
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: rgb(148 163 184);
}
.visit-dur-value {
  font-size: 13px; font-weight: 700; white-space: nowrap;
  padding: 3px 10px; border-radius: 999px;
  color: rgb(30 58 138); background: rgb(var(--c-primary) / .08);
}
.dark .visit-dur-value { color: rgb(147 197 253); background: rgb(var(--c-primary-dim) / .14); }
.visit-row.is-open .visit-dur-value { color: rgb(217 119 6); background: rgb(var(--c-warning) / .12); }

.visit-empty { font-size: 13px; color: rgb(100 116 139); }
.dark .visit-empty { color: rgb(148 163 184); }
.visit-empty-state { text-align: center; padding: 34px 12px; }
.visit-empty-state .material-symbols-outlined {
  display: block; margin-bottom: 8px; font-size: 40px; color: rgb(203 213 225);
}
.dark .visit-empty-state .material-symbols-outlined { color: rgb(61 80 121); }
.visit-empty-title { font-size: 14.5px; font-weight: 700; margin-bottom: 3px; color: rgb(15 23 42); }
.dark .visit-empty-title { color: rgb(226 232 240); }

/* --- footer / pager ----------------------------------------------------- */
.visit-panel-foot {
  flex-shrink: 0; padding: 10px 24px 12px;
  background: rgb(248 250 252); border-top: 1px solid rgb(226 232 240);
}
.dark .visit-panel-foot { background: rgb(15 23 42); border-color: rgb(61 80 121); }
/* The shared .pager (assets/js/pagination.js) carries a top margin for the
   admin tables, where it follows a table directly; here the strip supplies
   its own padding. */
.visit-panel-foot .pager { margin-top: 0; }

/* ---------------------------------------------------------------------
   Scroll-wheel time picker (dashboard.php's check-in modal). Replaces a
   typed HH:MM field: on a phone, "when will you leave" is a value you
   nudge to, not one you spell out, and the field made you type four
   digits to say "an hour from now".
   NOT the native <input type="time"> wheel, which was the original
   complaint and is exactly what the typed field was introduced to escape
   — this is an in-page list that scrolls with the thumb already on the
   screen, with no OS overlay to dismiss.
   Item height is mirrored by WHEEL_ITEM_H in assets/js/dashboard.js; the
   two must agree or the snapped row and the read-back value drift apart.
   --------------------------------------------------------------------- */
/* --- check-in dialog: สลับแท็บโดยกล่องไม่ขยับ --------------------------
   สองแพเนล ("เลือกเวลาที่จะออก" / "เลือกจำนวนชั่วโมง") สูงไม่เท่ากัน
   รอบก่อนแก้ด้วยการอนิเมต grid-template-rows 0fr -> 1fr ให้กล่องค่อย ๆ
   เปลี่ยนขนาด ซึ่งยังเป็นการขยับอยู่ดี: วัดได้ 530px ตอนอยู่แท็บเวลา และ
   418px ตอนอยู่แท็บชั่วโมง ปุ่มยืนยันจึงไถลขึ้น 112px ใต้นิ้วทุกครั้งที่กด
   สลับแท็บ ตอนนี้ทั้งสองแพเนลวางซ้อนกันในช่องกริดเดียว (.modal-panels)
   ความสูงของกรอบเท่ากับแพเนลที่สูงที่สุดเสมอ และสลับด้วยการจางเข้า-ออก */
.modal-panel {
  opacity: 0;
  overflow: hidden;
  /* visibility flips only after the fade finishes, so a hidden panel
     can't be tabbed into mid-animation. */
  visibility: hidden;
  transition: opacity .18s ease, visibility 0s linear .18s;
}
.modal-panel > .modal-panel-inner { min-height: 0; }
.modal-panel.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity .18s ease, visibility 0s linear 0s;
}
/* เดิมสองแพเนลนี้ยืด/ยุบความสูงตัวเองตอนสลับแท็บ (grid-template-rows 0fr↔1fr)
   กล่องจึงสูง 530px ตอนอยู่แท็บเวลา และ 418px ตอนอยู่แท็บชั่วโมง — ต่างกัน
   112px ปุ่ม "ยืนยันเช็คอิน" กับแถบแท็บเองเลยไถลขึ้นลงใต้นิ้วทุกครั้งที่กด
   สลับ ตอนนี้วางซ้อนกันในช่องกริดเดียว ความสูงของกรอบจึงเท่ากับแพเนลที่สูง
   ที่สุดเสมอ ไม่ขยับ แล้วสลับด้วยการจางเข้า-ออกแทน */
.modal-panels {
  display: grid;
  margin-bottom: 1rem;
}
.modal-panels > .modal-panel {
  grid-area: 1 / 1;
  align-self: stretch;
}
/* แพเนล "จำนวนชั่วโมง" เตี้ยกว่าแพเนลล้อเลือกเวลาราว 110px พอความสูงของกรอบ
   ถูกตรึงไว้เท่าอันที่สูงกว่า ที่ว่างส่วนนั้นจะค้างเปล่า ๆ — ให้ปุ่มยืดเต็มแทน
   ได้เป้ากดที่ใหญ่ขึ้นด้วย (h-10 = 40px บนปุ่มถูกแทนที่ด้วย height:auto ตรงนี้
   ตัวเลือกแบบ id + ชนิดแท็กจึงชนะคลาสของ Tailwind โดยไม่ต้องใช้ !important) */
#modal-panel-hours > .modal-panel-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
}
#modal-hour-buttons {
  flex: 1 1 auto;
  grid-auto-rows: 1fr;
}
#modal-hour-buttons > button { height: auto; min-height: 40px; }

/* Dialog itself fades and lifts in instead of appearing instantly. */
#checkin-modal { transition: opacity .24s ease; }
#checkin-modal > div {
  transition: transform .3s cubic-bezier(.4, 0, .2, 1), opacity .24s ease;
}
#checkin-modal.hidden > div { transform: translateY(10px) scale(.985); opacity: 0; }

/* The segmented tab control: the active pill slides/eases rather than
   snapping between the two halves. */
[data-modal-tab] {
  transition: background-color .28s ease, color .28s ease, box-shadow .28s ease;
}

/* Set by openCheckinModal() for the opening frame — see the comment there. */
#checkin-modal.no-panel-anim .modal-panel { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .modal-panel,
  .modal-panel.is-active,
  .modal-panels > .modal-panel,
  #checkin-modal > div,
  [data-modal-tab] { transition-duration: .01ms !important; }
}

.time-wheel {
  position: relative; display: flex; align-items: stretch; justify-content: center;
  gap: 4px; height: 176px; -webkit-user-select: none; user-select: none;
}
/* The stationary centre row. Scrolling moves the numbers past this, so the
   selected value is always the one framed here. */
.time-wheel-band {
  position: absolute; left: 0; right: 0; top: 50%; height: 44px;
  transform: translateY(-50%); border-radius: 10px;
  background: rgb(var(--c-primary) / .07); pointer-events: none;
}
.dark .time-wheel-band { background: rgb(var(--c-primary-dim) / .12); }
.time-wheel-col {
  flex: 0 0 88px; overflow-y: scroll; scroll-snap-type: y mandatory;
  text-align: center; outline: none; scrollbar-width: none; -ms-overflow-style: none;
  /* Momentum scrolling on iOS, so a flick behaves like every other wheel
     the person has used on that phone. */
  -webkit-overflow-scrolling: touch;
  /* เลื่อนจนสุดหัว/ท้ายล้อแล้วต้องหยุดแค่นั้น ค่าเดิม (auto) ส่งการเลื่อนต่อไป
     ให้ .ntc-modal ที่ครอบอยู่ กล่องทั้งกล่องเลยไถลตามนิ้วระหว่างเลือกเวลา
     ตัวเลขที่กำลังจะกดจึงขยับหนี */
  overscroll-behavior: contain;
  /* Fade the ends instead of slicing them. Without this the first and last
     visible rows are cut through the middle of a digit at a hard edge, which
     reads as a rendering fault rather than as numbers curving away. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
}
.time-wheel-col::-webkit-scrollbar { width: 0; height: 0; display: none; }
.time-wheel-col:focus-visible { box-shadow: 0 0 0 2px rgb(30 58 138); border-radius: 10px; }
.dark .time-wheel-col:focus-visible { box-shadow: 0 0 0 2px rgb(147 197 253); }
.time-wheel-item {
  height: 44px; line-height: 44px; scroll-snap-align: center;
  font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: rgb(148 163 184); opacity: .45;
  transition: opacity .12s ease, color .12s ease;
}
/* Two steps of fade rather than one, so the column reads as a wheel curving
   away instead of a list with one highlighted row. */
.time-wheel-item.is-near { opacity: .75; }
.time-wheel-item.is-selected { opacity: 1; color: rgb(15 23 42); }
.dark .time-wheel-item.is-selected { color: rgb(226 232 240); }
.time-wheel-sep {
  align-self: center; font-size: 22px; font-weight: 700;
  color: rgb(100 116 139); padding-bottom: 2px;
}
.dark .time-wheel-sep { color: rgb(148 163 184); }

/* Edit-member dialog (admin-members.php). The panel is a column so the profile
   fields scroll while the header, the role/delete strip and the save row stay
   put. Plain CSS because max-h-[85vh], grid-cols-2 and col-span-2 are not in
   the prebuilt styles.css bundle — writing them would have looked right in the
   markup and done nothing in the browser. */
/* dvh มาหลัง vh ด้วยเหตุผลเดียวกับ .visit-panel ข้างบน: บนมือถือ 1vh คิดจาก
   ความสูงตอนแถบที่อยู่เว็บถูกซ่อน กล่องที่สูง 88vh จึงยังล้นพื้นที่ที่มองเห็น
   จริงอยู่ดี ทำให้ฟอร์มต้องเลื่อนมากกว่าที่ควร */
.member-edit-panel {
  display: flex; flex-direction: column;
  max-height: 88vh;
  max-height: 88dvh;
}
.member-edit-body { overflow-y: auto; }
/* opacity-50 isn't in the bundle, and a disabled button that looks enabled is
   worse than no affordance at all. Keyed off :disabled so the JS only has to
   set the property. */
.member-edit-panel button:disabled { opacity: .5; cursor: not-allowed; }
.member-edit-panel select:disabled { opacity: .6; cursor: not-allowed; }

/* ---------------------------------------------------------------------
   Admin dashboard on a phone.
   The page was laid out for a desktop: the hero reserves 128px of bottom
   padding for a row of KPI cards that are then translated up into it, and
   each card stacks a 48px icon puck above its label and its number. On a
   390px screen that works out to roughly one card per screenful — four
   scrolls to read four numbers, with a screen of empty blue in between.
   Two-up, icon shrunk and the reserved gap cut to match.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .admin-hero-pattern { padding-top: 14px; padding-bottom: 28px; }
  .admin-hero-pattern h1,
  .admin-hero-pattern h2 { font-size: 23px; line-height: 1.25; margin-bottom: 4px; }
  .admin-hero-pattern #ad-subtitle { font-size: 13px; line-height: 1.4; }

  .admin-kpi-grid {
    /* minmax(0, 1fr) ไม่ใช่ 1fr เฉยๆ: 1fr = minmax(auto, 1fr) ซึ่งห้าม
       คอลัมน์หดต่ำกว่าความกว้างขั้นต่ำของเนื้อหา ป้ายภาษาไทยยาวๆ ไม่มี
       ช่องว่างจึงเป็น "คำเดียว" ที่ตัดไม่ได้ แล้วดันคอลัมน์ล้นออกนอกจอ
       (grid-cols-1 ของ Tailwind ที่ถูกทับไปใช้ minmax(0,1fr) อยู่แล้ว) */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    margin-top: 0;
  }
  /* Grid children default to min-width:auto, so a label that cannot wrap
     pushes its column wider than the track. Thai has no spaces, so
     "อยู่ในห้องสมุดตอนนี้ (โดยประมาณ)" is one unbreakable word — the card
     grew past the right edge of the screen and the page scrolled sideways. */
  .admin-kpi-grid > div {
    padding: 12px 12px 13px; border-radius: 14px;
    min-width: 0; overflow: hidden;
  }
  .admin-kpi-grid > div > p { overflow-wrap: anywhere; }
  .admin-kpi-grid .kpi-icon { width: 26px; height: 26px; margin-bottom: 7px; }
  .admin-kpi-grid .kpi-icon .material-symbols-outlined { font-size: 16px; }
  .admin-kpi-grid > div > p { font-size: 10px; line-height: 1.35; margin-bottom: 3px; }
  .admin-kpi-grid .kpi-value { font-size: 22px; line-height: 1.1; }
  .admin-kpi-grid .kpi-skeleton { height: 22px; width: 54px; }
  .admin-kpi-grid > div > p.kpi-note { font-size: 10.5px; line-height: 1.4; margin-bottom: 0; }
  /* KPI cards now sit in their own wrapper above this section, so it only
     needs a normal gap from them instead of clearance for translated cards. */
  .admin-kpi-clear { padding-top: 20px; }
}

/* ---------------------------------------------------------------------
   Visit history on a phone.

   The three-column row survives down to about 620px; below that the
   duration pill is what gives out first, so it drops to a second line under
   the times while the day block spans both rows and keeps anchoring the
   left edge. The horizontal padding on every strip comes in at the same
   time — 24px each side out of a 360px screen is a seventh of the width
   spent on nothing.
   --------------------------------------------------------------------- */
@media (max-width: 620px) {
  .visit-panel-head { padding: 15px 16px 13px; }
  .visit-filter { padding: 12px 16px; }
  .visit-list { padding: 0 16px 16px; }
  .visit-panel-foot { padding: 10px 16px 12px; }
  .visit-stat { padding: 10px 12px; }
  .visit-stat:first-child { padding-left: 16px; }
  .visit-stat-label { font-size: 10px; }
  .visit-stat-value { font-size: 13.5px; }

  .visit-panel-icon { width: 36px; height: 36px; font-size: 20px; }
  .visit-panel-head h3 { font-size: 17px; }

  /* The label and the select share the one row; the select takes whatever is
     left rather than sizing to its longest option, so the row is the same
     height whichever range is chosen. */
  .visit-select { flex: 1 1 auto; }
  /* Each date field takes half the row rather than shrinking to its content,
     so the pair stays one tidy line instead of two ragged ones. The dash
     between them has nothing to separate once they wrap, so it goes. */
  .visit-range { flex: 1 0 100%; gap: 8px 10px; }
  .visit-field { flex: 1 1 0; }
  .visit-field input { width: 100%; }
  .visit-range-dash { display: none; }

  .visit-row { grid-template-columns: auto minmax(0, 1fr); gap: 7px 12px; padding: 9px 11px; }
  .visit-day { grid-row: 1 / span 2; align-self: stretch; justify-content: center; }
  .visit-track { grid-column: 2; }
  /* Reads as a sentence on its own line ("รวม 2 ชม. 28 นาที") instead of a
     right-aligned stack that would sit under the day block. */
  .visit-dur {
    grid-column: 2; flex-direction: row; align-items: center;
    justify-content: flex-start; gap: 6px;
  }
  .visit-dur-value { font-size: 12px; padding: 2px 9px; }
  /* The one string in a row long enough to matter, and Thai gives the
     browser no spaces to break it at — left nowrap it sets the row's
     minimum width all by itself. */
  .visit-stop-time.is-open { white-space: normal; overflow-wrap: anywhere; }
}

/* ---------------------------------------------------------------------
   The summary strip on a small phone.

   Three stats across a 342px panel is 114px each, and "19 ชม. 41 นาที" does
   not fit that at the size the strip uses further up. Thai gives the browser
   no spaces to wrap at, so the value cannot fold to a second line either —
   it simply runs past the panel, which clips it. Below 480px the strip keeps
   its three columns and gives up the type size and side padding instead;
   stacking would cost 60px of height on the screen where the list itself has
   the least room.
   --------------------------------------------------------------------- */
@media (max-width: 480px) {
  .visit-stat { padding: 9px 8px; }
  .visit-stat:first-child { padding-left: 16px; }
  .visit-stat-label { font-size: 9.5px; letter-spacing: 0; }
  .visit-stat-value { font-size: 12.5px; }
}

/* ---------------------------------------------------------------------
   Admin tables on a phone.
   Seven columns in 390px means a horizontal scrollbar, names broken into
   five lines, and the row's action buttons parked off-screen where nobody
   finds them. Below 700px each row becomes a card instead: the name leads,
   the identifiers sit under it on one line, and the actions come last where
   a thumb can reach them. Ordering is per table because the three tables
   don't share a column order.
   --------------------------------------------------------------------- */
@media (max-width: 700px) {
  .admin-table thead { display: none; }
  .admin-table tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 8px;
    padding: 12px 14px;
  }
  .admin-table tbody td {
    padding: 0 !important;
    text-align: left !important;
    border: 0;
  }
  /* Everything defaults to sitting inline; the rules below promote the
     fields that deserve their own line. */
  .admin-table tbody td { flex: 0 1 auto; }
  .admin-table .row-full { flex: 1 0 100%; }

  /* ทำเนียบสมาชิก: รหัส ชื่อ แผนก ระดับ ปี ล่าสุด จัดการ */
  #members-tbody td:nth-child(2) { flex: 1 0 100%; order: 1; font-size: 14px; }
  #members-tbody td:nth-child(1) { flex: 1 0 100%; order: 2; font-size: 12px; }
  /* แผนก / ระดับชั้น / ชั้นปี เคยเป็นข้อความสามชิ้นคั่นด้วย "·" ซึ่งอ่านแล้ว
     แยกไม่ออกว่าอันไหนคือค่าอะไร และดูเหมือนข้อความที่พิมพ์ต่อกันมากกว่าข้อมูล
     ที่มีโครงสร้าง — เปลี่ยนเป็นชิปสามอัน (.chip ประกาศไว้ท้ายไฟล์) */
  #members-tbody td:nth-child(3) { order: 3; }
  #members-tbody td:nth-child(4) { order: 4; }
  #members-tbody td:nth-child(5) { order: 5; }
  #members-tbody td:nth-child(3),
  #members-tbody td:nth-child(4),
  #members-tbody td:nth-child(5) { margin-top: 4px; }
  #members-tbody td:nth-child(6) { flex: 1 0 100%; order: 6; font-size: 11.5px; }
  #members-tbody td:nth-child(7) { flex: 1 0 100%; order: 7; text-align: right !important; margin-top: 6px; }

  /* ประวัติการเข้าใช้: วันเวลา ชื่อ รหัส แผนก ประเภท */
  #logs-tbody td:nth-child(2) { flex: 1 0 100%; order: 1; font-size: 14px; }
  #logs-tbody td:nth-child(3) { order: 2; font-size: 12px; }
  #logs-tbody td:nth-child(5) { order: 3; margin-left: auto; }
  #logs-tbody td:nth-child(1) { flex: 1 0 100%; order: 4; font-size: 11.5px; }
  #logs-tbody td:nth-child(4) { flex: 1 0 100%; order: 5; font-size: 12px; }

  /* กำลังใช้งานอยู่: ชื่อ รหัส แผนก เช็คอินเมื่อ อยู่มาแล้ว จัดการ */
  #active-tbody td:nth-child(1) { flex: 1 0 100%; order: 1; font-size: 14px; }
  #active-tbody td:nth-child(2) { order: 2; font-size: 12px; }
  #active-tbody td:nth-child(3) { order: 3; margin-top: 4px; }
  #active-tbody td:nth-child(4) { flex: 1 0 100%; order: 4; font-size: 11.5px; }
  #active-tbody td:nth-child(5) { flex: 1 0 100%; order: 5; font-size: 12px; }
  #active-tbody td:nth-child(6) { flex: 1 0 100%; order: 6; text-align: right !important; margin-top: 6px; }

  /* แถวสถานะ (กำลังโหลด / ไม่พบข้อมูล) ใช้ colspan ปล่อยให้เต็มแถวตามเดิม */
  .admin-table tbody td[colspan] { flex: 1 0 100%; }
}

/* ปุ่มจัดการสองอันในเซลล์เดียวกันชนกันจนอ่านเป็นก้อนเดียว ("แก้ไขรีเซ็ตรหัสผ่าน")
   ทั้งบนจอใหญ่และจอเล็ก — คั่นให้เป็นสองปุ่มที่เห็นชัดว่าแยกกัน */
#members-tbody .reset-password-btn { margin-left: 14px; }

/* ---------------------------------------------------------------------
   หน้าล็อกอิน — เก็บความเทอะทะ
   เดิมมีหัวเรื่องสองชุดซ้อนกัน: โลโก้กับชื่อภาษาอังกฤษบรรทัดหนึ่ง แล้วกล่อง
   มีกรอบอีกใบใส่ชื่อภาษาไทย กล่องนั้นลอยอยู่กลางแถบน้ำเงินกว้างๆ จนอ่านเป็น
   ป้ายสุ่มมากกว่าหัวเว็บ เอากรอบออกแล้วรวมเป็นบรรทัดเดียว
   --------------------------------------------------------------------- */
.lib-banner .title-frame {
  display: block;
  margin-top: 2px;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
}
/* ห้องสมุด กับ ชื่อวิทยาลัย อยู่บรรทัดเดียวกัน ไม่ใช่กองซ้อนกันสองชั้น */
.lib-banner h1 { display: inline; font-size: 19px; vertical-align: baseline; }
.lib-banner h2 { display: inline; font-size: 15px; margin-left: 9px; vertical-align: baseline; }
.lib-banner .eyebrow { font-size: 9.5px; opacity: .85; }

/* การ์ดล็อกอินอยู่ชิดบน ปล่อยพื้นที่ว่างก้อนใหญ่ไว้ใต้มันจนดูเหมือนหน้ายังโหลด
   ไม่เสร็จ จัดให้อยู่กลางพื้นที่ที่เหลือแทน — เฉพาะจอที่สูงพอ ไม่งั้นบนมือถือ
   จะดันการ์ดลงไปใต้ fold */
@media (min-height: 700px) {
  .login-main {
    display: flex; align-items: center; justify-content: center;
    /* mt-5 กับ pb-16 ของ main ทำให้ "กลาง" เอียงลงล่าง 44px ปิดทั้งคู่
       ตอนจัดกลาง เพื่อให้ช่องว่างบนกับล่างเท่ากันจริง */
    margin-top: 0 !important;
    padding-bottom: 0 !important;
  }
  .login-main > div { width: 100%; }
}

/* ===================================================================
   แก้ตามผลตรวจ accessibility (WCAG 2.1 AA)
   =================================================================== */

/* --- คอนทราสต์ในโหมดมืด ---
   สีชุดนี้เป็นสีที่เลือกมาสำหรับพื้นสว่าง แต่ถูกใช้บนพื้นมืดด้วย
   ผลตรวจจากสีที่เรนเดอร์จริง: ป้าย "เช็คอิน" 2.75:1 และไอคอนช่องค้นหา
   2.47:1 ทั้งที่ต้องการ 4.5:1 และ 3:1 ตามลำดับ
   ในโหมดมืดจึงต้องใช้เฉดที่สว่างขึ้น ไม่ใช่เฉดเดียวกับพื้นสว่าง */
html.dark .text-outline { color: rgb(148 163 184); }
html.dark .text-status-success { color: rgb(52 211 153); }
html.dark .text-warning { color: rgb(251 191 36); }

/* --- ตัวหนังสือไล่สีในแบนเนอร์ ---
   h2 ใช้ background-clip: text คู่กับ color: transparent ถ้าเบราว์เซอร์
   ไม่รองรับ clip ตัวหนังสือจะหายทั้งบรรทัด และเครื่องมือตรวจอ่านค่าได้
   1.00:1 เสมอ ใช้สีทึบแทน อ่านได้แน่นอนและวัดค่าได้จริง */
.lib-banner h2 {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: rgba(219, 234, 254, .88);
  animation: lib-banner-fadeUp .7s ease .38s forwards;
}

/* --- เป้าแตะ (WCAG 2.5.8 ต้องไม่ต่ำกว่า 24x24) ---
   ปุ่มในตารางเป็นตัวหนังสือสูง 16px ทั้งที่เป็นทางเดียวที่จะแก้ข้อมูล
   หรือรีเซ็ตรหัสผ่าน บนมือถือกดพลาดง่ายมาก ขยายด้วย padding แทนการ
   เพิ่มขนาดตัวอักษร เพื่อไม่ให้ตารางแน่นขึ้น */
#members-tbody .edit-member-btn,
#members-tbody .reset-password-btn,
#active-tbody .force-checkout-btn,
#dashboard-view-history {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 4px 6px;
}
/* margin เดิมวัดจากขอบตัวอักษร พอมี padding แล้วต้องลดลงไม่ให้ห่างเกิน */
#members-tbody .reset-password-btn { margin-left: 8px; }

/* ลิงก์ข้อความในหน้า login/signup ที่เป็นทางเดียวไปหน้าถัดไป */
#login-signup-link a,
.signup-back-link {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 2px 4px;
}

/* ซ่อนจากสายตาแต่โปรแกรมอ่านหน้าจอยังอ่านเจอ — ไม่ใช้ display:none หรือ
   visibility:hidden เพราะทั้งคู่ทำให้ถูกข้ามไปด้วย
   ใช้กับ h1 ของหน้านักศึกษา ที่หน้าไม่มีหัวเรื่องซึ่งมองเห็นได้ */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ไอคอนในช่องค้นหา/ตัวกรอง เป็นไอคอนที่สื่อความหมาย (แว่นขยาย ปฏิทิน)
   จึงต้องผ่านเกณฑ์ 3:1 เหมือนองค์ประกอบกราฟิกอื่น ผลตรวจได้ 2.47-2.56:1
   เพราะสีมาจากค่า default ที่เลือกไว้สำหรับพื้นสว่าง */
html.dark .material-symbols-outlined.text-text-secondary { color: rgb(148 163 184); }
html.dark .kpi-icon.text-accent-stats { color: rgb(198 156 220); }

/* ลิงก์ "กลับไปหน้าเข้าสู่ระบบ" ในหน้าสมัครสมาชิก สูงแค่ 24px */
.signup-page-back {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 2px 4px;
}

/* ---------------------------------------------------------------------
   หน้าล็อกอิน — สัดส่วนแนวนอน
   วัดที่จอ 1440 ได้สามกล่องที่ใช้คนละมาตรวัดบนหน้าเดียวกัน:
     เนื้อในแบนเนอร์  640px   (ในแถบกว้าง 1424 เหลือว่างข้างละ 392)
     การ์ดล็อกอิน     576px
   แก้ให้ทุกกล่องใช้มาตรเดียวกับการ์ด

   (footer ของหน้าล็อกอินถูกถอดออกไปแล้วพร้อมการออกแบบหน้าใหม่ กฎที่เคยจัด
   ระยะให้มันจึงถูกลบตามไปด้วย)
   --------------------------------------------------------------------- */

/* แถบน้ำเงินยังเต็มความกว้างเหมือนเดิม แต่เนื้อในใช้มาตรเดียวกับการ์ด */
.lib-banner .content { max-width: 640px; }

/* ตัวหนังสือในแบนเนอร์เล็กเกินไปเมื่อเทียบกับแถบที่มันอยู่ — กลุ่มโลโก้กับ
   ชื่อรวมกันเป็นหมึกแค่ราว 200px กลางแถบ 1424px ขยายขึ้นให้เนื้อหาถือครอง
   แถบได้จริง ไม่ใช่ลอยอยู่ตรงกลางเหมือนสติกเกอร์ */
.lib-banner .logo-badge { width: 44px; height: 44px; }
.lib-banner .logo-badge img { width: 38px; height: 38px; }
.lib-banner .eyebrow { font-size: 10.5px; letter-spacing: .1em; }
.lib-banner h1 { font-size: 25px; }
.lib-banner h2 { font-size: 16.5px; margin-left: 11px; }
.lib-banner .content { padding-top: 16px; padding-bottom: 18px; }

@media (max-width: 760px) {
  .lib-banner h1 { font-size: 20px; }
  .lib-banner h2 { font-size: 13.5px; margin-left: 8px; }
  .lib-banner .logo-badge { width: 38px; height: 38px; }
  .lib-banner .logo-badge img { width: 33px; height: 33px; }
}

/* ---------------------------------------------------------------------
   หน้าล็อกอิน — แถบหัวเว็บ (23 ส.ค. 2569)
   แถบเดิมสูงราว 130px ซึ่งเป็นความสูงของ "แถบเมนู" ไม่ใช่ "แบนเนอร์" จึง
   อ่านเหมือน header มากกว่าหน้าปกของเว็บ ตรึงความสูงไว้ที่ ~190px แล้วจัด
   เนื้อหาให้อยู่กลางแถบ

   ใช้ min-height ไม่ใช่ padding: ฟอร์มล็อกอินต้องยังอยู่เหนือ fold บนมือถือ
   ถ้าดันด้วย padding ความสูงจะไหลตามขนาดตัวอักษรที่เปลี่ยนตามจอ คุมไม่ได้
   --------------------------------------------------------------------- */
.lib-banner .content {
  min-height: 192px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}

/* ขยายโลโก้และชื่อขึ้นราว 15% ตามที่ขอ — "ห้องสมุด วิทยาลัยเทคนิคนครนายก"
   เป็นบรรทัดที่ใหญ่ที่สุดในหน้า ส่วนชื่อภาษาอังกฤษยังเป็นบรรทัดรอง */
.lib-banner .logo-badge { width: 52px; height: 52px; }
.lib-banner .logo-badge img { width: 45px; height: 45px; }
.lib-banner .eyebrow { font-size: 11.5px; }
.lib-banner h1 { font-size: 29px; }
.lib-banner h2 { font-size: 19.5px; margin-left: 12px; }

@media (max-width: 760px) {
  .lib-banner h1 { font-size: 23px; }
  .lib-banner h2 { font-size: 16px; margin-left: 9px; }
  .lib-banner .logo-badge { width: 44px; height: 44px; }
  .lib-banner .logo-badge img { width: 38px; height: 38px; }
}

/* บนจอแคบ ปุ่มตั้งค่าธีมลอยอยู่มุมขวาบนของแถบ เว้นที่ด้านบนให้พ้นกัน
   (min-height ยังคุมความสูงรวมอยู่ padding แค่ดันจุดกึ่งกลางลงมา) */
@media (max-width: 480px) {
  .lib-banner .content { min-height: 186px; padding-top: 40px; padding-bottom: 18px; }
}

/* ---------------------------------------------------------------------
   ปุ่มเปิด/ปิดการมองเห็นรหัสผ่าน

   .pw-toggle เป็น "จุดเกาะของสคริปต์" อย่างเดียว ไม่มีรูปร่างของตัวเอง —
   assets/js/password-toggle.js หาปุ่มจากคลาสนี้ ส่วนหน้าตาเป็นของคลาสคู่กัน
   ที่แต่ละหน้าใส่มาเอง (.login-pw-toggle บนการ์ดกระจก, .cp-toggle บนการ์ดขาว)
   เพราะปุ่มบนพื้นสองแบบนี้ต้องใช้สีคนละชุด

   เดิมคลาสนี้มีรูปร่างของตัวเองด้วย ซึ่งไปทับกับคลาสคู่ทุกตัวและตัดสินกันด้วย
   ลำดับบรรทัดในไฟล์เท่านั้น — เลื่อนบล็อกไปมาทีเดียวปุ่มก็เปลี่ยนขนาดเอง
   --------------------------------------------------------------------- */

/* ---------------------------------------------------------------------
   ช่องพิมพ์ประกาศของเจ้าหน้าที่ (หน้าภาพรวมแอดมิน)
   เขียนเป็น CSS ธรรมดาเช่นเดียวกับ .pw-toggle — คลาส Tailwind ที่ยังไม่เคย
   ถูกใช้ที่อื่นจะไม่มีอยู่ใน styles.css ที่ build ไว้แล้ว
   --------------------------------------------------------------------- */
.ann-form { display: flex; flex-direction: column; gap: 12px; }
.ann-label {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--on-surface-variant, #49454f);
}
.ann-input {
  width: 100%; min-height: 88px; resize: vertical;
  padding: 12px 14px; border-radius: 12px;
  border: 1px solid var(--outline-variant, #cac4d0);
  background: var(--surface-container-low, #f7f8fb);
  color: inherit; font: inherit; line-height: 1.7;
}
.ann-input:focus { outline: none; border-color: var(--primary, #1a2947); box-shadow: 0 0 0 2px rgba(30, 58, 138, .18); }

/* ตัวนับตัวอักษรกับสวิตช์เปิด/ปิดอยู่บรรทัดเดียวกันบนจอกว้าง แต่บนมือถือ
   ต้องยอมให้ตกบรรทัด ไม่งั้นข้อความสวิตช์จะถูกบีบจนอ่านไม่ออก */
.ann-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
.ann-toggle { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.ann-toggle input { width: 18px; height: 18px; accent-color: var(--primary, #1a2947); cursor: pointer; }
.ann-count { font-size: 12px; color: var(--text-secondary, #6b7280); font-variant-numeric: tabular-nums; }
.ann-error {
  margin: 0; font-size: 13px; border-radius: 10px; padding: 8px 12px;
  background: var(--error-container, #fde8e8); color: var(--on-error-container, #8c1d18);
}
.ann-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
.ann-meta { margin: 0; font-size: 12px; color: var(--text-secondary, #6b7280); }
.ann-save {
  height: 44px; padding: 0 24px; border: 0; border-radius: 9999px; cursor: pointer;
  background: var(--primary, #1a2947); color: #fff; font: inherit; font-weight: 700;
  transition: filter .15s, opacity .15s;
}
.ann-save:hover { filter: brightness(1.1); }
.ann-save:disabled { opacity: .6; cursor: default; }

html.dark .ann-input { background: #131a25; border-color: #2a3648; }
html.dark .ann-label, html.dark .ann-count, html.dark .ann-meta { color: #9aa4b2; }

/* บนมือถือปุ่มบันทึกควรกดง่ายสุด ให้กว้างเต็มแถวแทนที่จะเป็นปุ่มเล็กมุมขวา */
@media (max-width: 640px) {
  .ann-actions { flex-direction: column; align-items: stretch; }
  .ann-save { width: 100%; }
  .ann-meta { order: 2; text-align: center; }
}

/* ---------------------------------------------------------------------
   หน้าต่างประวัติการเข้าใช้ — เลื่อนดูรายการที่เหลือไม่ได้ (23 ส.ค. 2569)

   markup เขียน max-h-[88vh] ไว้ แต่คลาสนั้นไม่เคยถูกใช้ที่อื่นในโปรเจค จึง
   ไม่มีอยู่ใน styles.css ที่ build ไว้ล่วงหน้า (ปัญหาเดียวกับ .member-edit-panel
   ที่ต้องเขียน max-height เองด้วยเหตุผลนี้) ผลคือกล่องไม่มีเพดานความสูง มัน
   ยืดตามจำนวนแถวจนทะลุขอบล่างของจอ และ .visit-list ก็ไม่เคยเตี้ยกว่าเนื้อหา
   ของตัวเอง overflow-y: auto จึงไม่เคยทำงาน — แถวที่เหลือเลยอยู่นอกจอถาวร

   dvh มาหลัง vh: บนมือถือ 1vh คิดจากความสูงตอนแถบที่อยู่เว็บถูกซ่อน กล่องที่
   สูง 88vh จึงยังล้นพื้นที่ที่มองเห็นจริงอยู่ดี ส่วน vh บรรทัดแรกเป็นค่าสำรอง
   ให้เบราว์เซอร์เก่าที่ไม่รู้จัก dvh
   --------------------------------------------------------------------- */
.visit-panel {
  max-height: 88vh;
  max-height: 88dvh;
}

/* กันเหนียวอีกชั้น: ถ้าเนื้อหาส่วนหัว/ตัวกรอง/สรุป รวมกันสูงจนกล่องยังล้น
   (จอเตี้ยมากๆ หรือผู้ใช้ตั้งขนาดตัวอักษรใหญ่) ให้เลื่อนทั้งกล่องได้ แทนที่
   จะตัดทิ้งเฉยๆ */
#history-modal { overflow-y: auto; }

/* ---------------------------------------------------------------------
   กล่องประกาศจากเจ้าหน้าที่ (หน้าหลักนักศึกษา)

   เดิมเป็นหัวเรื่องกับข้อความลอยอยู่บนแถบ hero เฉยๆ อ่านปนกับสถานะที่อยู่
   ใต้มัน ใส่กรอบให้เห็นว่าเป็นก้อนเดียวจบในตัว

   สีพื้นใช้ค่าโปร่งใสของสีหลัก ไม่ใช่สีทึบ: แถบ hero ที่กล่องนี้วางอยู่เป็น
   ไล่เฉดสีเข้ม กรอบจึงต้องยอมให้พื้นหลังด้านหลังส่องผ่าน ไม่งั้นจะเห็นเป็น
   สี่เหลี่ยมทึบแปะทับ และเมื่อสลับธีมสว่าง/มืด ความจางก็ปรับตามพื้นที่อยู่
   ข้างหลังเองโดยไม่ต้องกำหนดสีใหม่ทั้งชุด
   --------------------------------------------------------------------- */
.announcement-card {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 16px;
  border-radius: 16px;
  background: rgba(96, 165, 250, .13);
  border: 1px solid rgba(147, 197, 253, .32);
  box-shadow: 0 10px 24px -18px rgba(2, 6, 23, .9);
  backdrop-filter: blur(2px);
}
.announcement-icon {
  flex-shrink: 0;
  font-size: 22px;
  line-height: 1.35;
  color: rgb(147, 197, 253);
}
.announcement-body { min-width: 0; }
.announcement-title {
  font-size: 15px; font-weight: 700; line-height: 1.4;
  color: #fff; margin: 0 0 2px;
}
.announcement-text {
  /* เจ้าหน้าที่อาจขึ้นบรรทัดใหม่ในช่องพิมพ์ ให้แสดงตามที่พิมพ์จริง แต่ยัง
     ตัดบรรทัดเองเมื่อยาวเกินจอ */
  white-space: pre-line;
  margin: 0;
  font-size: 14px; line-height: 1.65;
  color: rgba(255, 255, 255, .82);
  overflow-wrap: anywhere;
}

html.dark .announcement-card {
  background: rgba(96, 165, 250, .09);
  border-color: rgba(147, 197, 253, .22);
}

@media (max-width: 480px) {
  .announcement-card { padding: 12px 14px; gap: 10px; border-radius: 14px; }
  .announcement-title { font-size: 14px; }
  .announcement-text { font-size: 13.5px; }
}

/* ---------------------------------------------------------------------
   หน้าภาพรวมของเจ้าหน้าที่ — แถวกราฟวงกลม (24 ส.ค. 2569)

   เขียนเป็น CSS ธรรมดาเช่นเดียวกับส่วนอื่นที่เพิ่มทีหลัง เพราะโปรเจคเสิร์ฟ
   styles.css ที่ build ไว้แล้ว คลาส Tailwind ที่ยังไม่เคยถูกใช้ที่อื่นจะไม่มี
   อยู่ในไฟล์ผลลัพธ์
   --------------------------------------------------------------------- */
.dash-donut-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.dash-panel {
  background: #fff;
  border: 1px solid rgba(202, 196, 208, .3);
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .06);
  padding: 20px 22px 22px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.dash-panel-head { margin-bottom: 14px; }
.dash-panel-head h2 {
  font-size: 17px; font-weight: 700; line-height: 1.35;
  color: var(--primary, #1a2947); margin: 0;
}
.dash-panel-head p {
  font-size: 12.5px; line-height: 1.5; margin: 2px 0 0;
  color: var(--on-surface-variant, #49454f);
}
.dash-panel-body { flex: 1; min-width: 0; }

/* วงกลมกับคำอธิบายเรียงกันในแนวตั้ง — กล่องกว้างเพียงหนึ่งในสามของหน้า
   การวางคำอธิบายไว้ข้างวงจะเหลือความกว้างให้ชื่อแผนกไม่พอ */
.donut-wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; min-width: 0; }
.donut-chart { position: relative; width: 168px; height: 168px; flex-shrink: 0; }
.donut-svg { width: 100%; height: 100%; display: block; }
.donut-center {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  pointer-events: none;
}
.donut-center-value { font-size: 27px; font-weight: 700; line-height: 1.1; color: var(--primary, #1a2947); }
.donut-center-unit { font-size: 12px; color: var(--text-secondary, #6b7280); margin-top: 1px; }

.donut-legend { list-style: none; margin: 0; padding: 0; width: 100%; min-width: 0; }
.donut-legend li {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; line-height: 1.5; padding: 3px 0; min-width: 0;
}
.donut-dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
/* ชื่อแผนกภาษาไทยยาวกว่าช่องที่มี ตัดด้วย ellipsis แทนการดันตัวเลขตกบรรทัด
   (min-width: 0 จำเป็น — flex item ตั้งต้นที่ auto ซึ่งไม่ยอมหดต่ำกว่าเนื้อหา) */
.donut-label {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--text-primary, #1f2937);
}
.donut-value { flex-shrink: 0; font-size: 12.5px; color: var(--text-secondary, #6b7280); font-variant-numeric: tabular-nums; }
.donut-empty { color: var(--text-secondary, #6b7280); font-style: italic; justify-content: center; }

html.dark .dash-panel { background: #101828; border-color: #2a3648; }
html.dark .dash-panel-head h2 { color: #9db8e8; }
html.dark .dash-panel-head p, html.dark .donut-value, html.dark .donut-center-unit { color: #9aa4b2; }
html.dark .donut-label { color: #e2e8f0; }
html.dark .donut-center-value { color: #9db8e8; }
html.dark { --donut-track: #26334a; }

@media (max-width: 1024px) {
  .dash-donut-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
  .dash-donut-row { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .dash-panel { padding: 16px 16px 18px; border-radius: 14px; }
  .donut-chart { width: 148px; height: 148px; }
  .donut-center-value { font-size: 24px; }
}

/* แถวคำอธิบายที่กดได้ (ชื่อแผนกในกราฟวงกลม) — ต้องดูออกว่ากดได้ก่อนกด */
.donut-legend li.donut-clickable { cursor: pointer; border-radius: 6px; margin: 0 -6px; padding-left: 6px; padding-right: 6px; }
.donut-legend li.donut-clickable:hover { background: rgba(37, 99, 235, .08); }
.donut-legend li.donut-clickable:hover .donut-label { text-decoration: underline; }
.donut-legend li.donut-clickable:focus-visible { outline: 2px solid var(--primary, #1a2947); outline-offset: 1px; }
html.dark .donut-legend li.donut-clickable:hover { background: rgba(147, 197, 253, .12); }

/* ===================================================================== */
/* Login page — glassmorphism card over an auto-sliding library backdrop  */
/* ===================================================================== */

.login-body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  /* align-items: center บนกล่องที่เลื่อนได้ จะ "ตัดยอด" การ์ดที่สูงกว่าจอ:
     ส่วนที่ล้นขึ้นไปด้านบนเลื่อนขึ้นไปดูไม่ได้เลย ใช้ flex-start คู่กับ
     margin: auto ที่ลูก (.login-shell) แทน — เมื่อมีที่เหลือจะจัดกึ่งกลางให้
     เหมือนเดิม แต่เมื่อการ์ดสูงกว่าจอจะไปเกาะขอบบนแล้วเลื่อนดูได้ครบ */
  align-items: flex-start;
  justify-content: center;
  padding: 24px;
  position: relative;
  /* Let a tall card scroll on short screens instead of being clipped; the
     backdrop is position:fixed so it stays put while the card scrolls. */
  overflow-x: hidden;
  overflow-y: auto;
  /* White text sits over photos regardless of the app's light/dark theme. */
  color: #fff;
}

/* --- backdrop ---------------------------------------------------------- */
.login-bg { position: fixed; inset: 0; z-index: 0; overflow: hidden; }

/* Premium gradient that gently drifts — visible before photos load, and the
   floor beneath the photo slides. */
.login-bg-base {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, #0f2027 0%, #1f3a44 35%, #2c5364 70%, #17262e 100%);
  background-size: 300% 300%;
  animation: login-bg-drift 26s ease-in-out infinite;
}
@keyframes login-bg-drift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

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

   ทำด้วย CSS ล้วน ไม่มี JavaScript คอยจับเวลา: ทั้ง opacity และ transform
   วิ่งบน compositor ของเบราว์เซอร์ เครื่องที่แรงน้อยจึงไม่กระตุก และหน้าเว็บ
   ไม่ต้องโหลดสคริปต์เพิ่มเพื่อทำสิ่งที่ CSS ทำได้อยู่แล้ว

   ซูมเข้าช้าๆ ตลอดรอบ (Ken Burns) ภาพนิ่งจึงไม่ดูเป็นภาพนิ่ง ค่าที่กระโดด
   กลับตอนจบรอบมองไม่เห็น เพราะตอนนั้นความทึบเป็นศูนย์อยู่แล้ว */
.login-bg-layer {
  position: absolute;
  inset: -4%;                 /* เผื่อขอบไว้ให้การเบลอและการซูม ไม่ให้เห็นขอบขาว */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
  will-change: opacity, transform;
  animation: login-bg-cycle 27s ease-in-out infinite;
}
.login-bg-layer.layer-1 { animation-delay: 0s; }
.login-bg-layer.layer-2 { animation-delay: 9s; }
.login-bg-layer.layer-3 { animation-delay: 18s; }

@keyframes login-bg-cycle {
  0%     { opacity: 0; transform: scale(1.06); }
  3.5%   { opacity: 1; }
  33%    { opacity: 1; }
  36.5%  { opacity: 0; }
  100%   { opacity: 0; transform: scale(1.16); }
}

/* ทั้งสามใบใช้การปรับภาพชุดเดียวกัน เพราะตอนนี้ผลัดกันเป็นภาพหลักคนละช่วง
   ไม่ได้แบ่งหน้าที่เป็นชั้นหน้า-ชั้นหลังเหมือนตอนวางซ้อนกัน */
.login-bg-layer {
  filter: blur(3px) saturate(1.04) contrast(1.05) brightness(0.74);
}

.login-bg-grade {
  position: absolute;
  inset: 0;
  /* โทนสีดึงมาจากตัวรูปเอง: ไม้สักอุ่น เพดานครีม ป้ายหมวดหมู่สีกรม
     จึงไล่จากกรมเข้มไปหาน้ำเงินอมเขียว ไม่ใช่ฟ้าสดที่ตีกับสีไม้ */
  background: linear-gradient(155deg, #1a2338 0%, #2f3a52 48%, #4a4536 100%);
  /* mix-blend-mode: color เปลี่ยนเฉพาะ "สี" ไม่แตะแสงเงาและรายละเอียด
     รูปทั้งสามใบจึงถูกดึงเข้าโทนเดียวกันโดยยังเห็นเนื้อภาพอยู่ ต่างจากการ
     เอาแผ่นสีทึบมาทาบซึ่งจะทำให้ภาพแบนไปทั้งใบ
     ค่านี้คือตัวหลักที่ทำให้ "สามรูปกลายเป็นฉากเดียว" — สูงกว่านี้จะเริ่ม
     กลบสีไม้จนห้องสมุดกลายเป็นสีเขียวอมเทาทั้งห้อง
     ภาพจริงชุดนี้ถ่ายห้องเดียวกันด้วยแสงเดียวกัน จึงกลมกลืนกันเองอยู่แล้ว
     ค่านี้เหลือไว้แค่พอให้ทั้งฉากมีโทนร่วม ไม่ได้ทำหน้าที่หลักเหมือนเมื่อก่อน */
  opacity: 0.14;
}

/* หรี่แสงเฉพาะบริเวณหลังการ์ด — ตรงกลางจอเข้มและเรียบที่สุด รายละเอียดของ
   รูปจึงเบาลงพอดีตรงที่ตัวหนังสือไปวางอยู่ ส่วนขอบจอยังเห็นห้องสมุดชัด */
.login-bg-scrim {
  position: absolute;
  inset: 0;
  background: radial-gradient(58% 62% at 50% 50%,
    rgba(5, 12, 24, 0.40) 0%,
    rgba(5, 12, 24, 0.22) 45%,
    rgba(5, 12, 24, 0.05) 75%,
    rgba(5, 12, 24, 0) 100%);
}

/* Soft, mostly-neutral wash: bright in the middle, a gentle darker frame at
   the edges so the card and its white text stay readable without making the
   whole scene heavy. */
.login-bg-overlay {
  position: absolute;
  inset: 0;
  background:
    /* แสงนวลจากด้านบน ให้ฉากมีทิศทางของแสงเดียวกันทั้งจอ */
    radial-gradient(110% 80% at 50% 0%, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0) 58%),
    /* ขอบจอเข้มลง สายตาจึงวิ่งเข้าหากลางจอเอง */
    radial-gradient(130% 120% at 50% 45%, rgba(6, 12, 24, 0) 0%, rgba(6, 12, 24, 0.10) 62%, rgba(4, 9, 18, 0.38) 100%),
    linear-gradient(180deg, rgba(6, 12, 24, 0.06) 0%, rgba(4, 9, 18, 0.20) 100%);
}

/* --- glass card -------------------------------------------------------- */
.login-shell {
  position: relative;
  z-index: 2;
  width: 100%;
  margin: auto 0;          /* คู่กับ align-items: flex-start ด้านบน */
  display: flex;
  justify-content: center;
}

/* ---- การ์ดกระจก ----
   สิ่งที่ทำให้อ่านเป็น "แผ่นแก้ว" ไม่ใช่ "กล่องขาวจางๆ" คือสี่อย่างรวมกัน:
     1. backdrop-filter เบลอ + เพิ่มความอิ่มสีของสิ่งที่อยู่ข้างหลัง แก้วจริง
        ไม่ได้แค่ทำให้มัว แต่รวมแสงให้สีข้างหลังเข้มขึ้นด้วย
     2. พื้นเป็นไล่เฉดเฉียง ไม่ใช่สีเดียวเรียบ แผ่นแก้วรับแสงไม่เท่ากันทั้งแผ่น
     3. เส้นขอบด้านในสว่าง (inset box-shadow ด้านบน) คือความหนาของขอบแก้ว
        ที่รับแสงจากด้านบน — รายละเอียดนี้ตัวเดียวที่ทำให้ดูเป็นวัสดุ
     4. เงาทอดยาวและนุ่ม บอกว่าแผ่นแก้วลอยอยู่เหนือฉาก ไม่ได้แปะติดกับฉาก */
.login-glass {
  position: relative;
  width: 100%;
  max-width: 408px;
  /* Belt and braces against a long unbreakable string widening the card past
     the viewport on small screens. */
  min-width: 0;
  padding: 38px 34px;
  border-radius: 26px;
  background:
    linear-gradient(152deg,
      rgba(255, 255, 255, 0.15) 0%,
      rgba(255, 255, 255, 0.06) 38%,
      rgba(255, 255, 255, 0.03) 62%,
      rgba(255, 255, 255, 0.08) 100%),
    rgba(17, 26, 44, 0.48);
  backdrop-filter: blur(30px) saturate(160%);
  -webkit-backdrop-filter: blur(30px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.20);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.40),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06),
    inset 1px 0 0 rgba(255, 255, 255, 0.08),
    0 2px 10px rgba(3, 8, 18, 0.30),
    0 28px 64px -18px rgba(3, 8, 18, 0.72);
  color: #fff;
}

/* ประกายแสงบนผิวแก้ว วางทับมุมบนซ้ายจางๆ แล้วไหลช้ามาก
   pointer-events: none เพื่อไม่ให้ไปบังการกดช่องกรอกที่อยู่ข้างใต้ */
.login-glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(135deg,
    rgba(255, 255, 255, 0.18) 0%,
    rgba(255, 255, 255, 0.05) 24%,
    rgba(255, 255, 255, 0) 46%);
  opacity: .9;
  animation: login-glass-sheen 22s ease-in-out infinite;
}
@keyframes login-glass-sheen {
  0%, 100% { opacity: .72; }
  50%      { opacity: 1; }
}
/* เนื้อหาต้องอยู่เหนือประกายแสง ไม่งั้นตัวหนังสือจะถูกฝ้าขาวคลุม */
.login-glass > * { position: relative; z-index: 1; }

/* Says which system this is before anything else — someone landing on the URL
   should not have to guess whose library they are signing in to. */
/* หัวการ์ดเรียงเป็นแนวตั้งกึ่งกลาง: ตราวิทยาลัย -> หัวเรื่อง -> ชื่อห้องสมุด */
.login-crest {
  display: block;
  width: 73px;
  height: 73px;
  margin: 0 auto 14px;
  border-radius: 9999px;
  /* ตราเป็นเหรียญพื้นขาว วางบนการ์ดกระจกสีเข้ม เติมวงแสงจางๆ รอบนอกให้ขอบ
     ไม่ตัดกับพื้นหลังจนดูเหมือนสติกเกอร์แปะ */
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .16), 0 10px 26px -10px rgba(3, 8, 18, .75);
}
/* Thai has no spaces, so "ห้องสมุดวิทยาลัยเทคนิคนครนายก" is one unbreakable
   word — without overflow-wrap it forces the card wider than a phone screen
   and the page scrolls sideways. */
.login-identity-org {
  margin: 0 0 22px;
  text-align: center;
  font-size: clamp(14px, 3.4vw, 17px); font-weight: 700;
  line-height: 1.35; letter-spacing: .01em;
  overflow-wrap: anywhere; word-break: break-word;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}
/* ชื่อห้องสมุดกับชื่อวิทยาลัยขึ้นบรรทัดของตัวเอง — ปล่อยให้ขึ้นบรรทัดเองเมื่อไร
   overflow-wrap ข้างบนจะตัดกลางคำเป็น "วิทยาลัยเทคนิค / นครนายก" */
.login-identity-org span { display: block; }

/* Card heading. clamp() so it scales with the viewport instead of staying at
   one desktop size that overwhelms a small phone. */
.login-title {
  font-size: clamp(22px, 5.2vw, 28px);
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  /* หัวเรื่องอยู่บนสุดของการ์ดแล้ว ระยะห่างด้านล่างจึงเป็นช่องไฟก่อนถึงตรา
     ระยะห่างด้านล่างเป็นช่องไฟก่อนถึงชื่อห้องสมุด ซึ่งชิดกว่าช่องไฟก่อนถึง
     ช่องกรอก เพราะสองบรรทัดนี้อ่านเป็นกลุ่มเดียวกัน */
  margin: 0 0 6px;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.28);
}

.login-error {
  margin: 0 0 16px;
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 14px;
  color: #fff;
  background: rgba(220, 38, 38, 0.35);
  border: 1px solid rgba(248, 113, 113, 0.55);
}
.login-error.hidden { display: none; }

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

.login-field label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: rgba(255, 255, 255, 0.7);
  margin-bottom: 7px;
}

.login-input-wrap { position: relative; }
.login-input-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: rgba(255, 255, 255, 0.7);
  font-size: 20px;
  pointer-events: none;
}
.login-input-wrap input {
  width: 100%;
  height: 52px;
  padding: 0 16px 0 46px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.30);
  color: #fff;
  font-size: 15px;
  outline: none;
  transition: border-color .2s, background .2s, box-shadow .2s;
}
.login-input-wrap input.has-toggle { padding-right: 46px; }
.login-input-wrap input::placeholder { color: rgba(255, 255, 255, 0.6); }
.login-input-wrap input:focus {
  border-color: rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
}
/* Browser autofill repaints the field white by default, which would hide the
   white text — force it back to the frosted look. */
.login-input-wrap input:-webkit-autofill,
.login-input-wrap input:-webkit-autofill:hover,
.login-input-wrap input:-webkit-autofill:focus {
  -webkit-text-fill-color: #fff;
  caret-color: #fff;
  transition: background-color 9999s ease-in-out 0s;
}

.login-pw-toggle {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.8);
  border-radius: 8px;
  cursor: pointer;
  transition: background .2s, color .2s;
}
.login-pw-toggle:hover { background: rgba(255, 255, 255, 0.15); color: #fff; }
.login-pw-toggle .material-symbols-outlined { font-size: 20px; }

.login-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  margin-top: 2px;
}
.login-remember {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  user-select: none;
}
.login-remember input {
  width: 16px;
  height: 16px;
  accent-color: #fff;
  cursor: pointer;
}
.login-forgot { color: rgba(255, 255, 255, 0.6); }

.login-submit {
  width: 100%;
  height: 52px;
  margin-top: 6px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
  font-weight: 700;
  font-size: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: background .2s, transform .08s, box-shadow .2s;
}
.login-submit:hover { background: rgba(255, 255, 255, 0.34); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); }
.login-submit:active { transform: scale(0.985); }
.login-submit:disabled { opacity: .6; cursor: not-allowed; }
.login-submit .material-symbols-outlined { font-size: 20px; transition: transform .2s; }
.login-submit:hover .material-symbols-outlined { transform: translateX(3px); }

.login-signup {
  text-align: center;
  margin: 14px 0 0;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.8);
}
/* ลิงก์สมัครสมาชิกเป็นสีฟ้าสว่าง ให้เด่นออกจากข้อความขาวรอบๆ และเข้าธีมเว็บ */
.login-signup a {
  color: #9fd0ff;
  font-weight: 700;
  text-decoration: none;
  transition: color .2s, text-shadow .2s;
}
.login-signup a:hover {
  color: #cbe6ff;
  text-shadow: 0 0 16px rgba(159, 208, 255, 0.5);
}

/* Phones: tighter card, smaller controls. 44px is still at/above the minimum
   comfortable touch target, so this trims bulk without hurting tappability. */
@media (max-width: 480px) {
  /* จอแคบครอบภาพเป็นแถบแคบแล้วขยายขึ้นมาก ภาพจึงเบลอด้วยตัวมันเองอยู่แล้ว
     ใช้ค่าเบลอเท่าจอใหญ่จะเหลือแต่ก้อนสีจนดูเหมือนภาพเสีย */
  /* ภาพเป็นแนวนอน 16:9 พอครอบลงจอมือถือที่สูงกว่ากว้าง background-size: cover
     จะขยายภาพจนเต็มความสูงแล้วตัดด้านข้างทิ้ง — การเลื่อนแนวตั้งจึงไม่มีผลเลย
     เพราะไม่มีส่วนเกินในแนวตั้งให้เลื่อน ต้องเลื่อนแนวนอนแทน
     ค่า 26% ดึงกรอบไปทางซ้ายของภาพ ซึ่งเป็นฝั่งที่มีชั้นหนังสือ แทนที่จะได้
     กลางห้องที่เป็นเพดานกับพื้นเป็นส่วนใหญ่ */
  .login-bg-layer { background-position: 26% 50%; }
  .login-bg-layer { filter: blur(2px) saturate(1.04) contrast(1.05) brightness(0.74); }
  .login-glass { padding: 26px 20px; border-radius: 18px; }
  .login-body { padding: 16px; }
  .login-crest { width: 60px; height: 60px; margin-bottom: 9px; }
  .login-identity-org { margin-bottom: 18px; }
  .login-field label { font-size: 10.5px; margin-bottom: 5px; }
  .login-input-wrap input { height: 46px; font-size: 14px; }
  .login-input-icon { font-size: 18px; left: 12px; }
  .login-input-wrap input { padding-left: 42px; }
  .login-submit { height: 46px; font-size: 15px; }
  .login-row { font-size: 12.5px; }
  .login-signup { font-size: 13px; }

  /* NOTE: the signup phone overrides live at the end of the signup section
     below, not here — the signup base rules are declared after this media
     query, so rules written here would lose to them on equal specificity. */
}

/* Short-but-wide screens (landscape phones): the card must not need scrolling
   just to reach the submit button. */
@media (max-height: 620px) and (min-width: 481px) {
  .login-glass { padding: 24px 28px; }
  /* จอเตี้ยแนวนอน: ตราเล็กลงเพื่อให้ปุ่มเข้าสู่ระบบยังอยู่ในจอโดยไม่ต้องเลื่อน */
  .login-crest { width: 46px; height: 46px; margin-bottom: 5px; }
  .login-title { margin-bottom: 4px; }
  .login-identity-org { margin-bottom: 14px; }
  .login-form { gap: 12px; }
  .login-input-wrap input { height: 46px; }
  .login-submit { height: 46px; }
}

/* เคารพการตั้งค่า "ลดการเคลื่อนไหว" ของเครื่องผู้ใช้: ฉากยังครบทุกชั้น
   แต่หยุดนิ่ง ไม่มีอะไรไหลหรือเต้น */
@media (prefers-reduced-motion: reduce) {
  .login-bg-base { animation: none; }
  /* ปิดการเคลื่อนไหว: ค้างไว้ที่ใบแรกใบเดียว ไม่สลับ ไม่ซูม */
  .login-bg-layer { animation: none !important; opacity: 0; transform: none; }
  .login-bg-layer.layer-1 { opacity: 1; }
  .login-glass::before { animation: none; }
}

/* ===================================================================== */
/* Signup — same glass-over-library-photo look as login, wider for the form */
/* ===================================================================== */
/* ฟอร์มสมัครสมาชิกยาวกว่า จึงเผื่อระยะบนล่างมากกว่าหน้าเข้าสู่ระบบ
   ส่วนการไม่ตัดยอดการ์ดจัดการที่ .login-body / .login-shell ด้านบนแล้ว
   (align-items: flex-start + margin: auto) ทั้งสองหน้าจึงใช้กติกาเดียวกัน */
.login-body.is-signup { padding-top: 40px; padding-bottom: 40px; }
.login-glass.signup-card { max-width: 660px; padding: 34px 34px 30px; }
/* หน้าสมัครสมาชิกมีแค่หัวเรื่อง ไม่มีชื่อห้องสมุดต่อท้ายเหมือนหน้าเข้าสู่ระบบ
   ระยะ 6px ที่ตั้งไว้ให้เป็นช่องไฟระหว่างสองบรรทัดจึงแคบเกินไปเมื่อบรรทัดถัดไป
   คือช่องกรอกช่องแรก */
.signup-card .login-title { margin-bottom: 22px; }

/* Back-to-login button, pinned inside the card so it never overlaps the form
   on narrow screens. */
.signup-back {
  display: inline-flex; align-items: center; gap: 5px;
  color: #fff; text-decoration: none; font-size: 13px; font-weight: 600;
  background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.24);
  padding: 6px 13px 6px 9px; border-radius: 999px; margin-bottom: 16px;
  transition: background .2s;
}
.signup-back:hover { background: rgba(255, 255, 255, 0.24); }
.signup-back .material-symbols-outlined { font-size: 17px; }

.signup-form { display: flex; flex-direction: column; gap: 18px; }
.signup-row { display: grid; gap: 16px; }
.signup-row.cols-3 { grid-template-columns: 140px 1fr 1fr; }
.signup-row.cols-2 { grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) {
  .signup-row.cols-3, .signup-row.cols-2 { grid-template-columns: 1fr; }
}

.signup-field label {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: .07em; color: rgba(255, 255, 255, 0.72); margin-bottom: 6px;
}
.signup-field input,
.signup-field select {
  width: 100%; height: 48px; padding: 0 14px; border-radius: 11px;
  background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.30);
  color: #fff; font-size: 14.5px; outline: none;
  appearance: none; -webkit-appearance: none;
  transition: border-color .2s, background .2s, box-shadow .2s;
}
/* Custom chevron so the native (dark) arrow isn't lost on the frosted field. */
.signup-field select {
  padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 16px;
}
.signup-field input::placeholder { color: rgba(255, 255, 255, 0.6); }
.signup-field input:focus,
.signup-field select:focus {
  border-color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
}
/* The open dropdown list renders on a white popup, so its text must be dark. */
.signup-field select option { color: #212430; }
.signup-field input:-webkit-autofill,
.signup-field input:-webkit-autofill:focus {
  -webkit-text-fill-color: #fff; caret-color: #fff;
  transition: background-color 9999s ease-in-out 0s;
}
.signup-field .pw-wrap { position: relative; }
.signup-field .pw-wrap .pw-icon {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: rgba(255, 255, 255, 0.7); font-size: 20px; pointer-events: none;
}
.signup-field .pw-wrap input { padding-left: 42px; }
.signup-hint { font-size: 12px; color: rgba(255, 255, 255, 0.66); margin: 2px 0 0; }

.signup-submit {
  width: 100%; height: 52px; margin-top: 6px; border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.22);
  color: #fff; font-weight: 700; font-size: 16px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  transition: background .2s, transform .08s, box-shadow .2s;
}
.signup-submit:hover { background: rgba(255, 255, 255, 0.34); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); }
.signup-submit:active { transform: scale(0.985); }
.signup-submit:disabled { opacity: .6; cursor: not-allowed; }
.signup-submit .material-symbols-outlined { font-size: 20px; transition: transform .2s; }
.signup-submit:hover .material-symbols-outlined { transform: translateX(3px); }

/* Phone sizing for the signup card. Declared here, after the base rules
   above, so it actually wins — see the note in the shared 480px block. */
@media (max-width: 480px) {
  .login-body.is-signup { padding-top: 24px; padding-bottom: 24px; }
  .login-glass.signup-card { padding: 24px 18px; }
  .signup-form { gap: 14px; }
  .signup-row { gap: 12px; }
  .signup-field label { font-size: 10.5px; margin-bottom: 5px; }
  .signup-field input,
  .signup-field select { height: 44px; font-size: 14px; }
  .signup-field .pw-wrap input { padding-left: 40px; }
  .signup-field .pw-wrap .pw-icon { font-size: 18px; left: 12px; }
  .signup-submit { height: 46px; font-size: 15px; }
  .signup-hint { font-size: 11.5px; }
  .signup-back { font-size: 12px; padding: 5px 11px 5px 8px; margin-bottom: 12px; }
}

/* =====================================================================
   ระบบจัดการรหัสผ่าน (24 ส.ค. 2569)
   — ปุ่มดูรหัสผ่านในหน้าสมัครสมาชิก
   — ข้อความผิดพลาดรายช่องในหน้าเข้าสู่ระบบ
   — ปุ่มหลักของหน้าเข้าสู่ระบบ/สมัครสมาชิก
   — หน้าเปลี่ยนรหัสผ่าน

   เขียนเป็น CSS ธรรมดา ไม่ใช่คลาสยูทิลิตี้ ด้วยเหตุผลเดิมของไฟล์นี้: โปรเจค
   เสิร์ฟ styles.css ที่ build ไว้ล่วงหน้า คลาสที่ยังไม่เคยถูกใช้ที่อื่นจะไม่มี
   อยู่ในผลลัพธ์และหายไปเงียบๆ โดยไม่มีข้อผิดพลาดให้เห็น
   ===================================================================== */

/* ---- ช่องรหัสผ่านในหน้าสมัครสมาชิก ต้องเว้นที่ให้ปุ่มดวงตาเหมือนหน้าล็อกอิน ---- */
.signup-field .pw-wrap input.has-toggle { padding-right: 46px; }
.signup-field .pw-wrap .login-pw-toggle { right: 6px; }

/* ---- ข้อความผิดพลาดรายช่อง (หน้าเข้าสู่ระบบ) ----
   การ์ดล็อกอินเป็นตัวหนังสือสีขาวบนภาพถ่าย สีแดงเข้มแบบที่ใช้บนพื้นขาวจะ
   อ่านไม่ออกที่นี่ ใช้เฉดอ่อนที่ยังตัดกับพื้นหลังเข้มได้ */
.login-field-error {
  display: block;
  margin: 6px 2px 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: #ffc9c9;
}
.login-field-error[hidden] { display: none; }
.login-field.is-invalid .login-input-wrap input {
  border-color: rgba(255, 138, 138, .95);
  background: rgba(180, 40, 40, .22);
}
.login-field.is-invalid .login-input-wrap input:focus {
  box-shadow: 0 0 0 3px rgba(255, 138, 138, .28);
}
.login-field.is-invalid .login-input-icon { color: #ffc9c9; }

/* ---- ปุ่มหลัก ----
   เดิมปุ่มเป็นกระจกขาวโปร่ง (rgba(255,255,255,.22)) เท่ากับช่องกรอกข้อมูล
   ที่อยู่เหนือมัน จึงอ่านเป็นปุ่มรองหรือปุ่มที่ถูกปิดใช้งาน ทั้งที่เป็นการ
   กระทำหลักของทั้งหน้า เปลี่ยนเป็นสีน้ำเงินทึบของระบบ ซึ่งเป็นสิ่งเดียวใน
   การ์ดที่มีสีเต็ม สายตาจึงไปหยุดที่มันโดยไม่ต้องอ่านก่อน */
.login-submit,
.signup-submit {
  border-color: transparent;
  background: linear-gradient(180deg, #1180d2 0%, #0a63a6 100%);
  box-shadow: 0 10px 22px -10px rgba(10, 22, 40, .78);
}
.login-submit:hover,
.signup-submit:hover {
  background: linear-gradient(180deg, #1a8de0 0%, #0d6fb8 100%);
  box-shadow: 0 14px 28px -12px rgba(10, 22, 40, .88);
}
.login-submit:focus-visible,
.signup-submit:focus-visible {
  outline: 3px solid rgba(255, 255, 255, .85);
  outline-offset: 2px;
}
.login-submit:disabled,
.signup-submit:disabled { box-shadow: none; }

/* วงหมุนระหว่างรอเซิร์ฟเวอร์ตอบ ปุ่มถูกปิดไว้ด้วยจึงกดซ้ำไม่ได้ */
.login-spinner,
.cp-spinner {
  display: none;
  width: 18px;
  height: 18px;
  border: 2.5px solid rgba(255, 255, 255, .42);
  border-top-color: #fff;
  border-radius: 50%;
  animation: pw-spin .7s linear infinite;
}
@keyframes pw-spin { to { transform: rotate(360deg); } }
.login-submit.is-loading .login-submit-arrow { display: none; }
.login-submit.is-loading .login-spinner { display: block; }
.login-submit.is-loading { opacity: 1; }

/* =====================================================================
   หน้าเปลี่ยนรหัสผ่าน (/change-password)
   ใช้มาตรเดียวกับการ์ดหน้าโปรไฟล์: มุมโค้ง 12px ช่องสูง 48px เส้นขอบ
   outline-variant และสีหลักของระบบ
   ===================================================================== */
.cp-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  margin-bottom: 18px;
  padding: 2px 4px;
  font-size: 14px;
  font-weight: 600;
  color: rgb(30 58 138);
  text-decoration: none;
  border-radius: 8px;
}
.cp-back:hover { text-decoration: underline; }
.cp-back:focus-visible { outline: 2px solid rgb(30 58 138); outline-offset: 2px; }
.cp-back .material-symbols-outlined { font-size: 18px; }
html.dark .cp-back { color: rgb(147 197 253); }
html.dark .cp-back:focus-visible { outline-color: rgb(147 197 253); }

.cp-alert,
.cp-success {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 20px;
  padding: 12px 14px;
  border-radius: 12px;
  font-size: 14.5px;
  line-height: 1.55;
}
.cp-alert[hidden],
.cp-success[hidden] { display: none; }
.cp-alert .material-symbols-outlined,
.cp-success .material-symbols-outlined { font-size: 20px; flex: none; margin-top: 1px; }
.cp-alert { background: #fef2f2; border: 1px solid #f0a9a9; color: #991b1b; }
.cp-success { background: #ecfdf5; border: 1px solid #86e0b8; color: #065f46; }
html.dark .cp-alert { background: rgba(127, 29, 29, .28); border-color: rgba(248, 113, 113, .45); color: #fca5a5; }
html.dark .cp-success { background: rgba(6, 78, 59, .35); border-color: rgba(52, 211, 153, .45); color: #6ee7b7; }

/* หน้านี้ทำงานอย่างเดียวคือเปลี่ยนรหัสผ่าน จึงจัดทุกอย่างไว้กึ่งกลางเป็นแนวตั้ง
   แถวเดียว ไม่ใช่วางชิดซ้ายแบบหน้าที่มีข้อมูลหลายคอลัมน์ให้กวาดสายตา
   แคบกว่าหน้าโปรไฟล์ด้วย เพราะการ์ดกว้าง 896px จะเหลือที่ว่างครึ่งขวาทั้งใบ */
.cp-measure { max-width: 560px; }
.cp-hero { text-align: center; }

.cp-form { display: grid; gap: 20px; }
.cp-field { display: grid; gap: 7px; }
.cp-field > label {
  font-size: 14px;
  font-weight: 600;
  color: rgb(69 85 108);
}
html.dark .cp-field > label { color: rgb(184 196 212); }

.cp-wrap { position: relative; display: block; }
.cp-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 20px;
  color: rgb(var(--c-ink-3));
  /* กดตรงไอคอนต้องโฟกัสช่องพิมพ์ ไม่ใช่โดนไอคอนรับไปเอง */
  pointer-events: none;
  transition: color .15s;
}
.cp-wrap input {
  width: 100%;
  height: 48px;
  padding: 0 46px;
  font-family: inherit;
  /* 16px: Safari บน iOS ซูมหน้าเข้าเองเมื่อโฟกัสช่องที่ตัวอักษรเล็กกว่านี้ */
  font-size: 16px;
  color: rgb(15 23 42);
  background: #fff;
  border: 1px solid rgb(203 213 225);
  border-radius: 12px;
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.cp-wrap input::placeholder { color: rgb(var(--c-ink-3)); }
.cp-wrap input:hover { border-color: rgb(var(--c-line-strong)); }
.cp-wrap input:focus {
  border-color: rgb(37 99 235);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .18);
}
.cp-wrap:focus-within .cp-icon { color: rgb(37 99 235); }
html.dark .cp-wrap input {
  color: rgb(233 238 247);
  background: rgb(15 23 42);
  border-color: rgba(148, 163, 184, .3);
}
html.dark .cp-wrap input:focus {
  border-color: rgb(124 176 251);
  box-shadow: 0 0 0 3px rgba(124, 176, 251, .22);
}
/* โหมดมืด: ไอคอนต้องสว่างขึ้น สีเดียวกับโหมดสว่างจะจมไปกับพื้นการ์ดสีเข้ม */
html.dark .cp-icon { color: rgb(var(--c-dm-text-2)); }
html.dark .cp-wrap:focus-within .cp-icon { color: rgb(var(--c-primary-dim)); }

.cp-toggle {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  border-radius: 9999px;
  cursor: pointer;
  color: rgb(100 116 139);
  transition: background-color .15s, color .15s;
}
.cp-toggle:hover { background: rgba(100, 116, 139, .12); color: rgb(30 58 138); }
.cp-toggle:focus-visible { outline: 2px solid rgb(37 99 235); outline-offset: 2px; }
.cp-toggle[aria-pressed="true"] { color: rgb(37 99 235); }
.cp-toggle .material-symbols-outlined { font-size: 20px; }
html.dark .cp-toggle { color: rgb(148 163 184); }
html.dark .cp-toggle:hover { background: rgba(255, 255, 255, .08); color: rgb(203 213 225); }

.cp-error {
  display: block;
  font-size: 13.5px;
  line-height: 1.5;
  color: #b91c1c;
}
.cp-error[hidden] { display: none; }
.cp-field.is-invalid .cp-wrap input { border-color: #dc2626; }
.cp-field.is-invalid .cp-wrap input:focus { box-shadow: 0 0 0 3px rgba(220, 38, 38, .18); }
.cp-field.is-invalid .cp-icon { color: #dc2626; }
html.dark .cp-error { color: #fca5a5; }
html.dark .cp-field.is-invalid .cp-wrap input { border-color: rgba(248, 113, 113, .7); }

/* ---- รายการข้อกำหนด ติ๊กถูกตามที่พิมพ์ ---- */
.cp-rules {
  padding: 14px 16px;
  border: 1px solid rgb(226 232 240);
  border-radius: 12px;
  background: rgb(248 250 252);
}
html.dark .cp-rules { border-color: rgba(148, 163, 184, .22); background: rgba(15, 23, 42, .5); }
.cp-rules-title {
  font-size: 13px;
  font-weight: 700;
  color: rgb(69 85 108);
  margin-bottom: 8px;
}
html.dark .cp-rules-title { color: rgb(184 196 212); }
.cp-rules ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.cp-rules li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: rgb(100 116 139);
}
.cp-rules li .material-symbols-outlined { font-size: 18px; flex: none; }
/* ผ่านแล้วเปลี่ยนทั้งสีและรูปไอคอน ไม่ใช่สีอย่างเดียว คนที่แยกสีเขียว/เทา
   ไม่ออกก็ยังเห็นความต่างจากวงกลมทึบกับวงกลมโปร่ง */
.cp-rules li.ok { color: rgb(21 128 61); }
.cp-rules li.ok .material-symbols-outlined { color: rgb(22 163 74); }
html.dark .cp-rules li { color: rgb(148 163 184); }
html.dark .cp-rules li.ok { color: rgb(110 231 183); }
html.dark .cp-rules li.ok .material-symbols-outlined { color: rgb(52 211 153); }

/* ปุ่มอยู่กึ่งกลางให้ตรงแนวกับทุกอย่างเหนือมัน ปุ่มชิดขวาเป็นแบบของฟอร์มที่
   อยู่ในหน้ารวมหลายส่วน แต่หน้านี้มีปุ่มเดียวและเป็นปลายทางของทั้งหน้า */
.cp-actions { display: flex; justify-content: center; margin-top: 4px; }
.cp-submit { min-width: 240px; }
.cp-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 50px;
  padding: 0 28px;
  font-family: inherit;
  font-size: 16px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(180deg, #1180d2 0%, #0a63a6 100%);
  border: 0;
  border-radius: 9999px;
  cursor: pointer;
  box-shadow: 0 10px 20px -10px rgba(8, 46, 82, .6);
  transition: filter .15s, transform .15s, box-shadow .15s, opacity .15s;
}
.cp-submit:hover:not(:disabled) { filter: brightness(1.07); transform: translateY(-1px); }
.cp-submit:active:not(:disabled) { transform: translateY(0); }
.cp-submit:focus-visible { outline: 3px solid rgba(10, 111, 184, .5); outline-offset: 2px; }
.cp-submit:disabled { opacity: .7; cursor: default; transform: none; filter: none; box-shadow: none; }
.cp-submit .material-symbols-outlined { font-size: 20px; }
.cp-submit.is-loading .material-symbols-outlined { display: none; }
.cp-submit.is-loading .cp-spinner { display: block; }
.cp-submit.is-loading { opacity: 1; }

/* ---- ทางเข้าหน้าเปลี่ยนรหัสผ่านจากหน้าโปรไฟล์ ---- */
.profile-security-link { text-decoration: none; }

@media (max-width: 560px) {
  .cp-form { gap: 18px; }
  .cp-actions { justify-content: stretch; }
  .cp-submit { width: 100%; min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .cp-submit, .cp-wrap input, .cp-icon, .login-submit, .signup-submit { transition: none; }
  .cp-submit:hover:not(:disabled) { transform: none; }
  .login-spinner, .cp-spinner { animation-duration: 1.6s; }
}

/* =====================================================================
   หน้าฝั่งเจ้าหน้าที่ — แก้การล้นแนวนอน (24 ส.ค. 2569)

   วัดจริงทุกความกว้างตั้งแต่ 320 ถึง 1920 ทั้งแนวตั้งและแนวนอน พบว่าหน้า
   แอดมินทั้งสี่หน้าเลื่อนแนวนอนได้ในบางช่วง ซึ่งเป็นอาการที่มือถือจอเล็กและ
   แท็บเล็ตเจอ แต่จอคอมไม่เจอ จึงไม่เคยถูกสังเกต
   ===================================================================== */

/* ต้นเหตุหลัก: <main> เป็น flex container และ flex item ทุกตัวมี min-width: auto
   เป็นค่าเริ่มต้น แปลว่ามันหดต่ำกว่าความกว้างขั้นต่ำของเนื้อหาข้างในไม่ได้
   ตารางที่กว้างจึงดัน main ให้กว้างตามไปด้วย ทั้งที่ตารางถูกห่อด้วย
   overflow-x-auto ไว้แล้ว — กล่องที่ควรเลื่อนได้เลยไม่มีวันได้เลื่อน
   เพราะพ่อของมันยอมกว้างตามให้ทุกครั้ง */
.admin-main { min-width: 0; }
.admin-main > * { min-width: 0; }

/* ---- แถบตัวกรองหน้าสมาชิก ----
   ช่องเลือกสี่ช่องตรึงไว้ที่ 160px และห้ามหด (sm:w-40 sm:shrink-0) รวมกับ
   ช่องค้นหาแล้วต้องการราว 990px พอแถบเมนูซ้ายกิน 256px ไป จอ 768-1024
   จึงเหลือไม่พอ ให้ห่อบรรทัดและหดได้ตามที่มีจริง */
.admin-filter-bar { min-width: 0; flex-wrap: wrap; }
.admin-filter-fields { min-width: 0; flex-wrap: wrap; }
.admin-filter-fields > div {
  min-width: 0;
  flex: 1 1 148px;
}
@media (min-width: 640px) {
  /* ทับ sm:shrink-0 ที่ห้ามช่องเลือกหดตัว */
  .admin-filter-fields > div { flex-shrink: 1; width: auto; }
}

/* flex-basis วัดตามแกนหลัก ไม่ใช่แนวนอนเสมอไป: ต่ำกว่า 640px ตัวครอบเป็น
   flex-col แกนหลักจึงเป็นแนวตั้ง "148px" ด้านบนเลยกลายเป็น "ช่องละสูง 148px"
   ทำให้มีที่ว่างใต้ทุก dropdown เกือบร้อยพิกเซล (เห็นชัดตอนกางตัวกรองบนมือถือ)
   ในแนวตั้งให้สูงเท่าเนื้อหาจริง แล้วปล่อยให้ gap เป็นตัวจัดระยะห่างแทน */
@media (max-width: 639px) {
  .admin-filter-fields > div { flex: 0 0 auto; }
}

/* ---- ช่องค้นหาหน้าประวัติ ----
   min-w-[260px] กว้างกว่าพื้นที่ที่เหลือบนจอ 320px (320 ลบขอบและ padding
   เหลือราว 224px) ปล่อยให้หดได้บนจอที่แคบกว่านั้น */
@media (max-width: 480px) {
  .admin-search-field { min-width: 0 !important; }
}

/* =====================================================================
   อนิเมชันตอนเปลี่ยนหน้า

   เว็บนี้เป็นหลายหน้าจริง (โหลดใหม่ทุกครั้งที่กดลิงก์) ไม่ใช่ single-page
   จังหวะที่ผู้ใช้รู้สึกว่า "เปลี่ยนหน้า" คือตอนหน้าใหม่ถูกวาด อนิเมชันจึงต้อง
   อยู่ที่ขาเข้าของหน้าใหม่ ไม่ใช่ขาออกของหน้าเดิม — ขาออกทำไม่ได้อยู่แล้ว
   เพราะเบราว์เซอร์ทิ้งหน้าเดิมไปตั้งแต่ก่อนเราจะได้ทำอะไร

   ใช้ opacity กับ transform เท่านั้น สองอย่างนี้เบราว์เซอร์ทำบน compositor
   ไม่ต้องคำนวณ layout ใหม่ เครื่องสเปกต่ำจึงไม่กระตุก

   ที่ <body> ใช้ opacity อย่างเดียว ห้ามใส่ transform เด็ดขาด: transform บน
   บรรพบุรุษทำให้ position: fixed ข้างในยึดกับกล่องนั้นแทนที่จะยึดกับหน้าจอ
   แถบหัวเว็บจะหลุดตำแหน่งทันที ส่วน opacity ไม่มีผลข้างเคียงแบบนั้น
   การเลื่อนขึ้นเล็กน้อยจึงไปทำที่ <main> ซึ่งไม่มี fixed อยู่ข้างใน */
body { animation: ntc-page-in 200ms ease-out both; }
@keyframes ntc-page-in { from { opacity: 0; } to { opacity: 1; } }

main { animation: ntc-main-in 260ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes ntc-main-in {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  body, main { animation: none; }
}

/* =====================================================================
   หน้าต่างซ้อน (modal) — ต้องเลื่อนดูได้เสมอเมื่อสูงกว่าจอ

   ทุกตัวเป็น flex ที่จัดกึ่งกลางแนวตั้ง ซึ่งใช้ได้ตราบใดที่กล่องเตี้ยกว่าจอ
   พอสูงกว่า การจัดกึ่งกลางจะดันส่วนเกินขึ้นไปพ้นขอบบน แล้ว overflow ที่เป็น
   visible ก็ไม่มีแถบเลื่อนให้ตามขึ้นไปดู — ส่วนที่หายไปคือหัวเรื่องกับปุ่มปิด

   วัดจริงบนมือถือแนวนอน 768x400: กล่องเช็คอินสูง 514px ขอบบนอยู่ที่ -57px
   และเลื่อนขึ้นไปไม่ได้เลย

   แก้ด้วยรูปแบบเดียวกับการ์ดหน้าเข้าสู่ระบบ: ยึดขอบบนแล้วให้ลูกใช้ margin
   auto — มีที่เหลือก็จัดกึ่งกลางเหมือนเดิม ไม่พอก็ไปเกาะขอบบนแล้วเลื่อนดูได้ครบ
   ===================================================================== */
.ntc-modal {
  align-items: flex-start;
  overflow-y: auto;
  overscroll-behavior: contain;   /* เลื่อนสุดแล้วไม่ไปลากหน้าข้างหลังต่อ */
  padding-top: 16px;
  padding-bottom: 16px;
}
.ntc-modal > * { margin-top: auto; margin-bottom: auto; }

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

/* ตัวเลขสรุป: อยู่ในแถบหัวเรื่องสีกรม จึงใช้สีขาวโปร่งบนพื้นนั้น */
.members-stat-row {
  display: flex;
  gap: 8px;
  width: 100%;
  flex-wrap: wrap;
}
/* บนมือถือไม่มีกล่อง: สามกล่องที่มีพื้นหลัง ขอบ และ padding กินความสูงไป
   ~76px เพื่อแสดงตัวเลขสามตัว. เหลือเป็นตัวเลขกับป้ายกำกับบนบรรทัดเดียวกัน
   (row-reverse ให้ตัวเลขมาก่อน: "1,268 สมาชิกทั้งหมด", "84% จาก 1,504 คน")
   วางบนพื้นกรมของแถบหัวเรื่องตรงๆ กล่องจะกลับมาเมื่อจอกว้างพอที่ md: */
.members-stat {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: row-reverse;
  align-items: baseline;
  justify-content: flex-end;
  gap: 5px;
}
.members-stat-label {
  font-size: 11.5px;
  letter-spacing: 0.02em;
  opacity: 0.75;
  white-space: nowrap;
}
.members-stat-value {
  font-size: 15px;
  line-height: 1.3;
  font-weight: 600;
  white-space: nowrap;
}
@media (max-width: 767px) {
  .members-stat-row { gap: 6px 16px; margin-top: 8px; }
}

@media (min-width: 768px) {
  /* nowrap is load-bearing: the header is a fixed 240px with items-end, so a
     third tile wrapping to a second row pushes the first two up out of it and
     they get clipped. Three tiles fit side by side at every width from md up. */
  .members-stat-row { width: auto; margin-bottom: 8px; gap: 12px; flex-wrap: nowrap; }
  .members-stat {
    flex: 0 0 auto;
    flex-direction: column;
    justify-content: flex-end;
    padding: 12px 16px;
    border-radius: 12px;
    background: rgb(255 255 255 / 0.10);
    border: 1px solid rgb(255 255 255 / 0.16);
    backdrop-filter: blur(4px);
  }
  .members-stat-label { font-size: 11px; letter-spacing: 0.04em; }
  .members-stat-value { font-size: 26px; line-height: 1.15; margin-top: 2px; }
}

/* แถวค้นหา: ช่องค้นหา + ปุ่มตัวกรอง อยู่บรรทัดเดียวกัน */
.members-search-row {
  display: flex;
  gap: 8px;
  align-items: center;
  width: 100%;
}
@media (min-width: 1024px) {
  .members-search-row { flex: 1; }
}

/* type="search" makes Chrome draw its OWN clear button, which sat on top of
   ours and appeared even when the field was empty. Ours is the one wired to
   the in-memory filter, so suppress the native one. */
#members-search::-webkit-search-cancel-button,
#members-search::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.members-search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  color: rgb(var(--c-ink-3));
}
/* .members-search-clear and Tailwind's .hidden are both single classes, and
   this block is appended after the utilities — so without an explicit rule the
   display:flex above wins and the clear button shows on an empty field. */
.members-search-clear.hidden { display: none; }
.members-search-clear:hover { background: rgb(var(--c-bg-2)); }
.members-search-clear .material-symbols-outlined { font-size: 18px; }
.dark .members-search-clear:hover { background: rgb(var(--c-dm-surface-alt)); }

/* ปุ่มพับ/กางตัวกรอง — เห็นเฉพาะจอที่แคบกว่า lg */
.members-filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid rgb(var(--c-line-strong));
  background: rgb(var(--c-bg-2));
  color: rgb(var(--c-ink-2));
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}
.members-filter-toggle .material-symbols-outlined { font-size: 20px; }
.dark .members-filter-toggle {
  border-color: rgb(var(--c-dm-border));
  background: rgb(var(--c-dm-surface-alt));
  color: rgb(var(--c-dm-text-2));
}
.members-filter-badge {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: rgb(var(--c-accent));
  color: #fff;
  font-size: 11px;
  line-height: 20px;
  text-align: center;
}
/* ปุ่มนี้ซ่อนคำว่า "ตัวกรอง" เมื่อจอแคบมาก เหลือแต่ไอคอน + ตัวเลข */
@media (max-width: 400px) {
  .members-filter-toggle span:not(.material-symbols-outlined):not(.members-filter-badge) {
    display: none;
  }
}

.members-filter-fields.is-collapsed { display: none; }
@media (min-width: 1024px) {
  /* จอกว้างพอวางทุกอย่างพร้อมกัน ปุ่มพับจึงไม่ต้องมี */
  .members-filter-toggle { display: none; }
  .members-filter-fields.is-collapsed { display: flex; }
}

/* แถบค้นหาเกาะบนสุดขณะเลื่อนรายชื่อ — แอดมินพิมพ์ค้นหาได้โดยไม่ต้องเลื่อนกลับ
   ขึ้นไป. ใช้เฉพาะจอแคบ เพราะจอกว้างเห็นตัวกรองครบอยู่แล้ว
   top เท่ากับความสูงแถบหัวเว็บ + แถบพอร์ทัลเจ้าหน้าที่ (pt-28 = 112px) */
@media (max-width: 1023px) {
  .members-filter-bar {
    position: sticky;
    top: 104px;
    z-index: 30;
  }
}

/* การ์ดสมาชิกบนมือถือ: ปุ่มจัดการเคยเป็นข้อความลอยชิดขวาสุดของการ์ด ห่างจาก
   ชื่อที่มันหมายถึงจนดูไม่เข้าพวก. เปลี่ยนเป็นปุ่มจริงสองปุ่มกว้างเท่ากัน
   เรียงเต็มความกว้างการ์ด และคั่นด้วยเส้นบางจากข้อมูลด้านบน */
@media (max-width: 700px) {
  #members-tbody td:nth-child(7) {
    text-align: left !important;
    margin-top: 10px;
    padding-top: 10px !important;
    border-top: 1px solid rgb(var(--c-line));
    display: flex;
    gap: 8px;
  }
  .dark #members-tbody td:nth-child(7) { border-top-color: rgb(var(--c-dm-border)); }
  #members-tbody td:nth-child(7) .edit-member-btn,
  #members-tbody td:nth-child(7) .reset-password-btn {
    flex: 1 1 0;
    padding: 9px 8px;
    border-radius: 8px;
    border: 1px solid rgb(var(--c-line-strong));
    background: rgb(var(--c-bg-2));
    font-size: 13px;
    text-align: center;
  }
  .dark #members-tbody td:nth-child(7) .edit-member-btn,
  .dark #members-tbody td:nth-child(7) .reset-password-btn {
    border-color: rgb(var(--c-dm-border));
    background: rgb(var(--c-dm-surface-alt));
  }
  /* margin-left ที่เคยใช้คั่นสองปุ่มตอนเป็นข้อความ ตอนนี้ gap ทำแทนแล้ว */
  #members-tbody .reset-password-btn { margin-left: 0; }
}

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

/* ชิป: ใช้แทนจุดคั่นในการ์ดบนมือถือ. แต่ละค่ามีกรอบของตัวเอง จึงอ่านออกว่า
   เป็นข้อมูลคนละชิ้นโดยไม่ต้องมีอะไรมาคั่น */
@media (max-width: 700px) {
  #members-tbody td:nth-child(3),
  #members-tbody td:nth-child(4),
  #members-tbody td:nth-child(5),
  #active-tbody td:nth-child(3) {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: 999px;
    background: rgb(var(--c-bg-2));
    border: 1px solid rgb(var(--c-line));
    color: rgb(var(--c-ink-2));
    font-size: 11.5px;
    line-height: 1.7;
    white-space: nowrap;
  }
  .dark #members-tbody td:nth-child(3),
  .dark #members-tbody td:nth-child(4),
  .dark #members-tbody td:nth-child(5),
  .dark #active-tbody td:nth-child(3) {
    background: rgb(var(--c-dm-surface-alt));
    border-color: rgb(var(--c-dm-border));
    color: rgb(var(--c-dm-text-2));
  }
  /* ระดับชั้นกับชั้นปีเป็นข้อมูลชิ้นเดียวกันในทางที่คนอ่าน ("ปวส.2") สองชิป
     ติดกันที่แยกกันบอกครึ่งเดียวจึงอ่านแล้วสะดุด — บนการ์ดรวมเป็นชิปเดียว
     โดยต่อเลขชั้นปีเข้ากับระดับชั้นที่ลงท้ายด้วยจุดอยู่แล้ว แล้วซ่อนชิปชั้นปี
     ตารางบนจอคอมยังเป็นสองคอลัมน์ตามเดิม เพราะที่นั่นมีหัวคอลัมน์กำกับ

     .has-year ติดเฉพาะแถวที่มีครบทั้งสองค่า ถ้าขาดค่าใดค่าหนึ่งก็ไม่รวม
     และชิปชั้นปีจะโผล่ตามปกติพร้อมคำว่า "ปี" นำหน้า */
  #members-tbody td:nth-child(4).has-year::after { content: attr(data-year); }
  #members-tbody td:nth-child(4).has-year + td { display: none; }
  #members-tbody td:nth-child(5)::before { content: 'ปี'; margin-right: 4px; }

  /* ชิปแผนกยาวกว่าเพื่อน ให้ตัดท้ายด้วย … แทนที่จะดันชิปอื่นตกบรรทัด
     ต้องใส่ให้ทั้งสองตาราง ไม่ใช่แค่หน้าสมาชิก — ชื่อแผนกที่ยาวที่สุด
     ("การจัดการโลจีสติกส์และซับพลายเซน") ล้นออกนอกขอบการ์ดที่ 390px */
  #members-tbody td:nth-child(3),
  #active-tbody td:nth-child(3) {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    display: inline-block;
  }
  /* ชื่อในการ์ดก็ห้ามตกบรรทัดเช่นกัน */
  #members-tbody td:nth-child(2),
  #active-tbody td:nth-child(1) {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* จอคอม/แท็บเล็ต: ตารางอยู่ใน .overflow-x-auto อยู่แล้ว การสั่ง nowrap จึงทำให้
   ตารางเลื่อนแนวนอนได้เมื่อจำเป็น แทนที่จะยัดคำจนชื่อคนแตกเป็นสองบรรทัด */
@media (min-width: 701px) {
  .admin-table th,
  .admin-table td { white-space: nowrap; }

  /* nowrap on seven columns overflowed the container by ~250px at 1280,
     pushing the จัดการ buttons off-screen. px-6 on every cell was 336px of
     padding alone; 14px a side buys most of that back without crowding. */
  .admin-table th,
  .admin-table td { padding-left: 14px !important; padding-right: 14px !important; }
  .admin-table th:first-child,
  .admin-table td:first-child { padding-left: 20px !important; }
  .admin-table th:last-child,
  .admin-table td:last-child { padding-right: 20px !important; }

  /* แผนกวิชา is the one genuinely long value ("การจัดการโลจีสติกส์และ
     ซับพลายเซน"). It is the column that can afford to be cut — the row is
     already identified by name and ID — so it ellipsises while the name,
     which must never wrap or be cut, keeps its full width. 15ch is the widest
     setting measured to leave the whole table inside 1280 with no horizontal
     scrollbar (the narrowest laptop this is used on); 17ch overflowed by 12px.
     The full name is on the cell's title attribute either way. */
  .members-table tbody td:nth-child(3) {
    max-width: 15ch;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ชื่อ-สกุลได้พื้นที่ที่เหลือทั้งหมด คอลัมน์อื่นกินเท่าเนื้อหาจริง.
     ผูกกับ .members-table เท่านั้น เพราะคอลัมน์ที่ 2 ของตารางประวัติและ
     ตารางกำลังใช้งานอยู่ไม่ใช่ชื่อ */
  .members-table thead th:nth-child(2),
  .members-table tbody td:nth-child(2) { width: 100%; }

  /* ปุ่มจัดการสองปุ่มเคยซ้อนกันสองบรรทัดเพราะคอลัมน์แคบ — เรียงข้างกัน */
  #members-tbody td:nth-child(7) { text-align: right; }
  #members-tbody td:nth-child(7) .reset-password-btn { margin-left: 14px; }
}

/* ---------------------------------------------------------------------
   แถบหัวเรื่องหน้าแอดมินบนมือถือ
   แอดมินติดว่า "มันใหญ่กินพื้นที่ไป" — วัดที่ 390px แล้วคำอธิบายใต้หัวเรื่อง
   กินไป 58px (2 บรรทัด) ในหน้าสมาชิกและหน้าประวัติ และ 86px (3 บรรทัด)
   ในหน้ากำลังใช้งานอยู่ รวมกับ padding แล้วเนื้อหาจริงเริ่มที่ ~334px จาก
   จอสูง 844px

   คำอธิบายพวกนี้เป็นข้อความแนะนำที่อ่านครั้งเดียวก็รู้แล้ว บนมือถือจึงซ่อน
   และเหลือไว้เฉพาะจอกว้างที่มีที่พอ — ไม่ได้ลบทิ้ง

   ข้อยกเว้น: #ad-subtitle ในหน้าภาพรวมไม่ได้ติดคลาสนี้ เพราะมันไม่ใช่ข้อความ
   แนะนำ แต่เป็นตัวเลขการเข้าใช้ที่โหลดมาจริง
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .admin-hero-desc { display: none; }

  /* หัวเรื่องไม่ต้องมีที่ว่างเหนือมันมากขนาดนั้นเมื่อไม่มีคำอธิบายแล้ว
     แตะเฉพาะ padding บน — padding ล่างของหลายหน้าเป็นตัวกำหนดระยะที่การ์ด
     ด้านล่างเกยขึ้นมา ถ้าไปลดด้วยการ์ดจะเลื่อนหลุดตำแหน่ง */
  .admin-hero-pattern { padding-top: 16px !important; }

  /* หัวเรื่องกับคำอธิบายเคยมีระยะห่างเผื่อไว้ ตอนนี้ไม่มีอะไรอยู่ใต้มัน */
  .admin-hero-pattern h1 { margin-bottom: 0 !important; }

  /* หน้าทำเนียบสมาชิกวาง h1 กับตัวเลขสรุปไว้ใน flex ที่เป็น items-end ซึ่งบน
     มือถือแกนขวางคือแนวนอน = ชิดขวา. เดิมมองไม่ออกเพราะคำอธิบายยาวจนกล่อง
     กว้างเต็มแถวอยู่แล้ว พอซ่อนคำอธิบาย กล่องหดเหลือเท่าหัวเรื่องแล้วไหลไป
     ชิดขวาทันที — บังคับให้ยืดเต็มแถวเหมือนเดิม */
  .admin-hero-pattern > div { align-items: stretch; }
}

/* ---------------------------------------------------------------------
   กล่องเตือนใกล้ถึงเวลาออก (หน้าเช็คอิน)
   เดิมเป็นแถบเล็กชิดขอบล่างจอ ซึ่งเล็กเกินไปสำหรับเรื่องที่ถ้าไม่ทำอะไรแล้ว
   ระบบจะเช็คเอาต์ให้เอง — ตอนนี้เป็นกล่องกลางจอพร้อมสั่นและเสียงเตือน
   --------------------------------------------------------------------- */
.reminder-head {
  padding: 26px 22px 20px;
  text-align: center;
  color: #fff;
  background: linear-gradient(160deg, rgb(var(--c-warning)), rgb(var(--c-warning) / 0.82));
}
.reminder-icon {
  font-size: 42px;
  line-height: 1;
  display: block;
  margin: 0 auto 10px;
}
.reminder-title {
  font-size: 19px;
  font-weight: 700;
  line-height: 1.35;
}
.reminder-detail {
  font-size: 14px;
  line-height: 1.6;
  margin-top: 6px;
  opacity: 0.92;
}
.reminder-actions {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.reminder-btn {
  width: 100%;
  min-height: 46px;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 600;
  font-family: inherit;
}
.reminder-btn-primary {
  background: rgb(var(--c-warning));
  color: #fff;
}
.reminder-btn-quiet {
  background: rgb(var(--c-bg-2));
  color: rgb(var(--c-ink-2));
  border: 1px solid rgb(var(--c-line-strong));
}
.dark .reminder-btn-quiet {
  background: rgb(var(--c-dm-surface-alt));
  color: rgb(var(--c-dm-text-2));
  border-color: rgb(var(--c-dm-border));
}

/* จอเตี้ยมากๆ (เช่นแนวนอนบนมือถือ) ยังต้องเห็นปุ่มครบ — .ntc-modal เลื่อนได้อยู่แล้ว
   ตรงนี้แค่ลดขนาดหัวกล่องลงไม่ให้ดันปุ่มตกจอตั้งแต่แรก */
@media (max-height: 520px) {
  .reminder-head { padding: 16px 18px 14px; }
  .reminder-icon { font-size: 30px; margin-bottom: 6px; }
  .reminder-title { font-size: 16px; }
}

/* ---------------------------------------------------------------------
   ประวัติการเช็คชื่อ — แยกแยะรายการให้ง่ายขึ้น
   ทุกแถวหน้าตาเหมือนกันหมด วันที่ซ้ำเต็มทุกบรรทัด และสิ่งเดียวที่บอกว่าเป็น
   เช็คอินหรือเช็คเอาต์คือชิปเล็ก ๆ ริมขวาสุด กวาดตาแล้วแยกไม่ออก จึง
   (1) ยกวันที่ขึ้นเป็นแถบคั่นวัน แล้วในแถวเหลือแค่เวลา
   (2) ใส่แถบสีซ้ายมือตามประเภท เห็นได้ตั้งแต่ต้นบรรทัด
   --------------------------------------------------------------------- */
.logs-day-row td {
  padding: 10px 24px;
  background: rgb(var(--c-line) / 0.55);
  border-top: 1px solid rgb(var(--c-line-strong) / 0.6);
  color: rgb(var(--c-ink) / 1);
}
.dark .logs-day-row td {
  background: rgb(var(--c-dm-surface-alt) / 1);
  border-top-color: rgb(var(--c-dm-border) / 1);
  color: rgb(var(--c-bg-2) / 1);
}
.logs-type-icon { font-size: 15px; line-height: 1; }
.logs-day-label {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.02em;
}
.logs-day-count {
  margin-left: 10px;
  font-size: 11.5px;
  opacity: 0.65;
}
#logs-tbody .logs-row td:first-child { border-left: 4px solid transparent; }
#logs-tbody .logs-row-in td:first-child { border-left-color: rgb(var(--c-success) / 1); }
#logs-tbody .logs-row-out td:first-child { border-left-color: rgb(var(--c-warning) / 1); }

@media (max-width: 700px) {
  /* บนมือถือแถวเป็นการ์ด แถบสีจึงย้ายมาอยู่ที่ขอบซ้ายของทั้งการ์ด */
  #logs-tbody .logs-row td:first-child { border-left: 0; }
  #logs-tbody .logs-row { border-left: 4px solid transparent; }
  #logs-tbody .logs-row-in { border-left-color: rgb(var(--c-success) / 1); }
  #logs-tbody .logs-row-out { border-left-color: rgb(var(--c-warning) / 1); }
  .logs-day-row td { padding: 8px 14px; }
}

/* ---------------------------------------------------------------------
   แผ่นบอกวิธีติดตั้งบน iOS (assets/js/header.js สร้างขึ้นมาเอง)
   Safari ไม่มีปุ่มติดตั้งให้เรียก จึงได้แค่บอกวิธี — เขียนเป็น CSS ของตัวเอง
   ไม่ใช้คลาส Tailwind เพราะ styles.css เป็นบันเดิลที่ build ไว้แล้ว คลาสที่
   ยังไม่มีในไฟล์จะไม่มีผลอะไรเลยแบบเงียบ ๆ
   --------------------------------------------------------------------- */
.install-help {
  position: fixed;
  inset: 0;
  z-index: 160;               /* เหนือโมดัลอื่น (100) ต่ำกว่า toast (200) */
  background: rgba(5, 12, 24, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.install-help-card {
  width: 100%;
  max-width: 360px;
  background: rgb(var(--c-card) / 1);
  color: rgb(var(--c-ink) / 1);
  border-radius: 18px;
  padding: 22px 20px 18px;
  box-shadow: 0 24px 60px rgba(5, 12, 24, 0.35);
}
.dark .install-help-card {
  background: rgb(var(--c-dm-surface) / 1);
  color: rgb(var(--c-bg-2) / 1);
}
.install-help-title { font-size: 17px; font-weight: 700; margin-bottom: 4px; }
.install-help-lead { font-size: 13px; opacity: 0.75; margin-bottom: 14px; }
.install-help-steps { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 12px; }
.install-help-steps li { display: flex; align-items: center; gap: 10px; font-size: 14px; }
.install-help-steps .material-symbols-outlined { font-size: 20px; opacity: 0.7; flex: 0 0 auto; }
.install-help-close {
  width: 100%;
  height: 44px;
  border: 0;
  border-radius: 12px;
  background: rgb(var(--c-primary) / 1);
  color: #fff;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
}

/* ข้อความอธิบายตอนถูกบังคับให้ตั้งรหัสใหม่ (change-password.php) — แทนที่ลิงก์
   ย้อนกลับ ซึ่งตอนนั้นไม่มีที่ให้กลับไป */
.cp-forced {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-bottom: 18px;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgb(var(--c-accent-soft) / 1);
  color: rgb(var(--c-ink) / 1);
  font-size: 13.5px;
  line-height: 1.55;
}
.dark .cp-forced {
  background: rgb(var(--c-dm-surface-alt) / 1);
  color: rgb(var(--c-bg-2) / 1);
}
.cp-forced .material-symbols-outlined { font-size: 20px; flex: 0 0 auto; opacity: 0.8; }

/* ---------------------------------------------------------------------
   การ์ดสถิติที่ "ลอย" ทับขอบล่างของแถบหัวสี (hero) — ประวัติการเช็คชื่อ และ
   กำลังใช้งานอยู่ ทั้งสองหน้าใช้ margin-top ติดลบดึงการ์ดขึ้นไปทับ hero
   ตั้งใจให้เป็นเอฟเฟกต์ลอยบนจอกว้าง ซึ่ง hero มีที่ว่าง (padding-bottom)
   เผื่อไว้พอดี

   บนมือถือ หัวข้อ + คำอธิบาย + ปุ่ม (ถ้ามี) เรียงต่อกันในแนวตั้งแทนแถวเดียว
   กับจอกว้าง เนื้อหาจึงสูงกว่าที่ padding เผื่อไว้ระดับความสูงเดียวกันนี้ยัง
   คงเดิม การดึงการ์ดขึ้นเท่าเดิมจึงไปทับเนื้อหาจริง (ปุ่ม "พิมพ์รายงาน" ใน
   หน้าประวัติ) ไม่ใช่ที่ว่างเปล่าอีกต่อไป — ปิดเอฟเฟกต์นี้บนจอแคบไปเลย ให้
   การ์ดอยู่ต่อจาก hero ตามลำดับปกติแทน */
.hero-stats-float-lg { margin-top: -48px; }
.hero-stats-float-md { margin-top: -32px; }

@media (max-width: 767px) {
  .hero-stats-float { margin-top: 0 !important; }
}

/* ชื่อระบบบนแถบหัว — มือถือใส่ชื่อย่อ ("ห้องสมุดพระครูฯ") จอกว้างใส่ชื่อเต็ม
   ทำเป็นคลาสของตัวเองเพราะ styles.css เป็นบันเดิล Tailwind ที่ build ไว้แล้ว
   sm:hidden / sm:inline ไม่มีอยู่ในไฟล์ ใส่ไปก็ไม่มีผลอะไรแบบเงียบ ๆ */
.app-brand-full { display: none; }
/* 1024px ไม่ใช่ 640px: ชื่อเต็มกว้าง 558px พอบวกปุ่มธีมกับเมนูบัญชีทางขวาแล้ว
   ที่ 640-768px มันไม่พอ และเพราะ whitespace-nowrap ตัวอักษรจะไม่หดแต่ไหล
   ไปทับปุ่มแทน (วัดแล้วที่ 640px ชื่อจบที่ x=606 ทับทั้งสองปุ่มพอดี) */
@media (min-width: 1024px) {
  .app-brand-short { display: none; }
  .app-brand-full { display: inline-block; }
}
/* กันเหนียว: ถ้าชื่อยาวกว่าที่เหลือจริง ๆ (ชื่อผู้ใช้ยาว, จอแคบกว่าที่คาด)
   ให้ตัดด้วย … แทนที่จะไหลไปทับปุ่ม — ทับคือมองไม่เห็นว่ามีปุ่มอยู่ */
.app-brand-full {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* ---------------------------------------------------------------------
   หัวเรื่องหน้าแอดมินที่ช่วงจอแท็บเล็ต (768-1023px)
   ที่ 768px คลาส md: เริ่มทำงาน: แถวหัวเรื่องกลายเป็นแนวนอน และเมนูข้างถูกตรึง
   กว้าง 256px พร้อมกัน เนื้อที่ที่เหลือให้หัวเรื่องจึงราว 200px ขณะที่กล่อง
   "ช่วงเวลา + พิมพ์รายงาน" ข้าง ๆ ไม่ยอมหด ภาษาไทยไม่มีช่องว่างระหว่างคำ
   "ภาพรวมการใช้งานห้องสมุด" จึงถูกหั่นเป็นบรรทัดละพยางค์ ("ภาพ / รวม / การใช้")
   ช่วงนี้กลับไปวางแนวตั้งเหมือนบนมือถือ ซึ่งมีที่ให้หัวเรื่องเต็มบรรทัด
   --------------------------------------------------------------------- */
@media (min-width: 768px) and (max-width: 1023px) {
  .admin-hero-pattern > div {
    flex-direction: column;
    align-items: stretch;
  }
}

/* เมนูข้างของนักศึกษา (หน้าโปรไฟล์/เปลี่ยนรหัสผ่าน) กว้าง 256px ชื่อเต็มยาวเกิน
   หนึ่งบรรทัด และภาษาไทยไม่มีช่องว่างในคำ เบราว์เซอร์จึงหั่นเป็น "ห้องสมุด
   พระครูสุขุม / สมาจารย์ วิทยาลัย / เทคนิคนครนายก" ให้ชื่อห้องสมุดกับชื่อ
   วิทยาลัยอยู่คนละบรรทัด และห้ามตัดกลางชื่อ — ที่ 16px ชื่อที่ยาวกว่ากว้างราว
   220px ยังพอดีในคอลัมน์ */
.sidebar-brand > span { display: block; white-space: nowrap; }
