:root{
  --brand-1:#4f46e5;
  --brand-2:#0ea5e9;
  --brand-dark:#111827;
  --brand-light:#f8fafc;
}
body{
  font-family:'Poppins','Hind Siliguri',system-ui,'Noto Sans Bengali',sans-serif;
  background:var(--brand-light);
  color:#1f2937;
}
.app-navbar{
  background:linear-gradient(90deg,var(--brand-1),var(--brand-2));
}
.hero{
  background:radial-gradient(circle at 20% 20%, #6366f1 0%, #111827 55%, #0f172a 100%);
  color:#fff;
  border-radius:0 0 2rem 2rem;
}
.hero h1{font-weight:700;}
.feature-card{
  border:none;
  border-radius:1rem;
  box-shadow:0 8px 24px rgba(17,24,39,.06);
  transition:transform .15s ease, box-shadow .15s ease;
  height:100%;
}
.feature-card:hover{transform:translateY(-4px);box-shadow:0 14px 30px rgba(17,24,39,.12);}
.feature-icon{
  width:52px;height:52px;border-radius:14px;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,var(--brand-1),var(--brand-2));
  color:#fff;font-size:1.4rem;
}
.stat-card{
  border:none;border-radius:1rem;
  background:#fff;box-shadow:0 8px 24px rgba(17,24,39,.06);
}
.badge-status-received{background:#e0e7ff;color:#3730a3;}
.badge-status-diagnosing{background:#fef3c7;color:#92400e;}
.badge-status-in_progress{background:#dbeafe;color:#1e40af;}
.badge-status-waiting_parts{background:#fee2e2;color:#991b1b;}
.badge-status-completed{background:#d1fae5;color:#065f46;}
.badge-status-delivered{background:#e5e7eb;color:#111827;}
.badge-status-cancelled{background:#f3f4f6;color:#6b7280;}
.auth-wrap{
  min-height:100vh;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#eef2ff,#e0f2fe);
}
.auth-card{
  max-width:460px;width:100%;
  border:none;border-radius:1.25rem;
  box-shadow:0 20px 50px rgba(17,24,39,.12);
}
.secret-note{
  background:#111827;color:#f9fafb;border-radius:.75rem;padding:1rem;
  font-family:ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;
}
.receipt-box{
  max-width:480px;margin:0 auto;border:1px dashed #94a3b8;border-radius:1rem;padding:1.5rem;background:#fff;
}
@media print{
  .no-print{display:none !important;}
  body{background:#fff;}
  .receipt-box{border:none;}
}

/* ============================================================
   UNIFIED PAGE TRANSITIONS
   Every page fades/slides in on load, and internal links do a
   quick fade-out before navigating, so moving between pages in
   the app feels like one connected product instead of separate
   reloading screens.
   ============================================================ */
/* NOTE: intentionally opacity-only. A transform on <body> (even a
   completed animation left at translateY(0) via fill-mode:forwards)
   makes <body> the containing block for every position:fixed element
   on the page -- which breaks Bootstrap modals/backdrops site-wide,
   since they're no longer fixed to the viewport. Do not add transform
   here; if you want the slide effect back, animate a wrapper div
   instead of <body>. */
body{opacity:0;animation:pageIn .35s ease forwards;}
@keyframes pageIn{from{opacity:0;}to{opacity:1;}}
body.page-leaving{animation:pageOut .16s ease forwards;}
@keyframes pageOut{to{opacity:0;}}
.app-navbar .nav-link{position:relative;transition:color .15s ease;}
.app-navbar .nav-link.active{color:#fff !important;font-weight:600;}
.app-navbar .nav-link.active::after{
  content:"";position:absolute;left:1rem;right:1rem;bottom:2px;height:2px;background:#fff;border-radius:2px;
}
.card, .btn{transition:box-shadow .15s ease, transform .1s ease;}
a{transition:color .12s ease;}

/* ============================================================
   VISUAL PIN / PATTERN LOCK REGISTER
   ============================================================ */
.lock-tabs{display:flex;gap:.4rem;flex-wrap:wrap;margin-bottom:.75rem;}
.lock-tab-btn{
  border:1px solid #e2e8f0;background:#fff;border-radius:2rem;padding:.35rem .9rem;font-size:.85rem;color:#475569;
  display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;transition:all .15s ease;
}
.lock-tab-btn:hover{border-color:var(--brand-1);color:var(--brand-1);}
.lock-tab-btn.active{background:linear-gradient(135deg,var(--brand-1),var(--brand-2));color:#fff;border-color:transparent;box-shadow:0 4px 12px rgba(79,70,229,.25);}
.lock-stage{background:#f8fafc;border:1px solid #e2e8f0;border-radius:1rem;padding:1rem;}
.lock-pattern-stage{max-width:220px;margin:0 auto;}
.lock-pattern-stage-sm{max-width:150px;margin:0 auto;}
.lock-pattern-svg{width:100%;touch-action:none;user-select:none;-webkit-user-select:none;}
.lock-dot-hit{fill:transparent;cursor:pointer;}
.lock-dot{fill:#cbd5e1;stroke:#94a3b8;stroke-width:1;transition:r .1s ease, fill .1s ease;}
.lock-dot-active{fill:var(--brand-1);stroke:var(--brand-2);}
.lock-line{stroke:var(--brand-1);stroke-width:6;stroke-linecap:round;opacity:.85;}
.lock-dot-order{font-size:11px;fill:#fff;text-anchor:middle;font-weight:700;pointer-events:none;}
.device-lock-view .lock-pattern-stage-sm .lock-dot-hit{cursor:default;}
.lock-pin-wrap{text-align:center;}
.lock-pin-display{display:flex;justify-content:center;gap:.5rem;margin-bottom:1rem;min-height:24px;}
.lock-pin-digit{width:18px;height:18px;border-radius:50%;border:2px solid #cbd5e1;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--brand-1);}
.lock-pin-digit.filled{border-color:var(--brand-1);background:#eef2ff;}
.lock-pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem;max-width:220px;margin:0 auto;}
.lock-pin-key{
  padding:.7rem 0;border-radius:.75rem;border:1px solid #e2e8f0;background:#fff;font-weight:600;font-size:1.05rem;
  color:#1f2937;cursor:pointer;transition:all .1s ease;
}
.lock-pin-key:hover{background:#eef2ff;border-color:var(--brand-1);}
.lock-pin-key:active{transform:scale(.94);}
.lock-pin-key-fn{font-size:.8rem;color:#64748b;}
.lock-password-view{font-family:ui-monospace,Menlo,Consolas,monospace;background:#111827;color:#f9fafb;border-radius:.5rem;padding:.5rem 1rem;display:inline-block;}

/* ============================================================
   STATUS TIMELINE (customer tracking + internal job view)
   ============================================================ */
.status-track{display:flex;flex-wrap:wrap;gap:0;margin:1.5rem 0;}
.status-track-step{flex:1;min-width:110px;text-align:center;position:relative;padding:0 .35rem;}
.status-track-step:not(:last-child)::after{
  content:"";position:absolute;top:20px;left:55%;right:-45%;height:3px;background:#e2e8f0;z-index:0;
}
.status-track-step.done:not(:last-child)::after{background:linear-gradient(90deg,var(--brand-1),var(--brand-2));}
.status-track-icon{
  width:42px;height:42px;border-radius:50%;background:#e2e8f0;color:#94a3b8;display:flex;align-items:center;justify-content:center;
  margin:0 auto .5rem;position:relative;z-index:1;font-size:1.1rem;transition:all .2s ease;
}
.status-track-step.done .status-track-icon{background:linear-gradient(135deg,var(--brand-1),var(--brand-2));color:#fff;box-shadow:0 4px 12px rgba(79,70,229,.3);}
.status-track-step.current .status-track-icon{animation:pulseRing 1.6s infinite;}
@keyframes pulseRing{0%{box-shadow:0 0 0 0 rgba(79,70,229,.45);}70%{box-shadow:0 0 0 10px rgba(79,70,229,0);}100%{box-shadow:0 0 0 0 rgba(79,70,229,0);}}
.status-track-label{font-size:.78rem;font-weight:600;color:#334155;}
.status-track-desc{font-size:.7rem;color:#94a3b8;}
.status-track.cancelled .status-track-icon{background:#fee2e2;color:#991b1b;}

/* On narrow screens, 5 steps in a row wrap into a ragged 2/2/1 grid with
   connector lines pointing at nothing -- switch to a clean vertical
   stepper instead (icon column + text column, connecting line runs down
   between icons). Distance math only depends on the icon's fixed 42px
   size, not on how tall each row's text is, so it lines up correctly
   whether a step has a description (track.php) or not (repair_view.php). */
@media (max-width:576px){
  .status-track{flex-direction:column;flex-wrap:nowrap;gap:0;margin:1rem 0;}
  .status-track-step{display:flex;align-items:stretch;text-align:left;width:100%;min-width:0;flex:none;padding:0;}
  .status-track-step:not(:last-child)::after{content:none;}
  .status-track-line{display:flex;flex-direction:column;align-items:center;position:relative;flex-shrink:0;margin-right:.85rem;}
  .status-track-step:not(:last-child) .status-track-line::after{
    content:"";position:absolute;top:42px;bottom:-21px;left:50%;transform:translateX(-50%);width:3px;background:#e2e8f0;z-index:0;
  }
  .status-track-step.done:not(:last-child) .status-track-line::after{background:linear-gradient(180deg,var(--brand-1),var(--brand-2));}
  .status-track-icon{margin:0;}
  .status-track-text{flex:1;min-width:0;padding:.4rem 0 1.1rem;}
  .status-track-step:last-child .status-track-text{padding-bottom:0;}
  .status-track-label{font-size:.88rem;}
  .status-track-desc{font-size:.76rem;margin-top:.15rem;display:block;}
}

/* ============================================================
   CHAT (shop <-> customer)
   ============================================================ */
.chat-panel{display:flex;flex-direction:column;height:420px;}
.chat-scroll{flex:1;overflow-y:auto;padding:1rem;background:#f8fafc;border-radius:.75rem 0.75rem 0 0;border:1px solid #e2e8f0;border-bottom:none;}
.chat-bubble-row{display:flex;margin-bottom:.6rem;}
.chat-bubble-row.mine{justify-content:flex-end;}
.chat-bubble{
  max-width:75%;padding:.5rem .85rem;border-radius:1rem;font-size:.88rem;line-height:1.35;box-shadow:0 1px 2px rgba(0,0,0,.06);
}
.chat-bubble-row.theirs .chat-bubble{background:#fff;border:1px solid #e2e8f0;border-bottom-left-radius:.25rem;}
.chat-bubble-row.mine .chat-bubble{background:linear-gradient(135deg,var(--brand-1),var(--brand-2));color:#fff;border-bottom-right-radius:.25rem;}
.chat-bubble-time{font-size:.65rem;opacity:.65;margin-top:.2rem;}
.chat-input-row{display:flex;gap:.5rem;border:1px solid #e2e8f0;border-top:none;border-radius:0 0 .75rem .75rem;padding:.6rem;background:#fff;}
.chat-empty{text-align:center;color:#94a3b8;font-size:.85rem;padding:2rem 0;}
.unread-dot{
  display:inline-flex;min-width:18px;height:18px;border-radius:50%;background:#ef4444;color:#fff;font-size:.65rem;
  align-items:center;justify-content:center;padding:0 5px;margin-left:.3rem;
}

/* ============================================================
   CUSTOMER PORTAL SHELL (public tracking page)
   ============================================================ */
.portal-nav{background:linear-gradient(90deg,var(--brand-1),var(--brand-2));}
.portal-shell{min-height:100vh;background:var(--brand-light);}
.portal-card{border:none;border-radius:1.25rem;box-shadow:0 12px 32px rgba(17,24,39,.08);background:#fff;}
.portal-hero-badge{
  width:64px;height:64px;border-radius:1rem;background:linear-gradient(135deg,var(--brand-1),var(--brand-2));
  color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.6rem;margin:0 auto;
}
.qr-box{background:#fff;border:1px solid #e2e8f0;border-radius:1rem;padding:1rem;display:inline-block;}

/* ============================================================
   CRM: customer profile
   ============================================================ */
.crm-stat{border-radius:1rem;background:#fff;box-shadow:0 8px 24px rgba(17,24,39,.06);padding:1rem;text-align:center;}
.crm-stat .val{font-size:1.4rem;font-weight:700;}
.crm-stat .lbl{font-size:.75rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.03em;}
.timeline-mini{border-left:2px solid #e2e8f0;margin-left:.5rem;padding-left:1rem;}
.timeline-mini-item{position:relative;padding-bottom:1rem;}
.timeline-mini-item::before{content:"";position:absolute;left:-1.31rem;top:.2rem;width:10px;height:10px;border-radius:50%;background:var(--brand-1);}

/* ============================================================
   MOBILE USABILITY PASS
   General fixes that apply across every page, not just the navbar:
   comfortable tap targets, no accidental horizontal scroll, readable
   table text at narrow widths, and less wasted padding on small
   screens where every pixel of width matters.
   ============================================================ */
html{ -webkit-text-size-adjust:100%; }
body{ overflow-x:hidden; } /* belt-and-braces: stops any one wide element from creating page-level horizontal scroll */

@media (max-width:575.98px){
  /* Bootstrap's default container padding (1.5rem/24px each side) eats
     50px of a 360px-wide phone screen before any content even starts. */
  .container, .container-fluid{ padding-left:.85rem; padding-right:.85rem; }
  .card, .stat-card, .feature-card, .portal-card, .auth-card{ padding:1rem !important; }
  h1, .display-1, .display-2, .display-3, .display-4{ font-size:1.7rem; }
  h2{ font-size:1.4rem; }
  h3{ font-size:1.2rem; }
  .table{ font-size:.82rem; }
  .table td, .table th{ padding:.5rem .4rem; }
  .btn{ padding-top:.55rem; padding-bottom:.55rem; } /* comfortable thumb target */
  .modal-dialog{ margin:.5rem; }
}

/* Icon-only nav links (mobile collapsed menu, and the compact xl band)
   still need a real tap target, not just the icon's own tiny box. */
@media (max-width:1199.98px){
  .icon-nav-link{ min-height:44px; }
  .navbar-nav .dropdown-item{ padding-top:.6rem; padding-bottom:.6rem; }
}

/* Long, unbreakable strings (emails, ticket codes, IMEIs) in table
   cells force horizontal scroll on narrow screens if left alone. */
@media (max-width:767.98px){
  .table td, .table th{ word-break:break-word; }
}

/* ============================================================
   iOS / notched-device safe areas
   viewport-fit=cover (see header.php/portal_header.php <meta viewport>)
   lets the page draw edge-to-edge on iPhones with a notch or the
   home-indicator bar -- but that means OUR content has to avoid
   sitting under them ourselves, via these env() insets. Falls back to
   0px automatically on every device that doesn't have a notch.
   ============================================================ */
body{
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.app-navbar, .portal-nav{
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  padding-top: env(safe-area-inset-top, 0px);
}
/* Fixed/sticky bottom bars (if any are added later) should use this too. */
.safe-bottom{ padding-bottom: env(safe-area-inset-bottom, 0px); }

/* Extra-small phones (iPhone SE, older/budget Android -- 320-360px) */
@media (max-width:359.98px){
  .navbar-brand{ font-size: 1rem; }
  .stat-card, .feature-card{ padding: .75rem !important; }
  .fs-3, .fs-4{ font-size: 1.25rem !important; }
}

/* ============================================================
   MODAL SCROLL FIX (applies to every modal site-wide)
   Several modals (announcement popup, GSM resources, add/edit forms,
   camera scanner, etc.) have content taller than the viewport on
   mobile, but weren't using Bootstrap's modal-dialog-scrollable class
   -- so the modal's own body couldn't scroll and only a sliver of
   content was reachable. Rather than hunting down and patching every
   individual modal's markup (easy to miss one, and it'd break again
   the next time a modal's content grows), this bakes the same
   behavior into every modal globally: cap the dialog to the viewport
   height and make the body its own scroll region.
   ============================================================ */
.modal-content{
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.modal-body{
  overflow-y: auto;
  -webkit-overflow-scrolling: touch; /* momentum scrolling on iOS */
}
@media (max-width:575.98px){
  .modal-content{ max-height: 85vh; } /* extra room for mobile browser chrome (address bar, etc.) */
  .modal-dialog{ margin-top: .5rem; margin-bottom: .5rem; }
}

/* ============================================================
   MOBILE NAV: don't let a long menu just take over the page
   Below the xl breakpoint the navbar collapses into the hamburger
   list. With ~10+ items (Dashboard, Customers, Inventory, Repairs,
   Sales, Discover, Staff...) that list is taller than most phone
   screens, so it used to just keep growing the page downward with no
   visual end -- it *looked* like the menu had taken over, and you had
   to scroll the whole page (not just the menu) to find the bottom
   items or close it. Capping it to the remaining viewport height and
   letting the LIST scroll internally, like every real app's nav
   drawer, fixes both complaints at once.
   ============================================================ */
@media (max-width:1199.98px){
  .app-navbar .navbar-collapse.show,
  .app-navbar .navbar-collapse.collapsing{
    max-height: calc(100vh - 60px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* Any dropdown menu (navbar "More", per-row table actions, admin
   "Manage", etc.) can end up with more items than fit on a short
   phone screen -- cap it the same way instead of letting it run off
   past the bottom edge. */
@media (max-width:767.98px){
  .dropdown-menu{
    max-height: 70vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* ============================================================
   RESPONSIVE DATA TABLES -> STACKED CARDS ON PHONES
   Paired with the labeling script in assets/js/app.js, which stamps
   each <td> with data-label="<column name>" read from the table's own
   <thead>. Below the breakpoint, the table stops looking like a
   table at all -- each row becomes a card, each cell becomes one
   labelled line inside it -- which is how virtually every mobile-
   first CRM/admin UI (and Bootstrap's own documented pattern) handles
   dense tables on narrow screens, instead of forcing a horizontal
   scroll strip over 6-8 columns.
   Opt out per-table with class="no-mobile-cards" on the wrapping
   .table-responsive if a specific table reads better staying a real
   (horizontally scrollable) table on mobile.
   ============================================================ */
@media (max-width:767.98px){
  .table-responsive:not(.no-mobile-cards) > table.table{
    border:0;
  }
  .table-responsive:not(.no-mobile-cards) > table.table thead{
    display:none; /* labels move onto each cell instead, via data-label */
  }
  .table-responsive:not(.no-mobile-cards) > table.table tbody tr{
    display:block;
    background:#fff;
    border:1px solid #e2e8f0;
    border-radius:.75rem;
    margin-bottom:.75rem;
    padding:.65rem .85rem;
    box-shadow:0 2px 8px rgba(17,24,39,.04);
  }
  /* Empty-state / colspan rows ("No repair jobs found.", etc.) --
     the label script deliberately skips these, so they stay plain
     centered text instead of turning into a weird 1-line "card". */
  .table-responsive:not(.no-mobile-cards) > table.table tbody tr td[colspan]{
    display:block;
    text-align:center !important;
  }
  .table-responsive:not(.no-mobile-cards) > table.table tbody td{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:.75rem;
    text-align:right;
    padding:.4rem 0;
    border:0;
    border-bottom:1px solid #f1f5f9;
  }
  .table-responsive:not(.no-mobile-cards) > table.table tbody td:last-child{
    border-bottom:0;
  }
  /* A cell with no meaningful column name (checkbox column, the
     trailing actions column with an empty <th></th>) gets no label
     prefix and its content just right-aligns on its own line, instead
     of showing an empty ":" — attr() with a blank value renders
     nothing, so this is automatic, not a special case. */
  .table-responsive:not(.no-mobile-cards) > table.table tbody td[data-label]::before{
    content:attr(data-label);
    font-weight:600;
    font-size:.72rem;
    text-transform:uppercase;
    letter-spacing:.03em;
    color:#94a3b8;
    text-align:left;
    flex-shrink:0;
  }
  /* Row-level checkboxes and the actions cell read better centered/
     left than squeezed into the label/value split above. */
  .table-responsive:not(.no-mobile-cards) > table.table tbody td:has(> input[type="checkbox"]:only-child),
  .table-responsive:not(.no-mobile-cards) > table.table tbody td:not([data-label]){
    justify-content:flex-start;
  }
}

/* ============================================================
   MOBILE PASS 3: unwrapped flex rows + rigid grid columns
   Two structural bugs kept reappearing on different pages under the
   same root cause, so fixing both once, globally, instead of chasing
   every page that uses them (there are 65+ uses of the header-row
   pattern alone):

   1) `.d-flex` rows with no flex-wrap (page header: title + action
      buttons; inbox rows: name + phone + status badge) don't shrink
      to fit -- by default a flex item's minimum width is its own
      content's width, so instead of wrapping to a 2nd line the whole
      row just overflows its container, which is what pushed Inbox
      rows into horizontal overflow and squeezed "Dashboard" into its
      buttons. Letting these wrap turns that into a normal stacked
      layout instead. Any existing gap-*/row-gap utility on these
      elements already handles spacing once they wrap -- no extra
      markup needed.
   2) Bootstrap's grid columns (.col-4 etc.) are flex items of `.row`
      and have the exact same default-minimum-width behavior -- a
      long unbroken value (like a currency amount) inside a `col-4`
      forces that column wider than its actual 33% share, bleeding
      into the next column instead of wrapping inside its own box
      (the Inventory hero "Low Stock Items" overlap). min-width:0
      lets the column actually shrink to its assigned share; normal
      block text inside still wraps at word boundaries same as ever.
   ============================================================ */
@media (max-width:767.98px){
  .d-flex{ flex-wrap: wrap; }
  .row > [class^="col-"], .row > [class*=" col-"]{ min-width: 0; }
}

/* Repair job page: title/status vs. the row of action buttons (Customer
   Chat, Print Token/Receipt, Email Receipt, Duplicate Job, Delete Job,
   etc.) share the same `.d-flex` as everything else above, so the
   "let it wrap" fix above turns a row of 5-6 buttons into a squeezed
   3-column grid with text wrapping inside each button -- the opposite of
   clean. A horizontally-scrollable strip is the standard mobile pattern
   for a row of several actions: buttons keep their natural size (no text
   wrapping, no squeezing), and swiping sideways reveals the rest. Needs
   to come after the ".d-flex{flex-wrap:wrap}" rule above to win the tie
   (same specificity, so source order decides). */
@media (max-width:767.98px){
  .repair-actions-row{
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin-left: -.85rem;
    margin-right: -.85rem;
    padding-left: .85rem;
    padding-right: .85rem;
    padding-bottom: .3rem;
  }
  .repair-actions-row::-webkit-scrollbar{ display:none; }
  .repair-actions-row > *{ flex: 0 0 auto; white-space: nowrap; }
}

/* Reserve room for the fixed "contact admin" headset button
   (includes/footer.php) so it never sits on top of the last button/
   card on a page -- previously nothing accounted for its 50px+margin
   footprint, so short pages had it overlapping real content. */
.app-main{
  padding-bottom: calc(86px + env(safe-area-inset-bottom, 0px));
}

/* ===== Site-improvement batch: accessibility, hero contrast, FAQ,
   sticky mobile CTA, trust strip, shop-profile polish ===== */

/* A11Y: visible focus ring everywhere (buttons, links, form fields, nav)
   instead of relying on the browser default, which Bootstrap partially
   suppresses. Uses :focus-visible so it only shows for keyboard users,
   not on every mouse click. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible{
  outline: 3px solid #0ea5e9;
  outline-offset: 2px;
  border-radius: 4px;
}

/* A11Y: skip-to-content link. Off-screen until keyboard-focused, then
   drops into view above everything else. */
.skip-link{
  position: fixed;
  top: -60px;
  left: 12px;
  z-index: 2000;
  background: #111827;
  color: #fff !important;
  padding: .6rem 1rem;
  border-radius: .5rem;
  text-decoration: none;
  transition: top .15s ease;
}
.skip-link:focus{ top: 12px; }

/* A11Y/contrast fix: the hero's lead paragraph used Bootstrap's
   text-white-50 (50% opacity white), which drops below comfortable
   contrast over the lighter indigo corner of the radial gradient.
   85% opacity keeps the same "muted" visual weight everywhere on the
   gradient while staying readable. */
.hero .lead{ color: rgba(255,255,255,.85) !important; }

/* Homepage trust strip under the hero buttons */
.trust-strip{
  display:flex; flex-wrap:wrap; gap: .5rem 1.25rem; margin-top:1rem;
  font-size:.9rem; color:rgba(255,255,255,.85);
}
.trust-strip span{ display:inline-flex; align-items:center; gap:.4rem; }
.trust-strip i{ color:#4ade80; }

/* FAQ accordion — keep it visually distinct from the default Bootstrap
   accordion so it reads as editorial content, not a settings panel. */
.faq-accordion .accordion-item{
  border:none; border-radius:1rem !important; overflow:hidden;
  margin-bottom:.75rem; box-shadow:0 8px 24px rgba(17,24,39,.06);
}
.faq-accordion .accordion-button{
  font-weight:600; background:#fff;
}
.faq-accordion .accordion-button:not(.collapsed){
  background: linear-gradient(90deg, var(--brand-1), var(--brand-2));
  color:#fff;
}
.faq-accordion .accordion-button:focus{ box-shadow:none; }

/* Sticky mobile "Get Started" bar — only shown on small screens, sits
   above the safe-area inset like the existing chat-launcher button does. */
.mobile-cta-bar{
  display:none;
}
@media (max-width:767.98px){
  .mobile-cta-bar{
    display:flex;
    position: fixed;
    left:0; right:0; bottom:0;
    z-index: 1040;
    padding: .65rem .85rem calc(.65rem + env(safe-area-inset-bottom, 0px));
    background: #fff;
    box-shadow: 0 -6px 20px rgba(17,24,39,.12);
  }
  .mobile-cta-bar .btn{ flex:1; }
  /* leave room so the sticky bar never covers the last bit of content */
  body.has-mobile-cta{ padding-bottom: 78px; }
}

/* Shop profile page polish: cover banner + verified badge + nicer
   empty/rating states, so a shop's public page reads as a real business
   listing instead of a bare data dump. */
.shop-cover{
  height: 140px;
  border-radius: 1rem 1rem 0 0;
  background: linear-gradient(120deg, var(--brand-1), var(--brand-2));
  position: relative;
}
.shop-cover-logo{
  width:84px; height:84px; border-radius:1rem; object-fit:cover;
  border:4px solid #fff; background:#fff;
  box-shadow:0 8px 20px rgba(17,24,39,.15);
  margin-top:-52px; margin-left:1.5rem; position:relative; z-index:2;
}
.shop-verified-badge{
  display:inline-flex; align-items:center; gap:.3rem;
  font-size:.78rem; font-weight:600; color:#059669;
  background:#ecfdf5; border:1px solid #a7f3d0;
  padding:.15rem .55rem; border-radius:999px;
}

/* Reports export: browser "Print / Save as PDF" gives a clean, no-cost
   PDF export with zero server-side PDF library needed. Hides nav/chrome
   and action buttons; keeps just the report content. */
@media print{
  .app-navbar, .mobile-cta-bar, .report-export-actions, form.row.g-2,
  #announcementModal, .skip-link{
    display:none !important;
  }
  .app-main{ padding-bottom:0 !important; }
  body{ background:#fff !important; }
  .card{ box-shadow:none !important; border:1px solid #ddd !important; }
}

/* ============================================================
   UI POLISH PASS — refined visuals + micro-interactions
   Purely additive: extends the tokens/rules already defined above
   instead of overriding them, so none of the earlier fixes (mobile
   cards, modal scroll, safe-area insets, dropdown clipping, print
   styles, etc.) are touched. Paired with the small additions at the
   bottom of assets/js/app.js (button ripple, navbar scroll shadow,
   scroll-reveal, chat bubble entrance).
   ============================================================ */

/* Respect "reduce motion" system setting across every animation in
   this file, old and new -- this is the one rule that must come
   last/most-specific to actually win, so it stays at the top of this
   final section rather than being scattered per-rule. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
}

:root{
  --brand-1-dark:#4338ca;
  --brand-2-dark:#0284c7;
  --ease-out:cubic-bezier(.16,1,.3,1);
  --shadow-md:0 10px 26px rgba(17,24,39,.09);
  --shadow-lg:0 18px 44px rgba(17,24,39,.16);
}

html{ scroll-behavior:smooth; }

/* Thin branded scrollbar on desktop -- skipped on touch/small
   screens where the OS already draws its own overlay scrollbar. */
@media (min-width:992px){
  html{ scrollbar-width:thin; scrollbar-color:var(--brand-1) transparent; }
  ::-webkit-scrollbar{ width:10px; height:10px; }
  ::-webkit-scrollbar-track{ background:transparent; }
  ::-webkit-scrollbar-thumb{
    background:linear-gradient(var(--brand-1),var(--brand-2));
    border-radius:999px; border:2px solid var(--brand-light);
  }
  ::-webkit-scrollbar-thumb:hover{ background:linear-gradient(var(--brand-1-dark),var(--brand-2-dark)); }
}

/* Buttons: consistent shape + springier hover/press feedback.
   overflow:hidden clips the JS-generated .ripple span to the
   button's own rounded corners; it does not clip the button's own
   box-shadow (that's drawn outside the border box, unaffected by an
   element clipping its own contents). */
.btn{
  border-radius:.7rem;
  font-weight:600;
  letter-spacing:.01em;
  position:relative;
  overflow:hidden;
  transition:transform .18s var(--ease-out), box-shadow .18s var(--ease-out), background-color .15s ease, border-color .15s ease;
}
.btn-primary{ background:linear-gradient(135deg,var(--brand-1),var(--brand-2)); border:none; }
.btn-primary:hover, .btn-primary:focus-visible{
  background:linear-gradient(135deg,var(--brand-1-dark),var(--brand-2-dark));
  box-shadow:0 10px 22px rgba(79,70,229,.28);
  transform:translateY(-1px);
}
.btn-outline-primary:hover{ transform:translateY(-1px); }
.btn:active{ transform:translateY(0) scale(.97); }
.btn .ripple{
  position:absolute; border-radius:50%; transform:scale(0);
  background:rgba(255,255,255,.55); pointer-events:none;
  animation:rippleGrow .55s var(--ease-out);
}
@keyframes rippleGrow{ to{ transform:scale(2.6); opacity:0; } }

/* Cards: one consistent hover lift across every card variant already
   defined above (feature/stat/portal/crm/auth/faq etc.), so hovering
   any card in the app feels like the same product responding, not a
   handful of one-off effects. */
.card, .feature-card, .stat-card, .portal-card, .crm-stat, .auth-card{
  transition:transform .22s var(--ease-out), box-shadow .22s var(--ease-out);
}
.card:hover{ box-shadow:var(--shadow-md); }
.feature-icon{ transition:transform .25s var(--ease-out); }
.feature-card:hover .feature-icon{ transform:scale(1.08) rotate(-4deg); }

/* Entrance stagger for the card grids used on the dashboard/homepage
   (feature cards, stat cards, portal cards) -- each one fades/rises
   in a beat after the one before it, instead of the whole grid
   popping in at once. Pure CSS (nth-child delay), so it degrades
   gracefully to "just appears" if CSS fails to load, and needs no
   markup changes on the ~10 pages that already use these classes. */
.feature-card, .stat-card, .portal-card{
  animation:cardIn .5s var(--ease-out) backwards;
}
.feature-card:nth-child(1),.stat-card:nth-child(1),.portal-card:nth-child(1){animation-delay:.02s;}
.feature-card:nth-child(2),.stat-card:nth-child(2),.portal-card:nth-child(2){animation-delay:.08s;}
.feature-card:nth-child(3),.stat-card:nth-child(3),.portal-card:nth-child(3){animation-delay:.14s;}
.feature-card:nth-child(4),.stat-card:nth-child(4),.portal-card:nth-child(4){animation-delay:.2s;}
.feature-card:nth-child(5),.stat-card:nth-child(5),.portal-card:nth-child(5){animation-delay:.26s;}
.feature-card:nth-child(6),.stat-card:nth-child(6),.portal-card:nth-child(6){animation-delay:.32s;}
.feature-card:nth-child(n+7),.stat-card:nth-child(n+7),.portal-card:nth-child(n+7){animation-delay:.36s;}
@keyframes cardIn{ from{ opacity:0; transform:translateY(14px); } to{ opacity:1; transform:translateY(0); } }

/* Generic scroll-reveal utility: add class="reveal-on-scroll" to any
   element and the JS below fades/rises it into view the first time it
   enters the viewport. Opt-in only (nothing gets this by default), so
   existing pages are unaffected until the class is deliberately added. */
.reveal-on-scroll{ opacity:0; transform:translateY(16px); transition:opacity .5s var(--ease-out), transform .5s var(--ease-out); }
.reveal-on-scroll.is-visible{ opacity:1; transform:translateY(0); }

/* Navbar: subtle elevation once the page has scrolled, so it reads as
   sitting above the content instead of flat-pasted to the top edge. */
.app-navbar, .portal-nav{ transition:box-shadow .25s ease; }
.app-navbar.is-scrolled, .portal-nav.is-scrolled{ box-shadow:0 4px 18px rgba(17,24,39,.18); }

/* Inputs: smoother, more visible focus state (kept within the same
   footprint as Bootstrap's default so layout never shifts). */
.form-control, .form-select{
  border-radius:.65rem;
  transition:border-color .15s ease, box-shadow .15s ease;
}
.form-control:focus, .form-select:focus{
  border-color:var(--brand-1);
  box-shadow:0 0 0 .2rem rgba(79,70,229,.15);
}

/* Skeleton loading placeholder -- opt-in via class="skeleton" for any
   block that's rendered after a fetch (search results, chat history,
   async widgets), so the wait reads as "loading" rather than a blank
   gap or a jarring pop-in once data arrives. */
.skeleton{
  position:relative; overflow:hidden;
  background:#e2e8f0 !important; border-radius:.5rem; color:transparent !important;
  pointer-events:none;
}
.skeleton::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.6), transparent);
  animation:skeletonShimmer 1.3s infinite;
}
@keyframes skeletonShimmer{ from{ transform:translateX(-100%);} to{ transform:translateX(100%);} }

/* Bootstrap's .alert/.toast pop in with zero transition by default --
   give them the same soft entrance as the rest of the app's motion. */
.alert, .toast{ animation:alertIn .3s var(--ease-out); }
@keyframes alertIn{ from{ opacity:0; transform:translateY(-6px);} to{ opacity:1; transform:translateY(0);} }

/* Chat: newly-added bubbles (see ChatWidget.bubble() in app.js) ease
   in instead of snapping into place, without affecting bubbles that
   were already rendered on load. */
.chat-bubble-row.bubble-in{ animation:bubbleIn .25s var(--ease-out); }
@keyframes bubbleIn{ from{ opacity:0; transform:translateY(6px) scale(.97);} to{ opacity:1; transform:translateY(0) scale(1);} }

/* Badges: consistent pill shape + a touch more weight for legibility
   at the small sizes status badges are usually shown at. */
.badge{ border-radius:999px; font-weight:600; letter-spacing:.01em; }

/* Loading spinner: slightly smoother, on-brand color instead of the
   plain Bootstrap default everywhere it's used bare (no color class). */
.spinner-border:not([class*="text-"]){ color:var(--brand-1); }

/* ============================================================
   PUBLIC PROFILE DESIGNER (settings.php) + themed profile page
   (shop_profile.php). See get_shop_profile_theme()/get_shop_profile_links()
   in includes/functions.php.
   ============================================================ */

/* Settings page: preset color swatches */
.theme-preset-swatch{
  width:34px;height:34px;border-radius:50%;border:2px solid #fff;
  box-shadow:0 0 0 1px #e2e8f0,0 2px 6px rgba(17,24,39,.12);
  cursor:pointer;padding:0;transition:transform .15s var(--ease-out,ease);
}
.theme-preset-swatch:hover{ transform:scale(1.12); }
.theme-preset-swatch:active{ transform:scale(.95); }

/* Public shop profile: banner style variants. Uses whatever --brand-1/
   --brand-2 are in scope -- see the inline style on .shop-profile-themed
   in shop_profile.php, which scopes these two variables to just the
   shop's own profile content (not the site-wide navbar/chrome). */
.shop-cover.banner-solid{ background:var(--brand-1); }
.shop-cover.banner-dark{ background:linear-gradient(120deg,#111827,var(--brand-1)); }

/* Public shop profile: social/contact link pills */
.profile-link-pill{
  display:inline-flex;align-items:center;gap:.4rem;
  padding:.4rem .85rem;border-radius:999px;font-size:.82rem;font-weight:600;
  color:#fff;text-decoration:none;box-shadow:0 3px 10px rgba(17,24,39,.12);
  transition:transform .15s var(--ease-out,ease), box-shadow .15s ease;
}
.profile-link-pill:hover{ transform:translateY(-2px); box-shadow:0 8px 18px rgba(17,24,39,.2); color:#fff; }
.profile-link-pill i{ font-size:.95rem; }

/* ============================================================
   AJAX FORM SUBMISSION LAYER (see bottom of app.js)
   Thin top-of-viewport progress indicator shown while a form submit
   or back/forward navigation is being fetched and swapped in, so the
   "no full page reload" experience still gives clear feedback that
   something is happening. Fixed positioning keeps it visible even
   though <main> itself is about to be replaced.
   ============================================================ */
#ajaxTopProgress{
  position:fixed; top:0; left:0; height:3px; width:0%;
  background:linear-gradient(90deg,var(--brand-1),var(--brand-2));
  z-index:2000; opacity:0; transition:width .3s ease, opacity .2s ease;
  pointer-events:none;
}
#ajaxTopProgress.active{ width:78%; opacity:1; transition:width 8s cubic-bezier(.1,.6,.2,1), opacity .2s ease; }
/* FOLLOWUP-WRAP: long unbroken identifiers (ticket codes, IMEI/serial strings) must wrap inside table cells instead of stretching the table. */
.table td, .table th{ overflow-wrap:anywhere; }
/* MOBILE-NAV-V1: mobile nav ease + More-panel back ways. Scoped below 1200px (collapsed navbar); desktop 1200px+ untouched. */
@media (max-width:1199.98px){
  .app-navbar .navbar-nav .icon-nav-link{min-height:44px;}
  .app-navbar .navbar-toggler{min-height:44px;min-width:44px;}
  #moreDropdownMenu li.nav-item .icon-nav-link{min-height:44px;}
  #moreDropdownMenu .dropdown-item{min-height:44px;}
  #moreDropdownMenu .rd-more-head{position:sticky;top:0;z-index:2;background:#fff;border-bottom:1px solid #dee2e6;padding:.5rem .75rem;list-style:none;}
  #moreDropdownMenu .rd-more-head-row{display:flex;align-items:center;gap:.5rem;}
  #moreDropdownMenu .rd-more-back{min-height:44px;padding:.5rem .75rem;border:1px solid #dee2e6;border-radius:.5rem;background:#f8f9fa;color:#212529;font-weight:600;font-size:.95rem;cursor:pointer;}
  #moreDropdownMenu .rd-more-back::before{content:"\2190  ";}
  #moreDropdownMenu .rd-more-close{margin-left:auto;min-height:44px;min-width:44px;padding:.5rem;border:1px solid #dee2e6;border-radius:.5rem;background:#f8f9fa;color:#212529;font-size:1.25rem;line-height:1;cursor:pointer;}
  #moreDropdownMenu .rd-more-filter{width:100%;min-height:44px;margin-top:.5rem;padding:.5rem .75rem;border:1px solid #dee2e6;border-radius:.5rem;font-size:.95rem;}
  #moreDropdownMenu .rd-more-noresults{display:none;padding:.75rem .25rem;color:#6c757d;font-size:.9rem;}
  #rdMoreBackdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);}
  html.rd-more-lock, html.rd-more-lock body{overflow:hidden;}
  .app-navbar .rd-mob-scan{display:inline-flex;align-items:center;gap:.3rem;min-height:44px;padding:.35rem .6rem;border-radius:.5rem;background:var(--rd-accent,#0e5a4e);color:#fff;text-decoration:none;font-weight:600;font-size:.85rem;white-space:nowrap;flex-shrink:0;}
  .app-navbar .rd-mob-scan img{width:18px;height:18px;filter:invert(1);}
}

/* COMBINED-AI-ADMIN-V1A: admin-pack small styles. */
.rd-admin-note {
  font-size: 0.8rem;
  color: #64748b;
}
.bulk-shop {
  width: 18px;
  height: 18px;
  accent-color: #0e5a4e;
  vertical-align: middle;
}

/* OWNER-AI-V1: shop-owner assistant floating button and panel. */
.rd-ai-btn {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 52px;
  height: 52px;
  border: 0;
  border-radius: 50%;
  background: var(--rd-accent2, #c25e3a);
  color: #fff;
  font-size: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  z-index: 1030;
  cursor: pointer;
}
.rd-ai-panel {
  position: fixed;
  right: 20px;
  bottom: 88px;
  width: min(380px, calc(100vw - 32px));
  max-height: 70vh;
  background: #fff;
  border: 1px solid #dee2e6;
  border-radius: 12px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.2);
  z-index: 1040;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.rd-ai-hidden {
  display: none;
}
.rd-ai-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.9rem;
  font-weight: 600;
  border-bottom: 1px solid #dee2e6;
}
.rd-ai-close {
  border: 0;
  background: transparent;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}
.rd-ai-remaining {
  padding: 0.4rem 0.9rem;
  font-size: 0.82rem;
  color: #6c757d;
  border-bottom: 1px solid #f1f3f5;
}
.rd-ai-log {
  flex: 1;
  min-height: 120px;
  overflow-y: auto;
  padding: 0.7rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.rd-ai-msg {
  max-width: 92%;
}
.rd-ai-msg-user {
  align-self: flex-end;
}
.rd-ai-msg-assistant {
  align-self: flex-start;
}
.rd-ai-msg-body {
  padding: 0.5rem 0.7rem;
  border-radius: 10px;
  font-size: 0.92rem;
  white-space: pre-wrap;
  word-wrap: break-word;
  background: #f1f3f5;
}
.rd-ai-msg-user .rd-ai-msg-body {
  background: #e7f5ff;
}
.rd-ai-copy {
  border: 0;
  background: transparent;
  color: #0d6efd;
  font-size: 0.8rem;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
}
.rd-ai-form {
  display: flex;
  gap: 0.5rem;
  padding: 0.6rem 0.9rem;
  border-top: 1px solid #dee2e6;
}
.rd-ai-input {
  flex: 1;
  min-width: 0;
  border: 1px solid #ced4da;
  border-radius: 8px;
  padding: 0.45rem 0.6rem;
  font-size: 0.92rem;
}
.rd-ai-send {
  border: 0;
  border-radius: 8px;
  background: var(--rd-accent2, #c25e3a);
  color: #fff;
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
.rd-ai-send:disabled {
  opacity: 0.6;
}
.rd-ai-note {
  margin: 0;
  padding: 0 0.9rem 0.6rem;
  font-size: 0.78rem;
  color: #6c757d;
}
@media (max-width: 991px) {
  .rd-ai-btn {
    bottom: calc(76px + env(safe-area-inset-bottom, 0px));
  }
  .rd-ai-panel {
    bottom: calc(144px + env(safe-area-inset-bottom, 0px));
  }
}

/* AI-EXPAND-V1 rd-ai-history: Recent-questions panel elements. */
.rd-ai-history { margin: 0.5rem 0 0; }
.rd-ai-history-btn { width: 100%; text-align: left; background: transparent; border: 1px solid #dee2e6; border-radius: 0.375rem; padding: 0.375rem 0.5rem; font-size: 0.875rem; cursor: pointer; }
.rd-ai-history-list { margin-top: 0.375rem; max-height: 10rem; overflow-y: auto; }
.rd-ai-history-item { display: block; width: 100%; text-align: left; background: transparent; border: 0; border-bottom: 1px solid #f1f3f5; padding: 0.375rem 0.25rem; font-size: 0.875rem; cursor: pointer; }
