/* =====================================================================================
   Apple-inspired design system for the offline clone.
   Font stack, easing curves, and 18px card radius are taken from the real Apple Store
   (MY) capture in /SAMPLE (common.css font-family rules; store-home.css cubic-bezier +
   border-radius values). Accent orange (#ee4d2d) stays Manta's own brand color.
   ===================================================================================== */
:root{
  --mx-ink:#1d1d1f; --mx-ink-soft:#6e6e73; --mx-ink-faint:#86868b;
  --mx-surface:#ffffff; --mx-surface-alt:#f5f5f7;
  --mx-border:#e5e5ea; --mx-border-soft:#eeeeef;
  --mx-accent:#ee4d2d; --mx-accent-hover:#e63e1e; --mx-accent-soft:#fff1ee;
  --mx-blue:#0071e3; --mx-blue-soft:#eaf3fe;
  --mx-green:#1d9a5b; --mx-green-soft:#e8f8ef;
  --mx-red:#d93025; --mx-red-soft:#fdeceb;
  --mx-amber:#b45309; --mx-amber-soft:#fef3c7;
  --mx-purple:#7c3aed; --mx-purple-soft:#f1e9fe;
  --mx-slate:#94a3b8; --mx-slate-soft:#f8fafc;
  --mx-radius-sm:10px; --mx-radius-md:14px; --mx-radius-lg:18px; --mx-radius-pill:999px;
  --mx-shadow-sm:0 1px 2px rgba(0,0,0,.04);
  --mx-shadow-md:0 1px 2px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.06);
  --mx-shadow-lg:0 4px 16px rgba(0,0,0,.06),0 20px 48px rgba(0,0,0,.10);
  --mx-ease:cubic-bezier(.4,0,.2,1); --mx-ease-out:cubic-bezier(0,0,.2,1); --mx-ease-spring:cubic-bezier(.2,.8,.2,1);
  /* Bolder, more geometric display face for the AI Verdict scorecard's numbers/headline/pill only
     (see the AI Verdict rules further down) -- loaded via <link> in dashboard.html's <head>, not
     @import here, so it doesn't block this stylesheet's own parse. Falls back to the exact same
     system-font stack the rest of the app already uses if that request is ever blocked, so a
     failed font load never breaks or reflows the layout, just quietly loses the flourish. */
  --mx-font-display:'Manrope',-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
html{scroll-behavior:smooth;}
html,body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Segoe UI",Roboto,Helvetica,Arial,sans-serif; color:var(--mx-ink); -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;}
body{background:var(--mx-surface-alt);}
* { scrollbar-width: thin; }

/* ---- typography scale (fixes oversized/inconsistent headings) ---- */
.mx-h1{font-size:clamp(22px,3vw,30px);font-weight:700;letter-spacing:-.02em;line-height:1.15;}
.mx-eyebrow{font-size:10.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;}
.mx-stat-value{font-size:clamp(17px,2.2vw,23px);font-weight:700;letter-spacing:-.01em;line-height:1.2;tabular-nums:1;}
.mx-stat-label{font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;}
.mx-panel-title{font-size:14.5px;font-weight:600;color:var(--mx-ink);letter-spacing:-.005em;}
.mx-panel-sub{font-size:12px;color:var(--mx-ink-soft);margin-top:2px;}

/* ---- panel/card system ---- */
.mx-panel{background:var(--mx-surface);border:1px solid var(--mx-border-soft);border-radius:var(--mx-radius-lg);box-shadow:var(--mx-shadow-sm);overflow:hidden;transition:box-shadow .35s var(--mx-ease), transform .35s var(--mx-ease);}
.mx-panel:hover{box-shadow:var(--mx-shadow-md);}
.mx-panel-head{padding:16px 20px;border-bottom:1px solid var(--mx-border-soft);}
.mx-panel-body{padding:18px 20px;}

/* ---- entrance animation ---- */
@keyframes kxFadeUp{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:translateY(0);}}
@keyframes kxFadeIn{from{opacity:0;}to{opacity:1;}}
@keyframes kxPop{0%{opacity:0;transform:scale(.94);}100%{opacity:1;transform:scale(1);}}
@keyframes kxShimmer{0%{background-position:-200% 0;}100%{background-position:200% 0;}}
@keyframes kxSpin{to{transform:rotate(360deg);}}
.mx-reveal{opacity:0;animation:kxFadeUp .6s var(--mx-ease-out) forwards;}
.mx-reveal-fast{opacity:0;animation:kxFadeIn .4s var(--mx-ease-out) forwards;}
.mx-view-enter{animation:kxFadeIn .45s var(--mx-ease-out);}
.mx-card-anim{opacity:0;animation:kxPop .45s var(--mx-ease-spring) forwards;}
.mx-skeleton{background:linear-gradient(90deg,#eee 25%,#f6f6f6 37%,#eee 63%);background-size:400% 100%;animation:kxShimmer 1.4s ease infinite;border-radius:8px;}
.mx-spin{animation:kxSpin .8s linear infinite;}

/* ---- interactive elements ---- */
.mx-pill{transition:transform .22s var(--mx-ease), box-shadow .22s var(--mx-ease), border-color .22s var(--mx-ease);}
.mx-pill:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(0,0,0,.08);}

/* Total Shopee commission chip on the main Sub ID list (see subidPill() in dashboard.js), sitting
   right next to the P/L figure it doesn't compete with -- soft-orange pill at rest, no chrome
   beyond that single color cue, so a dense list of 20+ pills doesn't turn into a wall of color.
   Flips to solid Shopee-orange with a small lift on hover (piggybacking on .mx-pill:hover, not a
   separate listener) so the number visibly responds along with the rest of the row instead of
   sitting there inert -- the "interactive, engaging" ask without adding new chrome that would
   compete with the P/L figure or clutter the row at rest. */
.mx-pill-trailing{min-width:0;}
.mx-pill-comm{
  display:inline-flex;align-items:center;gap:5px;flex-shrink:0;
  padding:4px 10px;border-radius:var(--mx-radius-pill);
  background:var(--mx-accent-soft);color:var(--mx-accent);
  font-size:11.5px;font-weight:700;letter-spacing:.01em;line-height:1.3;
  font-variant-numeric:tabular-nums;white-space:nowrap;
  transition:transform .22s var(--mx-ease), box-shadow .22s var(--mx-ease), background .22s var(--mx-ease), color .22s var(--mx-ease);
}
.mx-pill-comm-icon{width:13px;height:12px;flex-shrink:0;border-radius:3px;}
.mx-pill:hover .mx-pill-comm{
  background:var(--mx-accent);color:#fff;
  transform:translateY(-1px);
  box-shadow:0 4px 10px rgba(238,77,45,.28);
}
.mx-pill-pl{flex-shrink:0;}
@media (max-width:480px){
  .mx-pill-comm{font-size:11px;padding:3.5px 9px;}
}

/* Shopee-clicks-only chip on the main Sub ID list (see subidPill() in dashboard.js) -- shown
   alongside the commission chip when a Sub ID has real Shopee clicks but zero commission AND
   zero ad spend recorded yet (izuan, 2026-09-02: "show their Shopee clicks alongside the 0
   shopee commission and 0 meta ad spend in the subID rows"). Neutral grey, not Shopee-orange or
   P/L green/red, since this number isn't money -- it's saying "this Sub ID has real traffic, just
   no conversions or ad spend yet", not competing with either colored figure beside it. */
.mx-pill-clicks{
  display:inline-flex;align-items:center;gap:5px;flex-shrink:0;
  padding:4px 10px;border-radius:var(--mx-radius-pill);
  background:var(--mx-border-soft);color:var(--mx-ink-soft);
  font-size:11.5px;font-weight:700;letter-spacing:.01em;line-height:1.3;
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.mx-pill-clicks-icon{width:12px;height:12px;flex-shrink:0;}
@media (max-width:480px){
  .mx-pill-clicks{font-size:11px;padding:3.5px 9px;}
}
.mx-card-hover{transition:transform .28s var(--mx-ease), box-shadow .28s var(--mx-ease);}
.mx-card-hover:hover{transform:translateY(-3px);box-shadow:0 10px 24px rgba(0,0,0,.10);}
a{text-decoration:none;}

.mx-hidden{display:none !important;}
.mx-filter-menu{position:absolute;right:0;margin-top:8px;width:280px;background:#fff;border:1px solid var(--mx-border);border-radius:var(--mx-radius-md);box-shadow:var(--mx-shadow-lg);z-index:60;padding:8px;transform-origin:top right;animation:kxPop .18s var(--mx-ease-out);}
.mx-filter-opt{display:block;width:100%;text-align:left;padding:9px 11px;border-radius:9px;font-size:13px;color:#374151;cursor:pointer;border:none;background:transparent;transition:background .15s var(--mx-ease);}
.mx-filter-opt:hover{background:var(--mx-surface-alt);}
.mx-filter-opt.active{background:var(--mx-accent-soft);color:var(--mx-accent);font-weight:600;}
.mx-filter-custom{padding:8px 10px;border-top:1px solid var(--mx-border-soft);margin-top:4px;}
.mx-filter-custom input{width:100%;padding:6px 8px;border:1px solid var(--mx-border);border-radius:8px;font-size:12.5px;margin-bottom:6px;}
.mx-filter-custom button{width:100%;padding:8px;border-radius:8px;background:var(--mx-accent);color:#fff;font-size:12.5px;font-weight:600;border:none;cursor:pointer;transition:background .2s var(--mx-ease);}
.mx-filter-custom button:hover{background:var(--mx-accent-hover);}

.mx-badge{display:inline-block;font-size:9.5px;font-weight:700;padding:2px 8px;border-radius:var(--mx-radius-pill);margin-left:8px;letter-spacing:.03em;vertical-align:middle;cursor:help;}
.mx-badge-scale{background:var(--mx-green-soft);color:#15803d;}
.mx-badge-maintain{background:var(--mx-blue-soft);color:#1d4ed8;}
.mx-badge-reduce{background:var(--mx-amber-soft);color:var(--mx-amber);}
.mx-badge-pause{background:var(--mx-red-soft);color:#b91c1c;}
.mx-badge-harvest{background:var(--mx-purple-soft);color:var(--mx-purple);}

/* Sub ID Notes -- manual status tag (izuan, 2026-08-20). mx-status-icon is the compact chip shown
   on the dashboard's Sub ID list pills (icon only, next to the algorithmic mx-badge verdict);
   mx-status-tag is the full clickable icon+label chip row on the Sub ID detail page's header. Both
   share the same color-per-status classes below so the same tag always reads the same color
   wherever it appears. */
.mx-status-icon{display:inline-block;font-size:11px;margin-left:5px;vertical-align:middle;line-height:1;}
.mx-status-tag{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;padding:4px 10px;border-radius:var(--mx-radius-pill);border:1px solid transparent;cursor:pointer;opacity:.55;transition:opacity .15s var(--mx-ease),transform .1s var(--mx-ease);}
.mx-status-tag:hover{opacity:.85;}
.mx-status-tag:active{transform:scale(.96);}
.mx-status-tag-active{opacity:1;border-color:currentColor;}
.mx-status-scaling{background:var(--mx-green-soft);color:#15803d;}
.mx-status-testing{background:var(--mx-blue-soft);color:#1d4ed8;}
.mx-status-watch{background:var(--mx-amber-soft);color:var(--mx-amber);}
.mx-status-winner{background:var(--mx-purple-soft);color:var(--mx-purple);}
.mx-status-ccc{background:var(--mx-slate-soft);color:#475569;}
.mx-status-abandon{background:var(--mx-red-soft);color:#b91c1c;}

/* Sub ID Notes -- free-text box (izuan, 2026-08-20/21). Hand-authored, deliberately NOT Tailwind
   utility classes (bg-white/10, border-white/10, placeholder-white/40, resize-none, etc.) -- those
   were the ORIGINAL implementation and looked like a plain unstyled browser textarea/button sitting
   on the dark gradient header ("ugly box"), because app.css is a frozen precompiled Tailwind build
   with no live pipeline in this repo (same root cause as the .lg\:order-6 fix elsewhere in this
   file): none of those opacity-variant utility classes were ever actually compiled in, so they
   silently rendered as nothing. Verified this time against the real compiled class list instead of
   assumed. Two-state UI: .mx-note-view-box (has a saved note) / .mx-note-empty-btn (no note yet) are
   the default read view; .mx-note-textarea + .mx-note-actions are the edit form, toggled by
   renderSubidNoteView()/startEditSubidNote()/cancelEditSubidNote() in dashboard.js. */
.mx-note-view-box{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;border-radius:var(--mx-radius-sm);padding:8px 12px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);cursor:pointer;transition:background-color .15s var(--mx-ease),border-color .15s var(--mx-ease);}
.mx-note-view-box:hover{background:rgba(255,255,255,.11);border-color:rgba(255,255,255,.22);}
.mx-note-view-text{font-size:13px;color:rgba(255,255,255,.85);white-space:pre-wrap;flex:1;line-height:1.4;margin:0;}
.mx-note-view-icon{flex-shrink:0;width:14px;height:14px;color:rgba(255,255,255,.35);margin-top:2px;}
.mx-note-empty-btn{display:flex;align-items:center;gap:6px;width:100%;font-size:12px;color:rgba(255,255,255,.4);border-radius:var(--mx-radius-sm);padding:8px 12px;border:1px dashed rgba(255,255,255,.15);background:transparent;cursor:pointer;transition:color .15s var(--mx-ease),border-color .15s var(--mx-ease);}
.mx-note-empty-btn:hover{color:rgba(255,255,255,.7);border-color:rgba(255,255,255,.25);}
.mx-note-empty-icon{width:12px;height:12px;flex-shrink:0;}
.mx-note-textarea{display:block;width:100%;font-size:14px;font-family:inherit;border-radius:var(--mx-radius-sm);padding:8px 12px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.1);resize:none;min-height:44px;transition:border-color .15s var(--mx-ease);}
.mx-note-textarea::placeholder{color:rgba(255,255,255,.4);}
.mx-note-textarea:focus{outline:none;border-color:rgba(255,255,255,.3);}
.mx-note-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:6px;}
.mx-note-btn{padding:6px 12px;border-radius:var(--mx-radius-sm);font-size:12px;font-weight:500;cursor:pointer;border:1px solid transparent;transition:color .15s var(--mx-ease),background-color .15s var(--mx-ease),border-color .15s var(--mx-ease);}
.mx-note-btn-cancel{color:rgba(255,255,255,.6);background:transparent;}
.mx-note-btn-cancel:hover{color:rgba(255,255,255,.9);background:rgba(255,255,255,.1);}
.mx-note-btn-save{background:rgba(255,255,255,.1);color:rgba(255,255,255,.9);border-color:rgba(255,255,255,.1);}
.mx-note-btn-save:hover{background:rgba(255,255,255,.2);}

/* Shopee Consumer Time Pattern (izuan, 2026-08-20) -- metric tabs above the hour-of-day bar chart,
   auto-generated insight callouts, and the click-hour -> order-hour journey heatmap. */
.mx-tp-tabs{display:flex;gap:4px;flex-wrap:wrap;}
.mx-tp-tab{font-size:11px;font-weight:600;padding:5px 11px;border-radius:var(--mx-radius-pill);border:1px solid var(--mx-border);background:#fff;color:var(--mx-ink-soft);cursor:pointer;transition:all .15s var(--mx-ease);}
.mx-tp-tab:hover{background:var(--mx-surface-alt);}
.mx-tp-tab-active{background:var(--mx-accent);border-color:var(--mx-accent);color:#fff;}

.mx-tp-insights{display:flex;flex-direction:column;gap:6px;margin-bottom:4px;}
.mx-tp-insight{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--mx-ink);background:var(--mx-surface-alt);border-radius:10px;padding:8px 12px;line-height:1.4;}
.mx-tp-insight-icon{font-size:15px;flex-shrink:0;}

.mx-tp-heat-caption{font-size:11.5px;color:var(--mx-ink-soft);margin-bottom:8px;line-height:1.6;}
.mx-tp-heat-diag-demo{display:inline-block;width:9px;height:9px;border-radius:3px;border:2px solid var(--mx-accent);vertical-align:middle;}

/* Stretches to fill the panel on desktop (izuan, 2026-08-20: "too little on a desktop screen,
   maybe wider... looks dull maybe due to congested UI") -- table-layout:fixed + width:100% + an
   explicit <colgroup> (see tpHeatmapHtml()) makes every column share the FULL available width
   instead of shrinking to its content, so the grid on a wide screen is actually wide, not a small
   island of cells with empty panel space on either side. min-width keeps it from getting crushed
   below that on narrow screens -- the wrapping .overflow-x-auto div takes over with horizontal
   scroll at that point, same as every other table in this app. */
.mx-tp-heat-wrap{border:1px solid var(--mx-border-soft);border-radius:12px;padding:14px;background:linear-gradient(180deg,var(--mx-surface-alt),#fff 120px);}
.mx-tp-heat{width:100%;min-width:680px;table-layout:fixed;border-collapse:separate;border-spacing:5px;font-size:12px;}
.mx-tp-heat th{font-weight:600;color:var(--mx-ink-soft);padding:6px 5px;text-align:center;}
.mx-tp-heat-headicon{font-size:17px;line-height:1.3;}
.mx-tp-heat-headlabel{font-size:10px;font-weight:700;color:var(--mx-ink-soft);white-space:normal;line-height:1.2;margin:2px auto 0;}
.mx-tp-heat-rowhead{text-align:right !important;padding-right:10px !important;white-space:nowrap;font-size:11.5px;font-weight:700;}
.mx-tp-heat-cell{height:64px;text-align:center;vertical-align:middle;border-radius:10px;font-variant-numeric:tabular-nums;transition:transform .12s var(--mx-ease),box-shadow .12s var(--mx-ease);cursor:default;box-sizing:border-box;}
.mx-tp-heat-cell:hover{transform:scale(1.06);box-shadow:0 4px 14px rgba(238,77,45,.18);}
.mx-tp-heat-n{font-size:17px;font-weight:800;line-height:1.15;}
.mx-tp-heat-pct{font-size:10px;font-weight:600;line-height:1.3;margin-top:1px;}
.mx-tp-heat-diag{box-shadow:inset 0 0 0 2.5px var(--mx-accent);}
.mx-tp-heat-diag:hover{box-shadow:inset 0 0 0 2.5px var(--mx-accent),0 4px 14px rgba(238,77,45,.18);}
.mx-tp-heat-total{font-size:12.5px;font-weight:700;color:var(--mx-ink-soft);text-align:center;vertical-align:middle;background:var(--mx-surface-alt);border-radius:8px;}
.mx-tp-heat-totalsrow th{border-top:1px solid var(--mx-border);padding-top:9px;}
.mx-tp-heat-totalsrow td{border-top:1px solid var(--mx-border);padding-top:9px;}
.mx-tp-heat-grandtotal{color:var(--mx-accent);background:var(--mx-accent-soft);}

.mx-tp-journeys{margin-top:14px;padding-top:12px;border-top:1px solid var(--mx-border-soft);}
.mx-tp-journeys-label{font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--mx-ink-faint);margin-bottom:8px;}
.mx-tp-journey-row{display:flex;align-items:center;gap:10px;font-size:12.5px;padding:7px 0;border-bottom:1px dashed var(--mx-border-soft);flex-wrap:wrap;}
.mx-tp-journey-row:last-child{border-bottom:none;}
.mx-tp-journey-rank{flex-shrink:0;font-size:10px;font-weight:700;color:#fff;background:var(--mx-accent);width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;}
.mx-tp-journey-text{flex:1;min-width:200px;color:var(--mx-ink);}
.mx-tp-journey-arrow{color:var(--mx-ink-faint);margin:0 2px;}
.mx-tp-journey-tag{margin-left:6px;font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:var(--mx-radius-pill);background:var(--mx-green-soft);color:#15803d;vertical-align:middle;}
.mx-tp-journey-stat{flex-shrink:0;font-weight:700;color:var(--mx-ink);white-space:nowrap;}
.mx-tp-journey-pct{font-weight:600;color:var(--mx-ink-faint);}

.mx-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(8px);opacity:0;background:#1d1d1f;color:#fff;padding:11px 20px;border-radius:var(--mx-radius-pill);font-size:13px;z-index:100;box-shadow:var(--mx-shadow-lg);max-width:90vw;text-align:center;transition:opacity .25s var(--mx-ease), transform .25s var(--mx-ease);}
.mx-toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.mx-toast.error{background:var(--mx-red);}

.mx-scrollx{display:flex;gap:12px;overflow-x:auto;padding:2px 2px 10px;scroll-snap-type:x proximity;}
.mx-scrollx>*{scroll-snap-align:start;}
.mx-scrollx::-webkit-scrollbar{height:6px;}
.mx-scrollx::-webkit-scrollbar-thumb{background:var(--mx-border);border-radius:6px;}

/* A percentage width:100% (tried first, didn't work) is a no-op here: .mx-daycard itself has no
   definite width (only min-width:250px), so on mobile -- where the day-card row is a plain flex
   with w-max, not a grid -- the browser sizes each .mx-daycard to its own shrink-to-fit content
   during layout, and a percentage width on a descendant can't establish a size relative to an
   ancestor whose own width is *itself* still being computed from that same descendant's content.
   Confirmed still broken on the live site after that first attempt (izuan's follow-up mobile
   screenshot, 13/08/2026) -- the "Today's Shopee report isn't out yet" sub-line was still one
   un-wrapped line dragging every later day-card off-screen. A hardcoded max-width in px sidesteps
   the whole circular-sizing problem: it gives the browser a concrete number to wrap against
   regardless of what any ancestor's own width resolves to. 200px = .mx-daycard's 250px min-width
   minus its 32px of left+right p-4 padding, with a little headroom. */
.mx-daycard-empty p{color:var(--mx-ink-faint);max-width:200px;white-space:normal;word-wrap:break-word;}
.mx-cohort-table{width:100%;border-collapse:collapse;font-size:12px;}
.mx-cohort-table th{text-align:right;padding:8px 10px;color:var(--mx-ink-soft);font-weight:600;border-bottom:1px solid var(--mx-border-soft);white-space:nowrap;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;}
.mx-cohort-table th:first-child{text-align:left;}
.mx-cohort-celldate{font-weight:400;text-transform:none;letter-spacing:0;color:var(--mx-ink-faint);font-size:9.5px;margin-top:2px;}
.mx-cohort-clickdow{font-weight:400;color:var(--mx-ink-faint);font-size:9.5px;letter-spacing:.03em;margin-top:2px;}
/* Shopee's 3 recurring monthly campaign dates (see shopeeCampaignLabel() in dashboard.js) --
   Shopee's own brand orange (already --mx-accent everywhere else in this app) so it reads as
   "this is a Shopee thing", distinct from the app's own green/red/purple/blue semantic colors. */
.mx-cohort-campaign-badge{display:inline-block;margin-top:3px;font-size:8px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;padding:2px 7px;border-radius:var(--mx-radius-pill);background:var(--mx-accent-soft);color:var(--mx-accent);white-space:nowrap;}
.mx-cohort-table td{text-align:right;padding:7px 10px;border-bottom:1px solid var(--mx-border-soft);white-space:nowrap;}
.mx-cohort-table td:first-child{text-align:left;font-weight:500;}
.mx-cohort-table tbody tr{transition:background .15s var(--mx-ease);}
.mx-cohort-table tbody tr:hover{background:var(--mx-surface-alt);}
/* Recovery percentage: plain colored bold text, not a pill -- a soft-background badge on every
   single row read as too much chrome for a data-table number. The RM delta underneath (below)
   stays a quieter weight/size so it supports the headline number rather than competing with it. */
.mx-cohort-recovery-pct{font-weight:700;font-size:12.5px;font-variant-numeric:tabular-nums;}
.mx-recover-good{color:var(--mx-green);}
.mx-recover-bad{color:var(--mx-red);}
/* 50-99% band: not yet fully recovered but not badly underwater either -- amber, the same tone
   already used for the AI Verdict's "Reduce" badge, completing a 3-state green/amber/red
   traffic light instead of leaving this (the most common everyday state) uncolored. */
.mx-recover-mid{color:var(--mx-amber);}
.mx-cohort-amt{font-variant-numeric:tabular-nums;}
.mx-cohort-orders{display:flex;align-items:center;justify-content:flex-end;gap:2px;font-size:9.5px;font-weight:600;color:var(--mx-blue);margin-top:2px;letter-spacing:.01em;font-variant-numeric:tabular-nums;}
.mx-cohort-orders-total{font-size:9.5px;font-weight:600;color:var(--mx-blue);margin-top:2px;font-variant-numeric:tabular-nums;}
/* izuan, 2026-09-05: per-day recovery % inside each D1-D8 cell, distinct from the cumulative
   Recovery column at the row's end -- deliberately quieter/smaller than .mx-cohort-recovery-pct
   and uncolored (no green/red judgment), since one day recovering a small share of the whole
   cohort's spend isn't itself good or bad -- it only means something summed with the other days. */
.mx-cohort-daily-pct{font-size:9.5px;font-weight:600;color:var(--mx-ink-faint);margin-top:2px;letter-spacing:.01em;font-variant-numeric:tabular-nums;cursor:default;}
.mx-cohort-trend-icon{width:7px;height:7px;flex:none;}
.mx-cohort-trend-up{color:var(--mx-green);}
.mx-cohort-trend-down{color:var(--mx-red);}
.mx-cohort-paused{font-size:9.5px;font-weight:600;color:var(--mx-ink-faint);margin-top:2px;letter-spacing:.04em;text-transform:uppercase;}
.mx-cohort-harvest{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.04em;padding:2px 9px;border-radius:var(--mx-radius-pill);background:var(--mx-purple-soft);color:var(--mx-purple);}
/* Plain neutral marker for a click_date with nothing to report either way (pre-launch, dormant,
   or an ordinary zero-commission rest day) -- deliberately just a dash, no color/pill, so the
   Recovery column never gets more cluttered than the one short label already living in Spend
   (PAUSED/PRE-LAUNCH/DORMANT, see classifyZeroSpendDay() in dashboard.js). */
.mx-cohort-dash{color:var(--mx-ink-faint);font-size:13px;}
/* Info icon next to PRE-LAUNCH/DORMANT (see COHORT_INFO_ICON in dashboard.js). Visually ~11px,
   but padded out to a much larger invisible hit area -- 11px alone is far below any reasonable
   touch-target size, and this cell sits inside a dense data table on mobile where a mis-tap would
   land on a neighboring row. Negative margin cancels the padding's effect on layout so the extra
   tap area doesn't visibly push the row's other content around. */
/* box-sizing:content-box is deliberate and load-bearing here, not decorative -- Tailwind's
   preflight (see app.css) sets `*{box-sizing:border-box}` globally, which would otherwise fold
   this element's own 9px padding INTO its declared 11x11 box, leaving a zero (or negative)
   content area for the width:100%/height:100% <svg> child to size itself against. That's exactly
   why the icon was invisible in production while still eating mouse events over its full 11x11
   footprint (hover/tap kept firing on the empty box) -- the classic symptom of a border-box vs.
   content-box mismatch. Forcing content-box here restores the intended 11x11 *visible* icon with
   its 9px tap-padding still added on top, independent of whatever the page's global reset does. */
.mx-cohort-info-icon{box-sizing:content-box;display:inline-flex;align-items:center;justify-content:center;width:11px;height:11px;padding:9px;margin:-9px -9px -9px -3px;color:var(--mx-ink-faint);cursor:pointer;vertical-align:-1px;transition:color .15s var(--mx-ease);}
.mx-cohort-info-icon:hover{color:var(--mx-ink-soft);}
.mx-cohort-info-icon svg{width:100%;height:100%;}
.mx-cohort-table td.mx-cohort-pending-cell{background:var(--mx-slate-soft);}
.mx-cohort-pending{font-size:10px;font-weight:600;font-style:italic;letter-spacing:.02em;color:var(--mx-slate);}
.mx-cohort-table td.mx-cohort-ongoing-cell{background:var(--mx-amber-soft);}
.mx-cohort-ongoing{font-size:10px;font-weight:600;font-style:italic;letter-spacing:.02em;color:var(--mx-amber);}
.mx-daycard-ongoing-banner{font-size:10px;font-weight:700;letter-spacing:.02em;text-align:center;padding:4px 8px;border-radius:var(--mx-radius-pill);background:var(--mx-amber-soft);color:var(--mx-amber);margin-bottom:8px;}
.mx-daily-row-ongoing{background:var(--mx-amber-soft);}
.mx-daily-ongoing-tag{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 7px;border-radius:var(--mx-radius-pill);background:var(--mx-amber-soft);color:var(--mx-amber);vertical-align:middle;}
.mx-cohort-pl{font-size:10px;font-weight:600;margin-top:5px;font-variant-numeric:tabular-nums;}
.mx-cohort-pl-pos{color:#4d9d76;}
.mx-cohort-pl-neg{color:#c96a66;}

.mx-file-hint{font-size:11px;color:var(--mx-ink-faint);margin-top:3px;}
.mx-ingest-msg{font-size:12px;margin-top:8px;padding:9px 11px;border-radius:var(--mx-radius-sm);display:none;}
.mx-ingest-msg.show{display:block;animation:kxFadeIn .3s var(--mx-ease-out);}
.mx-ingest-msg.ok{background:var(--mx-green-soft);color:var(--mx-green);}
.mx-ingest-msg.err{background:var(--mx-red-soft);color:var(--mx-red);}
.mx-reset-link{font-size:11px;color:var(--mx-ink-faint);text-decoration:underline;cursor:pointer;margin-top:10px;display:inline-block;}
.mx-reset-link:hover{color:var(--mx-ink-soft);}
.mx-search-empty{padding:24px;text-align:center;color:var(--mx-ink-faint);font-size:13px;}

/* Tailwind's rose palette wasn't compiled into the copied stylesheet, so the negative
   day-card variant is defined here with real Tailwind rose hex values for an exact match. */
.mx-daycard-neg{border-color:#fecdd3;background-color:rgba(255,241,242,.8);}
.mx-pill-neg{background-color:#ffe4e6;color:#9f1239;}
/* Tailwind's orange palette also wasn't compiled into the copied stylesheet -- same fix as
   .mx-daycard-neg/.mx-pill-neg above, real Tailwind orange-50/orange-300 hex values. Used by
   subidPillTint() (izuan, 2026-08-23, ported from Kanta) for a Sub ID pill that's profitable but
   below the configurable Average ROI (%) threshold -- distinct from plain green (healthy) and
   red (loss). */
.mx-pill-orange{background-color:#fff7ed;border-color:#fdba74;}
.mx-card{width:156px;}

/* ---- KPI cards ---- */
.mx-kpi{background:var(--mx-surface);border:1px solid var(--mx-border-soft);border-radius:var(--mx-radius-md);padding:16px 18px;transition:transform .28s var(--mx-ease), box-shadow .28s var(--mx-ease);}
.mx-kpi:hover{transform:translateY(-2px);box-shadow:var(--mx-shadow-md);}
.mx-kpi-label{font-size:10.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--mx-ink-soft);}
.mx-kpi-value{margin-top:6px;font-size:clamp(17px,2vw,21px);font-weight:700;letter-spacing:-.01em;color:var(--mx-ink);}
.mx-kpi.pos{background:var(--mx-green-soft);border-color:#bff0d4;}
.mx-kpi.neg{background:var(--mx-red-soft);border-color:#f7c9c5;}
.mx-kpi.pos .mx-kpi-value{color:var(--mx-green);}
.mx-kpi.neg .mx-kpi-value{color:var(--mx-red);}
/* izuan, 2026-09-10: "Show the up/down trend with svg icons with color indicator and % vs...
   Make the dashboard metrics more interactive and informed." -- headrow puts the trend badge on
   the same line as the label instead of stacking (keeps every card the same height regardless of
   whether it has a badge). Reuses the same green/red/gray up/down/flat vocabulary as
   .mx-tt-trend and Cohort Recovery's own trend icon elsewhere for consistency, and the same
   hover-tooltip mechanism as every other info affordance in this app (registerTooltip/
   showTooltip) rather than a new interaction pattern. */
.mx-kpi-headrow{display:flex;align-items:center;justify-content:space-between;gap:8px;}
.mx-kpi-trend{display:inline-flex;align-items:center;gap:3px;flex-shrink:0;font-size:10.5px;font-weight:700;cursor:default;font-variant-numeric:tabular-nums;}
.mx-kpi-trend.up{color:var(--mx-green);}
.mx-kpi-trend.down{color:var(--mx-red);}
.mx-kpi-trend.flat{color:var(--mx-ink-faint);}
.mx-kpi-trend-icon{width:7px;height:7px;flex:none;}
/* izuan, 2026-09-10 (follow-up): "I don't wish to introduce new line for the SHOPEE CLICKS...
   Perhaps we can put side by side with i.e 115,522 69.11% capture with middle align... smaller
   font like the current." -- .mx-kpi-sub's own separate stacked line replaced with an inline row
   sitting right next to the value instead, vertically centered against it (align-items:center),
   same muted weight/size as before, just repositioned. Every KPI card uses this same
   .mx-kpi-value-row wrapper now (not only Shopee Clicks) so the value's own vertical position
   stays identical across every card whether or not it happens to have a second figure next to it. */
.mx-kpi-value-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.mx-kpi-inline-sub{font-size:10.5px;color:var(--mx-ink-faint);font-weight:600;white-space:nowrap;}

/* izuan, 2026-09-10 (screenshot, mobile #kpi-grid at its 2-column width): "the latest addition
   for the up/down trend on mobile should be fixed as it ruined the previous perfect UI display
   on mobile devices. Perhaps put it under the amount value just for mobile." -- .mx-kpi-headrow
   puts the trend badge on the SAME row as the label via justify-content:space-between, which
   only has room to breathe at the >=3-column widths (sm: and up). At the 2-column mobile width
   (#kpi-grid is grid-cols-2 below Tailwind's sm breakpoint, matched here), a long label like
   "Shopee Clicks" plus a badge like "-100.0%" don't fit that card width side by side.
   display:contents on the two row wrappers unwraps their children (label/trend, value/inline-sub)
   into direct flex children of .mx-kpi itself, so `order` can freely reshuffle them into label ->
   value -> inline-sub -> trend, without touching the HTML that renderKPIs() generates. */
@media (max-width:640px){
  .mx-kpi{display:flex;flex-direction:column;}
  .mx-kpi-headrow{display:contents;}
  .mx-kpi-label{order:1;}
  /* .mx-kpi-value-row stays a real flex row (not unwrapped) so the value and the Shopee Clicks
     capture-% sub-label keep sitting on the SAME line, middle-aligned (align-items:center is
     already set on the base .mx-kpi-value-row rule above) -- 3 lines total (label / value+capture
     / trend) instead of 4. .mx-kpi > .mx-kpi-value only targets the Profit & Loss card's value,
     which has no .mx-kpi-value-row wrapper and so is already a direct child of .mx-kpi here. */
  .mx-kpi-value-row{order:2;}
  .mx-kpi > .mx-kpi-value{order:2;}
  .mx-kpi-trend{order:3;margin-top:4px;}
  .mx-kpi-inline-sub{font-size:9px;}
}

/* ---- day cards ---- */
.mx-daycard{min-width:250px;border-radius:var(--mx-radius-md);border:1px solid var(--mx-border-soft);overflow:hidden;transition:transform .25s var(--mx-ease), box-shadow .25s var(--mx-ease);}
.mx-daycard:hover{transform:translateY(-2px);box-shadow:var(--mx-shadow-md);}
.mx-daycard-head{font-size:11.5px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--mx-ink-soft);}
.mx-daycard-headrow{display:flex;align-items:center;justify-content:space-between;gap:8px;}
/* "View Ad Setup" button next to each day-card's date -- see openAdSetupModal() in dashboard.js.
   Deliberately understated (matches this card header's own quiet, all-caps label styling) since
   it's a secondary lookup action, not a primary metric. */
.mx-adsetup-btn{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;padding:2px 7px;font-size:10px;font-weight:600;text-transform:none;letter-spacing:0;color:var(--mx-ink-faint);background:var(--mx-surface-alt);border:1px solid var(--mx-border-soft);border-radius:999px;cursor:pointer;transition:color .15s, border-color .15s, background .15s;}
.mx-adsetup-btn:hover{color:var(--mx-ink);border-color:var(--mx-border);background:#fff;}
.mx-adsetup-btn:disabled{opacity:.6;cursor:default;}
.mx-adsetup-btn svg{width:11px;height:11px;flex-shrink:0;}
/* izuan, 2026-09-09: "Pull Pace" button added next to the existing Setup button on today's
   day-card -- groups both action buttons together on the header row's right side (the row itself
   is justify-content:space-between against the date label on the left), reusing .mx-adsetup-btn's
   own look so the new button reads as part of the same family, not a one-off. */
.mx-daycard-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;}
.mx-daycard-row{display:flex;justify-content:space-between;align-items:baseline;font-size:13px;line-height:1.5;gap:10px;padding:4.5px 0;}
.mx-daycard-row .l{color:var(--mx-ink-soft);white-space:nowrap;}
.mx-daycard-row .v{font-weight:600;color:var(--mx-ink);tabular-nums:1;white-space:nowrap;}
.mx-daycard-clickbreak{margin-top:6px;padding-top:6px;border-top:1px dashed var(--mx-border-soft);}
.mx-daycard-clickbreak-title{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;color:var(--mx-ink-faint);margin-bottom:3px;}
.mx-daycard-clickbreak-row{display:flex;justify-content:space-between;align-items:baseline;font-size:11.5px;line-height:1.55;gap:8px;padding:2px 0;}
.mx-daycard-clickbreak-row .l{color:var(--mx-ink-soft);white-space:nowrap;}
.mx-daycard-clickbreak-row .v{font-weight:600;color:var(--mx-ink);tabular-nums:1;white-space:nowrap;}

/* ---- product cards ---- */
.mx-pcard{width:158px;}
.mx-pcard-img{height:104px;}

/* ---- AI panel ---- */
.mx-ai-panel{background:linear-gradient(155deg,#fbfaf9 0%,#ffffff 55%);border:1px solid var(--mx-border-soft);}
.mx-ai-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 18px;border-radius:var(--mx-radius-pill);background:var(--mx-ink);color:#fff;font-size:13px;font-weight:600;border:none;cursor:pointer;transition:transform .2s var(--mx-ease), background .2s var(--mx-ease);}
.mx-ai-btn:hover{transform:translateY(-1px);background:#333336;}
.mx-ai-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;}
.mx-ai-body{font-size:13.5px;line-height:1.7;color:#333;}
.mx-ai-body h4{font-size:12.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--mx-ink-soft);margin:16px 0 6px;}
.mx-ai-body h4:first-child{margin-top:0;}
.mx-ai-body p{margin:0 0 10px;}
.mx-ai-body ul{margin:0 0 10px;padding-left:20px;}
.mx-ai-body li{margin-bottom:4px;}
.mx-ai-body strong{color:var(--mx-ink);}
.mx-ai-verdict{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:var(--mx-radius-pill);font-family:var(--mx-font-display);font-weight:800;font-size:13px;letter-spacing:.02em;margin-bottom:14px;}

/* ---- AI Verdict scorecard: infographic-style redesign (ring gauges + icon-card reasons +
   collapsible narrative) so a glance lands on shapes and numbers first, with the fuller prose
   explanation one click away instead of leading the panel. Deliberately stays inside Manta's
   existing palette (--mx-green/-amber/-red/-accent/-blue/-purple, all already used elsewhere in
   this app) rather than introducing new colors, per explicit request not to drag the look away
   from the rest of the dashboard. --mx-font-display (Manrope, loaded in dashboard.html's <head>,
   system-sans fallback if that request is ever blocked) is used only for the numbers/headline in
   this panel -- a bolder, more geometric face than the rest of the app's body copy uses, the kind
   of face the reference infographic sheets use for their big stat numbers, without changing the
   font anywhere else in the product. */
.mx-verdict-headline{font-family:var(--mx-font-display);font-size:16.5px;font-weight:700;color:var(--mx-ink);line-height:1.45;letter-spacing:-.005em;margin:0 0 18px;}
.mx-verdict-scores{display:flex;flex-wrap:wrap;justify-content:space-around;gap:18px 10px;margin-bottom:18px;padding:18px 12px;background:var(--mx-surface-alt);border-radius:var(--mx-radius-md);}
.mx-verdict-tier-good{color:var(--mx-green);}
.mx-verdict-tier-mid{color:var(--mx-amber);}
.mx-verdict-tier-bad{color:var(--mx-red);}
/* Ring gauge: an SVG track + progress circle pair (same stroke-dasharray/-dashoffset technique as
   .mx-backtotop-ring elsewhere in this file), rotated -90deg so the arc starts at 12 o'clock like
   every other progress ring, with the icon+number absolutely centered on top via the wrap being
   the positioning context. */
.mx-verdict-ring-item{display:flex;flex-direction:column;align-items:center;gap:6px;width:92px;text-align:center;}
.mx-verdict-ring-wrap{position:relative;width:72px;height:72px;}
.mx-verdict-ring-svg{width:100%;height:100%;transform:rotate(-90deg);}
.mx-verdict-ring-bg{fill:none;stroke:var(--mx-border-soft);stroke-width:6;}
.mx-verdict-ring-fg{fill:none;stroke-width:6;stroke-linecap:round;transition:stroke-dashoffset .6s var(--mx-ease-out);}
.mx-verdict-ring-fg.mx-verdict-tier-good{stroke:var(--mx-green);}
.mx-verdict-ring-fg.mx-verdict-tier-mid{stroke:var(--mx-amber);}
.mx-verdict-ring-fg.mx-verdict-tier-bad{stroke:var(--mx-red);}
.mx-verdict-ring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;}
.mx-verdict-ring-icon{font-size:13px;line-height:1;}
.mx-verdict-ring-value{font-family:var(--mx-font-display);font-size:16px;font-weight:800;color:var(--mx-ink);line-height:1;font-variant-numeric:tabular-nums;}
.mx-verdict-ring-pct{font-size:10px;font-weight:700;margin-left:1px;}
.mx-verdict-ring-label{font-size:10.5px;font-weight:600;color:var(--mx-ink-soft);line-height:1.3;}
.mx-verdict-ring-tier{font-size:9.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;}
/* Reasons: numbered icon cards in a responsive grid instead of a bulleted list -- each reason is
   its own scannable chunk with a small accent-colored number badge, the same "circle + text"
   motif the reference infographic sheets use throughout. */
.mx-verdict-reasons-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin:0 0 14px;}
.mx-verdict-reason-card{display:flex;align-items:flex-start;gap:10px;padding:11px 13px;background:var(--mx-surface-alt);border-radius:var(--mx-radius-sm);}
.mx-verdict-reason-card p{margin:0;font-size:12.5px;line-height:1.55;color:#333;}
.mx-verdict-reason-num{flex-shrink:0;width:20px;height:20px;border-radius:50%;background:var(--mx-accent-soft);color:var(--mx-accent);font-family:var(--mx-font-display);font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;margin-top:1px;}
.mx-verdict-risk,.mx-verdict-action{display:flex;align-items:flex-start;gap:9px;font-size:13px;line-height:1.55;padding:11px 14px;border-radius:var(--mx-radius-sm);margin-bottom:10px;}
.mx-verdict-risk{background:var(--mx-amber-soft);color:#7a4a10;}
.mx-verdict-risk strong{color:#7a4a10;}
.mx-verdict-action{background:var(--mx-accent-soft);color:#8a3319;}
.mx-verdict-action strong{color:#8a3319;}
.mx-verdict-callout-icon{flex-shrink:0;font-size:14px;line-height:1.4;}
/* Narrative: collapsed behind a disclosure by default so the visuals above are what a glance
   lands on -- the full reasoning is still one click away, not removed. Custom marker (a small
   rotating chevron drawn with borders, no extra icon font/SVG needed) instead of the default
   browser triangle, which looks distinctly out of place next to the rest of this styled card. */
.mx-verdict-narrative{margin-top:2px;}
.mx-verdict-narrative summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;color:var(--mx-ink-soft);padding:2px 0;user-select:none;}
.mx-verdict-narrative summary::-webkit-details-marker{display:none;}
.mx-verdict-narrative summary::before{content:'';width:6px;height:6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .2s var(--mx-ease-out);flex-shrink:0;}
.mx-verdict-narrative[open] summary::before{transform:rotate(45deg);}
.mx-verdict-narrative summary:hover{color:var(--mx-ink);}
.mx-verdict-narrative .mx-ai-body{margin-top:12px;padding-top:12px;border-top:1px solid var(--mx-border-soft);}
.mx-verdict-caption{font-size:11px;color:var(--mx-ink-faint);margin-top:10px;}
.mx-verdict-parse-warning{display:flex;align-items:flex-start;gap:10px;background:var(--mx-amber-soft);color:#7a4a10;padding:14px 16px;border-radius:var(--mx-radius-md);}
.mx-verdict-parse-warning p{margin:0;font-size:13px;line-height:1.6;}
.mx-verdict-warning-icon{font-size:16px;line-height:1.4;flex-shrink:0;}
/* Sits above a perfectly good scorecard rather than replacing it -- see runAiAnalysis()'s
   staleNotice path in dashboard.js. Same amber banner as the full parse_error state, just with
   its own bottom margin since here it's a note stacked on top of real content, not the only thing
   on screen. */
.mx-verdict-stale-notice{margin-bottom:14px;}

/* ===================== CCC (Content Creator Club) weekly click-quota tracker =====================
   Rebuilt (2026-08-10) from an earlier single-progress-bar design into a multi-week table matching
   Shopee's own CCC page (izuan's screenshot: a 5-row table -- 2 weeks back, current, 2 weeks
   ahead -- each with editable 3%/5% targets, actual progress, and a resulting commission rate).

   Rebuilt AGAIN (izuan, 2026-09-10: "can we have the same table styling and design to replicate
   Shopee's actual weekly clicks targets page?" -- shared a screenshot of Shopee's own CCC page: an
   orange card, a navy "WEEKLY CLICKS TARGETS" title banner, a red pill "WEEK N" ribbon per week, a
   blue-headed/gold-bordered mini-table (Date / Target Clicks / Comms % / Current Progress), a red
   "YOUR COMMISSION RATE % IN WEEK N" bar, and one blue rules box at the bottom). This block
   replaces the plain generic-table styling above with Shopee's own banner/card palette; the two new
   colors below (navy title bar, gold cell borders) aren't part of the app's shared --mx- palette
   since they exist only to literally match Shopee's own screen, not as general design tokens. */
.mx-ccc-shopee-card{border-radius:var(--mx-radius-lg);padding:18px;display:flex;flex-direction:column;gap:18px;}
.mx-ccc-shopee-title{background:#132a4e;color:#fff;font-size:15px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;text-align:center;padding:12px 16px;border-radius:var(--mx-radius-md);}
.mx-ccc-week-block{background:#fff;border-radius:var(--mx-radius-md);overflow:hidden;box-shadow:var(--mx-shadow-sm);}
.mx-ccc-week-current{box-shadow:0 0 0 2px var(--mx-accent),var(--mx-shadow-sm);}
.mx-ccc-week-banner{background:var(--mx-accent);color:#fff;display:flex;align-items:center;gap:8px;font-size:13px;font-weight:800;letter-spacing:.04em;padding:9px 14px;}
.mx-ccc-week-banner svg{flex-shrink:0;}
.mx-ccc-current-tag{display:inline-block;margin-left:auto;font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 8px;border-radius:var(--mx-radius-pill);background:#fff;color:var(--mx-accent);vertical-align:middle;}
.mx-ccc-shopee-table{width:100%;border-collapse:collapse;font-size:12.5px;table-layout:fixed;}
.mx-ccc-shopee-table th{background:#0071e3;color:#fff;font-size:10px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;padding:7px 8px;text-align:center;}
.mx-ccc-shopee-table td{border:1.5px solid #f6c453;padding:7px 8px;text-align:center;vertical-align:middle;color:var(--mx-ink);}
.mx-ccc-date-cell{font-weight:700;font-size:11.5px;line-height:1.5;white-space:nowrap;background:#fffdf5;}
.mx-ccc-comms-cell{font-weight:700;color:var(--mx-accent);background:#fffdf5;}
.mx-ccc-progress-cell{font-weight:800;font-size:18px;font-variant-numeric:tabular-nums;background:#fffdf5;}
.mx-ccc-pace{font-size:9.5px;font-weight:500;color:var(--mx-ink-faint);margin-top:3px;white-space:normal;}
.mx-ccc-rate-bar{background:var(--mx-accent);color:#fff;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 14px;font-size:11.5px;font-weight:700;flex-wrap:wrap;}
.mx-ccc-rate-value{font-size:14px;}
.mx-ccc-rate-pill{display:inline-flex;align-items:center;padding:3px 12px;border-radius:var(--mx-radius-pill);font-weight:800;background:#fff;color:var(--mx-accent);}
.mx-ccc-rate-dash{opacity:.75;}
.mx-ccc-shopee-footer{background:#eaf3fe;border-radius:var(--mx-radius-md);padding:14px 16px;}
.mx-ccc-shopee-footer p{margin:0;font-size:11.5px;line-height:1.7;color:#0b3d7a;}
.mx-ccc-shopee-footer p+p{margin-top:4px;}
.mx-ccc-input{width:100%;max-width:88px;padding:5px 6px;border:1px solid var(--mx-border);border-radius:var(--mx-radius-sm);font-size:13px;text-align:center;font-variant-numeric:tabular-nums;background:#fff;font-weight:700;}
.mx-ccc-input:focus{border-color:var(--mx-accent);outline:none;}

/* ===================== Access Requests (admin only: izuan/boy/cho) =====================
   izuan, 2026-09-10: kanta.my rebrand -- new admin screen for reviewing "Request Access"
   submissions from the public landing page. Styled to match the rest of the dashboard's existing
   panel/table language (mx-panel, plain white table rows) rather than CCC's Shopee-banner
   treatment -- this is an internal admin tool, not something meant to visually match a third
   party's own page. */
.mx-access-requests-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;margin-left:5px;border-radius:var(--mx-radius-pill);background:var(--mx-accent);color:#fff;font-size:10px;font-weight:700;vertical-align:middle;}
.mx-ar-tabs{display:flex;gap:4px;background:var(--mx-surface-alt);border-radius:var(--mx-radius-pill);padding:3px;}
.mx-ar-tab{padding:6px 14px;border-radius:var(--mx-radius-pill);border:none;background:transparent;font-size:12.5px;font-weight:600;color:var(--mx-ink-soft);cursor:pointer;}
.mx-ar-tab.active{background:var(--mx-surface);color:var(--mx-ink);box-shadow:var(--mx-shadow-sm);}
.mx-ar-btn-sm{padding:5px 12px;border-radius:var(--mx-radius-pill);font-size:12px;font-weight:600;border:none;cursor:pointer;margin-left:6px;}
.mx-ar-btn-sm.mx-btn-primary{background:var(--mx-accent);color:#fff;}
.mx-ar-btn-sm.mx-btn-secondary{background:var(--mx-surface-alt);color:var(--mx-ink-soft);}
.mx-ar-reviewed{font-size:12px;color:var(--mx-ink-faint);}
.mx-ar-message-cell{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.mx-ar-form-row{margin-bottom:12px;}
.mx-ar-form-row label{display:block;font-size:12px;font-weight:600;color:var(--mx-ink-soft);margin-bottom:4px;}
.mx-ar-form-row input{width:100%;padding:8px 10px;border:1px solid var(--mx-border);border-radius:var(--mx-radius-sm);font-size:13px;background:#fff;font-family:inherit;}
.mx-ar-form-row input:focus{border-color:var(--mx-accent);outline:none;}
.mx-alert{background:var(--mx-red-soft);color:var(--mx-red);padding:9px 12px;border-radius:var(--mx-radius-sm);font-size:12.5px;margin-bottom:12px;}

@media (max-width:640px){
  .mx-ccc-shopee-table{font-size:11px;}
  .mx-ccc-shopee-table th{font-size:9px;padding:6px 4px;}
  .mx-ccc-shopee-table td{padding:6px 4px;}
  .mx-ccc-date-cell{font-size:10px;}
  .mx-ccc-progress-cell{font-size:15px;}
}
.mx-modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);backdrop-filter:blur(2px);z-index:200;display:flex;align-items:center;justify-content:center;animation:kxFadeIn .2s var(--mx-ease-out);}
.mx-modal{background:#fff;border-radius:var(--mx-radius-lg);box-shadow:var(--mx-shadow-lg);width:min(440px,92vw);padding:24px;animation:kxPop .25s var(--mx-ease-spring);
  /* The Settings modal grew past typical viewport height once the AI Agent Connectors section was
     added (tax %, subid trim, API key, and now a token list all in one modal) -- with no
     max-height/overflow here, the browser just clipped whatever didn't fit at the viewport edge,
     with no way to scroll to the rest (reported by izuan, screenshot showing the modal's own
     Cancel/Save buttons cut off entirely). max-height + overflow-y:auto makes the modal itself an
     independently scrollable region instead, so it's always fully reachable regardless of how much
     content is inside or how short the viewport is. */
  max-height:88vh;overflow-y:auto;-webkit-overflow-scrolling:touch;}
.mx-modal h3{font-size:17px;font-weight:700;margin:0 0 6px;}
.mx-modal p{font-size:12.5px;color:var(--mx-ink-soft);line-height:1.6;margin:0 0 14px;}
.mx-modal input{width:100%;padding:10px 12px;border:1px solid var(--mx-border);border-radius:var(--mx-radius-sm);font-size:13px;margin-bottom:12px;}
.mx-modal-label{display:block;font-size:12px;font-weight:600;color:var(--mx-ink);margin-bottom:4px;}

/* ===================== Settings modal: standardized sections (izuan, 2026-08-31 -- "too
   cluttered and not standardize in term of section spacing and UI"). Previously every section
   reused the modal's own <h3> title style (identical 17px/700 for both "Settings" and each
   section inside it), with ad hoc inline style="margin-top:Npx" hacks on individual labels papering
   over inconsistent spacing between fields. Sections are now real bounded blocks -- a small
   uppercase eyebrow title clearly distinct from the modal title, a divider between sections (same
   pattern already established by .mx-adsetup-adblock), and one consistent gap rule between fields
   within a section -- so the eye can scan section-by-section instead of one flat wall of text. */
.mx-modal-settings{width:min(520px,92vw);}
.mx-settings-section{padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid var(--mx-border-soft);}
.mx-settings-section:last-child{padding-bottom:0;margin-bottom:0;border-bottom:none;}
.mx-settings-section-title{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--mx-ink-faint);margin:0 0 10px;}
.mx-settings-field + .mx-settings-field{margin-top:14px;}
/* izuan, 2026-09-29: Shopee Singapore [SG] filter radio group -- .mx-modal input's own width:100%
   rule (above) would otherwise stretch each radio circle into a full-width bar, so reset that
   specifically for these. */
.mx-modal-radio-row{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--mx-ink);margin-bottom:8px;cursor:pointer;}
.mx-modal-radio-row input[type="radio"]{width:auto;margin:0;flex:none;}
.mx-modal-radio-row:last-child{margin-bottom:0;}

/* Danger zone (izuan, 2026-08-29: "Clear Data for a Date") -- flagged visually as a destructive
   action instead of blending into routine settings, using the app's existing --mx-red/-soft
   tokens (the same pair .mx-adsetup-status-paused and the "bad" verdict tier already use). */
.mx-settings-danger{background:var(--mx-red-soft);border:1px solid #f3c8c3;border-radius:var(--mx-radius-sm);padding:16px;}
.mx-settings-danger .mx-settings-section-title{color:var(--mx-red);}
.mx-settings-danger p{color:#9a3b32;}
.mx-cleardate-sources{display:flex;flex-wrap:wrap;gap:8px 18px;margin:2px 0 12px;}
.mx-cleardate-source{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:500;color:#9a3b32;cursor:pointer;user-select:none;}
.mx-cleardate-source input[type=checkbox]{width:auto;height:auto;margin:0;padding:0;accent-color:var(--mx-red);cursor:pointer;}
.mx-btn-danger{background:#fff;color:var(--mx-red);border:1px solid var(--mx-red);white-space:nowrap;padding:7px 14px;border-radius:var(--mx-radius-pill);font-size:12.5px;font-weight:600;cursor:pointer;transition:transform .15s var(--mx-ease);}
.mx-btn-danger:hover{transform:translateY(-1px);background:var(--mx-red-soft);}

/* Ad Accounts row editor (see adAccountRowHtml() in dashboard.js) -- one line per real Meta ad
   account: ID / label / currency / tax % / FX rate to MYR / remove. Grid instead of the base
   .mx-modal input's stacked full-width block, since these five short fields read as one row, not
   five separate form fields. */
.mx-adaccount-row{display:grid;grid-template-columns:1.3fr 1fr .6fr .8fr .9fr auto;gap:6px;align-items:center;margin-bottom:8px;}
.mx-adaccount-row input{width:100%;margin-bottom:0;padding:7px 9px;font-size:12.5px;}
.mx-btn-icon{flex-shrink:0;width:28px;height:28px;border-radius:50%;border:1px solid var(--mx-border);background:#fff;color:var(--mx-ink-faint);font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s var(--mx-ease),color .15s var(--mx-ease);}
.mx-btn-icon:hover{background:var(--mx-red-soft);color:var(--mx-red);border-color:var(--mx-red);}
@media (max-width:640px){
  .mx-adaccount-row{grid-template-columns:1fr 1fr;grid-auto-rows:auto;}
  .mx-adaccount-row .mx-btn-icon{justify-self:end;}
}

/* Ad Setup modal (izuan, 2026-08-20): redesigned from stacked per-campaign cards to compact
   tables -- a Sub ID like CawanPerli with 50+ campaigns made the old card layout an endless,
   unreadable scroll. Wider than the default modal so a 6-7 column table has room to breathe
   without every cell wrapping.
   Follow-up (izuan, 2026-08-20): "for desktop the big size without the need to slide (less usage
   of mouse)... mobile/tablet please show the modal size in bigger" -- the CSV Setup table is now up
   to 10 columns (Setup/Delivery/Budget/Bid/CPC/Link Clicks/Amount Spent/LPV%/CTR-CPM/Reach-Impr).
   Scaled up per breakpoint so a normal desktop window doesn't need the .overflow-x-auto fallback
   below to actually engage, while phones/tablets get more usable width too even though horizontal
   swipe-scroll on a touch table is normal there (only desktop mouse-drag scrolling was the
   complaint). The .overflow-x-auto wrapper stays in place regardless, as a safety net for a
   narrower/half-split browser window. */
.mx-modal-adsetup{width:min(920px,97vw);}
@media (min-width:768px){
  .mx-modal-adsetup{width:min(1200px,97vw);}
}
@media (min-width:1024px){
  .mx-modal-adsetup{width:min(1440px,96vw);padding:28px 32px;}
}
.mx-adsetup-adblock{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--mx-border-soft);}
.mx-adsetup-adblock:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0;}
.mx-adsetup-adname{font-size:13.5px;font-weight:700;color:var(--mx-ink);margin-bottom:6px;word-break:break-word;}
.mx-adsetup-perf{font-size:12px;color:var(--mx-ink-soft);margin-bottom:8px;display:flex;flex-wrap:wrap;align-items:center;gap:6px;}
.mx-adsetup-perf strong{color:var(--mx-ink);font-weight:700;}
.mx-adsetup-dot{color:var(--mx-border);}

.mx-adsetup-tablewrap{margin-top:12px;}
.mx-adsetup-tablehead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;font-size:10.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--mx-ink-faint);margin-bottom:6px;}
.mx-adsetup-asof{font-weight:600;text-transform:none;letter-spacing:0;font-size:10.5px;color:var(--mx-ink-faint);}

.mx-adsetup-table{width:100%;border-collapse:collapse;font-size:12px;white-space:nowrap;}
.mx-adsetup-table th{text-align:left;font-size:10px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--mx-ink-faint);padding:6px 10px;border-bottom:1px solid var(--mx-border);background:var(--mx-surface-alt);position:sticky;top:0;}
/* Sortable column headers (izuan, 2026-08-20: "make all the table columns sortable i.e high click,
   high amount spent etc") -- every <th> in both Ad Setup tables is clickable (see
   sortableTableHtml() in dashboard.js), toggling asc/desc with a small arrow on the active column. */
.mx-adsetup-th-sort{cursor:pointer;user-select:none;transition:color .15s, background .15s;}
.mx-adsetup-th-sort:hover{color:var(--mx-ink);background:var(--mx-border-soft);}
.mx-adsetup-th-sort-active{color:var(--mx-accent);}
.mx-adsetup-sort-arrow{display:inline-block;margin-left:3px;font-size:8px;}
.mx-adsetup-table td{padding:7px 10px;border-bottom:1px solid var(--mx-border-soft);color:var(--mx-ink);vertical-align:middle;}
.mx-adsetup-table tbody tr:hover{background:var(--mx-surface-alt);}
.mx-adsetup-td-name{max-width:200px;overflow:hidden;text-overflow:ellipsis;font-weight:600;}
.mx-adsetup-td-links a{color:var(--mx-blue);text-decoration:none;font-weight:600;}
.mx-adsetup-td-links a:hover{text-decoration:underline;}
.mx-adsetup-postid{font-size:10.5px;color:var(--mx-ink-faint);font-weight:500;} /* izuan, 2026-09-24: Post ID next to the Post link in the Links column */
.mx-adsetup-subtext{display:block;font-size:9.5px;color:var(--mx-ink-faint);font-weight:500;white-space:nowrap;}

.mx-adsetup-status{display:inline-block;font-size:9.5px;font-weight:700;padding:2px 8px;border-radius:var(--mx-radius-pill);letter-spacing:.02em;white-space:nowrap;}
.mx-adsetup-status-active{background:var(--mx-green-soft);color:#15803d;}
.mx-adsetup-status-paused{background:var(--mx-red-soft);color:#b91c1c;}
.mx-adsetup-status-other{background:var(--mx-slate-soft);color:#475569;}

/* ---- Date filter modal: calendar + preset list, matching a real "Data Filters" panel ---- */
.mx-modal-wide{width:min(760px,95vw);padding:0;}
.mx-df-head{padding:20px 24px;border-bottom:1px solid var(--mx-border-soft);}
.mx-df-head h3{margin:0;}
.mx-df-body{display:flex;flex-wrap:wrap;}
.mx-df-cal{flex:1 1 420px;padding:20px 24px;border-right:1px solid var(--mx-border-soft);}
.mx-df-side{flex:0 0 220px;padding:20px 20px;}
.mx-df-cal-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
.mx-df-cal-nav button{width:32px;height:32px;border-radius:999px;border:1px solid var(--mx-border);background:#fff;cursor:pointer;font-size:14px;color:var(--mx-ink-soft);transition:background .15s var(--mx-ease);}
.mx-df-cal-nav button:hover{background:var(--mx-surface-alt);}
.mx-df-cal-nav .label{font-size:15px;font-weight:700;color:var(--mx-ink);}
.mx-df-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;text-align:center;}
.mx-df-grid .dow{font-size:11px;font-weight:600;color:var(--mx-ink-faint);padding-bottom:8px;}
.mx-df-day{position:relative;padding:9px 0;font-size:13px;color:var(--mx-ink);cursor:pointer;border-radius:10px;transition:background .12s var(--mx-ease);tabular-nums:1;}
.mx-df-day:hover{background:var(--mx-surface-alt);}
.mx-df-day.mx-df-muted{color:var(--mx-ink-faint);opacity:.4;pointer-events:none;}
.mx-df-day.mx-df-sel{background:var(--mx-accent);color:#fff;font-weight:700;}
.mx-df-day.mx-df-range{background:var(--mx-accent-soft);border-radius:0;}
.mx-df-day.mx-df-range-start{border-radius:10px 0 0 10px;}
.mx-df-day.mx-df-range-end{border-radius:0 10px 10px 0;}
.mx-df-side-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--mx-ink-faint);margin-bottom:10px;}
.mx-df-preset{display:block;width:100%;text-align:left;padding:11px 14px;margin-bottom:8px;border-radius:10px;border:1px solid var(--mx-border);background:#fff;font-size:13.5px;color:var(--mx-ink);cursor:pointer;transition:all .15s var(--mx-ease);}
.mx-df-preset:hover{border-color:var(--mx-accent);}
.mx-df-preset.active{border-color:var(--mx-accent);color:var(--mx-accent);font-weight:600;background:var(--mx-accent-soft);}
.mx-df-foot{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px;border-top:1px solid var(--mx-border-soft);background:var(--mx-surface-alt);border-radius:0 0 var(--mx-radius-lg) var(--mx-radius-lg);}

/* ---- Sub ID verdict tooltip: a real floating card, not the tiny native browser tooltip ---- */
.mx-tt{position:fixed;z-index:300;width:308px;max-width:calc(100vw - 24px);background:#fff;border-radius:16px;box-shadow:0 12px 40px rgba(20,20,25,.18),0 2px 8px rgba(20,20,25,.08);border:1px solid rgba(0,0,0,.06);padding:16px 18px;pointer-events:none;animation:kxFadeUp .16s var(--mx-ease-out);}
.mx-tt-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;}
.mx-tt-badge{display:inline-flex;align-items:center;font-size:11px;font-weight:800;padding:3px 10px;border-radius:999px;letter-spacing:.03em;}
.mx-tt-subid{font-size:12px;font-weight:600;color:var(--mx-ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.mx-tt-lede{font-size:14.5px;font-weight:700;color:var(--mx-ink);line-height:1.4;margin:0 0 6px;letter-spacing:-.01em;}
.mx-tt-body{font-size:13px;color:var(--mx-ink-soft);line-height:1.6;margin:0;}
.mx-tt-stats{display:flex;flex-wrap:wrap;gap:8px 16px;margin-top:12px;padding-top:12px;border-top:1px solid var(--mx-border-soft);}
.mx-tt-stat{display:flex;flex-direction:column;gap:1px;}
.mx-tt-stat .l{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--mx-ink-faint);}
.mx-tt-stat .v{font-size:13px;font-weight:700;color:var(--mx-ink);tabular-nums:1;}
.mx-tt-trend{display:inline-flex;align-items:center;gap:3px;}
.mx-tt-trend.up{color:#0f9d58;} .mx-tt-trend.down{color:#d93025;} .mx-tt-trend.flat{color:var(--mx-ink-soft);}
.mx-modal-actions{display:flex;gap:8px;justify-content:flex-end;}
/* izuan, 2026-09-10: Pace History modal's day-picker -- one pill per day across the 14-day
   retention window (see openPaceHistoryModal in dashboard.js), horizontally scrollable since 15
   pills (today + 14 days back) won't all fit on one line at typical modal widths. Deliberately
   reuses .mx-adsetup-btn's pill shape/weight (understated, not a primary action) but needs its
   own class for the .active selected-day state, which that shared button style doesn't have. */
.mx-pace-history-pills{display:flex;gap:6px;overflow-x:auto;padding:2px 2px 12px;margin-bottom:4px;border-bottom:1px solid var(--mx-border-soft);}
.mx-pace-history-pill{flex-shrink:0;padding:5px 12px;font-size:11.5px;font-weight:600;color:var(--mx-ink-soft);background:var(--mx-surface-alt);border:1px solid var(--mx-border-soft);border-radius:999px;cursor:pointer;white-space:nowrap;transition:color .15s, border-color .15s, background .15s;}
.mx-pace-history-pill:hover{color:var(--mx-ink);border-color:var(--mx-border);background:#fff;}
.mx-pace-history-pill.active{color:#fff;background:var(--mx-accent);border-color:var(--mx-accent);}
#pace-history-body{min-height:200px;}
#pace-history-body .mx-hp-hourtable{max-height:min(52vh,520px);}
.mx-modal-actions button{padding:8px 16px;border-radius:var(--mx-radius-pill);font-size:13px;font-weight:600;border:none;cursor:pointer;transition:transform .15s var(--mx-ease);}
.mx-modal-actions button:hover{transform:translateY(-1px);}
.mx-btn-secondary{background:var(--mx-surface-alt);color:var(--mx-ink);}
.mx-btn-primary{background:var(--mx-accent);color:#fff;}
/* Base look for .mx-btn-secondary/.mx-btn-primary outside .mx-modal-actions (e.g. the AI Agent
   Connectors row below) -- that selector only styled color/background, relying on the surrounding
   .mx-modal-actions button rule above for padding/radius/cursor, which doesn't apply anywhere else
   these classes get reused. */
.mx-btn-secondary,.mx-btn-primary{padding:7px 14px;border-radius:var(--mx-radius-pill);font-size:12.5px;font-weight:600;border:none;cursor:pointer;transition:transform .15s var(--mx-ease);}
.mx-btn-secondary:hover,.mx-btn-primary:hover{transform:translateY(-1px);}

/* ===================== AI Agent Connectors (Settings modal) ===================== */
.mx-connector-url-row{display:flex;align-items:center;gap:8px;margin-bottom:14px;}
.mx-connector-url-row code{flex:1;min-width:0;overflow-x:auto;white-space:nowrap;background:var(--mx-surface-alt);border:1px solid var(--mx-border);border-radius:var(--mx-radius-sm);padding:8px 10px;font-size:12px;}
.mx-connector-tokens{margin-bottom:12px;}
.mx-connector-token-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 0;border-bottom:1px solid var(--mx-border-soft);}
.mx-connector-token-row:last-child{border-bottom:none;}
.mx-connector-token-label{font-size:13px;font-weight:600;color:var(--mx-ink);}
.mx-connector-token-meta{font-size:11.5px;color:var(--mx-ink-faint);margin-top:2px;}
.mx-connector-generate-row{display:flex;gap:8px;align-items:flex-start;margin-bottom:4px;}
.mx-connector-generate-row input{margin-bottom:0;flex:1;}
.mx-connector-generate-row button{white-space:nowrap;}
#connector-new-token-box:empty{display:none;}
#connector-new-token-box{margin-bottom:10px;}

::-webkit-scrollbar{width:8px;height:8px;}
::-webkit-scrollbar-thumb{background:#d1d1d6;border-radius:8px;}

@media (max-width:640px){ .mx-daycard{min-width:206px;} }
.mx-shop-icon{flex-shrink:0;}

/* Day-cards (#detail-daycards): desktop grid previously used grid-auto-columns:minmax(0,1fr)
   (Tailwind's auto-cols-fr), which divides the panel's full width evenly across *however many*
   day-cards exist with no floor. That's fine for a normal ~7-day week, but a wide filtered date
   range (e.g. 20+ days) squeezed every column down to a sliver far below its own readable
   min-width, and the nowrap text rows inside .mx-daycard overlapped/garbled into each other.
   Giving the auto column track a 215px floor (matching .mx-daycard's own min-width) fixes both
   directions at once: with few cards, minmax's 1fr still stretches them to fill the row (no
   trailing gutter); once the 215px floor across all cards would exceed the panel width, the grid
   simply overflows its own box instead of shrinking further, and the existing overflow-x-auto
   wrapper around #detail-daycards scrolls horizontally -- same behaviour mobile already had via
   its plain flex + w-max layout. */
@media (min-width:1024px){
  .mx-daycard-grid{ grid-auto-columns:minmax(250px,1fr); }
}

/* izuan, 2026-09-09: "should have wider width for this purpose for today's date since many
   useful data hard to see and it was clipped quite difficult to read... no harm to have wider
   width just for today's date although it might have inconsistent width with other dates,
   why no harm because all other dates after today's date showing empty data though." Scoped to
   .mx-daycard-today-wide only (applied to today's card alone in dashboard.js) so every other
   day-card keeps its existing 206-250px sizing untouched -- a deliberate one-off exception, not
   a base .mx-daycard change. Mobile/tablet (#detail-daycards is a plain flex row, see
   dashboard.html) gets a literal wider floor; desktop's CSS grid uses grid-auto-flow:column with
   every column sharing one grid-auto-columns track size, so a single item can't just declare a
   bigger width -- instead it spans 2 of that grid's own equal-width column tracks, giving it
   roughly double the room using the exact unit every other card is already built from. */
.mx-daycard-today-wide{ min-width:380px; }
@media (min-width:640px){ .mx-daycard-today-wide{ min-width:400px; } }
@media (min-width:1024px){ .mx-daycard-today-wide{ grid-column:span 2; } }
/* izuan follow-up: "make use until the bottom then only the scroll visible... make use until the
   allowed height level then only make the scroll visible" -- every day-card in the same row
   (flex on mobile, grid on desktop) stretches to match whichever sibling is tallest, which is
   usually a real Shopee data card, not today's still-empty one. A hardcoded max-height on the
   hour table left that already-allocated extra room as dead blank space below the LIVE footer
   instead of using it. Chaining flex-grow from the card down through its content area, the
   .mx-hourly-pulse widget, and finally .mx-hp-hourtable itself (all below) means the table fills
   exactly however tall this card was already stretched to -- no more, no less -- and only
   scrolls once the hour list's own rows actually exceed that real available space. Scoped to
   .mx-daycard-today-wide only (today's card exclusively) so no other day-card's layout changes. */
.mx-daycard-today-wide{ display:flex; flex-direction:column; }
.mx-daycard-empty-fill{ flex:1; min-height:0; }
/* The leading edge (padding-left on #detail-daycards, a plain non-scrolling flex/grid box one
   level in from the actual overflow-x-auto container) reliably shows up -- the *start* of a
   scrollable region's content is always honoured. The *trailing* edge is the genuinely unreliable
   one: browsers are inconsistent about whether trailing empty space at the very end of a
   scrollable region (however it gets there -- the scroll container's own padding, or, as
   confirmed by testing here, even a nested child's padding-right) actually extends how far you
   can scroll, versus getting clipped out of the scrollable range entirely. That's exactly what
   was happening: padding-right on #detail-daycards was real CSS, but the browser wouldn't let
   the row scroll far enough to ever reveal it. Margin-right on #detail-daycards-scroll itself
   sidesteps the whole issue -- it's ordinary static spacing between the scroll container and its
   own parent, entirely outside the part of the layout that scrolling math has to reason about,
   so the gap is simply always there regardless of scroll position. */
#detail-daycards{ padding-left:20px; }
#detail-daycards-scroll{
  scroll-snap-type:x proximity;
  scroll-behavior:smooth;
  scroll-padding-inline:20px;
  cursor:grab;
  margin-right:20px;
}
#detail-daycards-scroll.mx-dragging{
  cursor:grabbing;
  scroll-snap-type:none;
  scroll-behavior:auto;
  user-select:none;
}
.mx-daycard{ scroll-snap-align:start; }

/* Day-card scroll-linked reveal: reuses the exact fade/scale treatment and class names from the
   Sub ID list's ported AnimatedList (see .mx-animlist-pending/-in further below and
   initDaycardScroll() in dashboard.js), applied here to .mx-daycard directly so the day-card row
   gets the same "smooth, dynamic" scroll-triggered feel instead of only the plain one-shot
   pop-in it had before. */
.mx-daycard.mx-animlist-pending{
  opacity:0;
  transform:scale(.94) translateY(4px);
  transition:opacity .32s cubic-bezier(.2,.8,.2,1), transform .32s cubic-bezier(.2,.8,.2,1);
}
.mx-daycard.mx-animlist-pending.mx-animlist-in{
  opacity:1;
  transform:scale(1) translateY(0);
}
@media (prefers-reduced-motion: reduce){
  .mx-daycard.mx-animlist-pending{ opacity:1; transform:none; transition:none; }
}

/* Edge fades hint that there's more to scroll now that the scrollbar itself is hidden (see
   .mx-scroll-clean below) -- same idea as the Sub ID list's top/bottom fades, oriented
   horizontally. Faded in/out by updateDaycardFades() in dashboard.js as the row scrolls. */
.mx-daycard-fade{
  position:absolute; top:0; bottom:0; width:36px;
  pointer-events:none; opacity:0;
  transition:opacity .25s ease;
  z-index:2;
}
.mx-daycard-fade-left{ left:0; background:linear-gradient(to right, var(--mx-surface), transparent); }
.mx-daycard-fade-right{ right:0; background:linear-gradient(to left, var(--mx-surface), transparent); }

/* Hides the native scrollbar while keeping the element fully scrollable (wheel, trackpad,
   touch, and the drag-to-scroll handler in dashboard.js) -- used on the day-card row and the
   Sub ID list, both of which already have their own edge-fade affordance to signal overflow,
   so the scrollbar was just visual clutter on top of that. */
.mx-scroll-clean{ scrollbar-width:none; -ms-overflow-style:none; }
.mx-scroll-clean::-webkit-scrollbar{ display:none; }

/* Top Selling / Top GMV ranking card rows: on desktop, stretch the (now up-to-10) cards to
   evenly fill the widened panel instead of leaving a large trailing gutter after the last
   card. .mx-scrollx's own display:flex is unconditional and loads after app.css, so it would
   otherwise beat a same-specificity Tailwind lg:grid utility class -- overridden here instead,
   which has cascade priority as the last stylesheet loaded. Mobile/tablet keep the original
   horizontal-scroll flex row untouched. */
@media (min-width:1024px){
  .mx-rankgrid{display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr);}
  .mx-rankgrid > .mx-card{width:100%; min-width:0;}
}

/* Cohort Recovery: break-even gradient sweep. Each cohort row's D1..break-even cells carry a
   persistent background (set inline per-cell by renderDetailCohort() in dashboard.js) that's a
   precise slice of one continuous red-to-green gradient -- so the row visually reads as a single
   smooth transition from "still recovering" to "broke even", not discrete coloured blocks. This
   stylesheet only supplies the entrance flourish on top of that: a brief brightness flash that
   sweeps left-to-right on load, and a gentle scale "settle" + badge pop on the exact break-even
   cell. Using filter/transform (not background-color) here means the flash never fights with or
   hides the persistent gradient sitting underneath it. Rows that never break even within the
   window are left untouched (no spend data = nothing to animate). */
.mx-cohort-table td.mx-cohort-swept,
.mx-cohort-table td.mx-cohort-breakeven{
  position:relative;
  animation:mx-cohort-sweep .55s cubic-bezier(.16,1,.3,1) both;
  animation-delay:calc(var(--i,0) * 90ms);
}
@keyframes mx-cohort-sweep{
  0%{filter:brightness(1.22) saturate(1.35);}
  100%{filter:brightness(1) saturate(1);}
}
.mx-cohort-table td.mx-cohort-breakeven{
  animation:mx-cohort-sweep .55s cubic-bezier(.16,1,.3,1) both, mx-cohort-settle .6s cubic-bezier(.34,1.4,.64,1) 1 both;
  animation-delay:calc(var(--i,0) * 90ms), calc(var(--i,0) * 90ms + 460ms);
}
@keyframes mx-cohort-settle{
  0%{transform:scale(1);}
  40%{transform:scale(1.06);}
  100%{transform:scale(1);}
}
.mx-breakeven-badge{
  display:inline-block;
  margin-top:3px;
  background:var(--mx-green);
  color:#fff;
  font-size:8.5px;
  font-weight:700;
  letter-spacing:.02em;
  padding:2px 6px;
  border-radius:999px;
  white-space:nowrap;
  opacity:0;
  transform:scale(.4) translateY(4px);
  animation:mx-badge-pop .5s cubic-bezier(.34,1.56,.64,1) both;
  animation-delay:calc(var(--i,0) * 90ms + 460ms);
}
@keyframes mx-badge-pop{
  0%{opacity:0; transform:scale(.4) translateY(4px);}
  60%{opacity:1; transform:scale(1.15) translateY(-2px);}
  100%{opacity:1; transform:scale(1) translateY(0);}
}
@media (prefers-reduced-motion: reduce){
  .mx-cohort-table td.mx-cohort-swept,
  .mx-cohort-table td.mx-cohort-breakeven,
  .mx-breakeven-badge{ animation:none !important; }
}

/* Back-to-top FAB: desktop/tablet only (hidden on phone widths -- those pages are short
   enough there that a persistent floating button just adds clutter to an already tight
   viewport). A thin ring traces the page's scroll progress around the button itself, so it
   doubles as a lightweight "how far down am I" indicator instead of being a plain button.
   Fades/slides in once you're a screen-height past the top; smooth-scrolls back on click. */
.mx-backtotop{
  position:fixed;
  right:24px;
  bottom:28px;
  width:46px;
  height:46px;
  border:none;
  border-radius:50%;
  background:var(--mx-surface);
  color:var(--mx-accent);
  display:none;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  box-shadow:var(--mx-shadow-lg);
  opacity:0;
  transform:translateY(14px) scale(.82);
  transition:opacity .35s var(--mx-ease-out), transform .35s var(--mx-ease-spring), box-shadow .2s var(--mx-ease);
  z-index:60;
  padding:0;
  pointer-events:none;
}
@media (min-width:768px){
  .mx-backtotop{ display:flex; }
}
.mx-backtotop.mx-visible{
  opacity:1;
  transform:translateY(0) scale(1);
  pointer-events:auto;
}
.mx-backtotop:hover{
  box-shadow:0 6px 20px color-mix(in oklab, var(--mx-accent) 30%, transparent), var(--mx-shadow-md);
  transform:translateY(-3px) scale(1.06);
}
.mx-backtotop:active{ transform:translateY(-1px) scale(.98); }
.mx-backtotop-ring{
  position:absolute;
  inset:0;
  width:46px;
  height:46px;
  transform:rotate(-90deg);
  pointer-events:none;
}
.mx-backtotop-ring-bg{
  fill:none;
  stroke:var(--mx-border-soft);
  stroke-width:2;
}
.mx-backtotop-ring-fg{
  fill:none;
  stroke:var(--mx-accent);
  stroke-width:2;
  stroke-linecap:round;
  stroke-dasharray:125.66;
  stroke-dashoffset:125.66;
  transition:stroke-dashoffset .12s linear;
}
.mx-backtotop-arrow{
  position:relative;
  width:17px;
  height:17px;
  transition:transform .25s var(--mx-ease-spring);
}
.mx-backtotop:hover .mx-backtotop-arrow{ transform:translateY(-2px); }
@media (prefers-reduced-motion: reduce){
  .mx-backtotop{ transition:opacity .15s linear; }
  .mx-backtotop-arrow{ transition:none; }
}

/* Subid-detail loading skeleton. Opening a Sub ID link (usually into a brand-new tab) means a
   full fresh page load: auth check, settings, and the full state fetch all have to finish before
   there's anything real to show. Left alone, the browser just paints whatever's in the raw HTML
   for that window -- the dashboard view, since it's the one without mx-hidden by default -- so
   the visitor sees the *wrong page* for a couple of seconds before it swaps to the real Sub ID
   view. A tiny synchronous inline script (see dashboard.html, runs before dashboard.js even starts
   fetching) detects a #/subid/ deep link immediately and swaps views + fills these placeholders
   in, so the very first paint already has the *right* page shape, just with shimmering bars
   standing in for real numbers. renderDetail() overwrites every one of these containers with real
   content once the data's in, so the skeleton never lingers longer than the actual load time. */
.mx-skel{
  position:relative;
  overflow:hidden;
  background:var(--mx-surface-alt);
  border-radius:6px;
  display:inline-block;
}
.mx-skel::after{
  content:'';
  position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.7), transparent);
  transform:translateX(-100%);
  animation:mx-shimmer 1.5s ease-in-out infinite;
}
.mx-skel-dark{ background:rgba(255,255,255,.14); }
.mx-skel-dark::after{ background:linear-gradient(90deg, transparent, rgba(255,255,255,.28), transparent); }
@keyframes mx-shimmer{ 100%{ transform:translateX(100%); } }
.mx-skel-daycard{
  min-width:250px;
  height:132px;
  border-radius:var(--mx-radius-md);
  border:1px solid var(--mx-border-soft);
  padding:14px;
  display:flex;
  flex-direction:column;
  gap:10px;
  flex:1 1 0;
}
.mx-skel-card{
  border-radius:var(--mx-radius-md);
  border:1px solid var(--mx-border-soft);
  padding:12px;
  height:172px;
  display:flex;
  flex-direction:column;
  gap:8px;
  flex:1 1 0;
  min-width:0;
}
.mx-skel-table td{ padding:9px 10px; }
@media (prefers-reduced-motion: reduce){
  .mx-skel::after{ animation:none; display:none; }
}

/* Sub ID list: ported from React Bits' AnimatedList (reactbits.dev) to plain JS/CSS -- this
   project is a build-step-free static site with no React runtime, so rather than pulling in
   React + motion + a bundler for one list, the same scroll-linked reveal + edge fades +
   keyboard roving-select were reproduced natively. See initAnimatedSubidList() in dashboard.js. */
.mx-animlist-fade{
  position:absolute; left:0; right:0;
  pointer-events:none; opacity:0;
  transition:opacity .25s ease;
  z-index:2;
}
.mx-animlist-fade-top{
  top:0; height:28px;
  background:linear-gradient(to bottom, var(--mx-surface), transparent);
}
.mx-animlist-fade-bottom{
  bottom:0; height:44px;
  background:linear-gradient(to top, var(--mx-surface), transparent);
}
#subid-list .mx-pill.mx-animlist-pending{
  opacity:0;
  transform:scale(.94) translateY(4px);
  transition:opacity .32s cubic-bezier(.2,.8,.2,1), transform .32s cubic-bezier(.2,.8,.2,1);
}
#subid-list .mx-pill.mx-animlist-pending.mx-animlist-in{
  opacity:1;
  transform:scale(1) translateY(0);
}
#subid-list .mx-pill.mx-animlist-selected{
  box-shadow:0 0 0 2px var(--mx-accent);
}
@media (prefers-reduced-motion: reduce){
  #subid-list .mx-pill.mx-animlist-pending{ opacity:1; transform:none; transition:none; }
}

/* Manual patch for a missing Tailwind utility, NOT a bug in dashboard.html's markup.
   public/assets/css/app.css is a precompiled/static Tailwind v4.3.3 build artifact checked into
   the repo -- there is no build step (no tailwind.config, no build script in package.json, no
   Vercel buildCommand) that regenerates it from the classes actually used in the HTML. It was
   generated once by scanning the codebase as it existed at that time, and .lg\:order-6 was never
   used anywhere before the SubID page's section reorder introduced it for the Rankings panel.
   Since app.css only contains .lg\:order-1 through .lg\:order-5, the browser has no rule at all
   for .lg\:order-6, so Rankings' computed order falls back to flexbox's default of 0 -- sorting
   it BEFORE order:1 (Week) instead of after order:5 (ROI Trend), which is why Rankings rendered
   first on desktop even after ruling out caching (reproduced in incognito).
   This file (manta.css) is hand-authored and always ships as-is, so the fix is to hand-add the
   one missing utility here, matching Tailwind's own generated selector/escaping and breakpoint
   exactly. If more Tailwind classes are introduced later that aren't already in app.css, they'll
   need the same treatment here (or app.css needs a real rebuild pipeline). */
@media (min-width:1024px){
  .lg\:order-6{ order:6; }
}

/* izuan, 2026-09-09: live "how's today going so far" Meta pace widget inside today's day-card
   (see dashboard.js's renderHourlyPulse/buildHourlyPulseHtml) -- deliberately understated/muted
   relative to a real finished day-card (smaller stat grid, thin bars, no green/red P&L framing),
   since this is a provisional pace reading, not a confirmed result the way every other day-card's
   numbers are. */
.mx-hourly-pulse{ width:100%; margin-top:10px; padding-top:10px; border-top:1px dashed var(--mx-border); flex:1; min-height:0; display:flex; flex-direction:column; }
/* izuan, 2026-09-09: "the loading animation before the data showing up for the today's date pace
   can be nicer? maybe use svg animation instead of the clumsy text" -- three concentric SVG rings
   pinging outward in a staggered loop, same red already used for this card's own LIVE badge
   (.mx-hp-live-dot below), so it reads as "sensing a live signal" rather than a generic spinner.
   Pure CSS transform/opacity, no image/library, and switches off under prefers-reduced-motion
   further down. Only ever shown on the very first render of the day -- refreshes (auto or via
   Pull Pace) swap the numbers in directly with no loading state, so this never flickers back in
   every couple of minutes. */
.mx-hp-loading{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; padding:22px 0; }
.mx-hp-loading-svg{ overflow:visible; }
.mx-hp-loading-ring{ fill:none; stroke:#e11d48; stroke-width:2.5; transform-box:fill-box; transform-origin:center; animation:mxHpLoadingPing 1.8s cubic-bezier(.2,.7,.3,1) infinite; }
.mx-hp-loading-ring.r2{ animation-delay:.5s; }
.mx-hp-loading-ring.r3{ animation-delay:1s; }
@keyframes mxHpLoadingPing{
  0%{ transform:scale(.4); opacity:.85; }
  100%{ transform:scale(2.6); opacity:0; }
}
.mx-hp-loading-text{ font-size:10px; font-weight:600; letter-spacing:.02em; color:var(--mx-ink-faint); }
/* izuan, 2026-09-09 (follow-up): "Use better error-state svg placeholder than this ugly text" --
   same small-icon-above-caption treatment as the loading state right above, reused for the
   widget's three other non-happy-path states (needs Meta token, no pull yet, fetch failed).
   Quiet ink-faint icons throughout -- none of these are destructive/alarming, just "nothing to
   show yet" -- centered and vertically filling whatever space the flex-fill chain above gives
   this card, same as the real table does once there's data. */
.mx-hp-emptystate{ flex:1; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; padding:16px 10px; color:var(--mx-ink-faint); text-align:center; }
.mx-hp-emptystate svg{ opacity:.55; }
.mx-hp-emptystate-text{ font-size:11px; font-weight:600; color:var(--mx-ink-soft); }
.mx-hp-emptystate-sub{ font-size:10px; color:var(--mx-ink-faint); max-width:220px; }
@media (prefers-reduced-motion: reduce){
  .mx-hp-loading-ring{ animation:none; opacity:.5; }
}
.mx-hp-stats{ display:grid; grid-template-columns:repeat(2,1fr); gap:6px 10px; }
.mx-hp-stat{ display:flex; justify-content:space-between; align-items:baseline; font-size:11px; }
.mx-hp-stat .l{ color:var(--mx-ink-faint); }
.mx-hp-stat .v{ font-weight:600; color:var(--mx-ink); }
/* izuan, 2026-09-09: "too many empty spaces to use... clearer to see since our data display
   pattern is vertical so match the UI" -- replaced with a vertical hour-by-hour table, same
   row-list visual language as .mx-daycard-clickbreak-row elsewhere on this same card (label/value
   rows stacked vertically) rather than a horizontal bar chart. Five columns (hour, clicks, cpc,
   spent, lpv%) via CSS grid, most recent hour first. */
/* izuan, 2026-09-09 (follow-up x2): first "remove the scroll bar, we have ample space" (fixed
   height removed entirely) turned out to just move the problem -- a full 24-row table could be
   taller OR shorter than the row's other (real data) day-cards, either dragging siblings tall and
   empty or, once capped back to a flat 300px on mobile, leaving a dead gap of its own below the
   footer when the card had been stretched taller than that cap by a genuinely tall sibling. Final
   answer, per "make use until the bottom then only the scroll visible... make use until the
   allowed height level": flex:1 here (paired with the flex-fill chain on .mx-daycard-today-wide/
   .mx-daycard-empty-fill/.mx-hourly-pulse above) makes this table fill exactly however tall this
   card was already stretched to match its siblings -- no fixed number anywhere -- and
   overflow-y:auto only actually shows a scrollbar once the hour rows genuinely exceed that real,
   dynamic amount of room. min-height:80px is just a sane floor so it can never be squashed to a
   sliver. */
.mx-hp-hourtable{ margin-top:10px; border:1px solid var(--mx-border-soft); border-radius:var(--mx-radius-sm); flex:1; min-height:80px; overflow-y:auto; }
.mx-hp-hourtable-row{ display:grid; grid-template-columns:minmax(0,0.7fr) minmax(0,0.85fr) minmax(0,0.95fr) minmax(0,0.95fr) minmax(0,0.85fr); gap:6px; font-size:11px; line-height:1.5; padding:5px 10px; text-align:right; tabular-nums:1; }
.mx-hp-hourtable-row span:first-child{ text-align:left; }
.mx-hp-hourtable-row + .mx-hp-hourtable-row{ border-top:1px solid var(--mx-border-soft); }
/* sticky is a no-op on desktop (that table doesn't scroll -- see .mx-hp-hourtable above), but
   worth having again now the mobile/tablet breakpoint scrolls once more: keeps the column labels
   in view while scrolling through the hour list there instead of scrolling off after the first
   couple of rows. */
.mx-hp-hourtable-head{ position:sticky; top:0; background:var(--mx-surface-alt); font-weight:700; color:var(--mx-ink-faint); text-transform:uppercase; letter-spacing:.02em; font-size:9.5px; }
.mx-hp-hourtable-body .mx-hp-hourtable-row{ color:var(--mx-ink); font-weight:600; }
.mx-hp-hourtable-row-empty{ color:var(--mx-ink-faint); font-weight:400; }

.mx-hp-footer{ display:flex; align-items:center; justify-content:center; gap:6px; margin-top:8px; }
.mx-hp-live{ display:inline-flex; align-items:center; gap:4px; font-size:9.5px; font-weight:800; letter-spacing:.04em; color:#e11d48; }
.mx-hp-live-dot{ width:6px; height:6px; border-radius:50%; background:#e11d48; animation:mxHpPulse 1.6s ease-in-out infinite; }
.mx-hp-footer-time{ font-size:9.5px; color:var(--mx-ink-faint); }
@keyframes mxHpPulse{
  0%{ box-shadow:0 0 0 0 rgba(225,29,72,.55); }
  70%{ box-shadow:0 0 0 6px rgba(225,29,72,0); }
  100%{ box-shadow:0 0 0 0 rgba(225,29,72,0); }
}
@media (prefers-reduced-motion: reduce){
  .mx-hp-live-dot{ animation:none; }
}

/* izuan, 2026-09-10: "mark it with special tag stating it's new campaign and have pulse
   animation that it starts today so we not get confuse... find in the subid section as top on
   the list" -- Meta-only Sub IDs (real clicks today, no Shopee report yet -- see dashboard.js's
   PENDING_CAMPAIGNS/pendingCampaignPill()) pinned above the real Sub ID list. Reuses the exact
   same red .mx-hp-live-dot/mxHpPulse animation already established for the hourly pace widget's
   own LIVE badge just above, so this reads as the same "live signal" language rather than a new,
   unrelated animation. A soft blue tint (distinct from the pill list's green/orange/red/grey
   profit tiers below) keeps these from being mistaken for a real, judged result -- there isn't
   one yet. */
.mx-pill-pending{background:#eff6ff;border-color:#bfdbfe;}
.mx-badge-new{display:inline-flex;align-items:center;gap:4px;margin-left:6px;font-size:9px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;padding:2px 7px 2px 6px;border-radius:var(--mx-radius-pill);background:#dbeafe;color:#1d4ed8;vertical-align:middle;}
/* Overrides .mx-hp-live-dot's own red background/animation with this feature's blue -- applies
   equally to the pill's inline NEW badge and the detail-page banner's dot, both of which reuse
   .mx-hp-live-dot for its box-shadow-ping mechanics rather than duplicating them. */
.mx-badge-new-dot{background:#1d4ed8;animation:mxBadgeNewPulse 1.6s ease-in-out infinite;}
@keyframes mxBadgeNewPulse{
  0%{ box-shadow:0 0 0 0 rgba(29,78,216,.55); }
  70%{ box-shadow:0 0 0 5px rgba(29,78,216,0); }
  100%{ box-shadow:0 0 0 0 rgba(29,78,216,0); }
}
@media (prefers-reduced-motion: reduce){
  .mx-badge-new-dot{ animation:none; }
}
/* Same page-header banner treatment as the day-card's own "Ongoing" amber tone elsewhere in this
   file, but blue to match .mx-pill-pending/.mx-badge-new above -- a distinct "this is provisional,
   Meta-only" signal, not the amber "today just hasn't published yet but the campaign is
   established" one. */
.mx-pending-banner{display:flex;align-items:center;gap:7px;margin-top:8px;font-size:12px;font-weight:600;color:#1d4ed8;background:#eff6ff;border:1px solid #bfdbfe;border-radius:var(--mx-radius-sm);padding:7px 12px;}

/* izuan, 2026-09-11: "for mobile device make it under hamburger menu together with the logout to
   simplify the header" -- Panel (see dashboard.html's #nav-access-requests, renamed from "Access
   Requests" this same request) and Log out move into a hamburger dropdown below the 640px (sm)
   breakpoint already used everywhere else in this file; CCC and Settings stay put on every
   viewport. .mx-desktop-only hides the real header nav's own copies of those two links at that
   width -- .mx-mobile-menu-wrap (this whole block) is the mobile replacement, hidden again above
   640px so desktop never sees a redundant hamburger next to the links it already shows inline. */
@media (max-width:639px){
  .mx-desktop-only{display:none !important;}
}
.mx-mobile-menu-wrap{position:relative;display:none;}
@media (max-width:639px){
  .mx-mobile-menu-wrap{display:block;}
}
.mx-mobile-menu-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--mx-radius-sm);border:1px solid transparent;color:var(--mx-ink-soft);background:transparent;cursor:pointer;}
.mx-mobile-menu-btn:hover{background:var(--mx-surface-alt);color:var(--mx-ink);}
.mx-mobile-menu-panel{position:absolute;top:calc(100% + 8px);right:0;min-width:160px;background:var(--mx-surface);border:1px solid var(--mx-border-soft);border-radius:var(--mx-radius-md);box-shadow:var(--mx-shadow-md);padding:6px;display:flex;flex-direction:column;gap:2px;z-index:60;}
.mx-mobile-menu-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-radius:var(--mx-radius-sm);font-size:13px;font-weight:500;color:var(--mx-ink-soft);cursor:pointer;text-decoration:none;}
.mx-mobile-menu-item:hover{background:var(--mx-surface-alt);color:var(--mx-ink);}

/* izuan, 2026-09-15: "admin able to enter (view stats) this user account to view his/her
   dashboard as the user" -- the persistent, unmissable "you are viewing someone else's data"
   banner (dashboard.html's #view-as-banner, shown app-wide -- see enterViewAs/exitViewAs in
   dashboard.js). Amber/warning-toned (distinct from every other accent color in this file) since
   its whole job is to stay noticeable however long the admin stays in this mode, not blend into
   the header the way a normal nav element would. Sticky right under the real header so it can
   never scroll out of view and get forgotten about mid-review. */
.mx-view-as-banner{position:sticky;top:48px;z-index:49;display:flex;align-items:center;justify-content:center;gap:10px;padding:7px 12px;background:#fffbeb;border-bottom:1px solid #fde68a;color:#92400e;font-size:12.5px;font-weight:600;text-align:center;}
.mx-view-as-banner strong{font-weight:800;}
.mx-view-as-banner button{padding:3px 11px;border-radius:var(--mx-radius-pill);border:1px solid #d97706;background:#fff;color:#92400e;font-size:11.5px;font-weight:700;cursor:pointer;}
.mx-view-as-banner button:hover{background:#fef3c7;}
/* Users list rows (Panel page) -- same lightweight row treatment as .mx-ar-reviewed's table cells
   above, just with its own "View As" action button reusing .mx-ar-btn-sm's sizing. */
.mx-users-admin-tag{display:inline-block;margin-left:6px;font-size:9px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;padding:2px 7px;border-radius:var(--mx-radius-pill);background:var(--mx-accent-soft, #ffe8d9);color:var(--mx-accent);vertical-align:middle;}

/* izuan, 2026-09-15: View As is read-only (see dashboard.js's blockIfViewingAs) -- these two are
   the most obviously write-flavored controls that stay VISIBLE on the dashboard itself while
   impersonating (Settings opens a whole modal of editable fields; Ingest Data is a raw file
   upload), so they're visually dimmed and inert as a clear "not here" signal rather than letting
   an admin click through into a modal or trigger an upload just to be told no by the toast guard.
   Everything else write-flavored (Meta pull buttons, Clear Data, Sub ID notes, CCC targets, etc.)
   is JS-template-generated per render rather than a fixed element, so it's covered by
   blockIfViewingAs()'s toast alone rather than a matching CSS rule here. */
body.mx-view-as-mode #nav-settings-link,
body.mx-view-as-mode #ingest-data-panel{opacity:.45;pointer-events:none;filter:grayscale(60%);}
.mx-xtracomm-badge{display:flex;align-items:center;line-height:0;} /* izuan, 2026-10-02: Shopee's own XTRA Comm badge, shown on product cards whose Offer Type is "XTRA Comm" -- see productCard()/renderSameShop() in dashboard.js */

/* izuan, 2026-10-11: TOTAL row under Cohort Recovery (D1-D8) -- see cohortWeekSummary() in
   dashboard.js. Heavier top rule + tinted background so it reads as the table's bottom line, not
   just another cohort row; the net profit/loss figure is the headline (bigger/bolder than the
   per-row P/L) and its green/red reuses the same pos/neg tones as the rows above. */
.mx-cohort-table tfoot td{border-top:2px solid var(--mx-border);border-bottom:none;background:var(--mx-surface-alt);padding-top:12px;padding-bottom:12px;vertical-align:middle;}
.mx-cohort-total-label{font-size:11px;font-weight:800;letter-spacing:.08em;color:var(--mx-ink);}
.mx-cohort-table tfoot td.mx-cohort-total-verdict{text-align:left;white-space:normal;min-width:260px;}
.mx-cohort-total-verdict .mx-badge.mx-cohort-total-badge{margin-left:0;margin-right:10px;font-size:11px;padding:5px 14px;letter-spacing:.06em;}
.mx-cohort-total-note{display:block;margin-top:6px;font-size:11px;line-height:1.45;color:var(--mx-ink-soft);font-weight:500;max-width:520px;}
.mx-cohort-total-amt{font-weight:700;}
.mx-cohort-total-net{font-size:15px;font-weight:800;margin-top:3px;font-variant-numeric:tabular-nums;}
.mx-cohort-total-netlabel{font-size:8.5px;font-weight:700;letter-spacing:.08em;margin-top:1px;}
.mx-cohort-table tfoot .mx-cohort-pl-pos{color:var(--mx-green);}
.mx-cohort-table tfoot .mx-cohort-pl-neg{color:var(--mx-red);}

/* izuan, 2026-10-11: "Why this total differs from the numbers at the top" explainer under the Cohort
   Recovery table -- see cohortExplainHtml() in dashboard.js. Two side-by-side reconciliation columns
   (by order date vs by click date), a one-paragraph reason, a plain-language bottom line, and a
   collapsed how-to-read legend. */
.mx-cohort-explain{margin:4px 16px 18px;padding:16px 18px;border:1px solid var(--mx-border-soft);border-radius:var(--mx-radius-md);background:var(--mx-surface);}
.mx-cohort-explain-title{font-size:12px;font-weight:700;letter-spacing:.02em;color:var(--mx-ink);margin-bottom:12px;}
.mx-cx-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
@media (max-width:760px){.mx-cx-grid{grid-template-columns:1fr;}}
.mx-cx-col{padding:12px 14px;border-radius:var(--mx-radius-sm);background:var(--mx-surface-alt);}
.mx-cx-head{font-size:12px;font-weight:700;color:var(--mx-ink);}
.mx-cx-head span{font-weight:500;color:var(--mx-ink-faint);}
.mx-cx-sub{font-size:11px;line-height:1.45;color:var(--mx-ink-faint);margin:3px 0 10px;}
.mx-cx-line{display:flex;justify-content:space-between;gap:12px;font-size:12px;padding:4px 0;color:var(--mx-ink-soft);}
.mx-cx-val{font-variant-numeric:tabular-nums;font-weight:600;color:var(--mx-ink);white-space:nowrap;}
.mx-cx-carry .mx-cx-val{color:var(--mx-blue);}
.mx-cx-sum{border-top:1px solid var(--mx-border-soft);margin-top:4px;padding-top:7px;}
.mx-cx-total{font-weight:700;border-top:1px solid var(--mx-border);margin-top:4px;padding-top:7px;}
.mx-cx-total .mx-cx-val{font-size:14px;font-weight:800;}
.mx-cx-pos .mx-cx-val{color:var(--mx-green);}
.mx-cx-neg .mx-cx-val{color:var(--mx-red);}
.mx-cx-diff{font-size:12px;line-height:1.55;color:var(--mx-ink-soft);margin:12px 2px 0;}
.mx-cx-bottom{margin-top:12px;padding:10px 14px;border-radius:var(--mx-radius-sm);font-size:12.5px;line-height:1.5;}
.mx-cx-bottom-pos{background:var(--mx-green-soft);color:#166534;}
.mx-cx-bottom-mid{background:var(--mx-amber-soft);color:#92400e;}
.mx-cx-bottom-neg{background:var(--mx-red-soft);color:#991b1b;}
.mx-cx-legend{margin-top:12px;font-size:12px;color:var(--mx-ink-soft);}
.mx-cx-legend summary{cursor:pointer;font-weight:600;color:var(--mx-ink);}
.mx-cx-legend ul{margin:8px 0 0 16px;padding:0;list-style:disc;}
.mx-cx-legend li{margin:5px 0;line-height:1.5;}

/* izuan, 2026-10-11: Top Categories / Top Shops order-count suffix -- quieter than the commission amount it follows. */
.mx-rank-count{font-size:11px;font-weight:600;color:var(--mx-ink-faint);white-space:nowrap;}

/* izuan, 2026-10-11: glanceable Clicks Recovery badge card (see recoveryCardHtml() in dashboard.js) --
   one-line action, 3 numbers, recovery bar with a break-even line, one mini bar per click day. Green /
   amber / red = recovered >= 100% / >= 75% / below. Solid = earned so far, faded = still to come. */
.mx-rc-action{font-size:13.5px;font-weight:700;color:var(--mx-ink);line-height:1.35;margin:2px 0 10px;letter-spacing:-.01em;}
.mx-rc-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding:8px 0;border-top:1px solid var(--mx-border-soft);border-bottom:1px solid var(--mx-border-soft);}
.mx-rc-stats > div{display:flex;flex-direction:column;gap:1px;}
.mx-rc-stats .l{font-size:9.5px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--mx-ink-faint);}
.mx-rc-stats .v{font-size:13.5px;font-weight:800;color:var(--mx-ink);font-variant-numeric:tabular-nums;}
.mx-rc-stats .v.pos{color:var(--mx-green);} .mx-rc-stats .v.neg{color:var(--mx-red);}
.mx-rc-bar{position:relative;height:9px;border-radius:99px;background:var(--mx-surface-alt);margin-top:11px;overflow:visible;}
.mx-rc-bar-proj,.mx-rc-bar-fill{position:absolute;left:0;top:0;bottom:0;border-radius:99px;}
.mx-rc-bar-proj{opacity:.28;} .mx-rc-bar-fill{opacity:1;}
.mx-rc-bar-mark{position:absolute;top:-3px;bottom:-3px;width:2px;margin-left:-1px;background:var(--mx-ink);border-radius:2px;}
.mx-rc-bar-proj.g,.mx-rc-bar-fill.g,.mx-rc-solid.g,.mx-rc-ghost.g{background:var(--mx-green);} .mx-rc-bar-proj.a,.mx-rc-bar-fill.a,.mx-rc-solid.a,.mx-rc-ghost.a{background:#e0a100;} .mx-rc-bar-proj.r,.mx-rc-bar-fill.r,.mx-rc-solid.r,.mx-rc-ghost.r{background:var(--mx-red);}
.mx-rc-barlabels{display:flex;justify-content:space-between;font-size:10px;font-weight:600;color:var(--mx-ink-faint);margin-top:4px;font-variant-numeric:tabular-nums;}
.mx-rc-chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:9px;}
.mx-rc-chips-inline{margin-top:7px;}
.mx-rc-chip{font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:99px;background:var(--mx-surface-alt);color:var(--mx-ink-soft);font-variant-numeric:tabular-nums;white-space:normal;max-width:100%;line-height:1.35;}
.mx-rc-chip.pos{background:var(--mx-green-soft);color:#166534;} .mx-rc-chip.neg{background:var(--mx-red-soft);color:#991b1b;}
.mx-rc-chart{position:relative;display:flex;align-items:flex-end;gap:5px;height:78px;margin-top:12px;padding-top:2px;padding-right:30px;overflow:hidden;box-sizing:border-box;}
.mx-rc-col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;gap:3px;height:100%;justify-content:flex-end;}
.mx-rc-barwrap{position:relative;width:100%;height:60px;display:flex;flex-direction:column;justify-content:flex-end;}
.mx-rc-solid,.mx-rc-ghost{display:block;width:100%;}
.mx-rc-solid{border-radius:0 0 3px 3px;}
.mx-rc-ghost{opacity:.28;border-radius:3px 3px 0 0;}
.mx-rc-day{font-size:9px;font-weight:600;color:var(--mx-ink-faint);}
.mx-rc-line{position:absolute;left:0;right:0;border-top:1px dashed var(--mx-ink-faint);height:0;pointer-events:none;}
.mx-rc-line span{position:absolute;right:0;top:-6px;font-size:8.5px;font-weight:700;color:var(--mx-ink-faint);background:#fff;padding-left:3px;line-height:1;}
.mx-rc-warn{margin-top:9px;font-size:11px;font-weight:600;color:var(--mx-amber);background:var(--mx-amber-soft);padding:5px 9px;border-radius:8px;}
.mx-rc-foot{margin-top:9px;font-size:10px;color:var(--mx-ink-faint);line-height:1.4;}

.mx-tt{box-sizing:border-box;overflow:hidden;}
.mx-rc-cap{font-size:9.5px;color:var(--mx-ink-faint);margin-top:2px;}
.mx-rc-stats > div{min-width:0;}
.mx-rc-stats .v{white-space:nowrap;}
@media(max-width:480px){.mx-tt{padding:14px;}.mx-rc-stats .v{font-size:12.5px;}.mx-rc-chart{gap:3px;}.mx-rc-day{font-size:8px;}}
