/* munshi10.css — shared layout stylesheet for zz.pl (layouts/default).
   Extracted 2026-07-26 from the inline <style> block in zz.pl (UI plan §1.8).
   The only CSS still inline in the layout is the $use_sidebar-conditional
   body/sidebar offset override, which needs EP interpolation.
   Bump the ?v= query in the layout <link> when editing this file. */

/* ---------- Select2 → match Bootstrap form-control-sm ---------- */
.select2-container--default .select2-selection--single {
    border: 1px solid #dadce0;
    border-radius: .25rem;
    height: calc(1.8125rem + 2px);
    padding: .25rem .5rem;
    font-size: .875rem;
    outline: none;
    box-shadow: none;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 1.5;
    padding-left: 0;
    font-size: .875rem;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: calc(1.8125rem + 2px);
    width: 1.5rem;
}
.select2-container--default.select2-container--focus .select2-selection--single {
    border-color: #1a73e8;
    box-shadow: 0 0 0 .2rem rgba(26,115,232,.25);
}
.select2-container { width: 100% !important; }

/* ---------- Google-apps base (light) ---------- */
body {
    background-color: #f8f9fa;
    color: #202124;
    font-size: .75rem;
    padding-top: 108px; /* 60 app bar + 48 tab bar — JS overrides for precision */
}

/* Fallback on mobile/tablet — search drops to its own row so the app bar is taller.
   The script below measures the real app bar height and overrides both
   body padding-top and .g-tabbar top for precision. */
@media (max-width: 991.98px) {
    body { padding-top: 170px; }
    .g-tabbar { top: 116px; }
}


/* Typography pass 2026-08-16 (Shiji comparison): form controls at 14px and
   buttons at 13px on desktop — the 12px density stays for body text and
   tables (reports keep their row density), and the POS invoice screen pins
   its own sizes locally (keyboard-flow geometry must not move). Mobile
   already floors inputs at 1rem below. */
input[type="text"], input[type="password"], input[type="email"], textarea, select {
    font-size: .875rem;
    padding: .375rem .75rem;
    height: auto;
}
button { font-size: .8125rem; padding: .375rem .75rem; }
/* Labels: readable size + medium weight so the label/value hierarchy is
   visible (Bootstrap default is 400 at inherited 12px here). */
.form-label { font-size: .8125rem; font-weight: 500; }

/* ---------- Top app bar ---------- */
.g-appbar {
    background-color: var(--bs-primary);
    border-bottom: 0;
    min-height: 52px;
    box-shadow: none;
}
.g-brand {
    color: #fff;
    font-size: 1.25rem;
    font-weight: 500;
    text-decoration: none;
    display: flex;
    align-items: center;
    line-height: 1;
}
.g-brand:hover { color: rgba(255,255,255,.85); }
.g-brand .g-subtle {
    color: rgba(255,255,255,.85);
    font-size: .85rem;
    font-weight: 600;
}
/* System date — readable on the primary appbar in both light
   and dark mode. Bootstrap's .text-muted resolves to a gray
   that disappears against primary, so the span uses
   .g-systemdate instead and we win on specificity. */
.g-appbar .g-systemdate {
    color: rgba(255,255,255,.9);
    font-weight: 500;
}
/* Hotel logo rendered in place of the M / Munshi10 brand. Sized
   to fit the appbar's content area (min-height 52px minus the
   py-1 padding ≈ 44px). Width auto so non-square logos keep
   their aspect ratio; capped so a very wide logo doesn't push
   the search bar off-screen. */
.g-brand-logo {
    height: 36px;
    width: auto;
    max-width: 200px;
    object-fit: contain;
    display: block;
}

/* Circular icon button */
.g-icon-btn {
    width: 40px; height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: #fff;
    border: 0;
    background: transparent;
    text-decoration: none;
    transition: background-color .15s ease, color .15s ease;
}
.g-icon-btn:hover, .g-icon-btn:focus {
    background-color: rgba(255,255,255,.15);
    color: #fff;
}
.g-icon-btn i { font-size: 1.1rem; line-height: 1; }

/* Avatar */
.g-avatar {
    width: 32px; height: 32px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 500;
    font-size: .85rem;
    color: #fff;
    background-color: #1a73e8;
}
.g-avatar-lg { width: 56px; height: 56px; font-size: 1.4rem; border-radius: 12px; }
/* Plain sizing utilities for the generic .rounded-circle initial/icon avatars
   (they bring their own bg/text classes) — replaces inline width/height. */
.avatar    { width: 40px; height: 40px; flex-shrink: 0; }
.avatar-sm { width: 36px; height: 36px; flex-shrink: 0; }
/* Topbar variant: blends with the primary appbar background and
   is outlined with a white border so it reads as a chip rather
   than a coloured pill. */
.g-avatar-tb {
    background-color: transparent;
    border: 1.5px solid #fff;
    color: #fff;
}
/* Missing-photo nudge: a gently pulsing "1" badge that keeps drawing
   the eye until the user uploads a profile photo. */
.g-avatar-nudge { animation: g-avatar-nudge-pulse 2s ease-in-out infinite; }
@keyframes g-avatar-nudge-pulse {
    0%, 100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(220,53,69,.55); }
    50%      { transform: scale(1.18); box-shadow: 0 0 0 5px rgba(220,53,69,0); }
}
@media (prefers-reduced-motion: reduce) { .g-avatar-nudge { animation: none; } }
/* Photo variant: the uploaded avatar fills the square box. */
.g-avatar-img { padding: 0; overflow: hidden; background: none; }
.g-avatar-img > img {
    width: 100%; height: 100%;
    object-fit: cover;
    border-radius: inherit;
    display: block;
}
/* Property dbname pill next to the avatar — same readability
   treatment as the system date. */
.g-appbar .g-dbname {
    color: rgba(255,255,255,.9);
    font-weight: 600;
    letter-spacing: .02em;
}
/* Shared translucent-chip background used for the systemdate
   and dbname pills — gives them a Shiji-style "chip on chrome"
   look against the primary appbar. */
.g-appbar .g-chip {
    background-color: rgba(255,255,255,.12);
    padding: .22rem .6rem;
    border-radius: 999px;
    line-height: 1.3;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    transition: background-color .15s ease;
}
.g-appbar .g-chip:hover { background-color: rgba(255,255,255,.2); }
/* Per-property colour dot inside the dbname chip — derived from
   a hash of the dbname so each property gets a stable accent. */
.g-property-dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
    box-shadow: 0 0 0 1px rgba(255,255,255,.4) inset;
}
/* Vertical divider used to group right-side appbar items
   (date / tools / property / user). 1px hairline that fades
   into the primary background. */
.g-divider {
    display: inline-block;
    width: 1px;
    height: 22px;
    background-color: rgba(255,255,255,.22);
    margin: 0 .35rem;
    vertical-align: middle;
    flex-shrink: 0;
}
/* Greeting text beside the avatar on wide screens. */
.g-appbar .g-username {
    color: rgba(255,255,255,.92);
    font-size: .82rem;
    font-weight: 500;
    line-height: 1;
    max-width: 140px;
}
/* Keyboard hint shown inside the universal search field. */
.g-search .g-kbd {
    font-size: .75rem;
    font-weight: 600;
    color: #6c757d;
    background: rgba(0,0,0,.05);
    border: 1px solid rgba(0,0,0,.1);
    border-bottom-width: 2px;
    border-radius: 4px;
    padding: .05rem .35rem;
    margin: 0 .5rem 0 0;
    white-space: nowrap;
    font-family: inherit;
}

/* Pill search bar */
.g-search {
    background-color: #f1f3f4;
    border-radius: 8px;
    transition: background-color .15s, box-shadow .15s;
}
.g-search .form-control,
.g-search .input-group-text {
    background-color: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
    color: inherit !important;
}
.g-search:focus-within {
    background-color: #fff;
    box-shadow: 0 1px 6px rgba(32,33,36,.28);
}

/* Secondary tab bar */
.g-tabbar {
    background-color: #fff;
    border-bottom: 0;           /* joined with subtab-bar below */
    top: 60px;                  /* JS overrides for precision */
    z-index: 1020;
}
.g-tabbar .nav { flex-wrap: nowrap; overflow-x: auto; }
.g-tabbar .nav::-webkit-scrollbar { height: 0; }
.g-tabbar .nav-link {
    color: #5f6368;
    font-weight: 600;
    font-size: .8rem;
    padding: .7rem 1rem;
    border-bottom: 3px solid transparent;
    white-space: nowrap;
    border-radius: 0;
}
.g-tabbar .nav-link:hover {
    color: #202124;
    background-color: rgba(60,64,67,.04);
}
.g-tabbar .nav-link.active {
    color: #1a73e8;
    border-bottom-color: #1a73e8;
    background-color: transparent;
}

/* ---------- Guesty-style left sidebar (menu_style=sidebar) ----------
   Rendered instead of .g-tabbar when the user flips the top-bar
   toggle. Deep-navy rail with a blue active pill (Guesty palette),
   collapsible to a 64px icon rail (state kept in localStorage).
   The top offset is synced to the real app-bar height by the same
   syncStickyOffsets() script that positions the tab bar. */
#g-sidebar {
    /* --g-appbar-h is set synchronously right after the app bar's
       markup, so the sidebar's very first paint is already flush
       under it — no white-gap flicker between page loads. */
    position: fixed; left: 0; top: var(--g-appbar-h, 60px); bottom: 0;
    width: 232px; z-index: 1019;
    background-color: #13284c;
    display: flex; flex-direction: column;
    padding: .75rem .6rem;
    overflow: hidden;
    transition: width .15s ease;
}
/* The menu list is the scroll region — not the nav itself — so the
   collapse toggle under it stays pinned in view however long the menu is. */
#g-sidebar > ul {
    min-height: 0;
    overflow-y: auto; overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,.25) transparent;
}
body.g-has-sidebar { padding-left: 232px; transition: padding-left .15s ease; }
body.g-has-sidebar.g-sb-collapsed { padding-left: 64px; }
body.g-has-sidebar.g-sb-collapsed #g-sidebar { width: 64px; }
.sb-link {
    display: flex; align-items: center; gap: .65rem;
    padding: .5rem .7rem; margin-bottom: 2px;
    border-radius: .5rem;
    color: rgba(255,255,255,.72);
    font-size: .8rem; font-weight: 500;
    text-decoration: none; white-space: nowrap;
}
.sb-link i { font-size: 1rem; line-height: 1; width: 1.25rem; text-align: center; flex-shrink: 0; }
.sb-link:hover { background-color: rgba(255,255,255,.08); color: #fff; }
.sb-link.active {
    background-color: #3663f5; color: #fff;
    /* white edge-bar: the blue pill alone sits right at ~3:1 non-text
       contrast against the navy rail — the bar keeps "you are here"
       unmistakable (and survives the collapsed icon rail) */
    box-shadow: inset 3px 0 0 rgba(255,255,255,.85);
}
/* Accordion groups: module row + collapsible child links (the same
   sub-tabs the in-page tab strips show, from nav_subtabs). */
.sb-row { display: flex; align-items: center; }
.sb-row .sb-link { flex: 1 1 auto; min-width: 0; }
.sb-caret {
    border: 0; background: transparent;
    color: rgba(255,255,255,.45);
    /* full row height + wider pad: the old 25×21px target was below the
       WCAG 2.5.8 24×24 minimum and easy to miss next to the nav link */
    align-self: stretch; display: flex; align-items: center;
    padding: .3rem .7rem; margin-left: 2px;
    border-radius: .4rem; flex-shrink: 0;
}
.sb-caret:hover { color: #fff; background-color: rgba(255,255,255,.08); }
.sb-caret i { font-size: .7rem; line-height: 1; display: block; transition: transform .15s ease; }
.sb-group.open > .sb-row .sb-caret i { transform: rotate(90deg); }
.sb-kids { display: none; position: relative; margin: .1rem 0 .4rem; }
.sb-group.open > .sb-kids { display: block; }
/* Cloudflare-style indent guide: a thin vertical bar running down the
   left of an open group's children, aligned under the module icon. */
.sb-kids::before {
    content: '';
    position: absolute;
    left: 1.32rem;             /* centre of the 1.25rem module icon */
    top: .2rem; bottom: .2rem;
    width: 2px; border-radius: 1px;
    background-color: rgba(255,255,255,.16);
}
.sb-kid {
    display: block;
    padding: .32rem .7rem .32rem 2.6rem;
    border-radius: .5rem;
    /* .72 matches the parent links: .6 was the weakest text on the page
       (~6.2:1); .72 clears WCAG AAA like the parents do */
    color: rgba(255,255,255,.72);
    font-size: .76rem; font-weight: 400;
    text-decoration: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-kid:hover { color: #fff; background-color: rgba(255,255,255,.06); }
.sb-kid.active {
    color: #fff; background-color: rgba(54,99,245,.38);
    box-shadow: inset 2px 0 0 rgba(255,255,255,.7);
}
.sb-kid.flex-grow-1 { min-width: 0; }
.sb-kid-gear {
    color: rgba(255,255,255,.5);
    padding: .25rem .45rem; margin-right: .15rem;
    border-radius: .4rem; flex-shrink: 0;
    font-size: .75rem; text-decoration: none;
}
.sb-kid-gear:hover { color: #fff; background-color: rgba(255,255,255,.08); }
body.g-sb-collapsed #g-sidebar .sb-caret,
body.g-sb-collapsed #g-sidebar .sb-kids { display: none; }
.sb-collapse {
    border: 0; background: transparent;
    color: rgba(255,255,255,.55);
    display: flex; align-items: center;
    flex-shrink: 0;
    padding: .5rem .7rem; margin-top: .25rem;
    border-radius: .5rem;
    font-size: .75rem; white-space: nowrap;
}
.sb-collapse:hover { background-color: rgba(255,255,255,.08); color: #fff; }
body.g-sb-collapsed #g-sidebar .sb-label { display: none; }
/* Section headers (NN/g: group long nav lists). The text rides the
   .sb-label hide, leaving a thin divider in the collapsed rail. */
.sb-sect {
    padding: .85rem .7rem .2rem;
    font-size: .7rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: .06em;
    color: rgba(255,255,255,.45);
    white-space: nowrap;
}
body.g-sb-collapsed #g-sidebar .sb-sect {
    padding: 0; margin: .55rem .5rem;
    border-top: 1px solid rgba(255,255,255,.18);
}
/* Collapsed-rail flyout panel (built by the sidebar script on hover):
   instant replacement for the slow native title tooltip, and the only
   way to reach a group's children without expanding the whole rail. */
#g-sb-fly {
    position: fixed; z-index: 1050;
    min-width: 12rem; max-width: 16rem;
    background-color: #13284c;
    border: 1px solid rgba(255,255,255,.14);
    border-radius: .5rem;
    box-shadow: 0 .5rem 1.5rem rgba(0,0,0,.4);
    padding: .35rem;
    /* Long groups scroll INSIDE the panel — without this the links past
       the viewport bottom were unreachable (no way to scroll a fixed
       panel with no overflow). */
    max-height: calc(100vh - 16px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,.25) transparent;
}
[data-bs-theme="dark"] #g-sb-fly { background-color: #0e1d38; }
#g-sb-fly .sb-fly-head {
    display: block; padding: .45rem .7rem;
    color: #fff; font-size: .8rem; font-weight: 600;
    text-decoration: none; border-radius: .4rem;
}
#g-sb-fly .sb-fly-head:hover { background-color: rgba(255,255,255,.08); }
#g-sb-fly .sb-fly-kid {
    display: block; padding: .32rem .7rem;
    color: rgba(255,255,255,.72); font-size: .76rem;
    text-decoration: none; border-radius: .4rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#g-sb-fly .sb-fly-kid:hover { color: #fff; background-color: rgba(255,255,255,.08); }
#g-sb-fly .sb-fly-kid.active {
    color: #fff; background-color: rgba(54,99,245,.38);
    box-shadow: inset 2px 0 0 rgba(255,255,255,.7);
}
body.g-sb-collapsed #g-sidebar .sb-link,
body.g-sb-collapsed #g-sidebar .sb-collapse { justify-content: center; }
[data-bs-theme="dark"] #g-sidebar { background-color: #0e1d38; }
@media print { body.g-has-sidebar { padding-left: 0; } }

/* ── Mobile: off-canvas drawer ──────────────────────────────────
   Below lg even the collapsed rail eats too much of a phone
   screen, so the sidebar becomes a slide-in drawer (hamburger in
   the app bar, backdrop behind) and content gets the full width —
   the same footprint the old top menu has on mobile. The
   collapsed-rail styling is neutralised inside the drawer. */
@media (max-width: 991.98px) {
    body.g-has-sidebar,
    body.g-has-sidebar.g-sb-collapsed { padding-left: 0; }
    /* Selectors carry body.g-has-sidebar so they OUTRANK the desktop
       collapsed-rail rules above (body.g-has-sidebar.g-sb-collapsed
       #g-sidebar {width:64px}): fresh sessions under 1200px default to
       g-sb-collapsed (layout boot script), and with lower specificity the
       drawer rendered as a broken 64px rail on phones. */
    #g-sidebar,
    body.g-has-sidebar #g-sidebar,
    body.g-has-sidebar.g-sb-collapsed #g-sidebar {
        width: 272px;
        transform: translateX(-110%);
        transition: transform .2s ease;
        box-shadow: 0 0 24px rgba(0,0,0,.35);
    }
    body.g-sb-open #g-sidebar,
    body.g-has-sidebar.g-sb-open #g-sidebar {
        transform: translateX(0);
        /* above page-level sticky chrome (the POS action bar sits at 1030 —
           it used to paint over the open drawer's bottom items) */
        z-index: 1045;
    }
    body.g-sb-open #g-sb-backdrop { z-index: 1040; }
    body.g-has-sidebar.g-sb-collapsed #g-sidebar .sb-label { display: inline; }
    body.g-has-sidebar.g-sb-collapsed #g-sidebar .sb-link { justify-content: flex-start; }
    body.g-has-sidebar.g-sb-collapsed #g-sidebar .sb-caret { display: block; }
    body.g-has-sidebar.g-sb-collapsed #g-sidebar .sb-group.open > .sb-kids { display: block; }
    #g-sidebar .sb-collapse { display: none; }   /* rail sizing is a desktop concept */
    /* comfortable touch targets */
    #g-sidebar .sb-link  { padding-top: .65rem; padding-bottom: .65rem; }
    #g-sidebar .sb-kid   { padding-top: .5rem;  padding-bottom: .5rem; }
    #g-sidebar .sb-caret { padding: .5rem .6rem; }
    #g-sb-backdrop {
        display: none; position: fixed; inset: 0;
        background: rgba(0,0,0,.45); z-index: 1018;
    }
    body.g-sb-open #g-sb-backdrop { display: block; }
}

/* Info strip */
.g-infostrip {
    background-color: #e8f0fe;
    border-bottom: 1px solid #d2e3fc;
    color: #174ea6;
    font-size: .75rem;
}
.g-infostrip a { color: #1a73e8; font-weight: 500; }

/* Dropdowns */
.dropdown-menu { border: 1px solid #dadce0; border-radius: .5rem; }
.dropdown-item { font-size: .8rem; padding: .5rem 1rem; }
.dropdown-item:hover { background-color: #f1f3f4; }

/* ---------- Sub-tab bar (joined under g-tabbar) ---------- */
.subtab-bar {
    background-color: #fafafa;
    border-top: 1px solid #e8eaed;
    border-bottom: 1px solid #dadce0;
    box-shadow: none;
    margin-left:  calc(var(--bs-gutter-x, 1.5rem) * -0.5);
    margin-right: calc(var(--bs-gutter-x, 1.5rem) * -0.5);
    margin-top: -0.5rem;
    margin-bottom: 1rem;
    padding: 0 2rem;
}
.subtab-bar .nav {
    border-bottom: 0;
    flex-wrap: wrap;
    row-gap: 0;
    gap: 1rem;
}
.subtab-bar .nav-link {
    padding: .35rem 0;
    font-size: .75rem;
    font-weight: 500;
    color: #5f6368;
    line-height: 1.35;
}
.subtab-bar .nav-link:hover {
    color: #1a73e8;
    background-color: transparent;
}
.subtab-bar .nav-underline .nav-link.active,
.subtab-bar .nav-underline .show > .nav-link {
    color: #1a73e8;
}

/* =====================================================
   MOBILE KIT (2026-07-26 plan §Phase 0/1)
   ---------------------------------------------------- */
/* Sticky report headers: one class instead of 7 hand-rolled
   top-offset guesses. Put it on the .table-responsive wrapper's
   table; header sticks below the measured appbar. */
.table-sticky thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bs-body-bg);
    box-shadow: inset 0 -1px 0 var(--bs-border-color);
}
.table-responsive.sticky-wrap {
    max-height: calc(100vh - var(--g-appbar-h, 64px) - 8rem);
    overflow-y: auto;
}
/* Required-field convention: `required` attr on the control +
   .required on its form-label. Replaces hand-rolled asterisks. */
.form-label.required::after {
    content: " *";
    color: var(--bs-danger);
    font-weight: 600;
}
/* Form footer + sticky action bar (moved here from resform_modern so
   every long form shares them — UI plan §2.3). .gx-actionbar docks
   the Save/Post buttons to the viewport bottom while the form is
   scrolled and settles into its natural position at the end. */
.gx-formfoot {
    margin-top: 1rem;
    padding-top: .85rem;
    border-top: 1px solid var(--bs-border-color);
}
.gx-actionbar {
    position: sticky;
    bottom: 0;
    z-index: 100;              /* above form fields; below modals (1050+) */
    background: var(--bs-body-bg);
    padding: .6rem 0;
    border-top: 1px solid var(--bs-border-color);
    box-shadow: 0 -4px 10px -8px rgba(0,0,0,.35);
}
@media (max-width: 767.98px) {
    /* Sub-tab strip: one horizontally-swipeable chip row instead of
       4-5 wrapped rows eating the first screen (reservations-page
       pattern). Scrollbar hidden, edge-bleed padding keeps the
       first/last chips off the screen edge. */
    .subtab-bar { padding: 0 1rem; }
    .subtab-bar .nav {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        gap: 1.25rem;
    }
    .subtab-bar .nav::-webkit-scrollbar { display: none; }
    /* A sub-menu (e.g. the payroll Setup dropdown) must escape the chip
       scroller while open — overflow-x would clip the dropped menu. */
    .subtab-bar .nav:has(.dropdown-menu.show) { overflow: visible; }
    .subtab-bar .nav-link {
        white-space: nowrap;
        padding: .55rem 0;   /* >=40px touch row incl. line-height */
        font-size: .8125rem;
    }
    /* Touch floor: desktop keeps its dense .75rem controls; phones
       get readable text and >=40px targets. 16px on text inputs
       also stops iOS Safari's focus auto-zoom. */
    input.form-control, select.form-select, textarea.form-control {
        font-size: 1rem;
        min-height: 40px;
    }
    /* -sm variants: element-qualified so this outranks the generic
       input.form-control rule above (plain .form-control-sm would lose
       the specificity tie-break and never apply). 16px keeps iOS from
       auto-zooming; 36px is the reduced touch floor for compact controls. */
    input.form-control-sm, select.form-select-sm, textarea.form-control-sm {
        font-size: 1rem;
        min-height: 36px;
    }
    .btn { min-height: 40px; font-size: .9375rem; }
    .btn-sm { min-height: 36px; font-size: .875rem; }
    .form-label { font-size: .8125rem; }
    /* Row-action icon links in tables get a real tap box. Icons are
       markup like <a><i class="bi ..."></i></a>, so match the child —
       a.bi alone matches nothing. */
    td .btn-sm, td .g-icon-btn { min-width: 36px; }
    td a:has(> .bi) {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 36px;
        min-height: 36px;
    }
}

ol.dash-links a { color: var(--bs-primary); text-decoration: none; }
ol.dash-links a:hover { text-decoration: underline; }
ol.dash-links { padding-left: 1.25rem; margin-bottom: 0; }
ol.dash-links li { margin-bottom: .25rem; }
ol.dash-links a.muted { color: var(--bs-secondary); font-size: .85em; }

/* =====================================================
   SHARED PAGE TYPOGRAPHY
   These four classes used to be redeclared inside almost
   every inline template's <style> block — promoted here so
   per-template <style> can stay focused on local widgets.
   Override locally if you need a non-standard size.
   ===================================================== */
.page-title {
    font-size: 1.4rem; font-weight: 400;
    color: var(--bs-body-color);
    letter-spacing: -0.01em; line-height: 1.3; margin: 0;
}
.page-subtitle {
    font-size: 0.85rem; font-weight: 400;
    color: var(--bs-secondary-color);
    margin-top: 2px; line-height: 1.4;
}
.section-label {
    font-size: 0.75rem; font-weight: 500;
    color: var(--bs-secondary-color);
    text-transform: uppercase; letter-spacing: 0.08em;
    margin-bottom: 10px;
}
.section-title {
    font-size: 0.95rem; font-weight: 500;
    color: var(--bs-body-color);
    letter-spacing: -0.005em; margin-bottom: 8px;
}

/* =====================================================
   SHARED WIDGET CLASSES
   Promoted from per-template <style> blocks. Padding /
   font-size were normalised on the dominant variant; the
   handful of templates that previously had 1-pixel padding
   or 0.65/0.78 font-size differences now inherit these.
   The @media print overrides in those templates still win
   because they sit inside a higher-specificity block.
   ===================================================== */
.meta-pill {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px; border-radius: 999px;
    background: var(--bs-tertiary-bg);
    font-size: 0.78rem; color: var(--bs-body-color);
}
.meta-pill .meta-label {
    color: var(--bs-secondary-color); font-weight: 500;
    text-transform: uppercase; font-size: .75rem;
    letter-spacing: 0.05em;
}
.meta-pill .meta-value { font-weight: 500; color: var(--bs-body-color); }

.metric-card {
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    padding: 10px 12px;
}
.metric-card .metric-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
    margin-bottom: 2px;
}
.metric-card .metric-value {
    font-size: 1.3rem;
    font-weight: 400;
    color: var(--bs-body-color);
    line-height: 1.1;
}

/* KPI strip — master-gated dashboard hero stats (see the
   _kpi_strip partial and the masteradmin_kpis helper). */
.kpi-tile {
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    padding: 12px 14px;
    background: var(--bs-body-bg);
}
.kpi-tile .kpi-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--bs-secondary-color);
    margin-bottom: 2px;
}
.kpi-tile .kpi-value {
    font-size: 2rem;
    font-weight: 600;
    line-height: 1.15;
    color: var(--bs-body-color);
}
.kpi-tile .kpi-sub {
    font-size: 0.78rem;
    color: var(--bs-secondary-color);
}
/* Per-tile colour accents for the dashboard KPI strip, cycled by position
   (kpi-a1..6): a coloured left border only. Values stay body-colour — the
   cycled accents are decorative, and painting numbers in them made a
   negative folio balance render green (NN/g: colour should carry meaning,
   so only .kpi-alert colours a value). */
.kpi-tile[class*="kpi-a"] { border-left-width: 3px; }
.kpi-a1 { border-left-color: var(--bs-primary); }
.kpi-a2 { border-left-color: var(--bs-success); }
.kpi-a3 { border-left-color: var(--bs-info); }
.kpi-a4 { border-left-color: #6610f2; }
.kpi-a5 { border-left-color: var(--bs-warning); }
.kpi-a6 { border-left-color: #20c997; }
/* Genuinely alarming stats (overdue, negative stock): red wins the accent. */
.kpi-tile.kpi-alert { border-left-color: var(--bs-danger) !important; }
.kpi-tile.kpi-alert .kpi-value { color: var(--bs-danger); }
/* Tiles that drill down render as anchors — keep text colours, add a
   hover affordance so the click-through is discoverable. */
a.kpi-tile { display: block; text-decoration: none; color: inherit; transition: border-color .1s ease, box-shadow .1s ease; }
a.kpi-tile:hover { border-color: rgba(var(--bs-primary-rgb), .55); box-shadow: 0 2px 8px rgba(0,0,0,.06); }
/* Delta arrows inside kpi-sub */
.kpi-sub .kpi-up   { color: var(--bs-success); }
.kpi-sub .kpi-down { color: var(--bs-danger); }

.dash-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.dash-list > li {
    margin-bottom: 2px;
}
.dash-list > li > a {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border-radius: 6px;
    text-decoration: none;
    color: var(--bs-body-color);
    font-size: 0.9rem;
    transition: background 0.1s;
}
/* Chevron chip, not a number — the old counter implied a step sequence
   that doesn't exist (NN/g: reserve numbering for actual order). */
.dash-list > li > a::before {
    content: "\203A";
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-size: 0.7rem;
    font-weight: 500;
    flex-shrink: 0;
}
.dash-list > li > a:hover {
    background: var(--bs-tertiary-bg);
}
.dash-list > li > a:hover::before {
    background: var(--bs-body-bg);
}

/* FO dashboard: discoverability hint that the room number opens the
   status-change modal; brightens on row hover. */
.js-room-status .room-edit-hint { font-size: .7rem; opacity: .45; }
.js-room-status:hover .room-edit-hint { opacity: .9; }

/* FO dashboard tab strip: swipe chips on phones (house subtab pattern) —
   the wrapped pills pushed the KPI tiles a full screen down. */
@media (max-width: 767.98px) {
    #fo-dash-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    #fo-dash-tabs::-webkit-scrollbar { display: none; }
    #fo-dash-tabs .nav-link { white-space: nowrap; }
}

.video-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-body-bg);
    text-decoration: none;
    color: var(--bs-body-color);
    font-size: 0.85rem;
    transition: background 0.1s, border-color 0.1s;
}
.video-pill:hover {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
}

/* =====================================================
   DARK MODE OVERRIDES (Bootstrap 5.3 data-bs-theme)
   ===================================================== */
[data-bs-theme="dark"] body {
    background-color: #1b1b1f;
    color: #e8eaed;
}
/* Appbar stays on var(--bs-primary) in both themes — colors set
   in the light-mode rules above already use white-on-primary, so
   no dark-mode overrides are needed for .g-appbar / .g-brand /
   .g-icon-btn. */

[data-bs-theme="dark"] .g-search { background-color: #303134; }
[data-bs-theme="dark"] .g-search:focus-within {
    background-color: #3c4043;
    box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
[data-bs-theme="dark"] .g-search .form-control::placeholder { color: #9aa0a6; }
[data-bs-theme="dark"] .g-search .input-group-text i { color: #9aa0a6 !important; }

[data-bs-theme="dark"] .g-tabbar { background-color: #202124; }
[data-bs-theme="dark"] .g-tabbar .nav-link { color: #9aa0a6; }
[data-bs-theme="dark"] .g-tabbar .nav-link:hover {
    color: #e8eaed;
    background-color: rgba(232,234,237,.04);
}
[data-bs-theme="dark"] .g-tabbar .nav-link.active {
    color: #8ab4f8;
    border-bottom-color: #8ab4f8;
}

[data-bs-theme="dark"] .subtab-bar {
    background-color: #27282b;
    border-top-color: #3c4043;
    border-bottom-color: #3c4043;
}
[data-bs-theme="dark"] .subtab-bar .nav-link { color: #9aa0a6; }
[data-bs-theme="dark"] .subtab-bar .nav-link:hover { color: #8ab4f8; }
[data-bs-theme="dark"] .subtab-bar .nav-underline .nav-link.active,
[data-bs-theme="dark"] .subtab-bar .nav-underline .show > .nav-link { color: #8ab4f8; }

/* Flatpickr (the house date picker, loaded globally) — inputs look
   like normal Bootstrap fields; calendar follows the app theme. */
.flatpickr-input { background-color: var(--bs-body-bg); }
[data-bs-theme="dark"] .flatpickr-calendar {
    background: #27282b; color: #e8eaed; border-color: #3c4043;
}
[data-bs-theme="dark"] .flatpickr-day { color: #e8eaed; }
[data-bs-theme="dark"] .flatpickr-day:hover,
[data-bs-theme="dark"] .flatpickr-day.today {
    background: #3c4043; border-color: #3c4043;
}
[data-bs-theme="dark"] .flatpickr-day.selected {
    background: var(--bs-primary); border-color: var(--bs-primary);
}
[data-bs-theme="dark"] .flatpickr-months .flatpickr-month,
[data-bs-theme="dark"] .flatpickr-weekdays,
[data-bs-theme="dark"] .flatpickr-weekday {
    color: #9aa0a6; background: transparent;
}
[data-bs-theme="dark"] .flatpickr-current-month input.cur-year,
[data-bs-theme="dark"] .flatpickr-current-month .flatpickr-monthDropdown-months {
    color: #e8eaed;
}

[data-bs-theme="dark"] .dropdown-menu {
    background-color: #2d2e31;
    border-color: #3c4043;
    color: #e8eaed;
}
[data-bs-theme="dark"] .dropdown-item { color: #e8eaed; }
[data-bs-theme="dark"] .dropdown-item:hover { background-color: #3c4043; color: #fff; }

/* Select2 dark */
[data-bs-theme="dark"] .select2-container--default .select2-selection--single {
    background-color: #2d2e31;
    border-color: #3c4043;
    color: #e8eaed;
}
[data-bs-theme="dark"] .select2-container--default .select2-selection--single .select2-selection__rendered {
    color: #e8eaed;
}
[data-bs-theme="dark"] .select2-dropdown {
    background-color: #2d2e31;
    border-color: #3c4043;
    color: #e8eaed;
}
[data-bs-theme="dark"] .select2-results__option { color: #e8eaed; }
[data-bs-theme="dark"] .select2-results__option--highlighted[aria-selected] {
    background-color: #3c4043;
    color: #fff;
}
[data-bs-theme="dark"] .select2-search--dropdown .select2-search__field {
    background-color: #202124;
    border-color: #3c4043;
    color: #e8eaed;
}

/* Info strip dark */
[data-bs-theme="dark"] .g-infostrip {
    background-color: #1f3a5f;
    border-bottom-color: #2c4a7a;
    color: #aecbfa;
}
[data-bs-theme="dark"] .g-infostrip a { color: #8ab4f8; }

/* =====================================================
   REPORT KIT
   Promoted 2026-07-06 from ~33 report templates that
   carried per-page copies (two forks: with and without
   !important on the row backgrounds — unified on
   !important so the production-family pages keep their
   shading). See claude-notes/ui-css-consistency-review.
   ===================================================== */
.report-th a {
    color: inherit; text-decoration: none;
    display: inline-flex; align-items: center; gap: 4px;
}
.report-th a:hover { color: var(--bs-body-color); }
.report-th a .bi { font-size: .7rem; opacity: 0.8; }
.report-subtotal { background: var(--bs-tertiary-bg) !important; font-weight: 500; }
.report-grand-total {
    background: var(--bs-tertiary-bg) !important; font-weight: 600;
    border-top: 2px solid var(--bs-border-color) !important;
}
.checkbox-group {
    display: flex; flex-wrap: wrap; gap: 6px 14px;
    padding: 10px 12px;
    background: var(--bs-tertiary-bg);
    border-radius: 6px;
}
.section-label-inline {
    color: var(--bs-secondary-color); font-size: 0.75rem;
    font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase;
}
.dash-list > li .aux-link {
    color: var(--bs-secondary-color); font-size: 0.78rem;
    margin-left: 6px; text-decoration: none;
}
.dash-list > li .aux-link:hover { color: var(--bs-body-color); }

/* Filter kit — shared by guest/customer profiles, the reservations
   list, audittrail and the converted hand-rolled filter bars. */
.filter-grid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.4rem 0.85rem;
}
.filter-field { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }
.filter-field > label {
    flex: 0 0 auto; min-width: 60px; max-width: 88px; margin-bottom: 0;
    font-size: 0.75rem; font-weight: 500;
    color: var(--bs-secondary-color); text-align: right; line-height: 1.15;
}
.filter-field .form-control,
.filter-field .form-select {
    flex: 1 1 auto; min-width: 0;
    padding: 0.2rem 0.5rem; font-size: 0.82rem; line-height: 1.3; height: auto;
}
@media (max-width: 1399.98px) { .filter-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 991.98px)  { .filter-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 767.98px)  { .filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 575.98px)  { .filter-grid { grid-template-columns: 1fr; } }
@media (max-width: 767.98px) {
    /* Touch floor for the filter kit: the dense 0.82rem desktop sizing
       above outranks the generic input.form-control floor (two classes vs
       one), so restate it here — 16px also stops iOS focus auto-zoom. */
    .filter-field .form-control,
    .filter-field .form-select {
        font-size: 1rem;
        min-height: 40px;
        padding: .3rem .5rem;
    }
}

/* GX reservation-form kit — resform_modern/_search, idupload,
   edit_group, res_rates_daily (edit_group keeps its .gx-section
   fork; idupload its .gx-section-title fork). */
.gx-pagehead {
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
    padding: .5rem 0 .75rem;
    margin-bottom: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.gx-pagehead h5 { margin: 0; font-weight: 500; font-size: 1.05rem; }
.gx-pagehead .gx-sub {
    color: var(--bs-secondary-color);
    font-size: .8rem; margin-top: 2px;
}
.gx-section {
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: .5rem;
    padding: .9rem 1.1rem;
    margin-bottom: 1rem;
}
.gx-section-title {
    font-size: .75rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    margin-bottom: .65rem;
    padding-bottom: .3rem;
    border-bottom: 1px solid var(--bs-border-color);
}

/* Import-wizard kit — data_import, inventory_import_parts,
   pos_import_parts. */
.excel-sample { border: 1px solid var(--bs-border-color); border-radius: 8px; overflow: hidden; }
.excel-sample thead tr { background: var(--bs-tertiary-bg); }
.excel-sample th, .excel-sample td { padding: 4px 8px; border: 1px solid var(--bs-border-color); }
.excel-sample .excel-header-row { background: var(--bs-success-bg-subtle); font-weight: 500; }
.excel-sample .excel-row-num { background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); text-align: center; font-weight: 400; width: 36px; }
.excel-sample .excel-col-letter { color: var(--bs-secondary-color); text-align: center; font-weight: 400; }
.excel-sample .excel-data-row:nth-child(even) { background: var(--bs-tertiary-bg); }
.status-badge.req { background: var(--bs-danger-bg-subtle); color: var(--bs-danger-text-emphasis); }
.status-badge.opt { background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); }
.result-row-error   { background: var(--bs-danger-bg-subtle); }
.result-row-skipped { background: var(--bs-warning-bg-subtle); }

/* Profile-list kit — guest_profiles, customer_profiles. */
.contact-link { text-decoration: none; color: var(--bs-body-color); }
.contact-link:hover { color: var(--bs-primary); }
.profile-stack { line-height: 1.3; }
.profile-stack .addr-line {
    font-size: 0.76rem;
    color: var(--bs-secondary-color);
    line-height: 1.25;
}
.status-row {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 4px 14px;
    margin-top: 0.6rem;
}
.status-row .status-label {
    font-size: 0.75rem; font-weight: 500;
    color: var(--bs-secondary-color);
    text-transform: uppercase; letter-spacing: 0.04em;
}
.status-row .form-check { margin-bottom: 0; min-height: auto; font-size: 0.82rem; }

/* Print: black-on-white + shaded report rows. Promoted from
   the report pages; now applies to every layout page. */
@media print {
    body, h1, h2, h3, h4, h5, h6,
    .page-title, .page-subtitle, .section-label,
    .text-secondary, .text-body-secondary, .text-muted,
    table, td, th, p, div, span, strong, em,
    a, a:link, a:visited, a:hover, a:active {
        color: #000 !important;
    }
    a { text-decoration: none !important; }
    .report-subtotal    { background: #f0f0f0 !important; }
    .report-grand-total { background: #e0e0e0 !important; border-top: 2px solid #000 !important; }
    .meta-pill { background: transparent !important; border: 1px solid #999; }
    .meta-pill .meta-label { color: #000 !important; }
}


/* ── Phone appbar: fit the FULL icon set (2026-07-26, owner request) ──
   Compact icon buttons + hidden wordmark instead of hiding icons; the "?"
   KB icon is removed from the markup entirely. */
@media (max-width: 575.98px) {
    .g-appbar .g-icon-btn { width: 31px; height: 31px; font-size: .95rem; }
    .g-appbar .dropdown.me-1 { margin-right: .1rem !important; }
    .g-appbar .g-brand.me-3 { margin-right: .5rem !important; }
    .g-appbar .g-brand-word { display: none; }
    .g-appbar .g-brand-logo { max-height: 26px; }
}

/* Sticky context strip (shared by resform guest strip + POS bill strip;
   2026-08-16 Shiji-pattern pass). Pins under the measured app bar. */
.rf-guesthead {
    position: sticky; top: var(--g-appbar-h, 108px); z-index: 99;
    align-items: center; gap: .65rem;
    background: var(--bs-body-bg);
    border-bottom: 1px solid var(--bs-border-color);
    padding: .4rem .25rem;
    font-size: .85rem;
    margin-bottom: .25rem;
}
.rf-gh-avatar {
    width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: rgba(13,110,253,.12); color: var(--bs-primary);
    font-size: .72rem; font-weight: 700; letter-spacing: .02em;
}
.rf-gh-sep { width: 1px; height: 1.1rem; background: var(--bs-border-color); }

/* gxCombo — vanilla incremental-search combobox over native selects
   (public/js/gxcombo.js). The real <select> stays in the DOM hidden. */
.gxc-wrap { position: relative; }
.gxc-wrap select.gxc-native {
    position: absolute; left: 0; bottom: 0; width: 1px; height: 1px;
    opacity: 0; pointer-events: none;
}
.gxc-list {
    position: absolute; z-index: 1060; top: 100%; left: 0;
    min-width: 100%; width: max-content; max-width: 340px;
    max-height: min(420px, 60vh); overflow-y: auto;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color); border-radius: .375rem;
    box-shadow: 0 4px 12px rgba(0,0,0,.15);
}
.gxc-item {
    padding: .3rem .6rem; cursor: pointer; font-size: .8125rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gxc-item:hover, .gxc-item.hover { background: var(--bs-tertiary-bg); }
.gxc-item.active { background: rgba(13,110,253,.1); color: var(--bs-primary); }
.gxc-item.disabled { color: var(--bs-secondary-color); opacity: .55; cursor: not-allowed; }
.gxc-empty {
    padding: .3rem .6rem; font-size: .8125rem;
    color: var(--bs-secondary-color); font-style: italic;
}
/* invalid state mirrors from the hidden select onto the input (any form
   using Bootstrap's was-validated) */
form.was-validated .gxc-wrap select:invalid ~ .gxc-input {
    border-color: var(--bs-danger);
    box-shadow: 0 0 0 .15rem rgba(220,53,69,.15);
}

/* gxCombo inputs must SIGNAL they're dropdowns (NN/g signifier): Bootstrap's
   form-select chevron, gray so it reads in light and dark themes. */
.gxc-input {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236c757d' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right .5rem center;
    background-size: 16px 12px;
    padding-right: 1.75rem;
}

/* Failures-only validation, app default (NN/g): suppress Bootstrap's green
   :valid ticks on was-validated forms — untouched optional fields must not
   claim success. The red :invalid state is unaffected. */
form.was-validated .form-control:valid,
form.was-validated textarea.form-control:valid {
    border-color: var(--bs-border-color); background-image: none; padding-right: .75rem;
}
form.was-validated .form-select:valid {
    border-color: var(--bs-border-color);
    background-image: var(--bs-form-select-bg-img);
    padding-right: 2.25rem;
}
form.was-validated .form-control:valid:focus,
form.was-validated .form-select:valid:focus {
    border-color: #86b7fe; box-shadow: 0 0 0 .25rem rgba(13,110,253,.25);
}
form.was-validated .form-check-input:valid { border-color: rgba(0,0,0,.25); }
form.was-validated .form-check-input:valid ~ .form-check-label { color: inherit; }
