/* =====================================================
   HELNSOCIAL — V3 Theme (Blue + Semantic)
   Indigo Blue · Green/Red/Yellow · Developer Aesthetic
   ===================================================== */

/* PERF: the Google-Fonts @import that used to sit here was a byte-for-byte
   duplicate of the <link rel="stylesheet"> layout.twig already has in <head>
   (same two families, same weights, same display=swap) — and it was strictly
   worse: a <link> is discovered by the preload scanner and fetched in parallel,
   while an @import can only be found AFTER this file has been downloaded and
   parsed, so it serialised a second render-blocking request onto every single
   page load. The <link> also has the two <link rel="preconnect"> hints in front
   of it. Nothing changes visually; the same fonts arrive sooner.
   If this sheet is ever reused on a layout WITHOUT that <link>, re-add it in
   the <head> as a <link>, not here. */

/* ─── CSS Variables ─────────────────────────────────── */
:root {
  --font:       'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --font-mono:  'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;

  /* DARK theme (default) — deep teal-black canvas, soft-white text */
  --bg:          #06121a;
  --bg-2:        #0b1d27;
  --bg-3:        #12303d;
  --bg-card:     rgba(255,255,255,0.03);
  --bg-card-solid:#0b1d27;
  --border:      rgba(148,163,184,0.13);
  --border-hover:rgba(34,211,238,0.45);
  --text:        #eef2f9;
  --text-muted:  #9fb0c6;
  --text-faint:  #6b7a90;

  /* PRIMARY — turquoise */
  --accent:        #06b6d4;        /* primary turquoise */
  --accent-2:      #22d3ee;        /* lighter turquoise (hover) */
  --accent-3:      #0891b2;        /* deeper turquoise (active) */
  --accent-rgb:    6,182,212;      /* raw channels for rgba(var(--accent-rgb),…) */
  --accent-grad:   linear-gradient(135deg,#22d3ee 0%,#06b6d4 100%);
  --accent-grad-2: linear-gradient(135deg,#67e8f9 0%,#22d3ee 100%);
  --accent-soft:   rgba(6,182,212,0.14);
  /* Turquoise is a bright hue: white text on it only reaches ~2.4:1 contrast,
     which fails WCAG AA. Dark ink on turquoise reaches ~6.9:1 and reads crisper. */
  --accent-text:   #04222c;  /* text/icon colour on an accent background */

  /* SEMANTIC colors — deliberately kept clear of the turquoise primary */
  --success:     #22c55e;   /* green - completed, active */
  --success-soft:rgba(34,197,94,0.12);
  --danger:      #ef4444;   /* red - error, cancel */
  --danger-soft: rgba(239,68,68,0.12);
  --warning:     #f59e0b;   /* amber - pending, partial */
  --warning-soft:rgba(245,158,11,0.12);
  /* Info moved off cyan (it used to be #06b6d4) so it no longer collides
     with the new turquoise primary — and then off the retired brand blue
     (#3b82f6 / #2563eb), which must not appear anywhere in this theme. Indigo
     keeps "info" clearly separate from both the turquoise primary and the
     green/red/amber status colours, and reads better on the dark canvas than
     the old blue did (~7:1 against --bg instead of ~4:1). */
  --info:        #818cf8;   /* indigo - info */
  --info-soft:   rgba(129,140,248,0.12);

  /* Layout */
  --sidebar-w:          260px;
  --sidebar-w-collapsed: 68px;
  --header-h:   64px;
  --radius:     12px;
  --radius-lg:  18px;
  --radius-xl:  24px;

  /* Effects */
  --shadow-glow: 0 0 40px rgba(8,145,178,0.15);
  --shadow:      0 8px 32px rgba(0,0,0,0.5);
  --shadow-sm:   0 2px 10px rgba(0,0,0,0.3);

  --transition:  0.18s cubic-bezier(.4,0,.2,1);
  --blur:        blur(16px) saturate(1.5);

  /* PERF: the explicit property list that replaced every `transition:all`.
     `all` makes the engine set up and re-evaluate a transition for EVERY
     animatable property on every one of these elements — and the theme has
     hundreds of them at once (every sidebar link, every platform chip, every
     service-row button). It also means any layout property that happens to
     change (width, padding, font-size) animates and forces reflow on each
     frame. These six are the only properties the hover/active states in this
     sheet actually change, so the rendering is identical. */
  --transition-ui:
    color 0.18s cubic-bezier(.4,0,.2,1),
    background-color 0.18s cubic-bezier(.4,0,.2,1),
    border-color 0.18s cubic-bezier(.4,0,.2,1),
    box-shadow 0.18s cubic-bezier(.4,0,.2,1),
    transform 0.18s cubic-bezier(.4,0,.2,1),
    opacity 0.18s cubic-bezier(.4,0,.2,1);
}

html.light {
  --bg:          #ffffff;
  --bg-2:        #f7f9fc;
  --bg-3:        #eef2f8;
  --bg-card:     rgba(0,0,0,0.02);
  --bg-card-solid:#ffffff;
  --border:      rgba(15,23,42,0.10);
  --border-hover:rgba(8,145,178,0.35);
  --text:        #0f1b2e;
  --text-muted:  #4a5a72;
  --text-faint:  #8595ab;
  /* On white, the primary has to be deep enough for white button text to pass
     WCAG AA — #0e7490 gives ~5.4:1, the brighter tones do not. */
  --accent:      #0e7490;   /* deep turquoise for contrast on white */
  --accent-2:    #0891b2;
  --accent-3:    #155e75;
  --accent-rgb:  14,116,144;
  --accent-soft: rgba(14,116,144,0.09);
  /* --accent-grad had no override, so gradient-filled text (.text-gradient,
     .logo-text) and .sidebar-avatar kept the dark theme's #22d3ee → #06b6d4 on
     white — 1.5:1 at the bright end. Built from this theme's own darkened
     turquoises instead. It runs --accent → --accent-3 rather than mirroring the
     dark ramp's --accent-2 → --accent, because .sidebar-avatar prints its
     initial in --accent-text (white here): #0891b2 would leave that letter at
     3.7:1, while both steps below carry white at 5.4:1 or better, and the
     gradient text lands at 7.2:1+ on --bg. */
  --accent-grad: linear-gradient(135deg,#0e7490 0%,#155e75 100%);
  --accent-text: #ffffff;
  /* Indigo-600 on white: ~6.4:1, so info text passes WCAG AA here. */
  --info:        #4f46e5;   /* indigo - info (kept off the turquoise primary) */
  --info-soft:   rgba(79,70,229,0.09);
  /* The dark values (#22c55e / #ef4444 / #f59e0b) were never overridden here, so
     status TEXT — .text-success/.text-danger/.text-warning, .form-error and the
     three .alert-* colours — sat at roughly 2.2:1 on this white canvas. These are
     the same three hues one or two steps deeper: 7.1:1, 6.5:1 and 7.1:1 against
     --bg, and 6.6:1 or better against the matching -soft badge fill below.
     They also have to keep working as FILLS under white labels (.btn-success,
     .btn-danger, .badge-new, .sidebar-link-badge, .header-addfund-btn), so each
     one is the lightest step of its ramp that still passes 4.5:1 white-on-fill
     even while :hover opacity:0.9 composites that fill toward the white page —
     5.6:1 or better in every one of those places. One value per hue therefore
     serves both jobs and no separate text-only token is needed.
     The single exception is .btn-warning, which fills with --warning under DARK
     ink; its label is flipped for this theme next to the button rules. */
  --success:     #166534;
  --danger:      #b91c1c;
  --warning:     #92400e;
  --success-soft:rgba(34,197,94,0.09);
  --danger-soft: rgba(239,68,68,0.09);
  --warning-soft:rgba(245,158,11,0.09);
  --shadow:      0 10px 34px rgba(15,23,42,0.07);
  --shadow-sm:   0 2px 10px rgba(15,23,42,0.05);
  --shadow-glow: 0 0 40px rgba(8,145,178,0.08);
}

/* Respect user motion preference */
@media (prefers-reduced-motion: reduce) {
  *,*::before,*::after { animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important; scroll-behavior:auto!important; }
}

/* ─── Reset & Base ──────────────────────────────────── */
*,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }

/* INSTANT THEME SWITCH.
   Flipping dark/light rewrites every custom property at once. About forty components in
   this sheet carry a transition on colour, background-color, border-color and box-shadow,
   so a single click started that many animations on every element on screen at the same
   moment — hundreds of them on the services catalogue — each forcing a repaint per frame
   for 0.18s, over eleven backdrop-filter surfaces. The result was a visible crawl.
   The theme switcher adds this class, repaints, and drops it 60ms later, so the palette
   changes in one frame while hover animation everywhere else is left exactly as it was.
   scroll-behavior is included because html carries smooth scrolling and the forced
   reflow in the switch would otherwise be animated too. */
html.hs-theme-switching,
html.hs-theme-switching *,
html.hs-theme-switching *::before,
html.hs-theme-switching *::after {
  transition: none !important;
  animation: none !important;
  scroll-behavior: auto !important;
}
html { font-size:15.5px; scroll-behavior:smooth; }
body {
  font-family:'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  font-family:var(--font);
  /* LEGACY: engines with no CSS custom properties (Android WebView < 50,
     iOS < 9.3) discard every var() declaration. Without a literal first, body
     falls back to the UA default: black text on white while the panel's own
     cards stay unstyled — effectively an unreadable page. These literals are
     the dark-theme values (html.light also needs var(), so a no-var engine can
     only ever be in dark mode). Modern engines override on the next line, so
     nothing changes for them. */
  background:#06121a;
  background:var(--bg);
  color:#eef2f9;
  color:var(--text);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  /* PERF: `text-rendering:optimizeLegibility` used to be here. On <body> it
     asks the engine to run full kerning + ligature shaping over EVERY text node
     on the page before first paint — on a services page with 2 000+ rows that
     is measurable, and it is the classic cause of "the panel takes a moment
     before it shows anything". Plus Jakarta Sans ships sane default kerning, so
     the visible difference is nil. */
  min-height:100vh;
}
a { color:var(--accent); text-decoration:none; transition:color var(--transition); }
a:hover { color:var(--accent-2); }
a:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }
img { max-width:100%; height:auto; }
ul { list-style:none; }
input,textarea,select,button { font-family:inherit; }
button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible {
  outline:2px solid var(--accent); outline-offset:2px;
}

/* Text selection — readable highlight in both themes */
::selection      { background:var(--accent); color:var(--accent-text); }
::-moz-selection { background:var(--accent); color:var(--accent-text); }
.btn,.platform-btn,.nwo-cat-btn,.platform-filter-btn,.orders-filter-btn,.nav-link,.sidebar-link,.pub-nav-link,.cmd-item,.tk-subject-card,.theme-switcher button,.header-icon-btn,.header-addfund-btn,.page-item,.badge,kbd { user-select:none; -webkit-user-select:none; }

/* Scrollbar */
::-webkit-scrollbar { width:8px; height:8px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--border-hover); border-radius:99px; }
::-webkit-scrollbar-thumb:hover { background:var(--accent); }

/* ─── Typography ────────────────────────────────────── */
h1,h2,h3,h4,h5,h6 { font-weight:700; line-height:1.25; color:var(--text); letter-spacing:-0.02em; }
h1 { font-size:2rem; font-weight:800; }
h2 { font-size:1.6rem; }
h3 { font-size:1.3rem; }
h4 { font-size:1.1rem; }
p  { color:var(--text-muted); margin-bottom:0.75rem; }
small,.text-sm { font-size:0.8rem; }
code,kbd,pre { font-family:var(--font-mono); }
.text-muted  { color:var(--text-muted)!important; }
.text-faint  { color:var(--text-faint)!important; }
.text-accent { color:var(--accent)!important; }
.text-success{ color:var(--success)!important; }
.text-danger { color:var(--danger)!important; }
.text-warning{ color:var(--warning)!important; }
.text-gradient {
  /* LEGACY: `-webkit-text-fill-color:transparent` is honoured by engines that
     do NOT resolve var() (Android WebView < 50). Without a literal gradient
     underneath, the paint source is empty and the text renders INVISIBLE.
     The literal is byte-identical to --accent-grad and is overridden on the
     next line everywhere var() works. */
  background:linear-gradient(135deg,#22d3ee 0%,#06b6d4 100%);
  background:var(--accent-grad);
  -webkit-background-clip:text; -webkit-text-fill-color:transparent;
  background-clip:text;
}

/* ─── Logo ──────────────────────────────────────────── */
.logo-img { height:34px; width:auto; display:block; }
html.light .logo-img,
html.light .sidebar-logo img,
html.light .pub-nav-logo img { filter:none; } /* assume logo is color-friendly both modes */
.logo-text {
  font-size:1.35rem; font-weight:800; letter-spacing:-0.03em;
  /* LEGACY: see .text-gradient — the literal gradient keeps the brand name
     from disappearing on engines that ignore var(), and `color` is the last
     resort if background-clip:text is unsupported. -webkit-text-fill-color
     wins over `color` on every engine that supports the clip, so modern
     rendering is unchanged. */
  color:#22d3ee;
  background:linear-gradient(135deg,#22d3ee 0%,#06b6d4 100%);
  background:var(--accent-grad);
  -webkit-background-clip:text; -webkit-text-fill-color:transparent;
  background-clip:text;
}

/* ─── Buttons ───────────────────────────────────────── */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  font-size:0.875rem; font-weight:600; padding:0.55rem 1.2rem;
  border-radius:var(--radius); border:1px solid transparent;
  cursor:pointer; transition:var(--transition-ui); white-space:nowrap;
  text-decoration:none; -webkit-user-select:none; user-select:none; outline:none; position:relative;
  letter-spacing:-0.01em;
}
.btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

/* --accent-text, not #fff. Ten page templates already paint their own accent
   buttons with this variable; this sheet hardcoding white meant the same button
   looked different depending on which stylesheet won, and on the dark theme the
   white version was the ~2.4:1 pairing --accent-text exists to avoid. The hover
   state was the worst of it: --accent-2 is the brightest turquoise in the ramp,
   so a white label dropped to about 1.8:1 and the text vanished under the
   cursor. --accent-text is 9.1:1 on --accent-2 and 6.8:1 on --accent. */
.btn-primary {
  background:var(--accent); color:var(--accent-text); border-color:var(--accent);
  box-shadow:0 4px 14px rgba(8,145,178,0.3);
}
.btn-primary:hover { background:var(--accent-2); border-color:var(--accent-2); transform:translateY(-1px); box-shadow:0 6px 20px rgba(8,145,178,0.45); color:var(--accent-text); }
.btn-primary:active { transform:translateY(0); }

.btn-secondary { background:var(--bg-3); color:var(--text); border-color:var(--border); }
.btn-secondary:hover { background:var(--bg-2); border-color:var(--border-hover); color:var(--text); }

.btn-outline { background:transparent; color:var(--text); border-color:var(--border-hover); }
.btn-outline:hover { border-color:var(--accent); color:var(--accent); background:var(--accent-soft); }

.btn-success { background:var(--success); color:#fff; border-color:var(--success); }
.btn-success:hover { opacity:0.9; transform:translateY(-1px); color:#fff; }

.btn-danger { background:var(--danger); color:#fff; border-color:var(--danger); }
.btn-danger:hover { opacity:0.9; color:#fff; }

.btn-warning { background:var(--warning); color:#0f172a; border-color:var(--warning); }
.btn-warning:hover { opacity:0.9; color:#0f172a; }
/* The one place --warning is a FILL under dark ink, and therefore the one thing
   the light theme's deeper amber breaks: #0f172a on it is 2.5:1, white is 7.1:1.
   The :hover selector is repeated because .btn-warning:hover is 0,2,0 and would
   otherwise outrank the 0,1,1 rule on the line above it. */
html.light .btn-warning,
html.light .btn-warning:hover { color:#fff; }

.btn-ghost { background:transparent; color:var(--text-muted); border:none; }
.btn-ghost:hover { background:var(--accent-soft); color:var(--text); }

.btn-sm { font-size:0.78rem; padding:0.4rem 0.85rem; border-radius:9px; }
.btn-lg { font-size:0.95rem; padding:0.72rem 1.6rem; border-radius:14px; }
.btn-block { width:100%; }
.btn-icon  { padding:0.5rem; aspect-ratio:1; border-radius:10px; }
/* LEGACY: aspect-ratio needs Chrome/WebView 88+, Safari/iOS 15+, Firefox 89+.
   Where it is unknown the declaration is dropped and an icon-only button
   collapses to a narrow slab. Scoped inside @supports so engines that DO
   understand aspect-ratio never see these values — modern rendering is
   provably byte-identical. 2.45rem ≈ the 38px square aspect-ratio produces at
   the theme's 15.5px root size. */
@supports not (aspect-ratio: 1 / 1) {
  .btn-icon { min-width:2.45rem; min-height:2.45rem; }
}
.btn[disabled],.btn.disabled { opacity:0.45; pointer-events:none; }

/* ─── Forms ─────────────────────────────────────────── */
.form-group { margin-bottom:1.2rem; }
.form-label { display:block; font-size:0.8rem; font-weight:600; color:var(--text-muted); margin-bottom:0.45rem; letter-spacing:0.02em; }
.form-control {
  width:100%; background:var(--bg-2); color:var(--text);
  border:1.5px solid var(--border); border-radius:var(--radius);
  padding:0.6rem 0.95rem; font-size:0.92rem;
  transition:border-color var(--transition), box-shadow var(--transition), background var(--transition);
  -webkit-appearance:none; appearance:none;
}
.form-control:focus {
  outline:none; border-color:var(--accent); background:var(--bg-3);
  box-shadow:0 0 0 3px var(--accent-soft);
}
.form-control::placeholder { color:var(--text-faint); }
.form-control[readonly], .form-control:disabled { background:var(--bg-3); color:var(--text-muted); -webkit-text-fill-color:var(--text-muted); opacity:1; cursor:default; }
textarea.form-control { resize:vertical; min-height:110px; font-family:var(--font); }
select.form-control {
  cursor:pointer;
  min-height:56px;
  padding:0.85rem 2.5rem 0.85rem 1rem;
  font-size:0.9rem;
  border-radius:12px;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%237a8597' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 1rem center;
  background-size:12px 8px;
  line-height:1.4;
}
select.form-control option { background:var(--bg-card-solid); color:var(--text); padding:8px; }
.form-control-lg { padding:0.78rem 1.1rem; font-size:0.98rem; }
.input-group { display:flex; }
.input-group .form-control { border-radius:var(--radius) 0 0 var(--radius); border-right:none; }
.input-group .btn { border-radius:0 var(--radius) var(--radius) 0; }
.form-check { display:flex; align-items:center; gap:10px; cursor:pointer; }
.form-check input[type="checkbox"] { width:17px; height:17px; accent-color:var(--accent); cursor:pointer; }
.form-error { font-size:0.78rem; color:var(--danger); margin-top:0.35rem; }
.form-hint  { font-size:0.78rem; color:var(--text-faint); margin-top:0.35rem; }

/* ─── Cards (subtle glass) ──────────────────────────── */
.card {
  /* LEGACY: Android WebView 4.4 / Chromium below 49 and iOS below 9.3 have no
     CSS custom properties, so the var() line is dropped and Bootstrap 4.5's
     .card{background-color:#fff} survives underneath - while body keeps its
     literal near-white text. Literal dark value first, exactly as body does;
     modern engines still take the var() line below. */
  background:#0b1d27;
  background:var(--bg-card-solid);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  overflow:hidden;
  transition:border-color var(--transition);
}
.card:hover { border-color:var(--border-hover); }
.card-body   { padding:1.4rem 1.6rem; }
.card-header { padding:1.1rem 1.6rem; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; }
.card-footer { padding:1.1rem 1.6rem; border-top:1px solid var(--border); }
.card-title  { font-size:1rem; font-weight:700; color:var(--text); margin:0; letter-spacing:-0.01em; }
.card-shadow { box-shadow:var(--shadow); }
.card-glow   { box-shadow:var(--shadow-glow); }

/* ─── Alerts ────────────────────────────────────────── */
.alert {
  padding:0.9rem 1.2rem; border-radius:var(--radius);
  border:1px solid transparent; font-size:0.9rem; margin-bottom:1rem;
  display:flex; align-items:flex-start; gap:11px;
}
.alert-success { background:var(--success-soft);  border-color:rgba(34,197,94,0.3);  color:var(--success); }
.alert-danger  { background:var(--danger-soft);   border-color:rgba(239,68,68,0.3);  color:var(--danger); }
.alert-warning { background:var(--warning-soft);  border-color:rgba(245,158,11,0.3); color:var(--warning); }
/* The border used to be rgba(6,182,212,..) — the turquoise primary — around an
   otherwise blue alert, which is exactly the collision the --info comment in
   :root warns about. It now tracks --info like every other alert here does. */
.alert-info    { background:var(--info-soft);     border-color:rgba(129,140,248,0.3); color:var(--info); }
/* Bootstrap 4.5 ships TWO layout modes for this button that both fight the flex row
   above: `.close{float:right}` and `.alert-dismissible .close{position:absolute;top:0;
   right:0}`. The old rule here had the same 0,2,0 specificity but declared no
   `position`, so `position:absolute` survived: the X left the flex flow, margin-left:auto
   became dead code, and it was pinned to the alert's corner. Bootstrap's matching gutter
   `.alert-dismissible{padding-right:4rem}` had meanwhile been wiped out by the `.alert`
   padding shorthand above, so nothing was reserved for it and it was painted on top of
   the message (and directly on the first letter under dir="rtl").
   `.alert.alert-dismissible .close` is 0,3,0, so this wins whatever order the two
   uploaded stylesheets end up in. The button is a real flex item again: no float, no
   absolute positioning, >=40px tap target, and negative margins so the banner does not
   get taller than it is today. margin-left:auto is direction-agnostic here because the
   button is the last flex item in both LTR and RTL. */
.alert .close,
.alert.alert-dismissible .close {
  position:static; float:none; top:auto; right:auto; bottom:auto; left:auto;
  /* order, not DOM order. PerfectPanel and the factory theme write the dismiss button
     BEFORE the message text; this theme writes it after. With margin-left:auto alone, a
     close-first alert pushed the button right AND dragged the message along behind it —
     which is exactly the "text jammed against the right edge with the X on top of the
     first letter" the panel was showing. order:99 puts the button last whatever the
     markup does; the message is an anonymous flex item at order 0, so it always leads. */
  order:99;
  flex:0 0 auto; align-self:flex-start;
  margin:-9px -9px -9px auto; padding:9px;
  min-width:40px; min-height:40px;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:0; border-radius:8px;
  font-size:1.15rem; line-height:1; font-weight:400;
  color:inherit; text-shadow:none; opacity:0.6; cursor:pointer;
}
.alert .close:hover,
.alert.alert-dismissible .close:hover { opacity:1; }
.alert .close:focus-visible { outline:2px solid currentColor; outline-offset:2px; opacity:1; }
/* The X is a flex item now, so Bootstrap's 4rem reservation would only be dead space. */
.alert.alert-dismissible { padding-right:1.2rem; }
/* icon | message (takes the free space, may wrap) | dismiss. min-width:0 keeps a long
   unbreakable URL in an alert from widening the row past the viewport. */
.alert > span, .alert > p, .alert > div:not(.close) { flex:1 1 auto; min-width:0; }
.alert > i, .alert > svg { flex:0 0 auto; line-height:1.55; }
/* margin-left:auto above is direction-agnostic in a flex row, but margin-right:-9px is
   not: it names a PHYSICAL side. Under dir="rtl" (set on <html> by layout.twig) the row
   reverses, the dismiss button sits at the physical LEFT, and that negative margin then
   eats into the gap toward the message instead of into the alert's own padding.
   margin-inline would express this correctly but is not available on the old WebViews
   this theme still supports, so the two physical sides are simply swapped here.
   Specificity is 0,3,0 / 0,4,0 and this rule is later in the file, so it wins over both
   base rules above. */
[dir="rtl"] .alert .close,
[dir="rtl"] .alert.alert-dismissible .close { margin:-9px auto -9px -9px; }

/* ─── Badges ────────────────────────────────────────── */
.badge {
  display:inline-flex; align-items:center; padding:0.22rem 0.6rem;
  font-size:0.7rem; font-weight:700; border-radius:6px; letter-spacing:0.4px;
  text-transform:uppercase;
}
.badge-success { background:var(--success-soft); color:var(--success); }
.badge-danger  { background:var(--danger-soft); color:var(--danger); }
.badge-warning { background:var(--warning-soft); color:var(--warning); }
.badge-info    { background:var(--info-soft); color:var(--info); }
.badge-neutral { background:var(--bg-3); color:var(--text-muted); }
.badge-accent  { background:var(--accent-soft); color:var(--accent); }

/* "NEW" badge — for fresh items */
.badge-new {
  background:var(--success); color:#fff; padding:0.18rem 0.5rem;
  font-size:0.62rem; font-weight:800; border-radius:5px;
  letter-spacing:0.5px; text-transform:uppercase;
  box-shadow:0 0 8px rgba(34,197,94,0.4);
}

/* ─── Tables ────────────────────────────────────────── */
.table-wrap {
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  border-radius:var(--radius-lg); border:1px solid var(--border);
  background:var(--bg-card-solid);
}
.table { width:100%; border-collapse:collapse; font-size:0.88rem; }
.table thead th {
  padding:0.85rem 1.1rem; text-align:left; font-size:0.72rem; font-weight:700;
  color:var(--text-faint); text-transform:uppercase; letter-spacing:0.7px;
  background:var(--bg-2); border-bottom:1px solid var(--border); white-space:nowrap;
}
.table tbody tr { border-bottom:1px solid var(--border); transition:background var(--transition); }
.table tbody tr:last-child { border-bottom:none; }
.table tbody tr:hover { background:var(--accent-soft); }
.table tbody td { padding:0.8rem 1.1rem; vertical-align:middle; color:var(--text); }

/* ─── Pagination ────────────────────────────────────── */
.pagination { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.page-item .page-link {
  display:flex; align-items:center; justify-content:center;
  min-width:36px; height:36px; padding:0 8px;
  background:var(--bg-2); border:1px solid var(--border);
  color:var(--text-muted); border-radius:9px; font-size:0.85rem;
  transition:var(--transition-ui); cursor:pointer;
}
.page-item .page-link:hover { color:var(--accent); border-color:var(--accent); }
.page-item.active .page-link { background:var(--accent); border-color:var(--accent); color:var(--accent-text); }
.page-item.disabled .page-link { opacity:0.4; pointer-events:none; }

/* ─── Tabs ──────────────────────────────────────────── */
.nav-tabs { display:flex; gap:6px; border-bottom:1px solid var(--border); margin-bottom:1.4rem; overflow-x:auto; }
.nav-tabs .nav-link {
  padding:0.65rem 1.1rem; font-size:0.88rem; font-weight:600;
  color:var(--text-muted); border-bottom:2px solid transparent;
  border-radius:var(--radius) var(--radius) 0 0; cursor:pointer;
  transition:var(--transition-ui); white-space:nowrap; position:relative;
  display:inline-flex; align-items:center; gap:6px;
}
.nav-tabs .nav-link:hover { color:var(--text); }
.nav-tabs .nav-link.active { color:var(--accent); border-bottom-color:var(--accent); }
.tab-content .tab-pane { display:none; }
.tab-content .tab-pane.show.active { display:block; animation:fadeIn .18s ease; }
@keyframes fadeIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }

/* ─── Modal ────────────────────────────────────────── */
/* BOOTSTRAP COLLISION — this one block is the cause of BOTH open bugs:
   "every Detail modal opens transparent with unreadable text" and "the
   hCaptcha checkbox cannot be clicked and the dialog cannot be closed".
   layout.twig loads Bootstrap 4.5 from the CDN and Bootstrap owns the exact
   same four class names this theme uses:
     .modal          { z-index:1050; display:none; overflow:hidden }
     .modal-dialog   { pointer-events:none; margin:.5rem }
     .modal-content  { background-color:#fff; pointer-events:auto }
     .modal-backdrop { z-index:1040; background-color:#000; width:100vw }
   The theme had z-index:1000 on .modal, 1001 on .modal-dialog and NO z-index
   at all on .modal-backdrop — so the backdrop silently inherited Bootstrap's
   1040. Inside .modal's own stacking context 1040 > 1001, i.e. the black
   backdrop was painted ON TOP OF the dialog: the panel reads as washed
   out/unreadable, and every click (the hCaptcha checkbox, the X, the footer
   buttons) is swallowed by the backdrop instead of reaching the content.
   Fix — the same technique the sister panel uses: an explicit stacking order
   INSIDE .modal (backdrop 1, dialog 2), the whole layer lifted to 3000 so it
   clears Bootstrap's 1050 and the theme's own #cmdPalette (2000), an opaque
   backdrop, and pointer-events restored on .modal-dialog. The !important flags
   are what make this hold no matter which stylesheet the panel serves last. */
/* LEGACY: `inset` needs Chrome/WebView 87+, Safari/iOS 14.1+, Samsung Internet
   14+. Older engines drop the declaration and the layer collapses to 0x0 — no
   dialog at all. The longhands compute the identical box everywhere. */
.modal {
  display:none; position:fixed!important;
  top:0; right:0; bottom:0; left:0; inset:0;
  z-index:3000!important;
  align-items:center; justify-content:center; padding:1rem;
  /* Bootstrap sets overflow:hidden here, which clips a dialog taller than the
     viewport with no way to scroll down to its buttons. */
  overflow:auto!important;
}
/* Deliberately NOT `display:none!important` on the rule above: some
   PerfectPanel-injected dialogs are opened by a script writing an inline
   display, and an !important stylesheet declaration outranks an inline one —
   those dialogs would then never open again. Bootstrap already defaults
   .modal to display:none, so the plain declaration is enough, and the
   !important below is all that is needed to force ours open. */
.modal.show { display:flex!important; animation:fadeIn .15s ease; }
/* backdrop-filter needs the -webkit- alias for Safari/iOS < 18 and is disabled
   by default in Firefox; the rgba() layer alone already dims the page, so the
   blur is decoration, never a legibility requirement. */
.modal-backdrop {
  position:fixed!important; top:0; right:0; bottom:0; left:0; inset:0;
  /* Bootstrap's width:100vw/height:100vh include the classic scrollbar gutter
     and pushed the document sideways on Windows while a dialog was open. */
  width:100%!important; height:100%!important;
  z-index:1!important;
  background:rgba(0,0,0,0.7)!important; opacity:1!important;
  -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px);
}
/* The theme nests its backdrop INSIDE .modal, so z-index:1 above is measured
   against .modal's own stacking context and lands under the dialog — correct.
   Bootstrap's JS instead appends its backdrop as a direct child of <body>,
   where z-index:1 would drop it behind the sidebar (400) and the sticky header
   (300) and leave the page around a PP-opened dialog undimmed. Lift only that
   variant, to just under .modal's 3000. */
body > .modal-backdrop { z-index:2999!important; }
.modal-dialog {
  position:relative; z-index:2!important;
  /* Bootstrap's pointer-events:none is what kills clicks on a third-party
     widget (the hCaptcha iframe) that PP renders straight into the dialog
     without a .modal-content wrapper to restore them. */
  pointer-events:auto!important;
  width:100%; max-width:500px;
  /* margin:auto (not Bootstrap's .5rem): with the flex centring above, a
     dialog taller than the viewport would otherwise have its top edge — the
     header with the close button — cut off instead of scrolling into reach. */
  margin:auto;
  animation:modalPop .18s cubic-bezier(.4,0,.2,1);
}
@keyframes modalPop { from{opacity:0;transform:scale(0.96) translateY(8px)} to{opacity:1;transform:scale(1) translateY(0)} }
.modal-content {
  position:relative;
  /* !important beats Bootstrap's background-color:#fff — that white slab under
     white-ish theme text is the "transparent / unreadable" report. */
  /* LEGACY: no CSS custom properties below Chromium 49 / iOS 9.3, so BOTH
     declarations below are dropped (!important cannot rescue an invalid
     value), Bootstrap's background-color:#fff wins and the text inherits
     body's near-white literal - a blank white dialog. Literal dark values
     first; modern engines still take the var() line below. */
  background:#0b1d27!important; color:#eef2f9;
  background:var(--bg-card-solid)!important; color:var(--text);
  border:1px solid var(--border-hover);
  border-radius:var(--radius-lg); box-shadow:var(--shadow),var(--shadow-glow);
  pointer-events:auto!important;
}
/* Anything PP or a third party injects into a dialog stays interactive and
   stays above the backdrop layer. hCaptcha renders its checkbox as an <iframe>
   and its "Verify you're human" challenge as a position:fixed overlay next to
   it; both need to receive pointer events. */
.modal iframe,
.modal .h-captcha,
.modal .g-recaptcha,
.modal-body,
.modal-header,
.modal-footer { pointer-events:auto; }
.modal iframe { position:relative; z-index:1; max-width:100%; }
.modal-header { display:flex; align-items:center; justify-content:space-between; padding:1.2rem 1.6rem; border-bottom:1px solid var(--border); }
.modal-title  { font-size:1.05rem; font-weight:700; }
.modal-body   { padding:1.6rem; }
.modal-footer { padding:1.1rem 1.6rem; border-top:1px solid var(--border); display:flex; justify-content:flex-end; gap:8px; }
.modal-close  { background:none; border:none; color:var(--text-muted); font-size:1.25rem; cursor:pointer; padding:0; line-height:1; transition:color var(--transition); }
.modal-close:hover { color:var(--text); }
/* PP's own dialogs use Bootstrap's markup, <button class="close">&times;</button>,
   which Bootstrap paints as `color:#000; text-shadow:0 1px 0 #fff; opacity:.5`
   — a black glyph on the dark panel surface, i.e. an invisible close button.
   That is the other half of "the modal cannot be closed". */
.modal .close {
  background:none; border:none; float:none;
  color:var(--text-muted); text-shadow:none; opacity:0.75;
  font-size:1.4rem; line-height:1; cursor:pointer;
  padding:0.5rem; margin:-0.5rem -0.5rem -0.5rem auto;
  pointer-events:auto!important;
}
.modal .close:hover,
.modal .close:focus { color:var(--text); opacity:1; }

/* ─── Third-party captcha widgets (hCaptcha / reCAPTCHA) ─ */
/* THE SIGNUP BUG. An element with a non-none `filter`, `backdrop-filter`,
   `transform`, `perspective` or `will-change` becomes the CONTAINING BLOCK for
   every position:fixed descendant. signup.twig and signin.twig both give
   .land-auth-card `backdrop-filter:blur(10px)`, so hCaptcha's "Verify you're
   human" challenge — a position:fixed, full-viewport overlay hCaptcha injects
   right next to its own checkbox iframe — was laid out relative to a ~420px
   card instead of the viewport, and then clipped by .land-hero{overflow:hidden}
   around it. The overlay is visible but its checkbox and its close control sit
   outside the clip: exactly the "cannot click, cannot close" report, and it
   blocks every registration.
   Both cards paint on --bg-card-solid, which is fully opaque, so there is
   nothing behind them for the blur to reveal — removing it is invisible. It is
   also a Task-3 win: an always-on backdrop-filter over a large surface is one
   of the most expensive things a compositor can be asked to keep alive, and it
   is repainted on every scroll frame.
   These two pages are not mine to edit, hence !important from here. */
.land-auth-card,
.auth-card {
  -webkit-backdrop-filter:none!important;
  backdrop-filter:none!important;
}
/* Defensive: nothing in the theme may swallow taps meant for a captcha. */
.h-captcha,
.g-recaptcha,
.land-captcha,
iframe[src*="hcaptcha"],
iframe[src*="recaptcha"] { pointer-events:auto!important; }

/* ─── Progress Bar ──────────────────────────────────── */
.progress { background:var(--bg-3); border-radius:99px; height:7px; overflow:hidden; }
.progress-bar { height:100%; background:var(--accent-grad); border-radius:99px; transition:width .5s cubic-bezier(.4,0,.2,1); }

/* ─── Spinner ───────────────────────────────────────── */
.spinner { width:22px; height:22px; border:2.5px solid var(--border); border-top-color:var(--accent); border-radius:50%; animation:spin .7s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }

/* ─── Theme Switcher ────────────────────────────────── */
.theme-switcher { display:flex; background:var(--bg-2); border:1px solid var(--border); border-radius:10px; padding:2px; gap:2px; }
.theme-switcher button {
  padding:6px 11px; border-radius:7px; border:none;
  background:none; color:var(--text-muted); font-size:0.82rem;
  cursor:pointer; transition:var(--transition-ui);
}
.theme-switcher button.active { background:var(--accent); color:var(--accent-text); }
.theme-switcher button:hover:not(.active) { color:var(--text); }

/* ═══════════════════════════════════════════════════════
   DASHBOARD LAYOUT
   ═══════════════════════════════════════════════════════ */
#dash { display:flex; flex-wrap:wrap; align-content:flex-start; min-height:100vh; }
/* #notify-wrapper and whatever site['custom_footer'] injects are SIBLINGS of .app
   inside #dash — they have to stay there, ahead of the site scripts loop, for the
   reason spelled out in layout.twig above the wrapper. As plain flex items on a
   nowrap row they became second/third columns: .app shrank and the notification
   painted as a 100vh-tall strip down the right edge the moment PP unhid it.
   With wrap on, .app (width:100%) claims a whole line by itself and every sibling
   is pushed onto its own line, so each renders full width and nothing squeezes the
   dashboard. align-content:flex-start stops a wrapped line from being stretched to
   fill leftover height. */
#dash > #notify-wrapper { flex:0 0 100%; width:100%; order:-1; }
/* The sidebar is position:fixed, so it is out of normal flow and .app has only
   one in-flow child (.page). Keeping .app as a flex row made .page (flex:1)
   resolve to the FULL width of .app and then pushed it right by margin-left,
   overflowing the viewport by exactly the sidebar width — that was the extra
   empty strip on the right and the "page does not fit the screen" reports.
   As a block, .page simply fills what is left after its margin. */
.app  { display:block; width:100%; min-height:100vh; }

/* ─── Sidebar ───────────────────────────────────────── */
.sidebar {
  /* iOS: 100vh is the LARGE viewport - the height with the browser chrome
     hidden - so the bottom 50-85px of this fixed drawer sits underneath
     Safari's toolbar. .sidebar-content is the scroller and is the last child
     here, so its own bottom edge is down there too and the LAST menu entry can
     never be scrolled into a tappable position. dvh tracks the visible
     viewport. LEGACY: dvh needs iOS 15.4+, Chrome 108+, Firefox 101+; every
     older engine keeps the 100vh declaration in front of it, and on desktop
     dvh equals vh, so nothing else changes anywhere. */
  width:var(--sidebar-w); height:100vh; height:100dvh; position:fixed; left:0; top:0;
  background:var(--bg-2);
  border-right:1px solid var(--border);
  display:flex; flex-direction:column; z-index:400;
  transition:transform .3s cubic-bezier(.4,0,.2,1), width .25s ease;
  overflow:hidden;
}
.sidebar.collapsed { width:var(--sidebar-w-collapsed); }
.sidebar.collapsed .sidebar-user-name,
.sidebar.collapsed .sidebar-user-badge,
.sidebar.collapsed .sidebar-chevron,
.sidebar.collapsed .sidebar-link-text,
.sidebar.collapsed .sb-balance-label,
.sidebar.collapsed .sb-balance-value,
.sidebar.collapsed .logo-text,
.sidebar.collapsed .sidebar-section-label,
.sidebar.collapsed .sidebar-dismiss { display:none; }
.sidebar.collapsed .sidebar-avatar,
.sidebar.collapsed .sidebar-balance,
.sidebar.collapsed .sidebar-link { justify-content:center; }
.sidebar.collapsed .sidebar-profile { padding:0.55rem; justify-content:center; }

.sidebar-header {
  padding:1.2rem 1.2rem 0.85rem;
  display:flex; align-items:center; justify-content:space-between;
  flex-shrink:0;
}
.sidebar-logo { display:flex; align-items:center; gap:10px; text-decoration:none; }
.sidebar-dismiss { display:none; background:none; border:none; color:var(--text-muted); font-size:1.05rem; cursor:pointer; padding:4px; }

.sidebar-collapse-btn {
  position:absolute; top:16px; right:-14px; z-index:410;
  width:28px; height:28px; border-radius:50%;
  background:var(--bg-card-solid); border:1px solid var(--border-hover);
  color:var(--text-muted); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  font-size:0.7rem; transition:var(--transition-ui);
}
.sidebar-collapse-btn:hover { color:var(--accent); border-color:var(--accent); }
.sidebar.collapsed + .page .sidebar-collapse-btn i { transform:rotate(180deg); }

.sidebar-content { flex:1; overflow-y:auto; padding:0.6rem 0.7rem 1rem; }

/* User profile */
.sidebar-profile {
  display:flex; align-items:center; gap:10px; padding:0.6rem 0.75rem;
  background:var(--bg-3); border:1px solid var(--border); border-radius:var(--radius);
  cursor:pointer; width:100%; margin:0.3rem 0 0.5rem;
  transition:border-color var(--transition);
}
.sidebar-profile:hover { border-color:var(--border-hover); }
.sidebar-avatar {
  width:34px; height:34px; border-radius:50%;
  background:var(--accent-grad);
  display:flex; align-items:center; justify-content:center;
  font-weight:700; font-size:0.85rem; color:var(--accent-text); flex-shrink:0;
  box-shadow:0 4px 12px rgba(8,145,178,0.3);
}
.sidebar-user-name  { font-size:0.88rem; font-weight:700; color:var(--text); }
.sidebar-user-badge { font-size:0.68rem; font-weight:700; letter-spacing:0.4px; color:var(--accent); }
.sidebar-chevron    { margin-left:auto; color:var(--text-faint); font-size:0.75rem; transition:transform var(--transition); }
.sidebar-profile[aria-expanded="true"] .sidebar-chevron { transform:rotate(180deg); }

/* Navigation */
.sidebar-nav { padding:0; }
.sidebar-section-label {
  font-size:0.66rem; font-weight:700; color:var(--text-faint);
  text-transform:uppercase; letter-spacing:1px;
  padding:0.85rem 0.75rem 0.35rem;
}
.sidebar-link {
  display:flex; align-items:center; gap:11px; padding:0.6rem 0.75rem;
  color:var(--text-muted); border-radius:var(--radius); font-size:0.88rem;
  font-weight:500; cursor:pointer; transition:var(--transition-ui); margin-bottom:2px;
  text-decoration:none; position:relative;
}
.sidebar-link:hover { background:var(--bg-3); color:var(--text); }
.sidebar-link.active {
  background:var(--accent-soft); color:var(--accent); font-weight:600;
}
.sidebar-link.active .sidebar-icon { color:var(--accent); }
.sidebar-link.sidebar-link-highlight { color:var(--success); font-weight:600; }
.sidebar-link.sidebar-link-highlight .sidebar-icon { color:var(--success); }
.sidebar-icon { font-size:1rem; width:20px; text-align:center; flex-shrink:0; color:var(--text-faint); }
.sidebar-link-text { flex:1; }
.sidebar-link-badge {
  margin-left:auto; font-size:0.65rem; font-weight:700;
  padding:0.1rem 0.4rem; background:var(--success); color:#fff;
  border-radius:99px; letter-spacing:0.3px;
}

/* Balance block */
.sidebar-balance {
  margin:0 0.75rem 0.75rem;
  padding:0.75rem 0.9rem;
  background:linear-gradient(135deg, var(--bg-3), var(--bg-2));
  border:1px solid var(--border);
  border-radius:var(--radius);
  position:relative; overflow:hidden;
}
.sidebar-balance::before {
  content:''; position:absolute; top:0; right:0; width:80px; height:80px;
  background:var(--accent); opacity:0.07; border-radius:50%;
  transform:translate(30%,-30%);
}
.sb-balance-label { font-size:0.7rem; color:var(--text-faint); font-weight:600; text-transform:uppercase; letter-spacing:0.6px; }
.sb-balance-value { font-size:1.2rem; font-weight:800; color:var(--text); margin-top:2px; letter-spacing:-0.02em; font-family:var(--font-mono); }

/* ─── Page ──────────────────────────────────────────── */
/* No flex:1 here — see the note on .app. min-width:0 stops a wide child (a
   services/orders table) from stretching the page instead of scrolling itself. */
.page { margin-left:var(--sidebar-w); min-width:0; min-height:100vh; display:flex; flex-direction:column; transition:margin-left .25s ease; }
/* LEGACY: a selector LIST is thrown away in full when ONE of its selectors is
   unparseable, so keeping `.sidebar.collapsed ~ .page` in the same comma list as
   the `:has()` variant meant every engine without :has() (Safari <15.4,
   Chrome/WebView <105, Firefox <121) lost the collapsed page margin entirely.
   Split into standalone rules: the sibling combinator needs no :has() at all
   (#sidebar and .page are siblings inside .app), the body-class rule is mirrored
   from JS, and the :has() rule stays LAST as the progressive enhancement.
   All three set the identical value, so modern rendering is unchanged. */
.sidebar.collapsed ~ .page { margin-left:var(--sidebar-w-collapsed); }
body.hs-sidebar-collapsed .page { margin-left:var(--sidebar-w-collapsed); }
body:has(.sidebar.collapsed) .page { margin-left:var(--sidebar-w-collapsed); }
/* min-width:0 lets wide tables scroll inside their own wrapper instead of
   stretching the whole page. */
.page-content { flex:1; min-width:0; padding:1.75rem 2rem; }

/* ─── Header ────────────────────────────────────────── */
.top-header {
  /* LEGACY: iOS Safari 8–12 and old Android WebView only know -webkit-sticky.
     The unprefixed value stays LAST so every modern engine uses it; engines
     that cannot parse `sticky` fall back to the prefixed one instead of
     silently becoming `static` and losing the pinned header. */
  height:var(--header-h); position:-webkit-sticky; position:sticky; top:0; z-index:300; flex-shrink:0;
  background:var(--bg-card-solid);
  border-bottom:1px solid var(--border);
  display:flex; align-items:center; padding:0 1.75rem; gap:12px;
}
.header-menu-btn {
  background:none; border:none; color:var(--text-muted); font-size:1.15rem;
  cursor:pointer; padding:8px; border-radius:10px; transition:var(--transition-ui);
  display:none;
}
.header-menu-btn:hover { background:var(--accent-soft); color:var(--text); }

/* Absolutely centring the search took it out of flow, so the header could not
   reserve space for it: it sat on top of the balance chip and the action
   buttons, and any extra button pushed the row past the viewport. Laying it out
   as a normal flex item that is allowed to shrink fixes both. */
.header-search {
  position:relative;
  flex:0 1 420px;
  min-width:0;
}
.header-search input {
  width:100%; padding:0.5rem 0.9rem 0.5rem 2.5rem;
  background:var(--bg-3); border:1px solid var(--border); border-radius:10px;
  color:var(--text); font-size:0.88rem;
  transition:var(--transition-ui);
}
.header-search input:focus { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.header-search i { position:absolute; left:0.85rem; top:50%; transform:translateY(-50%); color:var(--text-faint); font-size:0.85rem; pointer-events:none; }
.header-search kbd {
  position:absolute; right:0.75rem; top:50%; transform:translateY(-50%);
  font-family:var(--font-mono); font-size:0.7rem;
  padding:2px 7px; background:var(--bg-card-solid); border:1px solid var(--border); border-radius:5px;
  color:var(--text-faint);
}

.header-actions { display:flex; align-items:center; gap:8px; margin-left:auto; }
.header-icon-btn {
  background:none; border:none; color:var(--text-muted);
  width:38px; height:38px; display:flex; align-items:center; justify-content:center;
  border-radius:10px; cursor:pointer; transition:var(--transition-ui); font-size:1rem;
  text-decoration:none; position:relative;
}
.header-icon-btn:hover { background:var(--accent-soft); color:var(--accent); }
.header-balance-chip {
  display:flex; align-items:center; gap:7px; padding:6px 14px;
  background:var(--accent-soft); border:1px solid rgba(8,145,178,0.2);
  border-radius:99px; font-size:0.82rem; font-weight:700; color:var(--text);
  font-family:var(--font-mono);
}
.header-balance-chip .dot { width:7px; height:7px; border-radius:50%; background:var(--success); box-shadow:0 0 8px var(--success); }

/* 🌟 AddFund quick button in header */
.header-addfund-btn {
  display:inline-flex; align-items:center; gap:6px;
  padding:6px 14px; background:var(--success); color:#fff;
  border:none; border-radius:99px; font-size:0.82rem; font-weight:700;
  text-decoration:none; transition:var(--transition-ui);
  box-shadow:0 4px 12px rgba(34,197,94,0.25);
}
.header-addfund-btn:hover { transform:translateY(-1px); box-shadow:0 6px 18px rgba(34,197,94,0.4); color:#fff; opacity:0.95; }

/* Notification badge dot */
.header-icon-btn .badge-dot {
  position:absolute; top:7px; right:7px; width:8px; height:8px;
  background:var(--danger); border-radius:50%; border:2px solid var(--bg-card-solid);
}

/* ─── Page Header ───────────────────────────────────── */
.page-header { margin-bottom:1.75rem; }
.page-title  { font-size:1.45rem; font-weight:800; color:var(--text); letter-spacing:-0.025em; }
.page-subtitle { font-size:0.9rem; color:var(--text-muted); margin-top:4px; }
.page-header-row { display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; }

/* Sidebar overlay (mobile) */
/* LEGACY: inset longhands + -webkit- alias — see the note on .modal-backdrop.
   Without the longhands this overlay is 0x0 on iOS <14.1, so "tap outside to
   close the menu" silently stops working. */
.sidebar-overlay { display:none; position:fixed; top:0; right:0; bottom:0; left:0; inset:0; background:rgba(0,0,0,0.65); z-index:399; -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); }
.sidebar-overlay.show { display:block; }

/* ═══════════════════════════════════════════════════════
   FLOATING CHAT WIDGET (new — bottom right)
   ═══════════════════════════════════════════════════════ */
.floating-contact {
  position:fixed; bottom:1.5rem; right:1.5rem; z-index:900;
  display:flex; flex-direction:column; gap:12px;
}
.fc-btn {
  width:52px; height:52px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  color:#fff; font-size:1.5rem; text-decoration:none;
  box-shadow:0 6px 20px rgba(0,0,0,0.28);
  transition:transform .18s ease, box-shadow .18s ease;
}
.fc-btn:hover { transform:translateY(-3px) scale(1.06); color:#fff; box-shadow:0 10px 26px rgba(0,0,0,0.38); }
.fc-wa { background:#25D366; }
.fc-tg { background:#229ED9; }
@media (max-width:600px) {
  .floating-contact { bottom:1rem; right:1rem; gap:10px; }
  .fc-btn { width:46px; height:46px; font-size:1.3rem; }
}

/* ═══════════════════════════════════════════════════════
   PUBLIC LAYOUT
   ═══════════════════════════════════════════════════════ */
#outside { min-height:100vh; }
.pub-nav {
  /* LEGACY: -webkit-sticky first for iOS 8–12 / old Android WebView. */
  position:-webkit-sticky; position:sticky; top:0; z-index:500; height:64px; display:flex; align-items:center;
  background:var(--bg-card-solid);
  border-bottom:1px solid var(--border);
}
.pub-nav-inner {
  max-width:1180px; margin:0 auto; padding:0 1.75rem;
  display:flex; align-items:center; gap:1.5rem; width:100%;
}
.pub-nav-logo { display:flex; align-items:center; text-decoration:none; flex-shrink:0; }
.pub-nav-menu { display:flex; align-items:center; gap:4px; flex:1; margin-left:1.5rem; }
.pub-nav-link { padding:0.45rem 0.9rem; border-radius:9px; color:var(--text-muted); font-size:0.875rem; font-weight:600; transition:var(--transition-ui); }
.pub-nav-link:hover { color:var(--text); background:var(--accent-soft); }
.pub-nav-actions { display:flex; align-items:center; gap:8px; margin-left:auto; flex-shrink:0; }
.pub-nav-hamburger { display:none; background:none; border:none; color:var(--text-muted); font-size:1.2rem; cursor:pointer; padding:7px; border-radius:8px; }
.pub-nav-drawer { display:none; position:fixed; top:64px; left:0; right:0; background:var(--bg-card-solid); border-bottom:1px solid var(--border); padding:0.9rem 1.1rem; z-index:499; flex-direction:column; gap:4px; }
.pub-nav-drawer.show { display:flex; }
.pub-nav-drawer .pub-nav-link { width:100%; display:block; }
.pub-nav-drawer .pub-nav-actions { flex-direction:column; margin:10px 0 0; }
.pub-nav-drawer .pub-nav-actions .btn { width:100%; }

/* Auth pages */
.auth-page { min-height:100vh; display:flex; align-items:center; justify-content:center; padding:1.5rem; position:relative; }
.auth-page::before {
  /* LEGACY: inset longhands first — without them this decorative glow layer is
     0x0 on iOS <14.1 / Chrome <87 and the auth page just loses its backdrop. */
  content:''; position:absolute; top:0; right:0; bottom:0; left:0; inset:0; z-index:-1;
  background:
    radial-gradient(circle at 20% 30%, rgba(8,145,178,0.12) 0%, transparent 55%),
    radial-gradient(circle at 80% 70%, rgba(8,145,178,0.10) 0%, transparent 55%);
}
.auth-box { width:100%; max-width:440px; }
.auth-logo { display:flex; justify-content:center; margin-bottom:2rem; }
.auth-card { background:var(--bg-card-solid); border:1px solid var(--border-hover); border-radius:var(--radius-xl); padding:2.25rem; box-shadow:var(--shadow),var(--shadow-glow); }
.auth-title { font-size:1.5rem; font-weight:800; margin-bottom:0.35rem; letter-spacing:-0.02em; }
.auth-sub   { color:var(--text-muted); font-size:0.9rem; margin-bottom:1.6rem; }
.auth-footer { text-align:center; margin-top:1.4rem; font-size:0.87rem; color:var(--text-muted); }
.auth-divider { display:flex; align-items:center; gap:10px; margin:1.3rem 0; }
.auth-divider span { flex:1; height:1px; background:var(--border); }
.auth-divider p { font-size:0.76rem; color:var(--text-faint); letter-spacing:0.5px; text-transform:uppercase; }

/* ═══════════════════════════════════════════════════════
   NEW ORDER PAGE
   ═══════════════════════════════════════════════════════ */
.order-layout { display:grid; grid-template-columns:1fr 400px; gap:1.75rem; align-items:start; }

.platform-swiper-wrap { position:relative; overflow:hidden; margin-bottom:1.4rem; }
.platform-swiper { display:flex; gap:10px; overflow-x:auto; padding-bottom:6px; scrollbar-width:none; -ms-overflow-style:none; scroll-behavior:smooth; }
.platform-swiper::-webkit-scrollbar { display:none; }
.platform-btn {
  flex-shrink:0; display:flex; flex-direction:column; align-items:center; gap:7px;
  padding:0.85rem 1.15rem; border-radius:var(--radius); cursor:pointer;
  border:1.5px solid var(--border); background:var(--bg-2); color:var(--text-muted);
  font-size:0.78rem; font-weight:600; transition:var(--transition-ui); min-width:80px;
  -webkit-user-select:none; user-select:none; text-align:center;
}
.platform-btn:hover { border-color:var(--border-hover); color:var(--text); background:var(--bg-3); transform:translateY(-2px); }
.platform-btn.active { border-color:var(--accent); background:var(--accent-soft); color:var(--accent); box-shadow:0 6px 20px rgba(8,145,178,0.2); }
.platform-icon { font-size:1.5rem; line-height:1; }
.swiper-nav-btn {
  position:absolute; top:50%; transform:translateY(-50%); z-index:5;
  width:32px; height:32px; background:var(--bg-card-solid); border:1px solid var(--border-hover);
  border-radius:50%; display:flex; align-items:center; justify-content:center;
  cursor:pointer; color:var(--text-muted); font-size:0.78rem; transition:var(--transition-ui);
  box-shadow:var(--shadow-sm);
}
.swiper-nav-btn:hover { color:var(--accent); border-color:var(--accent); }
.swiper-nav-prev { left:4px; }
.swiper-nav-next { right:4px; }

.price-display {
  background:linear-gradient(135deg, var(--bg-2), var(--bg-3));
  border:1px solid var(--border); border-radius:var(--radius);
  padding:0.9rem 1.15rem; display:flex; align-items:center; justify-content:space-between;
  position:relative; overflow:hidden;
}
.price-display::before {
  content:''; position:absolute; top:0; right:0; width:120px; height:120px;
  background:var(--accent); opacity:0.08; border-radius:50%; transform:translate(40%,-40%);
}
.price-label { font-size:0.82rem; color:var(--text-muted); }
.price-value { font-size:1.2rem; font-weight:800; color:var(--accent); letter-spacing:-0.02em; font-family:var(--font-mono); }

.service-desc-box { background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius); padding:0.9rem 1.1rem; font-size:0.87rem; color:var(--text-muted); margin-bottom:1rem; }
.service-desc-box:empty { display:none; }

.order-info-panel { display:flex; flex-direction:column; gap:1rem; }
.info-tabs .tab-pane { padding:1rem 0 0; }
.updates-list { display:flex; flex-direction:column; gap:8px; max-height:340px; overflow-y:auto; }
.update-item { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; padding:0.75rem 0.95rem; background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius); font-size:0.82rem; transition:border-color var(--transition); }
.update-item:hover { border-color:var(--border-hover); }
.update-item.enabled, .update-item.add  { border-left:3px solid var(--success); }
.update-item.disabled, .update-item.remove { border-left:3px solid var(--danger); }
.update-item.decrease, .update-item.price-decrease, .update-item.min-decrease { border-left:3px solid var(--success); }
.update-item.increased, .update-item.price-increase, .update-item.min-increase, .update-item.max-decrease { border-left:3px solid var(--warning); }
.update-service-name { color:var(--text); font-weight:600; }
.update-status { color:var(--text-muted); font-size:0.76rem; margin-top:2px; }
.update-time   { color:var(--text-faint); font-size:0.72rem; white-space:nowrap; }

/* ═══════════════════════════════════════════════════════
   ORDERS PAGE — Status badges with semantic colors
   ═══════════════════════════════════════════════════════ */
.orders-toolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:1.4rem; }
.orders-search { position:relative; }
.orders-search .form-control { padding-left:2.4rem; max-width:280px; }
.orders-search-icon { position:absolute; left:0.85rem; top:50%; transform:translateY(-50%); color:var(--text-faint); font-size:0.85rem; pointer-events:none; }
.orders-filter-btn {
  display:flex; align-items:center; gap:6px; padding:0.5rem 1rem;
  background:var(--bg-2); border:1.5px solid var(--border); border-radius:99px;
  color:var(--text-muted); font-size:0.85rem; font-weight:600; cursor:pointer; transition:var(--transition-ui);
}
.orders-filter-btn:hover { border-color:var(--border-hover); color:var(--text); }
.orders-filter-btn.active { border-color:var(--accent); background:var(--accent); color:var(--accent-text); }
.order-id     { font-size:0.76rem; color:var(--text-faint); font-weight:600; font-family:var(--font-mono); }
.order-service{ font-size:0.88rem; color:var(--text); font-weight:500; max-width:280px; line-height:1.4; }
.order-url    { font-size:0.8rem; color:var(--accent); max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:block; }
.order-qty    { font-weight:700; font-size:0.88rem; font-family:var(--font-mono); }
.order-charge { font-weight:700; color:var(--text); font-family:var(--font-mono); }
.order-time   { font-size:0.78rem; color:var(--text-muted); }
.order-actions{ display:flex; gap:6px; align-items:center; }

/* SEMANTIC status badges */
.status-badge-completed { background:var(--success-soft);  color:var(--success); padding:4px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; white-space:nowrap; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(34,197,94,0.3); }
.status-badge-pending   { background:var(--warning-soft);  color:var(--warning); padding:4px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(245,158,11,0.3); }
.status-badge-progress  { background:var(--accent-soft);   color:var(--accent);  padding:4px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(8,145,178,0.3); }
.status-badge-cancelled { background:var(--danger-soft);   color:var(--danger);  padding:4px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(239,68,68,0.3); }
.status-badge-partial   { background:var(--warning-soft);  color:var(--warning); padding:4px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(245,158,11,0.3); }

/* ═══════════════════════════════════════════════════════
   SERVICES PAGE
   ═══════════════════════════════════════════════════════ */
.services-toolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:1.4rem; }
.services-search  { position:relative; flex:1; max-width:360px; }
/* High-specificity selector to beat #orderForm input.form-control */
body .services-search input.form-control,
#orderForm .services-search input.form-control { padding-left:2.8rem !important; }
.services-search-icon { position:absolute; left:1rem; top:50%; transform:translateY(-50%); color:var(--text-faint); font-size:0.9rem; pointer-events:none; z-index:2; }
body .orders-search input.form-control { padding-left:2.8rem !important; }
.orders-search-icon { position:absolute; left:1rem; top:50%; transform:translateY(-50%); color:var(--text-faint); font-size:0.9rem; pointer-events:none; z-index:2; }
.services-platform-filter { display:flex; gap:6px; flex-wrap:wrap; }
.platform-filter-btn {
  display:flex; align-items:center; gap:6px; padding:0.45rem 0.95rem;
  background:var(--bg-2); border:1.5px solid var(--border); border-radius:99px;
  color:var(--text-muted); font-size:0.82rem; font-weight:600; cursor:pointer;
  transition:var(--transition-ui); white-space:nowrap;
}
.platform-filter-btn:hover { border-color:var(--border-hover); color:var(--text); }
.platform-filter-btn.active { border-color:var(--accent); background:var(--accent); color:var(--accent-text); box-shadow:0 4px 14px rgba(8,145,178,0.3); }

.svc-id     { font-size:0.76rem; color:var(--text-faint); font-weight:600; font-family:var(--font-mono); }
.svc-name   { font-size:0.88rem; color:var(--text); }
.svc-rate   { font-weight:700; color:var(--text); font-family:var(--font-mono); }
.svc-minmax { font-size:0.85rem; color:var(--text-muted); font-family:var(--font-mono); }
.svc-time   { font-size:0.8rem; color:var(--success); }

/* Favorite / Notify icons on service rows */
.svc-fav, .svc-notify {
  background:none; border:none; color:var(--text-faint); cursor:pointer;
  padding:2px; transition:color var(--transition);
}
.svc-fav:hover { color:var(--warning); }
.svc-fav.active { color:var(--warning); }
.svc-notify:hover { color:var(--accent); }
.svc-notify.active { color:var(--accent); }

/* ═══════════════════════════════════════════════════════
   ADD FUNDS PAGE
   ═══════════════════════════════════════════════════════ */
.funds-layout { display:grid; grid-template-columns:1fr 360px; gap:1.75rem; align-items:start; }
/* The payment-method TILE rules are gone. Add Funds renders no tiles any more - the
   gateway is chosen from the plain visible <select id="method">, because PerfectPanel
   does not drive a hidden or script-mirrored control reliably and every attempt to put a
   custom layer over it broke a different gateway. Nothing in any template emits
   .payment-grid or .payment-method now, so these were 40 lines of stylesheet describing
   a UI that does not exist. The .method-instruction rules below are LIVE and stay. */

/* The admin's per-method "Instruction" text. Deliberately plain: the panel owner writes
   it in a rich-text editor and changes the bonus rates there, so the theme styles the box
   and never the content. */
.method-instruction {
  background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius);
  padding:0.9rem 1.1rem; margin-bottom:1rem;
  font-size:0.85rem; line-height:1.6; color:var(--text-muted);
}
.method-instruction p { margin:0 0 0.45rem; }
.method-instruction p:last-child { margin-bottom:0; }
.method-instruction strong, .method-instruction b { color:var(--text); }
.method-instruction a { color:var(--accent); }
/* Admin rich text often arrives centred; left-aligned reads better in this narrow box.
   Only the wrapper is forced — inline styles inside the content still win, so the owner
   keeps control if they deliberately centre something. */
.method-instruction { text-align:left; }

/* The whole card is an --accent fill, so every piece of ink on it is
   --accent-text. The decorative ::before disc stays a literal white wash: it is
   a highlight over the gradient, not text. .balance-label was rgba white at 70%
   — expressed as the token plus opacity so the label keeps its softer weight in
   both themes instead of turning white ink onto light-theme turquoise. */
.balance-card { padding:1.75rem; text-align:center; background:linear-gradient(135deg, var(--accent), var(--accent-3)); position:relative; overflow:hidden; color:var(--accent-text); }
.balance-card::before { content:''; position:absolute; top:0; right:0; width:200px; height:200px; background:rgba(255,255,255,0.1); border-radius:50%; transform:translate(40%,-40%); }
.balance-amount {
  font-size:2.8rem; font-weight:800; color:var(--accent-text);
  margin:0.3rem 0; letter-spacing:-0.04em; font-family:var(--font-mono);
}
.balance-label { font-size:0.82rem; color:var(--accent-text); opacity:0.75; font-weight:600; letter-spacing:0.3px; }

/* REMOVED: .bonus-banner and .region-warning rule sets.
   No template in this theme has ever used those classes — they were dead styling that
   arrived from another panel's design vocabulary in an earlier pass. Nothing in
   Helnsocial may carry anything from that panel, so they are gone rather than left
   sitting unused. If a promotional banner is wanted here later it gets its own classes,
   written for this theme. */

/* Crypto logo strip */
.crypto-logos {
  display:flex; flex-wrap:wrap; gap:8px; padding:0.85rem;
  background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius);
  margin-top:0.7rem;
}
.crypto-logos img { height:28px; opacity:0.9; }

/* ═══════════════════════════════════════════════════════
   TICKETS PAGE
   ═══════════════════════════════════════════════════════ */
.ticket-status-open     { background:var(--warning-soft);  color:var(--warning); padding:3px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(245,158,11,0.3); }
.ticket-status-closed   { background:var(--bg-3);          color:var(--text-faint); padding:3px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; }
.ticket-status-answered { background:var(--success-soft);  color:var(--success); padding:3px 10px; border-radius:6px; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.3px; border:1px solid rgba(34,197,94,0.3); }

.ticket-chat { display:flex; flex-direction:column; gap:14px; padding:1rem 0; }
.ticket-msg { max-width:78%; }
.ticket-msg.mine  { align-self:flex-end; }
.ticket-msg.other { align-self:flex-start; }
.msg-bubble { padding:0.85rem 1.1rem; border-radius:var(--radius-lg); font-size:0.88rem; line-height:1.6; }
.ticket-msg.mine  .msg-bubble { background:var(--accent); color:var(--accent-text); border-bottom-right-radius:4px; }
.ticket-msg.other .msg-bubble { background:var(--bg-2); border:1px solid var(--border); border-bottom-left-radius:4px; }
.msg-meta { font-size:0.72rem; color:var(--text-faint); margin-top:4px; }
.ticket-msg.mine  .msg-meta { text-align:right; }

/* 🌟 Subject category grid */
.tk-subject-grid {
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));
  gap:0.6rem;
  margin-bottom:1rem;
}
.tk-subject-card {
  display:flex; align-items:center; gap:8px;
  padding:0.8rem 1rem;
  background:var(--bg-2); border:1.5px solid var(--border); border-radius:var(--radius);
  color:var(--text-muted); font-size:0.85rem; font-weight:600;
  cursor:pointer; transition:var(--transition-ui);
}
.tk-subject-card:hover { border-color:var(--border-hover); color:var(--text); }
.tk-subject-card.active { background:var(--accent); border-color:var(--accent); color:var(--accent-text); }
.tk-subject-card i { font-size:0.95rem; }

/* ═══════════════════════════════════════════════════════
   API PAGE
   ═══════════════════════════════════════════════════════ */
.api-key-box { display:flex; align-items:center; background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; }
.api-key-value { flex:1; padding:0.7rem 1.1rem; font-family:var(--font-mono); font-size:0.85rem; color:var(--text); word-break:break-all; }
.api-key-copy { padding:0.7rem 1.1rem; border-left:1px solid var(--border); cursor:pointer; color:var(--text-muted); transition:color var(--transition); background:none; border-top:none; border-right:none; border-bottom:none; }
.api-key-copy:hover { color:var(--accent); }
.code-block {
  background:var(--bg-2); border:1px solid var(--border); border-radius:var(--radius);
  padding:1rem 1.15rem; font-family:var(--font-mono); font-size:0.82rem;
  color:var(--text-muted); overflow-x:auto; line-height:1.75;
}

/* ═══════════════════════════════════════════════════════
   STATS CARDS / EMPTY STATES
   ═══════════════════════════════════════════════════════ */
.stat-card { padding:1.4rem; text-align:center; }
.stat-value { font-size:2rem; font-weight:800; letter-spacing:-0.03em; color:var(--accent); font-family:var(--font-mono); }
.stat-label { font-size:0.82rem; color:var(--text-muted); font-weight:600; margin-top:4px; }
.empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:4.5rem 1rem; text-align:center; gap:14px; }
.empty-icon { font-size:2.75rem; color:var(--text-faint); opacity:0.7; }
.empty-text { color:var(--text-muted); font-size:0.92rem; }

.hidden { display:none!important; }
.vis-hidden { visibility:hidden; }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }

/* ═══════════════════════════════════════════════════════
   TOAST STACK
   ═══════════════════════════════════════════════════════ */
#toast-stack {
  position:fixed; bottom:1.5rem; left:1.5rem; z-index:9999;
  display:flex; flex-direction:column; gap:10px; pointer-events:none;
}
.toast {
  background:var(--bg-card-solid); border:1px solid var(--border-hover);
  color:var(--text); padding:0.8rem 1.2rem; border-radius:var(--radius);
  font-size:0.88rem; box-shadow:var(--shadow);
  display:flex; align-items:center; gap:10px;
  animation:toastIn .22s cubic-bezier(.4,0,.2,1); pointer-events:auto;
  max-width:360px;
}
.toast.hide { animation:toastOut .22s ease forwards; }
@keyframes toastIn  { from{opacity:0;transform:translateX(-30px)} to{opacity:1;transform:translateX(0)} }
@keyframes toastOut { from{opacity:1;transform:translateX(0)} to{opacity:0;transform:translateX(-30px)} }

/* ═══════════════════════════════════════════════════════
   COMMAND PALETTE (Cmd+K)
   ═══════════════════════════════════════════════════════ */
#cmdPalette {
  /* LEGACY: inset longhands + -webkit-backdrop-filter, see .modal-backdrop. */
  position:fixed; top:0; right:0; bottom:0; left:0; inset:0; z-index:2000; display:none;
  align-items:flex-start; justify-content:center; padding:8rem 1rem 1rem;
  background:rgba(0,0,0,0.6); -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
#cmdPalette.show { display:flex; }
.cmd-box {
  width:100%; max-width:560px;
  background:var(--bg-card-solid); border:1px solid var(--border-hover);
  border-radius:var(--radius-lg); box-shadow:var(--shadow),var(--shadow-glow);
  overflow:hidden;
}
.cmd-input { width:100%; padding:1.1rem 1.25rem; background:transparent; border:none; outline:none; font-size:1rem; color:var(--text); border-bottom:1px solid var(--border); font-family:var(--font); }
.cmd-results { max-height:400px; overflow-y:auto; padding:0.5rem; }
.cmd-item { display:flex; align-items:center; gap:12px; padding:0.7rem 0.95rem; border-radius:var(--radius); cursor:pointer; color:var(--text-muted); transition:background var(--transition); text-decoration:none; }
.cmd-item:hover,.cmd-item.selected { background:var(--accent-soft); color:var(--text); }
.cmd-item i { color:var(--accent); font-size:0.95rem; width:18px; text-align:center; }

/* ═══════════════════════════════════════════════════════
   PAYMENT FOOTER STRIP
   ═══════════════════════════════════════════════════════ */
.hs-pay-strip { color:var(--text-faint); }
.hs-pay-badge {
  display:inline-block; padding:2px 8px;
  font-size:0.65rem; font-weight:700;
  background:var(--bg-3); border:1px solid var(--border);
  border-radius:5px; color:var(--text-muted);
  letter-spacing:0.5px;
}
/* REMOVED: a modifier for a Turkish card processor. No template used it, and this panel
   does not accept cards at all — crypto and Binance Pay only. Another piece of dead
   styling carried over from a different panel's footer. */

/* ═══════════════════════════════════════════════════════
   UTILITIES
   ═══════════════════════════════════════════════════════ */
.d-flex{display:flex}.d-grid{display:grid}.d-block{display:block}.d-none{display:none}.d-inline-flex{display:inline-flex}
.flex-col{flex-direction:column}.flex-1{flex:1}.flex-wrap{flex-wrap:wrap}
.items-center{align-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}
.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}
.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}
.w-full{width:100%}.h-full{height:100%}
.text-center{text-align:center}.text-right{text-align:right}.text-left{text-align:left}
.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-medium{font-weight:500}
.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rounded{border-radius:var(--radius)}.rounded-full{border-radius:99px}
.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}
.relative{position:relative}.absolute{position:absolute}.sticky{position:-webkit-sticky;position:sticky}
.cursor-pointer{cursor:pointer}.select-none{user-select:none}
.opacity-60{opacity:.6}
.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}
.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}
.p-0{padding:0}.p-2{padding:.5rem}.p-4{padding:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-4{padding-left:1rem;padding-right:1rem}
.grid-2{grid-template-columns:repeat(2,1fr)}.grid-3{grid-template-columns:repeat(3,1fr)}.grid-4{grid-template-columns:repeat(4,1fr)}

/* ═══════════════════════════════════════════════════════
   RTL HELPER CLASSES
   layout.twig puts dir="rtl" on <html> when the active language is right-to-left,
   and the page templates additionally tag individual elements with these four
   PerfectPanel class names — notifications (alerts, the preferences form and the
   matrix table), massorder and neworder (their forms and error alerts), neworder
   and affiliates (their admin-written content blocks).
   The factory theme ships the rules inside its own bootstrap.css. This theme
   loads Bootstrap 4.5 from the CDN instead, which has no rtl- rules at all, so
   every one of those class names was inert: an RTL customer got the opt-in
   markup and none of the flipping. Defined here, matching the [dir="rtl"] rule
   in the Alerts section.
   Deliberately placed after .table and .alert rather than next to them: the
   .rtl-table alignment below has to beat `.table thead th{text-align:left}`,
   which needs both the same 0,1,2 specificity AND a later source position.
   ═══════════════════════════════════════════════════════ */
/* The catch-all: any block of admin-written or panel-written prose. */
.rtl-content { direction:rtl; }

.rtl-alert { direction:rtl; text-align:right; }
/* Same negative-margin swap the [dir="rtl"] rule in the Alerts section makes, so
   the class also works on its own. Deliberately NOT re-floating .alert .close
   the way the factory sheet does — the flex row up there is the fix for the X
   landing on top of the first letter, and a float would undo it. */
.rtl-alert .close,
.rtl-alert.alert-dismissible .close { margin:-9px auto -9px -9px; }

.rtl-form { direction:rtl; }
/* select.form-control paints its chevron as a background image pinned to the
   PHYSICAL right edge, and the 2.5rem of padding that keeps the label clear of
   that chevron is on the right too. Neither follows `direction`, so in a
   mirrored form the arrow sat on top of the text. Both sides swapped. */
.rtl-form select.form-control {
  padding:0.85rem 1rem 0.85rem 2.5rem;
  background-position:left 1rem center;
}

.rtl-table { direction:rtl; }
.rtl-table thead th,
.rtl-table tbody td { text-align:right; }

/* ═══════════════════════════════════════════════════════
   RESPONSIVE
   ═══════════════════════════════════════════════════════ */
@media (max-width:1100px) {
  .order-layout,.funds-layout { grid-template-columns:1fr; }
  .order-info-panel { order:-1; }
  .header-search { max-width:240px; }
}
@media (max-width:900px) {
  .sidebar { transform:translateX(-100%); box-shadow:var(--shadow); }
  .sidebar.open { transform:translateX(0); }
  /* LEGACY — THE most damaging bug in the sheet before this pass: `.page` was in
     the same comma list as a `:has()` selector, so on Safari <15.4 /
     Chrome-WebView <105 / Firefox <121 the WHOLE rule was discarded and .page
     kept margin-left:260px on a phone — every page rendered ~260px off-screen
     with a permanent sideways scroll. Each selector now stands alone, and each
     one mirrors (same specificity, later source order) the desktop rule it must
     override. Modern engines resolve to the identical margin-left:0. */
  .page { margin-left:0; }
  .sidebar.collapsed ~ .page { margin-left:0; }
  body.hs-sidebar-collapsed .page { margin-left:0; }
  body:has(.sidebar.collapsed) .page { margin-left:0; }
  .header-menu-btn { display:flex; }
  .sidebar-dismiss { display:block; }
  .sidebar-collapse-btn { display:none; }
  .header-search { display:none; }
}
@media (max-width:700px) {
  /* A second :root rule setting --sidebar-w to full width used to be the first
     line here. It was already dead: the other max-width:700px block near the end
     of this file sets the same property to 82vw and, at equal specificity, the
     later one wins. Worse, full width is the value the note down there explains
     is wrong — a full-width drawer covers its own backdrop, so "tap outside to
     close" stops working. Removed rather than left to silently take effect if
     this file is ever reordered; the 82vw declaration and its note stay. */
  .pub-nav-menu { display:none; }
  .pub-nav-hamburger { display:block; }
  .auth-card { padding:1.6rem; }
  .page-content { padding:1.1rem; }
  .top-header { padding:0 1.1rem; }
  .header-balance-chip { display:none; }
  .header-addfund-btn span { display:none; }
  .header-addfund-btn { padding:6px 10px; }
  .table { font-size:0.82rem; }
  .table thead th,.table tbody td { padding:0.65rem 0.8rem; }
}
@media (max-width:480px) {
  .page-header-row { flex-direction:column; align-items:flex-start; }
  .orders-toolbar { flex-direction:column; align-items:stretch; }
  .orders-toolbar .form-control { max-width:100%; }
  h1 { font-size:1.5rem; }
}

/* ─── Select2 overrides ─────────────────────────────── */
.select2-container .select2-selection--single { background:var(--bg-card-solid)!important; border:1px solid var(--border)!important; height:auto!important; min-height:56px!important; border-radius:12px!important; color:var(--text)!important; display:flex!important; align-items:stretch!important; padding:0!important; }
.select2-container--default .select2-selection--single .select2-selection__rendered { color:var(--text)!important; padding:0.85rem 2.5rem 0.85rem 1rem!important; line-height:1.5!important; flex:1!important; display:flex!important; align-items:center!important; white-space:normal!important; word-break:break-word!important; min-height:56px!important; font-size:0.9rem!important; }
.select2-container--default .select2-selection--single .select2-selection__arrow { height:100%!important; top:0!important; right:0.5rem!important; }
.form-control:not(textarea):not(select) { min-height:56px!important; padding:0.85rem 1rem!important; font-size:0.9rem!important; border-radius:12px!important; }
/* The rule above rounds all four corners with !important, which outranks even an
   inline style — so the deposit form's #amount kept a 12px left edge butted against
   the square right edge of the currency prefix that sits immediately before it,
   leaving a wedge of background and a visibly doubled border on the one field that
   matters most. Squaring only the joined edge welds the two into one control.
   Scoped to #amount inside an .input-group on purpose: it must not touch any other
   .form-control, and if PP's core JS re-renders #amount-fields without the prefix
   wrapper the field goes back to being fully rounded on its own. */
.input-group #amount { border-radius:0 var(--radius) var(--radius) 0!important; }
.select2-container--default.select2-container--open .select2-selection--single { border-color:var(--accent)!important; }
.select2-dropdown { background:var(--bg-card-solid)!important; border:1px solid var(--border-hover)!important; border-radius:var(--radius-lg)!important; box-shadow:var(--shadow)!important; }
.select2-container--default .select2-results__option { color:var(--text-muted)!important; padding:9px 13px!important; font-size:0.88rem; }
.select2-container--default .select2-results__option--highlighted { background:var(--accent-soft)!important; color:var(--text)!important; }
.select2-search--dropdown .select2-search__field { background:var(--bg-2)!important; border-color:var(--border)!important; color:var(--text)!important; border-radius:8px; padding:7px 11px; }

/* The Swiper stub is gone with the library - see the note in layout.twig where the
   bundle used to load. Nothing in this theme renders swiper-wrapper or swiper-slide. */

/* ═══════════════════════════════════════════════════════
   MOBILE HARDENING
   Appended last on purpose: everything here either (a) lives
   inside a max-width media query, so the desktop rendering above
   is untouched, or (b) only adds scroll / shrink behaviour that
   has no effect while there is room on the line.
   Existing breakpoints this section builds on:
     1100px — order/funds sidebars stack, header search narrows
      900px — sidebar becomes an off-canvas drawer, page margin 0
      700px — public nav collapses, page gutters shrink,
              balance chip + "Add Funds" label hide
      600px — floating chat buttons shrink
      480px — page header + orders toolbar stack
   ═══════════════════════════════════════════════════════ */

/* ─── Horizontal-scroll containment (all widths) ───────── */
/* Momentum scrolling on iOS plus overscroll-behavior-x so a swipe
   past the end of a wide table does NOT chain to the browser's
   back-gesture — customers report that as "the page slid sideways".
   LEGACY: overscroll-behavior-x is iOS 16+/Chrome 63+; where it is
   unknown the declaration is simply dropped and the old behaviour
   remains. Do not "fix" that with preventDefault, which kills
   momentum scrolling for everyone. */
.table-wrap,
.code-block,
.nav-tabs,
.platform-swiper {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  max-width: 100%;
}
.sidebar-content,
.cmd-results,
.updates-list,
.modal-body {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* Defensive containment on the page wrapper only. Deliberately NOT
   overflow-x:hidden on <body>: that hides real overflow bugs and it
   turns the viewport into a scroll container, which silently kills
   every position:sticky in the theme (.top-header, .api-tabs,
   .svc-toolbar, .terms-toc). These rules cannot clip anything —
   they only stop a flex/grid child from GROWING its parent. */
#dash, .app, .page, .page-content { max-width: 100%; }
.page-content > * { min-width: 0; }
iframe, video, canvas, embed, object { max-width: 100%; }

/* A flex item's default min-width is `auto`, so an <input> inside
   .input-group could not shrink below its ~20-character intrinsic
   width and pushed the whole row past a 360px screen. */
.input-group { max-width: 100%; }
.input-group .form-control { min-width: 0; }

/* Same story for the flex/grid boxes the shared sheet owns. */
.alert, .card, .modal-content, .toast, .price-display,
.orders-toolbar, .services-toolbar, .header-actions, .top-header { min-width: 0; }
.alert, .card, .modal-content, .toast { max-width: 100%; }

/* The floating chat column was z-index:900 — ABOVE the sidebar (400)
   and its backdrop (399), so on a phone the WhatsApp/Telegram bubbles
   floated on top of the open navigation drawer and swallowed taps
   meant for the menu. 390 keeps them over all page content and over
   the sticky header (300) but under the drawer and its backdrop. */
.floating-contact { z-index: 390; }
/* LEGACY: script.js mirrors the sidebar's .open state onto <body> as
   .hs-sidebar-open, so engines without :has() hide the chat bubbles too.
   The :has() rule stays last as the enhancement — identical result. */
body.hs-sidebar-open .floating-contact { display: none; }
body:has(.sidebar.open) .floating-contact { display: none; }

/* LEGACY: `inset` is unknown to iOS Safari <14.1 and Chrome <87, which
   discard the whole declaration and leave these full-screen layers
   0x0 — no backdrop, no modal, no command palette. The longhands give
   those engines a real full-screen box; modern engines compute the
   identical result, so nothing changes visually anywhere. */
.sidebar-overlay, .modal, .modal-backdrop, #cmdPalette {
  top: 0; right: 0; bottom: 0; left: 0;
}
.sidebar-overlay { overscroll-behavior: contain; -webkit-tap-highlight-color: transparent; }

/* ─── iOS input zoom (≤900px) ──────────────────────────── */
/* iOS Safari zooms the entire document when a focused control is
   under 16px, and it never zooms back out — the customer is left on a
   permanently side-scrolling panel. This is the single most common
   SMM-panel complaint on iPhone. html{font-size:15.5px} here, so even
   `1rem` (15.5px) still trips it: the literal 16px is required.
   !important is needed to beat the global
   `.form-control:not(textarea):not(select){font-size:0.9rem!important}`
   in the Select2 block above. Desktop is untouched — this is inside a
   media query, and the page-level 16px rules some templates already
   carry resolve to the same value. */
@media (max-width: 900px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]),
  select,
  textarea,
  .form-control,
  .form-control-lg,
  .cmd-input,
  .header-search input,
  .select2-container--default .select2-selection--single .select2-selection__rendered,
  .select2-search--dropdown .select2-search__field {
    font-size: 16px !important;
  }
  .form-check input[type="checkbox"] { width: 20px; height: 20px; }
}

/* --- iOS input zoom in LANDSCAPE and on iPad ------------ */
/* The width-bounded block above stops matching the instant the phone is
   rotated: an iPhone 14 Plus is 926 CSS px wide in landscape, a 15 Pro Max and
   a 16 Plus 932, a 16 Pro Max 956 - and every iPad is 1024px or more in either
   orientation. Above 900px .form-control falls back to 0.9rem, 13.95px at
   html font-size 15.5px, and WebKit resumes auto-zooming on focus, which is
   exactly the permanently side-scrolling panel the block above exists to
   prevent, at exactly the moment a reseller rotates to paste a long link or
   read the orders table.
   -webkit-touch-callout is iOS-only, so desktop, Android, Firefox and Samsung
   Internet never enter this block, and the selectors and value are identical
   to the block above, so no engine that was already correct changes at all.
   confirmemail, resetpassword, setnewpassword and faq already ship this same
   duplicate for their own fields; this is the shared-sheet copy they were
   working around. */
@supports (-webkit-touch-callout: none) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]),
  select,
  textarea,
  .form-control,
  .form-control-lg,
  .cmd-input,
  .header-search input,
  .select2-container--default .select2-selection--single .select2-selection__rendered,
  .select2-search--dropdown .select2-search__field {
    font-size: 16px !important;
  }
}

/* ─── Off-canvas sidebar drawer (≤900px) ───────────────── */
@media (max-width: 900px) {
  /* A visitor who collapsed the sidebar on a desktop carries
     hsSidebarCollapsed=1 in localStorage, and layout.twig re-applies
     .collapsed on every device. On a phone that opened the drawer as a
     68px icon-only strip with every label display:none — unusable, and
     there is no collapse button at this width to undo it. The collapsed
     state is meaningless once the sidebar is a drawer, so neutralise it.
     Explicit display values rather than `revert`, which needs
     Chrome 84+/Safari 13.1+. */
  .sidebar.collapsed { width: var(--sidebar-w); }
  .sidebar.collapsed .sidebar-user-name,
  .sidebar.collapsed .sidebar-user-badge,
  .sidebar.collapsed .sb-balance-label,
  .sidebar.collapsed .sb-balance-value,
  .sidebar.collapsed .sidebar-section-label,
  .sidebar.collapsed .sidebar-link-text { display: block; }
  .sidebar.collapsed .logo-text,
  .sidebar.collapsed .sidebar-chevron { display: inline-block; }
  .sidebar.collapsed .sidebar-avatar,
  .sidebar.collapsed .sidebar-balance,
  .sidebar.collapsed .sidebar-link { justify-content: flex-start; }
  .sidebar.collapsed .sidebar-profile { padding: 0.6rem 0.75rem; justify-content: flex-start; }

  /* The close control was a 1.05rem glyph with 4px of padding — about a
     24px target, well under the ~44px minimum, and it is the only way
     out of the drawer when it covers the screen. */
  .sidebar-dismiss,
  .sidebar.collapsed .sidebar-dismiss {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0; margin-right: -8px;
  }
  .sidebar-link { min-height: 44px; }
  /* The last menu entry sat under the iOS home indicator. */
  /* LEGACY: env() is unknown to Chrome/WebView <69 and iOS <11.2, which drop
     the whole calc(). The plain value first keeps the intended gutter. */
  .sidebar-content { padding-bottom: 1rem; padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); }

  /* Off-screen drawer links stayed in the tab order and in the
     accessibility tree while the drawer was closed. */
  .sidebar { visibility: hidden; transition: transform .3s cubic-bezier(.4,0,.2,1), visibility 0s linear .3s; }
  .sidebar.open { visibility: visible; transition: transform .3s cubic-bezier(.4,0,.2,1), visibility 0s; }

  /* The page kept scrolling behind the open drawer, so a customer who
     swiped to close instead scrolled the orders list underneath. */
  /* LEGACY: same :has() story — .hs-sidebar-open is mirrored onto <body> from
     script.js so the page behind an open drawer stops scrolling on older
     WebKit/Blink/Gecko too. :has() stays last as the enhancement. */
  body.hs-sidebar-open { overflow: hidden; }
  body:has(.sidebar.open) { overflow: hidden; }

  /* 42px tap targets in the header bar. */
  .header-menu-btn {
    display: flex; align-items: center; justify-content: center;
    width: 42px; height: 42px; padding: 0;
  }
  .header-icon-btn { width: 40px; height: 40px; }
}

/* ≤700px the drawer used to be width:100% — it covered its own
   backdrop, so "tap outside to close" was impossible and the X was the
   only exit. A gutter of ~18vw keeps the backdrop reachable. */
@media (max-width: 700px) {
  :root { --sidebar-w: 82vw; }
  .sidebar { max-width: 330px; }
}

/* ─── Top header on small phones ───────────────────────── */
/* At 360px the row measured ~376px: menu button + Add Funds + the
   3-way theme switcher + 3 icon buttons + gaps. A flex row that
   overflows scrolls the whole DOCUMENT sideways, which is exactly the
   "panel does not fit my screen" report. Account and Logout are both
   duplicated in the sidebar profile menu, so dropping the duplicate
   header icons is lossless and buys ~90px. */
@media (max-width: 560px) {
  .top-header { padding: 0 0.85rem; gap: 8px; }
  .header-actions { gap: 5px; }
  .header-icon-btn[title="Account"],
  .header-icon-btn[title="Logout"] { display: none; }
}
@media (max-width: 400px) {
  .theme-switcher button { padding: 6px 8px; }
  .header-addfund-btn { padding: 6px 9px; }
}

/* ─── Floating chat buttons vs. primary actions ────────── */
@media (max-width: 700px) {
  /* The chat column is ~46x102px pinned to the bottom-right corner —
     exactly where the last row's Order button, a form's submit button
     and the pagination strip land on a phone. Extra bottom padding
     lets the page scroll clear of it. Pages that already reserve more
     (addfunds, services, neworder, affiliates) win, they are loaded
     after this sheet. */
  .page-content { padding-bottom: 4.5rem; }
  /* Auth pages have no footer under them, so they carry the full gap. */
  .auth-page { padding-bottom: 6.5rem; }
  /* Both footers carry an inline `padding` shorthand, and an inline
     declaration outranks any stylesheet rule that is not !important —
     without this the Terms / FAQ / Support row sat under the chat
     bubbles at the very bottom of the document. */
  .page > footer,
  #outside > footer {
    /* LEGACY: env() drops on Chrome/WebView <69 and iOS <11.2, and with it the
       !important that beats the footer's inline padding — the Terms/FAQ row
       then sits under the chat bubbles. Plain value first. */
    padding-bottom: 5rem !important;
    padding-bottom: calc(5rem + env(safe-area-inset-bottom, 0px)) !important;
  }
}
@media (max-width: 600px) {
  .floating-contact {
    right: 0.9rem;
    bottom: 1rem;
    bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
  }
}
@media (max-width: 400px) {
  .floating-contact { gap: 8px; }
  .fc-btn { width: 42px; height: 42px; font-size: 1.15rem; }
}

/* ─── Toast stack ──────────────────────────────────────── */
@media (max-width: 600px) {
  /* bottom-left + max-width:360px put a near screen-wide toast on the
     same 360px row as the chat bubbles: the toast (z-index 9999)
     covered them, and a toast that persists blocked support contact.
     Stack the toasts ABOVE the buttons and let them span the screen. */
  #toast-stack {
    left: 0.75rem;
    right: 0.75rem;
    bottom: 7.5rem;
    bottom: calc(7.5rem + env(safe-area-inset-bottom, 0px));
  }
  .toast { max-width: 100%; }
}

/* ─── Modals: full-height with internal scrolling ──────── */
@media (max-width: 600px) {
  /* .modal-content had no max-height and .modal-body no overflow, so a
     tall dialog overflowed equally above and below a centred viewport —
     the header with the X went off-screen upward and could not be
     reached at all. Pages that scope their own (#orderDetailModal,
     #svcDetailModal, #apiKeyModal) win on ID specificity; this is the
     baseline every other dialog now inherits. */
  .modal { padding: 0.75rem; }
  .modal-dialog { max-width: none; }
  .modal-content {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 1.5rem);
    /* dvh keeps it right while mobile Safari's URL bar collapses. */
    max-height: calc(100dvh - 1.5rem);
  }
  .modal-header, .modal-footer { flex-shrink: 0; }
  .modal-body { overflow-y: auto; min-height: 0; padding: 1.1rem; }
  .modal-header { padding: 1rem 1.1rem; }
  .modal-footer { padding: 1rem 1.1rem; flex-wrap: wrap; }
  .modal-footer .btn { flex: 1 1 auto; min-height: 44px; }
  .modal-close {
    width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    margin: -8px -8px -8px 8px;
  }
  /* Command palette: 8rem of top padding wasted a third of a phone
     screen and the 400px result list ran off the bottom. */
  #cmdPalette { padding: 4.5rem 0.75rem 0.75rem; }
  .cmd-results { max-height: 50vh; max-height: 50dvh; }
  .cmd-item { min-height: 44px; }
}
@media (max-width: 400px) {
  .modal { padding: 0.5rem; }
}

/* ─── Touch targets & rows that must wrap (≤700px) ─────── */
@media (max-width: 700px) {
  .btn { min-height: 42px; }
  .btn-sm { min-height: 38px; }
  .btn-icon { min-width: 42px; min-height: 42px; }
  .page-item .page-link { min-width: 44px; height: 44px; }
  .pagination { justify-content: center; row-gap: 6px; }
  .theme-switcher button { min-height: 38px; }
  .svc-fav, .svc-notify {
    min-width: 38px; min-height: 38px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* A long alert body plus a close button overflowed instead of wrapping. */
  .alert { flex-wrap: wrap; }
  .alert > * { min-width: 0; }
  .alert .close {
    min-width: 40px; min-height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* .card-header is space-between with no wrap: a long title next to an
     action button pushed the card wider than the screen. */
  .card-header { flex-wrap: wrap; gap: 10px; padding: 1rem 1.15rem; }
  .card-body   { padding: 1.15rem 1.15rem; }
  .card-footer { padding: 1rem 1.15rem; }
  .page-header-row { gap: 0.75rem; }
  .order-actions { flex-wrap: wrap; }
  .ticket-msg { max-width: 92%; }
  .nav-tabs { flex-wrap: nowrap; scrollbar-width: none; }
  .nav-tabs::-webkit-scrollbar { display: none; }
  .nav-tabs .nav-link { flex: 0 0 auto; min-height: 44px; }
  .pub-nav-inner { padding: 0 1.1rem; gap: 0.75rem; }
  .pub-nav-hamburger {
    display: flex; align-items: center; justify-content: center;
    width: 42px; height: 42px; padding: 0;
  }
  /* The drawer is position:fixed with no height cap — with a long admin
     menu the last links and both buttons fell off the bottom edge with
     no way to scroll to them. */
  .pub-nav-drawer {
    max-height: calc(100vh - 64px);
    max-height: calc(100dvh - 64px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
  .pub-nav-drawer .pub-nav-link {
    min-height: 44px; display: flex; align-items: center; padding: 0.55rem 0.9rem;
  }
}

/* ─── Unbreakable strings (≤900px) ─────────────────────── */
/* Service names, order links, ticket subjects and API keys are
   user/admin supplied and routinely arrive as one unbroken 60-character
   token. CROSS-BROWSER: `anywhere` is invalid below Chrome/WebView 80,
   Safari/iOS 15.4 and Samsung Internet 13 and takes the whole
   declaration with it, so the safe alias goes first. */
@media (max-width: 900px) {
  .card-title, .page-title, .page-subtitle,
  .svc-name, .order-service, .update-service-name,
  .api-key-value, .cmd-item, .msg-bubble, .toast,
  .service-desc-box, .payment-method-name, .balance-amount {
    overflow-wrap: break-word;
    overflow-wrap: anywhere;
  }
  /* Admin-authored HTML (Pages, service descriptions, ticket replies)
     can contain anything. Wide children scroll inside themselves; the
     document never scrolls sideways. Theme tables all carry a class, so
     :not([class]) only reaches injected markup. */
  .page-content table:not([class]) {
    display: block; max-width: 100%;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .page-content pre {
    max-width: 100%;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .page-content img { max-width: 100%; height: auto; }
}

/* ─── Small-phone toolbars & auth cards (≤480px) ───────── */
@media (max-width: 480px) {
  .services-toolbar { flex-direction: column; align-items: stretch; }
  .services-search { max-width: 100%; min-width: 0; }
  .services-platform-filter { width: 100%; }
  .orders-search .form-control { max-width: 100%; }
  .auth-page { padding: 1.25rem 0.85rem 6.5rem; }
  .auth-card { padding: 1.35rem 1.15rem; }
  .auth-logo { margin-bottom: 1.4rem; }
  .platform-btn { min-width: 72px; padding: 0.75rem 0.9rem; }
  .tk-subject-card { min-height: 44px; }
}

/* ═══════════════════════════════════════════════════════
   WIDE-SCREEN WIDTH CHAIN
   "The orders page leaves a lot of empty space."
   The chain is: .page (margin-left:260px for the sidebar)
                 → .page-content (padding:1.75rem 2rem)
                 → the page template's own centred wrapper.
   Every data-heavy template caps that last wrapper at 1180–1240px, so on a
   1920px monitor the orders table is drawn 1240px wide inside a 1596px column
   and ~356px is left as dead margin — while .ord-table itself carries
   min-width:980px and is the widest thing on the page.
   Below ~1570px viewport nothing here changes anything (the column is already
   narrower than the old cap), so laptops and every mobile breakpoint are
   untouched — this whole section only ever ADDS width, never removes it.
   Specificity: the templates declare `.ord-wrap` (0,0,1,0) inside a page-level
   <style>, which is parsed after this file. `#dash .ord-wrap` (0,1,1,0) wins on
   specificity, so no !important is needed. #dash is the authenticated layout,
   #outside the public one (the services catalogue is reachable logged out).
   ═══════════════════════════════════════════════════════ */
@media (min-width: 1400px) {
  #dash .ord-wrap,    #outside .ord-wrap,        /* orders          */
  #dash .rf-wrap,     #outside .rf-wrap,         /* refill, refunds */
  #dash .sub-wrap,    #outside .sub-wrap,        /* subscriptions   */
  #dash .df-wrap,     #outside .df-wrap,         /* drip feed       */
  #dash .ntf-wrap,    #outside .ntf-wrap,        /* notifications   */
  #dash .aff-wrap,    #outside .aff-wrap,        /* affiliates      */
  #dash .acc-wrap,    #outside .acc-wrap,        /* account         */
  #dash .cp-wrap,     #outside .cp-wrap,         /* child panel     */
  #dash .tk-wrap,     #outside .tk-wrap,         /* tickets         */
  #dash .svc-container,   #outside .svc-container,
  #dash .svc-hero-inner,  #outside .svc-hero-inner {
    max-width: 1560px;
  }
}
@media (min-width: 1800px) {
  #dash .ord-wrap,    #outside .ord-wrap,
  #dash .rf-wrap,     #outside .rf-wrap,
  #dash .sub-wrap,    #outside .sub-wrap,
  #dash .df-wrap,     #outside .df-wrap,
  #dash .ntf-wrap,    #outside .ntf-wrap,
  #dash .aff-wrap,    #outside .aff-wrap,
  #dash .acc-wrap,    #outside .acc-wrap,
  #dash .cp-wrap,     #outside .cp-wrap,
  #dash .tk-wrap,     #outside .tk-wrap,
  #dash .svc-container,   #outside .svc-container,
  #dash .svc-hero-inner,  #outside .svc-hero-inner {
    max-width: 1760px;
  }
}
/* The services hero is a full-bleed band whose inner row must line up with the
   list below it, so both are widened together above — widening only one of
   them puts the "1 240 services" counter out of line with the table edge. */
/* Deliberately LEFT NARROW:
     .api-wrap    — API documentation. Code samples and the parameter table are
                    read line by line; a 1760px measure hurts.
     .land-wrap   — sign-in / sign-up marketing hero, a designed two-column
                    composition, not a data surface.
     neworder's 1100px form column — a single-column form at 1760px is worse,
                    not better, and Ozi likes the current order page. */

/* A little more breathing room in the gutters once the wrappers are this wide,
   so the table border does not sit ~2rem from the sidebar edge. */
@media (min-width: 1400px) {
  .page-content { padding: 1.75rem 2.5rem; }
}

/* ═══════════════════════════════════════════════════════
   PERFORMANCE
   Everything here is invisible: it removes work the browser was doing that
   never showed up on screen. See also, earlier in this file:
     · the removed Google-Fonts @import   (one serialised blocking request)
     · the removed text-rendering:optimizeLegibility on <body>
     · --transition-ui replacing `transition:all` on 17 components
     · backdrop-filter:none on .land-auth-card / .auth-card
   ═══════════════════════════════════════════════════════ */

/* The services catalogue renders EVERY category and EVERY service row up front
   — routinely 2 000+ table rows — and the customer only ever sees two or three
   categories at a time. content-visibility:auto lets the engine skip layout,
   style and paint for the blocks that are off-screen and do the work lazily as
   they scroll in. .svc-cat-block is a bare wrapper <div> with no styles of its
   own, nothing sticky inside it, and services.twig only ever writes
   `block.style.display` on it — it never measures it — so there is nothing here
   for containment to break.
   contain-intrinsic-size gives the skipped blocks a placeholder height so the
   scrollbar does not jump; the plain form is listed first because the
   `auto <length>` ("remember the real size once measured") form needs
   Chrome/Edge 98+, and an engine that cannot parse it must not lose the
   fallback. Engines with no content-visibility at all (Safari < 15.4,
   Firefox < 125) simply render as before. */
.svc-cat-block {
  content-visibility: auto;
  contain-intrinsic-size: 0 640px;
  contain-intrinsic-size: auto 640px;
}

/* Long scrollable lists: paint containment keeps a repaint inside the box
   instead of invalidating the page around it. Both already clip their own
   overflow, so `paint` cannot hide anything that was visible before. */
.updates-list,
.cmd-results { contain: paint; }

/* ─── Header dropdowns: currency + language ─────────────
   Markup lives in layout.twig. The currency list keeps PerfectPanel's required
   ids/data-attributes (currencies-list / currencies-item / data-rate-*), so PP's
   own conversion script binds to it unchanged — this file only styles the panel. */
.hs-dropdown { position:relative; display:inline-flex; }
.hs-dropdown-menu {
  position:absolute; top:calc(100% + 8px); right:0; z-index:1200;
  min-width:150px; max-height:320px; overflow-y:auto; -webkit-overflow-scrolling:touch;
  margin:0; padding:6px; list-style:none;
  background:var(--bg-card-solid); border:1px solid var(--border);
  border-radius:12px; box-shadow:var(--shadow);
  display:none;
}
.hs-dropdown-menu.open { display:block; }
.hs-dropdown-menu li { list-style:none; }
.hs-dropdown-menu a {
  display:block; padding:10px 12px; border-radius:8px;
  font-size:0.84rem; font-weight:600; color:var(--text-muted);
  text-decoration:none; white-space:nowrap;
  transition:background var(--transition), color var(--transition);
}
.hs-dropdown-menu a:hover,
.hs-dropdown-menu li.active > a,
.hs-dropdown-menu a.active { background:var(--accent-soft); color:var(--accent); }

/* On a phone the menu is anchored to the screen instead of the button, so a long
   currency list cannot push the header sideways. */
@media (max-width:600px) {
  .hs-dropdown-menu { right:auto; left:50%; -webkit-transform:translateX(-50%); transform:translateX(-50%); }
  /* Touch target: 10px padding + 0.84rem line is ~34px, below the ~40px minimum. */
  .hs-dropdown-menu a { padding:12px; }
}

/* ─── Add Funds button on small screens ─────────────────
   The label was hidden below 900px, leaving a bare "+" that customers did not
   recognise as the top-up action. Keep the word down to phone width and only drop
   it on the very narrowest screens, where the header genuinely runs out of room. */
@media (max-width:900px) {
  .header-addfund-btn span { display:inline; font-size:0.8rem; }
  .header-addfund-btn { padding-left:0.7rem; padding-right:0.85rem; gap:5px; }
}
@media (max-width:420px) {
  .header-addfund-btn span { display:none; }
  .header-addfund-btn { padding-left:0.6rem; padding-right:0.6rem; }
}

/* ═══════════════════════════════════════════════════════
   THEME SWITCHER IN THE PUBLIC HEADER (signed-out visitors)
   Placement note: this control lives inside .pub-nav-actions, NOT in a
   floating corner bubble. PerfectPanel injects its own Telegram button at
   bottom-left and its WhatsApp button at bottom-right, and .floating-contact
   (bottom-right) plus #toast-stack (bottom-left) already contest those
   corners — a third floating control would sit on top of one of them.
   .pub-nav is sticky at z-index 500, so the switcher stays reachable on scroll
   anyway, and the sign-in / sign-up hero stays untouched.
   Everything below only sizes the control: the colours come from the shared
   .theme-switcher rules, which are all var()-driven and therefore already
   correct under html.light.
   ═══════════════════════════════════════════════════════ */
.theme-switcher-pub { flex-shrink:0; }
.theme-switcher-pub button {
  /* The dashboard buttons are text-padded; these are icon-only, so a fixed box
     keeps the three of them equal width whatever the icon metrics are. */
  width:34px; height:30px; padding:0;
  display:-webkit-box; display:-ms-flexbox; display:flex;
  -webkit-box-align:center; -ms-flex-align:center; align-items:center;
  -webkit-box-pack:center; -ms-flex-pack:center; justify-content:center;
  line-height:1;
}
/* On the light palette --bg-2 (#f7f9fc) is nearly the same white as the nav bar
   (--bg-card-solid #ffffff), so the group loses its edge and reads as three
   loose icons. One shade darker restores the pill. Dark already has contrast. */
html.light .theme-switcher-pub { background:var(--bg-3); }

@media (max-width:700px) {
  /* 700px is where .pub-nav-menu is hidden and .pub-nav-hamburger appears, so
     below it the actions row is switcher + hamburger only — there is room to
     grow the tap targets rather than shrink them. Same specificity as the
     generic `.theme-switcher button { min-height:38px }` earlier in this file,
     and later in source order, so it wins. */
  .theme-switcher-pub button { width:38px; height:38px; min-height:38px; }
}
@media (max-width:420px) {
  /* 360-420px phone: logo + three buttons + hamburger on one row. Tighten the
     group so the nav can never push the document into horizontal scroll, but
     stay at 36px so the tap target does not collapse. */
  .theme-switcher-pub { padding:1px; gap:1px; }
  .theme-switcher-pub button { width:32px; height:36px; min-height:36px; font-size:0.78rem; }
  /* .pub-nav-logo is flex-shrink:0, so a wide wordmark cannot give way to the
     new control — cap the image instead of letting the row overflow. The logo
     img carries an inline `height:32px;width:auto`, and an inline declaration
     outranks any non-!important rule, so these have to be !important. */
  .pub-nav-logo img {
    max-width:36vw !important;
    height:auto !important;
    max-height:30px !important;
  }
}
