/* Portal-wide (guest/customer-facing www pages) theme, including /login. Applies to
   Frappe's website chrome (navbar, sidebar, footer, tables, forms, dropdowns) plus
   this app's own components, not just one page's content -- portal visitors have no
   other way to control theme (portal_theme_toggle.js).

   Both states are "glass": a gradient backdrop with soft blurred color blobs, and
   content sitting in frosted translucent cards rather than flat panels. "Dark Glass"
   (navy/teal, the portal's default) and "Light Glass" (soft cool white, its
   toggled-off counterpart) share the same structure below -- only the palette
   variables and blob/card opacity differ between the two [data-theme] blocks. */
:root {
	--santek-bg: #eef2f7;
}

:root[data-theme="glass-dark"] {
	--santek-navy: #1c1c38;
	--santek-navy-2: #0b0a1c;
	--santek-teal: #0f4457;
	--santek-teal-2: #082b36;
	--santek-accent: #3f8ae0;
	--santek-bg: radial-gradient(circle at 15% 15%, #16324f 0%, var(--santek-navy-2) 55%, #050509 100%);
	--santek-card-bg: rgba(255, 255, 255, 0.08);
	--santek-card-border: rgba(255, 255, 255, 0.16);
	--santek-card-shadow: rgba(0, 0, 0, 0.35);
	--santek-card-highlight: rgba(255, 255, 255, 0.14);
	--santek-input-bg: rgba(255, 255, 255, 0.06);
	--santek-blur: blur(34px) saturate(160%);
	--santek-blob-1: var(--santek-teal);
	--santek-blob-1-opacity: 0.4;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0.28;
	--santek-blob-3: #7a5cff;
	--santek-blob-3-opacity: 0.16;

	/* This app's other CSS (timeline.css, theme_switch.css) already reads these
	   exact token names via var(name, <light-fallback>) -- redefining them here
	   re-themes every .santek-* component and the toggle for free, with no
	   per-component dark rule needed. */
	--fg-color: rgba(255, 255, 255, 0.08);
	--text-color: #f4f6fb;
	--text-muted: #a9b4c6;
	--border-color: rgba(255, 255, 255, 0.16);

	/* Added migrating to the multi-theme engine (feat/portal-multi-theme-engine) --
	   these five were previously hardcoded literals duplicated across separate
	   dark/light rule blocks (native <select>/.dropdown-menu opaque backgrounds, the
	   mobile drawer's solid backdrop, .dropdown-item's hover tint, and the shared
	   card corner radii), which meant a future theme pair inherited none of them and
	   would've needed its own copies of those rules just to reskin these five spots.
	   Promoting them to tokens collapsed each pair of rules into one theme-agnostic
	   rule (see the selectors themselves) with no value change for Dark/Light Glass. */
	--santek-solid-surface: var(--santek-navy);
	--santek-mobile-menu-bg: rgba(28, 28, 56, 0.97);
	--santek-hover-tint: rgba(255, 255, 255, 0.08);
	--santek-card-radius: 28px;
	--santek-card-radius-sm: 18px;
}

/* "Light Glass" -- modeled on Apple's iOS 26 Liquid Glass, not a flipped-color copy
   of Dark Glass: a strong blur + saturation boost (so color from the blobs/content
   behind actually bleeds through and reads as glass, not frosted paper), a bright
   specular highlight along the top inside edge (real glass catches light there), low
   card-fill opacity so it stays airy, and larger continuous-feeling corner radii
   (see the border-radius bump on .page_content/.for-login below, shared with dark
   for consistency but the effect reads most "Apple" here against a light backdrop). */
:root[data-theme="glass-light"] {
	--santek-navy: #1c1c38;
	--santek-teal: #0f4457;
	--santek-accent: #2f6fc2;
	/* Color has to reach the middle of the screen, not just sit in the corners --
	   glass only reads as glass when there's something colorful behind it to
	   refract, wherever a card happens to land. */
	--santek-bg: radial-gradient(circle at 18% 22%, rgba(15, 68, 87, 0.32) 0%, transparent 42%),
		radial-gradient(circle at 82% 20%, rgba(47, 111, 194, 0.3) 0%, transparent 45%),
		radial-gradient(circle at 50% 85%, rgba(122, 92, 255, 0.24) 0%, transparent 50%),
		radial-gradient(circle at 85% 80%, rgba(15, 68, 87, 0.2) 0%, transparent 40%),
		#eef2f8;
	--santek-card-bg: rgba(255, 255, 255, 0.42);
	--santek-card-border: rgba(255, 255, 255, 0.9);
	--santek-card-shadow: rgba(20, 40, 70, 0.18);
	--santek-card-highlight: rgba(255, 255, 255, 0.95);
	--santek-input-bg: rgba(255, 255, 255, 0.5);
	--santek-blur: blur(40px) saturate(200%) brightness(1.05);
	--santek-blob-1: var(--santek-teal);
	--santek-blob-1-opacity: 0.32;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0.28;
	--santek-blob-3: #7a5cff;
	--santek-blob-3-opacity: 0.2;

	--fg-color: rgba(255, 255, 255, 0.55);
	--text-color: #1c1c38;
	/* Darker than a "plain white card" design would need -- Light Glass's card
	   backgrounds are translucent over a saturated gradient, not flat white, so
	   secondary/muted text needs the extra contrast margin to stay legible
	   wherever a vivid blob happens to sit behind it. */
	--text-muted: #3d4552;
	--border-color: rgba(28, 28, 56, 0.1);

	/* See :root[data-theme="glass-dark"]'s matching comment for why these five exist. */
	--santek-solid-surface: #ffffff;
	--santek-mobile-menu-bg: rgba(244, 246, 251, 0.97);
	--santek-hover-tint: rgba(28, 28, 56, 0.06);
	--santek-card-radius: 28px;
	--santek-card-radius-sm: 18px;
}

/* "Corporate Slate" (feat/portal-slate-theme) -- the multi-theme engine's first real
   proof that a second pair doesn't need a CSS rewrite: every rule below this point in
   the file (the $= suffix-matched majority the migration to compound data-theme keys
   was built to enable) needed zero edits to support this. Only this file's two root
   token blocks are new.

   Deliberately not a recolored Glass: opaque, flat surfaces (no backdrop blur, no
   translucent card fill, no specular highlight), neutral cool-gray slate instead of
   navy/teal, a single confident indigo-blue accent, and tighter, less-rounded corners.
   Reads as precise technical/hardware equipment rather than liquid glass -- a
   deliberate fit for Santek's own camera-repair and lighting-fixture catalog, and for
   an ERP portal where the point is finishing a task quickly, not admiring the chrome.
   --santek-blur: none and --santek-card-highlight: transparent are what actually do
   the "flat" work -- every consuming rule already reads backdrop-filter and the inset
   specular shadow through these two tokens, so setting them inert here is enough to
   turn every glass card in the app into a plain flat panel with no further CSS. */
:root[data-theme="slate-dark"] {
	--santek-navy: #171a21;
	--santek-navy-2: #101318;
	--santek-teal: #3b4453;
	--santek-teal-2: #262b35;
	--santek-accent: #5469d4;
	--santek-bg: #0d0f13;
	--santek-card-bg: #171a21;
	--santek-card-border: #262b35;
	--santek-card-shadow: rgba(0, 0, 0, 0.4);
	--santek-card-highlight: transparent;
	--santek-input-bg: #12141a;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: transparent;
	--santek-blob-1: var(--santek-teal);
	--santek-blob-1-opacity: 0;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0;
	--santek-blob-3: var(--santek-teal-2);
	--santek-blob-3-opacity: 0;

	--fg-color: rgba(255, 255, 255, 0.04);
	--text-color: #e7e9ee;
	--text-muted: #8b93a3;
	--border-color: rgba(255, 255, 255, 0.09);

	--santek-solid-surface: #171a21;
	--santek-mobile-menu-bg: rgba(13, 15, 19, 0.98);
	--santek-hover-tint: rgba(255, 255, 255, 0.05);
	--santek-card-radius: 10px;
	--santek-card-radius-sm: 8px;
}

:root[data-theme="slate-light"] {
	--santek-navy: #171a21;
	--santek-teal: #b8c0cc;
	--santek-accent: #445fc7;
	--santek-bg: #f4f5f7;
	--santek-card-bg: #ffffff;
	--santek-card-border: #e2e4e9;
	--santek-card-shadow: rgba(15, 23, 42, 0.1);
	--santek-card-highlight: transparent;
	--santek-input-bg: #f8f9fb;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: transparent;
	--santek-blob-1: var(--santek-teal);
	--santek-blob-1-opacity: 0;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0;
	--santek-blob-3: var(--santek-teal);
	--santek-blob-3-opacity: 0;

	--fg-color: #eef0f3;
	--text-color: #1a1d24;
	--text-muted: #5b6270;
	--border-color: #e2e4e9;

	--santek-solid-surface: #ffffff;
	--santek-mobile-menu-bg: rgba(255, 255, 255, 0.98);
	--santek-hover-tint: rgba(15, 17, 21, 0.045);
	--santek-card-radius: 10px;
	--santek-card-radius-sm: 8px;
}

/* "Safelight" (feat/portal-safelight-theme) -- the third pair, and deliberately not a
   third variation on "dark UI, one glowing accent" (the exact AI-design cliche flagged
   at impeccable.style/antipattern-examples/lazy-cool: neon glow bolted onto a surface
   for no reason other than looking cutting-edge) nor the other well-worn default, warm
   cream + serif + terracotta. Grounded instead in Santek's own subject matter -- this
   is a Kodak camera repair shop -- rather than either generic AI-design attractor:
   dark mode reads as an actual darkroom (warm near-black, one low-opacity red glow in
   the corner standing in for a safelight bulb, everything else flat), light mode reads
   as a contact sheet on a warm kraft light table (tan table surface, ivory paper
   cards, crisp square corners like a printed photograph, no glow -- tried carrying the
   dark mode's glow across into light too on 2026-08-25 for cross-mode consistency,
   reverted on direct feedback: here the two modes deliberately read as two different
   states of the same room, lit and unlit, not one recolored surface). One accent hue
   runs through both -- a matte, non-glowing Kodak red.

   Flat like Slate (--santek-blur/--santek-btn-blur/--santek-btn-glow all inert) --
   this is a working portal, not a mood board, so the chrome stays out of the way.
   The one deliberate flourish is the single re-lit blob-1 in dark mode only (opacity
   restored, low, and alone -- blob-2/blob-3 stay off): every other pair in this file
   either runs all three blurred blobs (Glass) or none (Slate); one lit blob is new, and
   it's a
   restrained, thematically-motivated choice, not decoration for its own sake --
   "spend your boldness in one place." Card radius drops to 4px, sharper than either
   existing pair (Glass 28px, Slate 10px) -- a print's own square corner, not a rounder
   screen-native one, and the one place this pair diverges structurally, not just in
   color, from what came before it. */
:root[data-theme="safelight-dark"] {
	--santek-navy: #1a140f;
	--santek-navy-2: #0d0a08;
	--santek-teal: #3a2620;
	--santek-teal-2: #241a15;
	--santek-accent: #d81e2c;
	--santek-bg: radial-gradient(circle at 12% 10%, rgba(216, 30, 44, 0.1) 0%, transparent 35%), #120e0c;
	--santek-card-bg: #1c1611;
	--santek-card-border: #33261e;
	--santek-card-shadow: rgba(0, 0, 0, 0.45);
	--santek-card-highlight: transparent;
	--santek-input-bg: #171310;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: transparent;
	/* The lone safelight bulb -- top-left, same corner body::before already paints
	   every pair's blob-1 into, just switched back on and alone. */
	--santek-blob-1: var(--santek-accent);
	--santek-blob-1-opacity: 0.14;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0;
	--santek-blob-3: var(--santek-accent);
	--santek-blob-3-opacity: 0;

	--fg-color: rgba(255, 240, 220, 0.035);
	--text-color: #f2e9df;
	--text-muted: #a89686;
	--border-color: rgba(255, 240, 220, 0.08);

	--santek-solid-surface: #1c1611;
	--santek-mobile-menu-bg: rgba(18, 14, 12, 0.98);
	--santek-hover-tint: rgba(255, 240, 220, 0.05);
	--santek-card-radius: 4px;
	--santek-card-radius-sm: 3px;
}

:root[data-theme="safelight-light"] {
	--santek-navy: #241a13;
	--santek-teal: #ddd0c4;
	--santek-accent: #b8101f;
	/* First pass here (body #f2ece3, cards #fdfbf8) read as "plain white" on a real
	   screen -- both tones sat too close to white to register as paper at all.
	   Widened the gap deliberately: the page itself is a visibly tan/kraft table
	   surface, cards are warm ivory paper sitting on top of it, not a near-white
	   page with near-white cards. */
	--santek-bg: #e8ddc8;
	--santek-card-bg: #fbf6ec;
	--santek-card-border: #cdb890;
	--santek-card-shadow: rgba(50, 32, 12, 0.16);
	--santek-card-highlight: transparent;
	--santek-input-bg: #f4ecdd;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: transparent;
	/* Tried carrying the dark-mode glow into light mode too (2026-08-25), on the
	   reasoning that a pair should share one design language across both modes --
	   reverted on direct feedback. No lit glow here; the accent still runs through
	   every button/link via --santek-accent below, just without an ambient blob. */
	--santek-blob-1: var(--santek-accent);
	--santek-blob-1-opacity: 0;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0;
	--santek-blob-3: var(--santek-accent);
	--santek-blob-3-opacity: 0;

	--fg-color: #e9ddc7;
	--text-color: #241a13;
	--text-muted: #6b5c4d;
	--border-color: #cdb890;

	--santek-solid-surface: #fbf6ec;
	--santek-mobile-menu-bg: rgba(251, 246, 236, 0.98);
	--santek-hover-tint: rgba(36, 26, 19, 0.06);
	--santek-card-radius: 4px;
	--santek-card-radius-sm: 3px;
}

/* "Neon" (feat/portal-neon-theme) -- the fourth pair, built to an explicit brief
   ("the keyword is neon"), not backed into. This is the one direction Safelight's
   own header comment above named as the cliche to avoid by default (a saturated
   glow accent on a dark surface) -- appropriate here specifically because it was
   asked for outright, not defaulted into: a pinned brief overrides a
   saturated-pattern caution, it doesn't get watered down to dodge one.

   Executed as genuine neon-sign material rather than the flagged single-accent
   cliche: three tube colors (magenta, cyan, violet), not one, the same three-blob
   mechanism Glass already uses just re-hued, so it reads as a lit sign with
   several tubes rather than one decorative glow bolted onto black. Cyan carries
   every interactive role (buttons, links, borders, the card's top edge-light);
   magenta is the ambient glow color (card shadow, blob-1); violet is background
   atmosphere only (blob-3) -- one consistent hue-to-role mapping, kept identical
   across both modes per Safelight's own light/dark lesson: same design language,
   recolored, not two different treatments sharing a name.

   Flat, not frosted -- --santek-blur/--santek-btn-blur stay inert. The glow here
   comes entirely from saturated color (the card's big soft --santek-card-shadow
   blooming outward, --santek-card-highlight relit as a bright cyan top edge-line,
   like a tube's own edge lighting) rather than from backdrop blur, which keeps
   this a distinct fourth material rather than "Glass, but neon-colored." */
:root[data-theme="neon-dark"] {
	--santek-navy: #0a0a0f;
	--santek-navy-2: #050507;
	--santek-teal: #ff2fc0;
	--santek-teal-2: #7c3aed;
	--santek-accent: #00e5ff;
	--santek-bg: #08080c;
	--santek-card-bg: #101018;
	--santek-card-border: rgba(0, 229, 255, 0.28);
	--santek-card-shadow: rgba(255, 47, 192, 0.32);
	--santek-card-highlight: rgba(0, 229, 255, 0.5);
	--santek-input-bg: #0d0d14;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: rgba(0, 229, 255, 0.6);
	/* Three tubes, not one -- magenta (ambient/ink), cyan (interactive, matches
	   the accent), violet (background atmosphere only, never used on content). */
	--santek-blob-1: #ff2fc0;
	--santek-blob-1-opacity: 0.32;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0.26;
	--santek-blob-3: #7c3aed;
	--santek-blob-3-opacity: 0.2;

	--fg-color: rgba(0, 229, 255, 0.05);
	--text-color: #f1f2fb;
	--text-muted: #9a93b8;
	--border-color: rgba(0, 229, 255, 0.16);

	--santek-solid-surface: #101018;
	--santek-mobile-menu-bg: rgba(8, 8, 12, 0.97);
	--santek-hover-tint: rgba(0, 229, 255, 0.08);
	--santek-card-radius: 16px;
	--santek-card-radius-sm: 10px;
}

:root[data-theme="neon-light"] {
	--santek-navy: #14131f;
	--santek-teal: #e822ac;
	--santek-teal-2: #6d28d9;
	/* Deepened from the dark pair's pure #00e5ff -- a saturated cyan this bright
	   fails text/border contrast on a light surface, same reasoning Light Glass's
	   own accent already got a deeper blue than Dark Glass's. */
	--santek-accent: #0aa8c9;
	--santek-bg: #f7f6fc;
	--santek-card-bg: #ffffff;
	--santek-card-border: rgba(10, 168, 201, 0.3);
	--santek-card-shadow: rgba(232, 34, 172, 0.26);
	--santek-card-highlight: rgba(10, 168, 201, 0.55);
	--santek-input-bg: #f7f4fc;
	--santek-blur: none;
	--santek-btn-blur: none;
	--santek-btn-glow: rgba(10, 168, 201, 0.5);
	/* Same three tubes, same hue-to-role mapping as dark, opacity pulled down --
	   a saturated blob this size reads as a lit sign at dark-mode opacity, but as
	   a muddy stain at the same opacity against white. */
	--santek-blob-1: #e822ac;
	--santek-blob-1-opacity: 0.18;
	--santek-blob-2: var(--santek-accent);
	--santek-blob-2-opacity: 0.16;
	--santek-blob-3: #6d28d9;
	--santek-blob-3-opacity: 0.12;

	--fg-color: rgba(10, 168, 201, 0.06);
	--text-color: #14131f;
	--text-muted: #5c5878;
	--border-color: rgba(10, 168, 201, 0.22);

	--santek-solid-surface: #ffffff;
	--santek-mobile-menu-bg: rgba(255, 255, 255, 0.97);
	--santek-hover-tint: rgba(10, 168, 201, 0.06);
	--santek-card-radius: 16px;
	--santek-card-radius-sm: 10px;
}

html[data-theme$="-dark"] body,
html[data-theme$="-light"] body {
	background: var(--santek-bg);
	background-attachment: fixed;
	color: var(--text-color);
	position: relative;
}

/* Soft blurred color blobs behind the page content -- pure CSS, no extra markup, so
   every portal page gets them for free regardless of how its own template is built. */
html[data-theme$="-dark"] body::before,
html[data-theme$="-dark"] body::after,
html[data-theme$="-light"] body::before,
html[data-theme$="-light"] body::after {
	content: "";
	position: fixed;
	z-index: 0;
	border-radius: 50%;
	filter: blur(70px);
	pointer-events: none;
}

html[data-theme$="-dark"] body::before,
html[data-theme$="-light"] body::before {
	width: 480px;
	height: 480px;
	background: var(--santek-blob-1);
	opacity: var(--santek-blob-1-opacity);
	top: -140px;
	left: -140px;
}

html[data-theme$="-dark"] body::after,
html[data-theme$="-light"] body::after {
	width: 420px;
	height: 420px;
	background: var(--santek-blob-2);
	opacity: var(--santek-blob-2-opacity);
	bottom: -140px;
	right: -100px;
}

/* Third blob -- a different hue in the mix (not just navy/teal twice) is what makes
   the saturated blur behind the glass actually read as colorful "liquid" rather than
   a single flat tint. */
html[data-theme$="-dark"] #santek-portal-mark::after,
html[data-theme$="-light"] #santek-portal-mark::after {
	content: "";
	position: fixed;
	z-index: 0;
	top: 35%;
	left: 55%;
	width: 380px;
	height: 380px;
	border-radius: 50%;
	filter: blur(70px);
	pointer-events: none;
	background: var(--santek-blob-3);
	opacity: var(--santek-blob-3-opacity);
}

/* Live-reproduced on a real handheld (Unitech EA600, /warehouse, 2026-09-04): at a
   360 CSS px viewport, the 380px third blob is wider than the entire screen -- and the
   whole warehouse fleet is narrower than every blob's fixed size (TC20 320px, iData T1
   and this EA600 360px, all under the smallest blob's 380px, let alone the 420-480px
   corner pair). getComputedStyle confirmed `filter: blur(70px)` and the intended
   opacity were both genuinely applied, but on this device's old Adreno GPU the actual
   painted pixels were a hard-edged, near-opaque disc, not a soft glow -- the compositor
   silently isn't rasterizing the blur, not a CSS mistake. So this isn't "make the
   blobs smaller and they'll look right" (a scaled-down blob would likely paint the
   same way, just a smaller hard disc): below the width every target handheld actually
   ships at, the decorative blobs are simply not worth the risk and are turned off
   entirely, same threshold this file already uses for its other narrow-viewport rule
   below. */
@media (max-width: 480px) {
	html[data-theme$="-dark"] body::before,
	html[data-theme$="-dark"] body::after,
	html[data-theme$="-light"] body::before,
	html[data-theme$="-light"] body::after,
	html[data-theme$="-dark"] #santek-portal-mark::after,
	html[data-theme$="-light"] #santek-portal-mark::after {
		display: none;
	}
}

/* Frappe's website theme sets an explicit heading color that doesn't inherit from
   body and isn't driven by any variable this app redefines -- confirmed live at
   rgb(23, 23, 23), which nearly matches the dark palette's background, making every
   page title/section heading effectively invisible without this override. Light
   Glass doesn't need it (that same near-black color already reads fine against a
   light background), so this stays dark-only. */
html[data-theme$="-dark"] h1,
html[data-theme$="-dark"] h2,
html[data-theme$="-dark"] h3,
html[data-theme$="-dark"] h4,
html[data-theme$="-dark"] h5,
html[data-theme$="-dark"] h6 {
	color: var(--text-color) !important;
}

/* Every custom page in this app (My Returns, Packaging Designs, ...) titles itself
   with a literal <h1>, which core's own Inter/56px/600 website theme already styles
   generously. Core's *own* portal list/detail pages (Orders, Quotations, Invoices,
   Shipments, Material Request, Projects, Timesheets, and Project's own detail page)
   title themselves through a completely different path instead -- frappe/www/
   portal.html's shared {% block header %} and erpnext/templates/pages/projects.html
   both render an <h3 class="my-account-header">, which core's own website.scss sizes
   much smaller (confirmed live: 30.4px/600, next to My Returns' 56px/600) -- the
   reason those page titles read noticeably smaller than this app's own pages before
   this rule. Matched here rather than left as two different heading scales across
   one portal. Not theme-gated, unlike the color override above -- size/weight don't
   depend on which glass palette is active.

   2026-08-22: font-size/weight/line-height matching turned out not to be the whole
   story -- confirmed live, once /issues/list and /address/list's own headings had
   already been matched to Orders' exact 56px/600 scale (see the dedicated rule
   below), that Orders' own margin (56px 0 8px, from whatever core rule actually
   applies it -- not this class, which never set one) still didn't match this app's
   *other* custom pages' own <h1> margin (Home/Returns/Freight/Packaging Designs, all
   64px 0 20px -- core's own generic h1 default, not overridden by anything here
   before now). Both groups already shared the identical 56px/600/line-height:56px
   values, so the visible ~2px heading-position drift between "Orders-style" pages
   and "this app's own" pages had gone unnoticed until enough pages existed on both
   sides to compare directly. Pinned the margin explicitly here too (rather than
   leaving Orders' side dependent on an unidentified core rule that a future upgrade
   could silently change) so this class is now the single, complete source of truth
   for every property this "unify the heading scale" fix cares about. */
.my-account-header {
	font-size: 56px !important;
	font-weight: 600 !important;
	line-height: 56px !important;
	margin: 56px 0 8px !important;
}

/* The other side of the same fix: this app's own custom <h1> pages (Home, My
   Returns, My Freight Shipments, Packaging Designs -- every www page's own index.html
   that titles itself with a plain <h1>, confirmed live via getComputedStyle across
   all four) get core's generic h1 margin (64px 0 20px) instead of the 56px 0 8px
   value .my-account-header pins above -- font-size/weight/line-height already
   matched via core's own generous h1 styling, only the margin ever differed.
   Targeted by data-path rather than a bare `h1` selector, which would be far too
   broad (would also touch headings inside record details, dialogs, anything else
   using an <h1> for unrelated reasons) -- confirmed live these four are the
   complete set of this app's own plain-<h1> portal pages today. Extend this list
   (and re-check its own getComputedStyle margin first) if a new one is added. */
body[data-path="home"] main.container h1,
body[data-path="returns"] main.container h1,
body[data-path="freight"] main.container h1,
body[data-path="packaging-designs"] main.container h1 {
	margin: 56px 0 8px !important;
}

/* 2026-08-22: /issues/list and /address/list have the exact same undersized-heading
   problem as .my-account-header above, for a third reason neither of that rule's own
   two cases covers -- confirmed live via getComputedStyle, not assumed: their own
   <h1>s (real erpnext Web Form headers, "Issue"/"Address") render at 36px/margin:0,
   next to Orders' actual 56px/600/line-height:56px/margin:56px 0 8px. Not core's
   generous website-wide <h1> styling (that's what My Returns' own <h1> already gets
   for free, per the note above) and not .my-account-header either (that class isn't
   present on this markup at all -- Frappe's Web Form list template renders a bare
   <h1>, styled instead by web_form.bundle.css's own, deliberately compact
   form-heading convention, sized for an actual form title rather than a marquee page
   headline). Matched here to the same 56px scale and 56px 0 8px margin every other
   portal page now shares (see .my-account-header's own dated note above, and the
   sibling rule for this app's own custom <h1> pages right after it -- this is the
   third and last of the three places that single margin value needed pinning
   explicitly) -- confirmed live the margin was what was actually missing once
   font-size/weight alone still left the heading sitting hard against the top edge
   with no breathing room before the filter/search chrome below it. */
body[data-path="issues/list"] main.container h1,
body[data-path="address/list"] main.container h1 {
	font-size: 56px !important;
	font-weight: 600 !important;
	line-height: 56px !important;
	margin: 56px 0 8px !important;
}

html[data-theme$="-dark"] a,
html[data-theme$="-light"] a {
	color: var(--santek-accent);
}

html[data-theme$="-dark"] .navbar {
	background-color: var(--santek-navy) !important;
	border-color: var(--border-color);
}

html[data-theme$="-light"] .navbar {
	background-color: var(--fg-color) !important;
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border-color: var(--border-color);
	box-shadow: inset 0 -1px 0 var(--santek-card-highlight);
}

/* .navbar is already position: relative (core, not this file) but z-index: auto --
   confirmed live via getComputedStyle/document.elementFromPoint that the language
   picker's dropdown panel (.santek-lang-picker-panel, z-index: 1060) was rendering
   BEHIND main.container's own z-index: 1 despite that huge-looking number:
   z-index only competes within the *same* stacking context, and a positioned
   ancestor with z-index: auto doesn't hand its descendants a context of their own
   to win from -- the navbar's whole subtree was stacking at an effective 0 against
   main.container's explicit 1, so clicks on "Türkçe" were landing on
   .page-header-wrapper underneath it instead of the option itself, not a
   theme-specific bug even though it was first reported that way. Any current or
   future popover living in the navbar needs this, not just this one picker. */
.navbar {
	z-index: 2;
}

html[data-theme$="-dark"] .navbar .navbar-brand,
html[data-theme$="-dark"] .navbar .nav-link,
html[data-theme$="-light"] .navbar .navbar-brand,
html[data-theme$="-light"] .navbar .nav-link {
	color: var(--text-color) !important;
}

/* Sidebar/aside text has no card of its own in Frappe's stock markup -- it would
   otherwise sit directly on the colorful body gradient with nothing but muted-gray
   text, unreadable wherever a saturated blob peeks through. Giving it the same
   frosted surface as every other content area is what actually fixes that, not just
   a darker text color (which would still fail against a vivid patch behind it). */
html[data-theme$="-dark"] .web-sidebar,
html[data-theme$="-light"] .web-sidebar {
	position: relative;
	z-index: 1;
	background: var(--santek-card-bg);
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border: 1px solid var(--santek-card-border);
	border-radius: 20px;
	padding: 1rem;
}

html[data-theme$="-dark"] .web-sidebar .sidebar-item a,
html[data-theme$="-light"] .web-sidebar .sidebar-item a {
	color: var(--text-muted);
}

html[data-theme$="-dark"] .web-sidebar .sidebar-item a:hover,
html[data-theme$="-dark"] .web-sidebar .sidebar-item.selected a,
html[data-theme$="-light"] .web-sidebar .sidebar-item a:hover,
html[data-theme$="-light"] .web-sidebar .sidebar-item.selected a {
	color: var(--text-color);
}

/* Mobile navigation, 2026-08-22 -- superseding a first attempt (a separate
   "Menu" button + JS revealing .sidebar-column, since that column is
   unconditionally `display: none` below Bootstrap's lg breakpoint in core
   Frappe's own frappe/public/scss/website/doc.scss with no trigger anywhere
   in core to reveal it). That fix genuinely worked, but was reported back as
   a real duplicate: core Frappe's OWN navbar already has a second, separate
   mobile fallback -- confirmed live by inspecting the DOM directly --
   #navbarSupportedContent already contains the *entire* portal menu a second
   time, in a `<div class="d-block d-lg-none">` block inside `.navbar-nav`,
   already reachable via the existing .navbar-toggler hamburger. Two controls
   that both opened the same navigation. portal_mobile_sidebar.js and its
   toggle button were removed entirely; this section instead reshapes the
   ALREADY-WORKING existing hamburger, per direct request: one control, moved
   to the top-left, and its reveal changed from Bootstrap's default
   top-to-bottom accordion to a left-to-right slide-in drawer. */
@media (max-width: 991.98px) {
	/* The hamburger icon itself was real but completely invisible, for two
	   independent reasons confirmed live via computed style: (1) an
	   untethed color (rgba(0,0,0,0.5), not a theme token) and (2) its own
	   <path> elements (#icon-menu, a stroke-based Lucide icon) carry no
	   fill/stroke attributes at all and nothing set stroke: currentColor --
	   fill alone was computing to transparent regardless of color, so the
	   icon painted nothing whatsoever, not just low-contrast. */
	html[data-theme$="-dark"] .navbar-toggler svg,
	html[data-theme$="-light"] .navbar-toggler svg {
		color: var(--text-color) !important;
		fill: none !important;
		stroke: currentColor !important;
		stroke-width: 2px;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	/* Bootstrap's own navbar puts brand first, toggler last (pushed right by
	   .container's flex layout) -- `order` swaps their visual position
	   without touching markup we can't fork (core Frappe's navbar.html,
	   same install-order constraint documented throughout this app). */
	.navbar-toggler {
		order: -1;
		margin-right: 12px;
		/* Needs its own stacking context (a z-index alone does nothing on a
		   position: static element, which this is by Bootstrap default) so it
		   can paint ABOVE the drawer below (z-index: 1045) once open --
		   otherwise the same button that opens the drawer becomes invisible
		   and unreachable the moment it's needed to close it again. Both live
		   inside .navbar's own stacking context (z-index: 2, above), so
		   1046 only has to beat 1045 here, not compete site-wide. */
		position: relative;
		z-index: 1046;
	}

	/* Off-canvas drawer. Bootstrap's collapse JS still runs unmodified (adds/
	   removes .collapsing during the transition and .show once open) -- only
	   what those classes *look like* is overridden, not the show/hide logic
	   itself, so nothing about the toggler's existing click behavior needs
	   touching. Fixed positioning + transform:translateX is what turns a
	   height-animated accordion into a left-to-right slide; !important on the
	   .collapsing rule is required because Bootstrap's own JS sets an inline
	   `height` style directly on the element for the duration of that class,
	   which would otherwise still animate height even with this override
	   present (confirmed live: without !important here, the drawer briefly
	   flashed full-height before the transform kicked in). */
	/* height: 100vh !important on all three states below, not a top:0/bottom:0
	   auto-stretch -- confirmed live that relying on the stretch left the
	   drawer's real computed height wherever Bootstrap's own collapse JS last
	   set it (an inline height it sets/clears around the transition, sized
	   for its own -- unused here -- accordion animation), reproducibly ~104px
	   in one theme and the full viewport in another with no CSS difference
	   between them: purely a timing artifact of code that still thinks it's
	   animating a height-based collapse. An explicit !important height
	   removes that ambiguity outright rather than fighting it state by state.
	   100dvh redeclared right after 100vh (not instead of it -- vh stays as the
	   fallback for browsers without dvh support) because real mobile browser
	   chrome (the URL bar hiding/showing as you scroll) makes 100vh and the
	   *actual* visible viewport two different numbers on a real phone --
	   confirmed as the cause of "drawer stops partway down when I scroll,"
	   reported live on a real device. An iframe-based desktop test can't
	   reproduce this by construction (no dynamic browser chrome to shrink/grow
	   against), which is exactly why it passed there and failed on the phone. */
	/* Own background deliberately far more opaque than --santek-card-bg (the
	   normal 8%/42% dark/light token every other card in this app uses) --
	   confirmed live that even with the backdrop scrim below darkening
	   everything OUTSIDE the drawer, the drawer's OWN panel at normal card
	   opacity still let the page content directly behind IT bleed through
	   (the scrim can't help with that, it only covers the area outside the
	   drawer's own 280px). A full-height navigation takeover needs to
	   actually replace the view, not stay "glass" over it -- unlike a
	   card that's meant to let the backdrop show through by design. No
	   backdrop-filter here either (unlike every other card in this app) --
	   at 0.97 opacity it was contributing nothing visually, while
	   backdrop-filter + overflow-y: auto + position: fixed together is a
	   known WebKit compositing bug (content inside the scrolling container
	   intermittently failing to repaint, i.e. "items disappear when I scroll
	   down and up again," reported live on a real phone in light theme).
	   Full-width/100dvw, not the original narrow 280px column -- explicitly
	   requested, matching a true full-screen mobile nav takeover rather than
	   a partial off-canvas strip. border-right/box-shadow dropped along with
	   the narrow width -- both existed only to give the drawer's own right
	   edge a visible boundary against the page peeking through beside it,
	   which can't happen anymore at full width.

	   top/height reworked entirely (an earlier version of this fix tried a
	   padding-top big enough to just clear .navbar's real height instead --
	   see the git history on this comment -- and it wasn't enough: content
	   scrolled all the way UP TO that padding boundary before disappearing,
	   so it still spent real, visible time sitting immediately adjacent to
	   the hamburger, looking like a collision even though the two elements'
	   actual stacking order was already correct -- confirmed live, then
	   reproduced directly in this app's own iframe test harness, by
	   screenshotting the transition itself rather than only checking the
	   final resting z-order via elementFromPoint, which had missed exactly
	   this "technically correct, still looks wrong" gap). Padding cannot
	   fix this -- overflow-y: auto has no way to stop scrolled content
	   short of a boundary; padding just changes how far it has to travel to
	   reach one.

	   The actual fix: don't let this element's own box occupy the navbar's
	   screen space at all. top: 44px (not 0) starts the drawer's box
	   immediately below .navbar's real ~41px height (a few px of margin
	   for minor cross-device variance in that real height), and height
	   drops the same 44px off the viewport so the drawer's bottom edge
	   still lands exactly at the true bottom of the screen. Content
	   scrolling inside now gets clipped by *this element's own top edge* --
	   a hard, structural boundary -- the instant it would reach the
	   navbar's space, rather than by hoping a separately-positioned opaque
	   element visually covers wherever it happens to still be showing.
	   padding-top drops to a plain 12px of breathing room against the
	   drawer's own now-correct top edge, not a clearance value that has to
	   account for anything happening above this element at all anymore. */
	html[data-theme$="-dark"] #navbarSupportedContent,
	html[data-theme$="-light"] #navbarSupportedContent {
		position: fixed;
		top: 44px;
		left: 0;
		height: calc(100vh - 44px) !important;
		height: calc(100dvh - 44px) !important;
		width: 100vw;
		width: 100dvw;
		max-width: 100%;
		margin: 0;
		padding: 12px 20px 20px;
		overflow-y: auto;
		transform: translateX(-100%);
		transition: transform 0.3s ease;
		z-index: 1045;
		/* Reported live on real devices: the drawer's own background visibly
		   thinned out mid-scroll (page content ghosting through much more
		   strongly than the resting-state opacity), correlated with actively
		   scrolling the nav list inside this same element. A fixed-position
		   element with its own scrolling child is exactly the shape that
		   triggers mobile browsers to demote/repromote it across compositing
		   layers mid-gesture -- isolate + will-change pins it to one stable
		   layer for the gesture's duration instead of leaving that to the
		   browser's own dynamic heuristics. */
		isolation: isolate;
		will-change: transform;
	}

	html[data-theme] #navbarSupportedContent {
		background: var(--santek-mobile-menu-bg);
	}

	html[data-theme$="-dark"] #navbarSupportedContent.collapsing,
	html[data-theme$="-light"] #navbarSupportedContent.collapsing {
		height: calc(100vh - 44px) !important;
		height: calc(100dvh - 44px) !important;
		transform: translateX(-100%);
	}

	html[data-theme$="-dark"] #navbarSupportedContent.show,
	html[data-theme$="-light"] #navbarSupportedContent.show {
		height: calc(100vh - 44px) !important;
		height: calc(100dvh - 44px) !important;
		transform: translateX(0);
	}

	/* Backdrop scrim behind the open drawer -- found live (user report, then
	   confirmed via document.elementFromPoint that the drawer really was
	   rendering correctly on top, just imperceptibly) that the drawer's own
	   translucent glass panel has nowhere near enough contrast against Light
	   Glass's own light gradient backdrop to read as a distinct panel at
	   all -- the page content behind it (still fully visible through both
	   the panel's low opacity AND the page's own light colors) bled through
	   into a confusing double-exposure rather than a clean drawer takeover.
	   A dimming scrim is the standard off-canvas-drawer pattern for exactly
	   this reason, and fixes it in both themes at once rather than needing a
	   separate higher-opacity drawer background just for light mode. `:has()`
	   keeps this a pure-CSS toggle, no JS beyond what already adds/removes
	   .show, matching this app's own established :has()-driven pattern. */
	body:has(#navbarSupportedContent.show)::after {
		content: "";
		position: fixed;
		inset: 0;
		background: rgba(0, 0, 0, 0.45);
		z-index: 1040;
	}

	/* Two real bugs reported live from actual touch-scroll on real devices (Android
	   Chrome + iPad Safari): (1) the drawer's own nav links, scrolling inside their
	   overflow-y:auto container, visually reached and overlapped the hamburger's
	   screen position instead of stopping short of it; (2) the hamburger disappeared
	   entirely partway through a scroll gesture. Root cause for both: nothing
	   actually locked the underlying PAGE from also scrolling while the drawer was
	   open, and .navbar itself (home to the toggler) was never more than
	   position:relative -- a normal, non-fixed element that scrolls away with the
	   page underneath the fixed drawer, taking the toggler with it despite the
	   toggler's own z-index bump (that bump only ever helped the toggler paint
	   *above* the drawer at whatever position it currently occupied -- it did
	   nothing to keep it fixed in place once the page under it moved).

	   Fixed by pinning the whole .navbar (not just the toggler) to the true top of
	   the viewport for as long as the drawer is open. body: overflow: hidden below
	   turned out to be an incomplete lock on its own -- plain overflow: hidden is a
	   documented no-op against real touch-scroll on iOS Safari, confirmed live when
	   the collision persisted on a real phone even after this shipped; the actually-
	   robust fix is mobile_nav_scroll_lock.js's position:fixed-with-scroll-offset
	   technique, kept here alongside this CSS as a first line of defense, not the
	   whole fix. Separately, the assumption that the drawer's original 84px top
	   padding already matched .navbar's real height was wrong -- see the padding-top
	   value's own comment above, corrected to 48px after reproducing the resulting
	   gap directly in this app's own iframe test harness (this bug, unlike the two
	   above, turned out not to need a real device to trigger after all -- it was a
	   static height mismatch, not a live compositing/scroll-locking quirk). */
	body:has(#navbarSupportedContent.show) {
		overflow: hidden;
	}

	body:has(#navbarSupportedContent.show) .navbar {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		z-index: 1050;
	}

	/* The mobile-only portal-menu block core already nests inside this same
	   collapse (see this section's own header comment) -- themed to match
	   the sidebar link language elsewhere in this file, since it was
	   rendering as plain unstyled nav-links before. */
	html[data-theme$="-dark"] #navbarSupportedContent .d-lg-none .nav-link,
	html[data-theme$="-light"] #navbarSupportedContent .d-lg-none .nav-link {
		color: var(--text-muted);
		padding: 0.5rem 0;
	}

	html[data-theme$="-dark"] #navbarSupportedContent .d-lg-none .nav-link:hover,
	html[data-theme$="-light"] #navbarSupportedContent .d-lg-none .nav-link:hover {
		color: var(--text-color);
	}

	/* The theme switch + language picker (navbar_right_extension, see
	   navbar_items.html) sit in the SAME top-level nav-item list as the Home/
	   Products links above -- navbar_right_extension isn't scoped to
	   .d-lg-none the way core's duplicated page-link block is, it's one
	   shared <li> list the collapse reveals whole. .santek-navbar-control's
	   own margin-left: 10px (theme_switch.css) exists to give it breathing
	   room from the desktop avatar dropdown next to it in the same row --
	   but applied here too, it landed the toggle/picker 10px right of every
	   link above them (confirmed live via getBoundingClientRect: links at
	   x=20, controls at x=30), a stray misalignment reported directly.
	   Zeroed only inside this drawer so the desktop row is untouched. */
	html[data-theme$="-dark"] #navbarSupportedContent .santek-navbar-control,
	html[data-theme$="-light"] #navbarSupportedContent .santek-navbar-control {
		margin-left: 0;
	}

	/* Reported live, 2026-08-24: the language picker's own dropdown panel
	   (theme_switch.css's .santek-lang-picker-panel) shoots off the visible
	   area inside this drawer. Root cause: that panel is `position: absolute;
	   right: 0`, anchoring its right edge to its trigger button's right edge --
	   correct on desktop, where .santek-navbar-control sits far to the right of
	   the screen next to the avatar dropdown, so a 220px-wide panel extending
	   leftward from there lands comfortably on-screen. Inside this drawer the
	   same trigger sits near the drawer's own LEFT edge (every control here is
	   left-aligned per the fix immediately above), so the identical `right: 0`
	   anchor pulls the panel 220px further left than the trigger itself --
	   confirmed live via getBoundingClientRect: panel.left trails trigger.left
	   by exactly panel.width - trigger.width, proving it's genuinely anchored
	   by its right edge, not just visually clipped. Flipping to `left: 0`
	   anchors the panel to the trigger's own left edge instead, extending
	   rightward into the drawer's remaining width -- safe even at the
	   narrowest iPad-flyout band (280px min-width, 240px of that after this
	   drawer's own 20px side padding), comfortably more than the panel's
	   220px. Scoped to inside this drawer only -- the desktop instance of this
	   same DOM node (this control only ever renders once; Bootstrap's own
	   collapse just relocates it visually per breakpoint) keeps its original
	   right-anchored behavior untouched. */
	html[data-theme$="-dark"] #navbarSupportedContent .santek-lang-picker-panel,
	html[data-theme$="-light"] #navbarSupportedContent .santek-lang-picker-panel {
		left: 0;
		right: auto;
	}
}

/* Theme switch + language picker moved to the persistent top bar on mobile/
   tablet, next to the hamburger -- requested directly, 2026-08-24, and also the
   real fix for a bug found investigating that request: the language picker's
   own dropdown panel was getting clipped by #navbarSupportedContent's own
   overflow-y:auto scroll boundary depending on how far down the trigger sat
   within the drawer's scrolled content (search box and/or trailing languages
   silently cut off, not actually missing from the DOM -- see the rule pair
   right above this one, whose left:0 fix only ever addressed the HORIZONTAL
   half of that problem). mobile_navbar_controls.js does the actual DOM move
   (re-parenting the existing <li> elements into a new wrapper here, not
   cloning them); this is purely the wrapper's own positioning.

   position: absolute (not a flex item in .navbar .container's own
   space-between row) is deliberate -- .container currently distributes
   exactly two visible children at this breakpoint (the toggler, pulled to the
   far left via its own order: -1, and the brand logo, pushed to the far right
   by space-between). Adding this wrapper as a THIRD normal-flow flex item
   would have space-between insert it as an evenly-spaced middle island
   between them, not tucked next to the logo where it visually belongs.
   Taking it out of flow entirely sidesteps that without having to also
   restructure the brand/toggler markup just to group them. right: 56px
   clears the logo -- confirmed live this needed accounting for TWO things,
   not just the logo's own ~30px width: Bootstrap's .navbar-brand also
   carries its own built-in margin-right: 16px, which space-between includes
   in its distribution. right: 44px (30 + a flat 14px gap, the logo-width-only
   math) landed the wrapper's own right edge 2px INTO that margin -- a small
   but real reported overlap. 56px (30 + 16 + a clean 10px gap) accounts for
   both; verified comfortably clear of the toggler even at a real 320px-wide
   phone. */
@media (max-width: 991.98px) {
	.navbar .container {
		position: relative;
	}

	html[data-theme$="-dark"] .santek-mobile-navbar-controls,
	html[data-theme$="-light"] .santek-mobile-navbar-controls {
		position: absolute;
		top: 0;
		right: 56px;
		height: 40px;
		display: flex;
		align-items: center;
		gap: 8px;
	}

	/* .santek-navbar-control's own margin-left: 10px (theme_switch.css) is
	   redundant here -- this wrapper's own `gap` already spaces the two
	   controls, and the extra margin would also offset the first one from
	   the wrapper's own left edge for no reason. */
	.santek-mobile-navbar-controls .santek-navbar-control {
		margin-left: 0;
	}
}

/* iPad-width flyout, not the full-screen takeover above -- requested directly,
   2026-08-24: a phone benefits from the drawer replacing the whole screen (there's
   nowhere else useful to look), but on an iPad-sized viewport the same full-screen
   takeover hides the entire page behind a nav menu on a device with plenty of room
   to show both at once. Layered on top of the shared block above (position:fixed,
   top/height, transform slide, background, scroll-lock, scrim) via source order --
   only width/right-edge treatment change here, everything else about how the drawer
   opens/closes/locks scroll is identical between phone and iPad. Scoped to
   768-991.98px specifically: below that is "phone," matching every other
   phone/tablet split in this app (e.g. webshop_theme.css's own #product-filters
   breakpoints); at 992px+ Bootstrap's own navbar-expand-lg already shows the real
   desktop nav row instead of the hamburger, so there's no drawer to narrow.

   width: 33.333vw is literally "a third of the screen," as asked -- min-width keeps
   it from getting cramped at the narrow end of the band (768px * 33% = ~256px,
   workable but tight for a nav list with a language-picker dropdown inside it),
   max-width keeps it from reading as "basically full-screen anyway" at the wide end
   (991px * 33% = ~330px, already close to a phone's own width). border-right +
   box-shadow are reintroduced here (the full-screen rule above deliberately drops
   both, per its own comment, since there's no page visible beside a full-width
   drawer to need a boundary against) -- at a third of the screen, the real page is
   visible in the remaining two-thirds, so the drawer's own right edge needs a
   visible boundary again. mobile_nav_outside_close.js closes the drawer on a tap in
   that now-visible remaining space (the scrim already dims it; this just makes it
   interactive), matching the standard off-canvas-drawer convention -- the
   full-screen phone case has no "outside" to tap by construction, so that script is
   a no-op there regardless of screen size, not just gated by this same breakpoint. */
@media (min-width: 768px) and (max-width: 991.98px) {
	html[data-theme$="-dark"] #navbarSupportedContent,
	html[data-theme$="-light"] #navbarSupportedContent,
	html[data-theme$="-dark"] #navbarSupportedContent.collapsing,
	html[data-theme$="-light"] #navbarSupportedContent.collapsing,
	html[data-theme$="-dark"] #navbarSupportedContent.show,
	html[data-theme$="-light"] #navbarSupportedContent.show {
		width: 33.333vw;
		min-width: 280px;
		max-width: 420px;
	}

	html[data-theme$="-dark"] #navbarSupportedContent,
	html[data-theme$="-light"] #navbarSupportedContent {
		border-right: 1px solid var(--santek-card-border);
		box-shadow: 4px 0 24px -8px rgba(0, 0, 0, 0.4);
	}
}

html[data-theme$="-dark"] .web-footer,
html[data-theme$="-dark"] .web-footer *,
html[data-theme$="-light"] .web-footer,
html[data-theme$="-light"] .web-footer * {
	color: var(--text-muted) !important;
	background-color: transparent !important;
}

html[data-theme$="-dark"] hr,
html[data-theme$="-dark"] .table th,
html[data-theme$="-dark"] .table td,
html[data-theme$="-dark"] .card,
html[data-theme$="-dark"] .list-group-item,
html[data-theme$="-light"] hr,
html[data-theme$="-light"] .table th,
html[data-theme$="-light"] .table td,
html[data-theme$="-light"] .card,
html[data-theme$="-light"] .list-group-item {
	border-color: var(--border-color) !important;
}

html[data-theme$="-dark"] .table,
html[data-theme$="-dark"] .card,
html[data-theme$="-dark"] .list-group-item,
html[data-theme$="-light"] .table,
html[data-theme$="-light"] .card,
html[data-theme$="-light"] .list-group-item {
	background-color: transparent;
	color: var(--text-color);
}

html[data-theme$="-dark"] .text-muted,
html[data-theme$="-light"] .text-muted {
	color: var(--text-muted) !important;
}

html[data-theme$="-dark"] .btn-default,
html[data-theme$="-dark"] .btn-light,
html[data-theme$="-light"] .btn-default,
html[data-theme$="-light"] .btn-light {
	background-color: var(--fg-color);
	color: var(--text-color);
	border-color: var(--border-color);
}

/* .input-with-feedback is a real, separate core Frappe control class (Web Form
   fields in edit mode, confirmed live on /address/<name>/edit -- distinct from the
   plain .form-control this rule already covered) carrying its own opaque
   near-white background (rgb(243,243,243)) that isn't a --santek-input-bg-style
   token at all. Confirmed live: white --text-color on that background was
   completely unreadable, worse than low-contrast -- the value was there
   (el.value), just invisible. !important because this is fighting a real, separate
   core rule, not just relying on selector order. */
html[data-theme$="-dark"] input,
html[data-theme$="-dark"] select,
html[data-theme$="-dark"] textarea,
html[data-theme$="-dark"] .form-control,
html[data-theme$="-dark"] .input-with-feedback,
html[data-theme$="-light"] input,
html[data-theme$="-light"] select,
html[data-theme$="-light"] textarea,
html[data-theme$="-light"] .form-control,
html[data-theme$="-light"] .input-with-feedback {
	background-color: var(--santek-input-bg) !important;
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	color: var(--text-color) !important;
	border-color: var(--border-color) !important;
}

/* Web Forms (e.g. /address/<name>, viewed before hitting "Edit") render every field
   as a real, plain :disabled <input> -- Bootstrap's own `.form-control:disabled`
   rule (a near-white, opaque background) wins over the general rule above, since
   that one doesn't account for the disabled state at all. Confirmed live: light
   themed text on that near-white background was effectively unreadable, the same
   low-contrast class of bug already fixed once on the order detail page's item rows
   (portal_theme.css's own .page-header-actions-block note above). Text goes to
   --text-muted rather than the full --text-color the enabled rule above uses --
   still legible, but reads as "not editable right now" the way a disabled field
   should, rather than looking identical to a live one. */
html[data-theme$="-dark"] input:disabled,
html[data-theme$="-dark"] select:disabled,
html[data-theme$="-dark"] textarea:disabled,
html[data-theme$="-dark"] .form-control:disabled,
html[data-theme$="-dark"] input[readonly],
html[data-theme$="-dark"] textarea[readonly],
html[data-theme$="-dark"] .control-value.like-disabled-input,
html[data-theme$="-light"] input:disabled,
html[data-theme$="-light"] select:disabled,
html[data-theme$="-light"] textarea:disabled,
html[data-theme$="-light"] .form-control:disabled,
html[data-theme$="-light"] input[readonly],
html[data-theme$="-light"] textarea[readonly],
html[data-theme$="-light"] .control-value.like-disabled-input {
	background-color: var(--santek-input-bg) !important;
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	color: var(--text-muted) !important;
	border-color: var(--border-color) !important;
	opacity: 1;
}

/* .control-value.like-disabled-input is a *different* core control shape from
   everything above -- a plain <div> Frappe uses to show a field's value read-only
   when the user can't edit it at all (e.g. New Issue's Status/Customer, locked to
   their default), not a disabled <input>. Found live on /issues/new: same
   near-white-box-on-dark-card problem, different element entirely, so it needed its
   own selector rather than being covered by the input-focused rules above.

   Web Form's rich text editor (Description, Quill-based) is a third shape again --
   .ql-editor is a contenteditable <div>, matched by none of the input selectors
   above. Also found live at rgb(243,243,243), the exact same core "form control"
   near-white default every other shape on this page turned out to use. .ql-blank is
   Quill's own "show the placeholder" state; themed the same as filled since the
   placeholder text itself already goes through --text-muted via a separate rule
   below. .ql-toolbar is already dark by default here (confirmed live,
   rgb(23,23,23)) -- only the editable body needed fixing. */
/* .ql-container is Quill's own outer wrapper, a *separate* element sitting behind
   .ql-editor (not a parent-of-parent, the direct next box out) -- it carries this
   same core rgb(243,243,243) near-white on its own, confirmed live bleeding through
   .ql-editor's translucent 0.06-alpha overlay since that alone was never meant to
   be opaque by itself. Transparent rather than the same --santek-input-bg token --
   .ql-editor already paints that tint on top; stacking the same translucent layer
   on both would double the alpha and read visibly lighter than every other input. */
html[data-theme$="-dark"] .ql-container,
html[data-theme$="-light"] .ql-container {
	background-color: transparent !important;
}

html[data-theme$="-dark"] .ql-editor,
html[data-theme$="-light"] .ql-editor {
	background-color: var(--santek-input-bg) !important;
	color: var(--text-color) !important;
}

html[data-theme$="-dark"] .ql-editor.ql-blank::before,
html[data-theme$="-light"] .ql-editor.ql-blank::before {
	color: var(--text-muted) !important;
}

/* A native <select>'s own closed box is themed by the generic input rule above, but
   its *expanded option list* is OS-rendered chrome outside CSS's reach in most
   browsers -- confirmed live (language switcher, portal_lang_switch.js) opening as
   a plain white system popup no styling here touched. option background-color/color
   is the one part of it Chromium/Edge do actually respect (Firefox/Safari mostly
   don't); not a full theme, but real, visible progress on the browsers most portal
   visitors use, for zero downside on the ones that ignore it. */
html[data-theme] select option {
	background-color: var(--santek-solid-surface);
	color: var(--text-color);
}

html[data-theme] .dropdown-menu {
	background-color: var(--santek-solid-surface);
	border-color: var(--border-color);
}

html[data-theme$="-dark"] .dropdown-item,
html[data-theme$="-light"] .dropdown-item {
	color: var(--text-color);
}

html[data-theme] .dropdown-item:hover,
html[data-theme] .dropdown-item:focus {
	background-color: var(--santek-hover-tint);
	color: var(--text-color);
}

/* Frosted glass content card. Targets the *page's* main.container (tag-scoped: the
   outer wrapper around both sidebar and content columns is also class="container",
   on a plain div, so the tag qualifier is what picks out the right one) rather than
   .page_content -- confirmed live that the page title (<h1>Orders</h1> etc.) is a
   sibling *before* .page_content inside this element, not inside it, so carding
   .page_content alone left the heading floating above the card with nothing behind
   it, sitting ~100px above where the sidebar card started. Carding main.container
   instead pulls the heading inside the same glass panel and lines its top edge up
   with the sidebar's (see .web-sidebar's margin-top below). /login is excluded:
   its own special login-card markup (.for-login below) gets the frosted treatment
   directly, and double-nesting this rule around it would put a card inside a card. */
html[data-theme$="-dark"] main.container,
html[data-theme$="-light"] main.container {
	position: relative;
	z-index: 1;
	background: var(--santek-card-bg);
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border: 1px solid var(--santek-card-border);
	border-radius: var(--santek-card-radius);
	padding: 2rem;
	/* The inset highlight is the specular catch-light along the glass's top inside
	   edge -- the detail that reads as "real glass" instead of a translucent panel. */
	box-shadow:
		inset 0 1px 0 var(--santek-card-highlight),
		0 30px 80px var(--santek-card-shadow);
}

html[data-theme$="-dark"] #page-login main.container,
html[data-theme$="-light"] #page-login main.container {
	background: transparent;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: none;
	border-radius: 0;
	padding: 0;
	box-shadow: none;
}

/* Core's generic error/message page (frappe/www/message.html -- 403/404/500,
   "Not Permitted", etc.) renders its own centered .error-page block inside the
   same main.container every ordinary page gets carded -- reported directly:
   the frosted card behind an error message reads as a design mistake here,
   not a content surface worth framing. Same strip as #page-login above, since
   .error-page (unlike /cart or /me) has no other layout of its own depending
   on main.container's padding staying in place. */
html[data-theme$="-dark"] main.container:has(.error-page),
html[data-theme$="-light"] main.container:has(.error-page) {
	background: transparent;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: none;
	border-radius: 0;
	padding: 0;
	box-shadow: none;
}

/* No big glass card behind this app's own pages (reported: "the big card behind all the assets ... on every page we
   created"). The site-wide rule above wraps every page's main.container in one frosted panel; these pages already
   compose their own cards, timelines and tables, so a second panel behind them all is noise. Same opt-out the login,
   error and cart pages use, keyed on the route prefix so a detail page (/returns/<name>) is covered along with its
   list. Only the card BOX goes: padding is left alone, as the cart rule does, because these layouts were built
   assuming main.container's own 2rem padding. Pages that render a .website-list are already stripped further down;
   the product page (/01/...) strips itself in product_page.css. */
html[data-theme$="-dark"] body[data-path="home"] main.container,
html[data-theme$="-dark"] body[data-path="me"] main.container,
html[data-theme$="-dark"] body[data-path^="returns"] main.container,
html[data-theme$="-dark"] body[data-path^="new-return"] main.container,
html[data-theme$="-dark"] body[data-path^="repair-status"] main.container,
html[data-theme$="-dark"] body[data-path^="freight"] main.container,
html[data-theme$="-dark"] body[data-path^="packaging-designs"] main.container,
html[data-theme$="-dark"] body[data-path^="packing-declaration"] main.container,
html[data-theme$="-dark"] body[data-path^="order-documents"] main.container,
html[data-theme$="-light"] body[data-path="home"] main.container,
html[data-theme$="-light"] body[data-path="me"] main.container,
html[data-theme$="-light"] body[data-path^="returns"] main.container,
html[data-theme$="-light"] body[data-path^="new-return"] main.container,
html[data-theme$="-light"] body[data-path^="repair-status"] main.container,
html[data-theme$="-light"] body[data-path^="freight"] main.container,
html[data-theme$="-light"] body[data-path^="packaging-designs"] main.container,
html[data-theme$="-light"] body[data-path^="packing-declaration"] main.container,
html[data-theme$="-light"] body[data-path^="order-documents"] main.container {
	background: transparent;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: none;
	border-radius: 0;
	box-shadow: none;
}

/* /cart -- reported live on both themes: user wanted "the card behind items
   and payment summary etc." gone. First attempt (now reverted) misread this
   as the individual .frappe-card sections (Items, Payment Summary, Shipping/
   Billing Address) and stripped those instead -- wrong target, and it made
   the page look like everything had collapsed into one undifferentiated
   card, since those per-section cards are what visually separates them from
   each other. The actual "card behind" everything is main.container itself
   (confirmed live via getComputedStyle on every ancestor of .cart-container:
   background rgba(255,255,255,.08), blur(34px), border, border-radius 28px)
   -- the same site-wide glass wrapper the base rule above gives every page,
   sitting one level further out than any of the sections the user was
   pointing at. Stripped the same way #page-login does above; padding is
   left alone (not the #page-login shape) since /cart's layout, like /me's,
   was built assuming main.container's own 2rem padding stays in place --
   only the card "box" itself goes away. The per-section .frappe-card boxes
   (Items/Payment Summary/Address) are untouched here and keep the normal
   glass-card treatment everywhere in the app, including this page. */
/* /impeccable adapt, 2026-08-24 added max-width: 1600px to this rule -- a
   second, deeper cause of the same squeeze webshop_theme.css's own
   #page-cart .col-md-8/.col-md-4 fix addresses for 768-991px, this one hits
   every width up to 1200px. main.container carries core Frappe's plain
   `.container` class, which caps its own max-width in four tiers regardless
   of actual viewport room (540px at 576px+, 840px at 768px+, 1090px at
   992px+, 1290px at 1200px+ -- confirmed by fetching website.bundle.css
   directly, not guessed). That tiering makes sense for a page that's still
   visually a centered card (readable line length, doesn't stretch edge to
   edge) -- but /cart no longer reads as a card at all once this rule strips
   its background/border/blur below, so the same narrow tiers now just waste
   width for no visual reason, on top of being real content, not a design
   choice: confirmed live at a real 767px window, main.container capped at
   exactly 540px, leaving .col-md-8 only 476px to work with -- not enough for
   the cart table's own qty column (105px stepper + 12px gap + 32px button,
   already fixed width) plus a readable item title/attribute column, so
   .remove-cart-item spilled past its own card's edge again, independent of
   the column-split fix above. User confirmed live it's fine to widen this
   "card" if that's what fixing the squeeze needs. One generous ceiling
   replaces all four tiers -- comfortably covers every width these
   adaptation fixes needed to cover, without letting the page stretch
   absurdly wide on an ultra-wide monitor the way a bare `max-width: none`
   would. */
html[data-theme$="-dark"] #page-cart main.container,
html[data-theme$="-light"] #page-cart main.container {
	background: transparent;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: none;
	border-radius: 0;
	box-shadow: none;
	max-width: 1600px;
}

/* ERPNext's standard transaction list pages (Orders/Invoices/Quotations/Shipments/
   Timesheets/Material Request/Projects/Issues -- anything rendered by
   erpnext.controllers.website_list_for_contact's get_list_context, identifiable by
   the .website-list wrapper it always renders) get individual per-row glass cards
   instead of one big card behind the whole list -- confirmed live this reads much
   better than a single card once there's more than a couple of rows. The
   :has(.website-list) qualifier keeps this scoped to just those pages; anything
   else keeps the single main.container card above. My Returns now renders its own
   rows through the same .website-list/.web-list-item markup (not erpnext's
   get_list_context -- a plain Jinja loop in www/returns/index.html -- but the same
   wrapper class names, so it inherits this treatment for free instead of needing a
   parallel rule set).

   .website-list itself turned out to carry its own solid background from Frappe's
   core CSS (rgba(255,255,255,.55) in light mode) -- confirmed live via
   getComputedStyle, not assumed -- reading the exact same --fg-color token this file
   already redefines for buttons/dropdowns. Neutralizing main.container alone left
   that solid panel still fully visible behind the row cards; both have to go.

   margin: 0 for the same reason, found later and separately live (reported as "the
   card is snapped to the left, not centered like the heading/search bar above it,"
   on a real phone under 567px wide): erpnext's own erpnext-web.bundle.css carries
   `@media (max-width: 567px) { .website-list { margin-left: -2rem } }`, meant to
   cancel that *same stylesheet's* `.website-list { padding: 0 var(--padding-lg) }`
   or a similar padded ancestor -- a self-consistent "go edge-to-edge on phones" pair
   in erpnext's own design. Since padding is already zeroed here unconditionally
   (above), there was nothing left for that negative margin to cancel; it just
   pulled the whole list (and the row cards inside it) 32px left of everything
   else on the page, confirmed live via getBoundingClientRect (main.container's own
   left edge at 20px vs. .website-list's at -12px, a 32px value matching -2rem
   exactly). Not media-query-scoped on this side -- unconditionally overriding here
   is simpler than mirroring erpnext's own breakpoint, and there's no width above
   567px where a non-zero margin on this element would ever be wanted anyway. */
html[data-theme$="-dark"] main.container:has(.website-list),
html[data-theme$="-light"] main.container:has(.website-list),
html[data-theme$="-dark"] .website-list,
html[data-theme$="-light"] .website-list {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
	padding: 0 !important;
	margin: 0 !important;
}

/* /issues/list never matched any of the above -- confirmed live via getComputedStyle
   that main.container here wraps a .web-list-table, not a .website-list, so it kept
   the default big single-card look (rgba(255,255,255,.08) background still visible
   behind the per-row cards the reflow below already builds) even after that reflow
   shipped. Same "both have to go" lesson as .website-list's own separate solid
   background above: .web-list-container (core's own wrapper div one level inside
   main.container) carries its own independent 0.8px border + 32px padding, confirmed
   live via the DOM chain's computed styles -- stripping main.container alone left
   that second, lighter-colored box still fully visible around the row cards. */
html[data-theme$="-dark"] main.container:has(.web-list-table),
html[data-theme$="-light"] main.container:has(.web-list-table),
html[data-theme$="-dark"] .web-list-container,
html[data-theme$="-light"] .web-list-container {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
	padding: 0 !important;
}

/* /cart's empty state (webshop's own .cart-empty.frappe-card -- a plain bordered
   box with an illustration and "Your cart is Empty") got double-carded: reported
   live with the page's own raw HTML, main.container's default big-glass-card wrapped
   webshop's already-carded empty state, the same "card behind a card" bug class as
   the zero-row list case below and the original /me fullscreen-card fix. Stripped
   the identical way, letting .frappe-card's own native look show directly against
   the page's gradient background instead of sitting inside a second, larger card. */
html[data-theme] main.container:has(.cart-empty) {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
	padding: 0 !important;
}

html[data-theme$="-dark"] .website-list .result,
html[data-theme$="-light"] .website-list .result {
	display: flex;
	flex-direction: column;
	gap: 20px;
	margin-top: 20px;
}

/* .web-list-item.mb-3 is Address's own row shape (erpnext's address_row.html) --
   a different class than every other list page's .transaction-list-item, so it
   never picked up this treatment even though main.container's own card gets
   stripped for :has(.website-list) pages regardless of which row shape they use
   (see that rule above). Confirmed live: real address rows rendered as bare text
   with no card of any kind behind them, on either side of that split -- neither
   this per-row card nor the single main.container card applied. */
html[data-theme$="-dark"] .web-list-item.transaction-list-item,
html[data-theme$="-light"] .web-list-item.transaction-list-item,
html[data-theme$="-dark"] .web-list-item.mb-3,
html[data-theme$="-light"] .web-list-item.mb-3 {
	background: var(--santek-card-bg);
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border: 1px solid var(--santek-card-border);
	border-radius: var(--santek-card-radius-sm);
	padding: 18px 20px;
	/* Blur radius kept smaller than the .result gap above (14px reach vs. 20px gap)
	   on purpose -- confirmed live that a bigger blur (the same 30px main.container
	   uses) bleeds between adjacent cards and merges into what looks like one
	   continuous card behind the whole stack, exactly the thing this rule exists to
	   get rid of. */
	box-shadow:
		inset 0 1px 0 var(--santek-card-highlight),
		0 6px 14px -4px var(--santek-card-shadow);
	position: relative;
	transition: transform 0.15s ease;
}

html[data-theme$="-dark"] .web-list-item.transaction-list-item:hover,
html[data-theme$="-light"] .web-list-item.transaction-list-item:hover,
html[data-theme$="-dark"] .web-list-item.mb-3:hover,
html[data-theme$="-light"] .web-list-item.mb-3:hover {
	transform: translateY(-2px);
}

/* /home's dashboard tiles reuse .website-list/.result/.web-list-item.transaction-
   list-item verbatim (same main.container-strip, same per-row glass+hover, same
   152px sidebar alignment below -- all free for any .website-list page, not
   /home-specific) rather than a bespoke card system -- confirmed live earlier this
   session that main.container's big single-card treatment is the *default*, and
   only .website-list-scoped rules like the one above ever strip it back down, so
   any new page's own markup needs to route through this exact shape or it renders
   nested glass (a big card behind a grid of small ones) instead of one clean grid.
   Scoped to just this page via body[data-path="home"] (same convention as the
   address/issues/returns block below) since .result's default flex-column stacking
   is correct everywhere else -- a grid only makes sense where every item is a
   short, equal-weight tile rather than a full-width transaction row. */
html[data-theme$="-dark"] body[data-path="home"] .website-list .result,
html[data-theme$="-light"] body[data-path="home"] .website-list .result {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: 20px;
}

html[data-theme$="-dark"] .santek-home-card,
html[data-theme$="-light"] .santek-home-card {
	flex-direction: column;
	align-items: flex-start;
	gap: 10px;
	text-decoration: none;
	color: inherit;
}

/* The sprite's own <path> elements carry no fill/stroke attributes at all -- the
   exact bug already found and fixed once this session for .navbar-toggler svg.
   Sized larger (icon-lg, ~28px via Frappe core's own .icon-lg class) and given the
   accent color rather than the muted default every other status/meta icon in this
   app uses, since here the icon IS the tile's primary visual, not a small
   supporting glyph next to real text. */
html[data-theme$="-dark"] .santek-home-card-icon,
html[data-theme$="-light"] .santek-home-card-icon {
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75px;
	stroke-linecap: round;
	stroke-linejoin: round;
	color: var(--santek-accent);
}

/* Sidebar realignment for these per-row-card pages specifically: without
   main.container's own padding pushing it down, the page's <h1> now sits directly
   above .website-list with nothing to visually connect it to the sidebar's top edge
   the way the single-card pages' shared card boundary already did -- confirmed live
   via getBoundingClientRect that the first row card sits ~120px below the sidebar's
   default top before this. Scoped via the same .row:has(.website-list) qualifier so
   pages without a .website-list keep the plain 32px alignment below. */
.row:has(.website-list) .web-sidebar {
	margin-top: 152px;
}

/* Any page carrying the shared filter-pill bar (.santek-list-filters -- My Returns,
   Packaging Designs, ...) between the <h1> and .website-list that Orders/Invoices/etc.
   don't have -- the 152px value above was tuned against Orders' shorter header and
   undershoots here. Aligned to the pill bar's own top edge specifically, not the
   search bar below it (search bar sits between the pills and .website-list, so it
   doesn't affect this measurement). Every page using .santek-list-filters has an
   identical h1 -> filters -> search -> .website-list shape, so one shared value
   works for all of them, not just My Returns. Higher specificity than the rule above
   via the extra :has(), no !important needed.

   Second selector, same declaration: Orders/Quotations/Invoices/Shipments/Material
   Request/Projects/Timesheets don't render .santek-list-filters themselves --
   portal_list_enhancements.js inserts it after the page has already loaded. Matching
   only on .santek-list-filters meant this rule didn't apply until that script ran,
   so the sidebar rendered at 152px on first paint and visibly jumped to 172px the
   instant the filter bar was inserted -- confirmed live as exactly the "sidebar
   ghosting" reported after the filter-pill flicker fix, and the real cause the
   pill-bar CSS/JS changes never touched, since the sidebar's own position was never
   the thing they were fixing. .website-list[data-doctype] is core's own
   templates/includes/list/list.html markup (data-doctype="{{ doctype }}"), already
   present in the server-rendered HTML on all seven of these pages before any script
   runs -- unlike .santek-list-filters, it costs nothing to match from the first
   paint. My Returns/Packaging Designs don't carry this attribute (their own
   .website-list is hand-written Jinja, not core's list.html), so this selector is
   naturally scoped to just the seven core pages, no doctype allowlist needed here. */
.row:has(.website-list):has(.santek-list-filters) .web-sidebar,
.row:has(.website-list[data-doctype]) .web-sidebar {
	margin-top: 172px;
}

/* /issues/list and /address/list belong in this same "real list page" tier -- both
   render an <h1>, a filter/search chrome row, then their own row-card list, the
   identical shape every :has(.santek-list-filters) page above already gets 172px
   for -- but neither carries .website-list or .santek-list-filters at all (Web
   Form-rendered .web-list-table instead, see portal_web_form_list_enhancements.js),
   so neither of the :has()-based selectors above can reach them. Targeted directly
   by data-path instead, same mechanism as the detail-page rule this fixes a
   miscategorization from (see that rule's own 2026-08-22 note). 172px, not a new
   value tuned separately for either page -- confirmed live this lands both
   sidebars at the exact same 220.8px screen position Orders' own sidebar sits at,
   which is what "aligned with every other list page" actually means here, not
   matching either page's own main.container.

   !important is load-bearing, not decorative: both Web Form *list* routes also
   render a real .page-breadcrumbs element (confirmed live -- not just their own
   detail routes, which is what that rule was originally written for), so
   .row:has(.page-breadcrumbs) .web-sidebar's 97px still matches here too, at
   (0,3,0) specificity -- higher than this plain body[data-path=]/.web-sidebar
   selector's (0,2,1) even though that rule's own *reasoning* has nothing to do
   with a list page. Confirmed live without !important this rule was silently
   outranked and had zero effect (sidebar stayed at the old 145.8px). */
body[data-path="issues/list"] .web-sidebar,
body[data-path="address/list"] .web-sidebar {
	margin-top: 172px !important;
}

/* /home -- reported directly: its sidebar "moves a tiny bit" navigating in from
   every other list-shaped page. Root cause: /home has its own hand-written
   .website-list (card tiles, not rows) with neither .santek-list-filters nor
   core's [data-doctype] attribute, so it fell through to the plain 152px
   .row:has(.website-list) rule above (landing at 200.8px) instead of the 172px
   every other list page in the app converges on (220.8px, confirmed live --
   My Returns, Packaging Designs, and the seven core pages via
   [data-doctype] all land there). Home is genuinely shaped like those pages
   (h1, then a grid of tiles, nothing else) -- there was never a structural
   reason for it to be the one outlier. !important needed for the same reason
   as the rule above: the plain 152px rule is (0,3,0) specificity, higher than
   this plain body[data-path=]/.web-sidebar selector's (0,2,0). */
body[data-path="home"] .web-sidebar {
	margin-top: 172px !important;
}

/* /address/new and /issues/new specifically (not /address/<name> or /issues/<name>
   -- viewing an existing record keeps the detail-page 97px rule above, confirmed
   correct and untouched) -- reported directly: the sidebar visibly jumps up when
   navigating from the list page (Addresses/Issues, sidebar at 220.8px) into "New",
   which otherwise gets the plain detail-page treatment (145.8px) like every other
   single-record view. Scoped narrowly to this one pair per the user's own explicit
   choice: keep the sidebar consistent between a list page and the "new" page you
   reach it from, not force one universal position across every page shape (which
   would misalign the sidebar from its own page's content on pages like My Returns,
   where the header above the content is genuinely taller). Same 172px/!important
   as the list routes immediately above -- this intentionally leaves the sidebar
   sitting lower than main.container on this one page (matching how the list page
   itself already doesn't align main.container to the sidebar either, per that
   rule's own note), trading this page's own internal alignment for a seamless
   transition from the list it's almost always reached from. */
body[data-path="address/new"] .web-sidebar,
body[data-path="issues/new"] .web-sidebar {
	margin-top: 172px !important;
}

/* /address/<name> and /issues/<name> (viewing an existing record) -- "best of
   both" per direct user feedback, superseding an earlier version of this rule
   (210px, sidebar-only) that traded away the list-page-matching sidebar
   position to align with .web-form-header instead. Reported directly that the
   resulting jump from Addresses/Issues (sidebar at 220.8px) into a selected
   record (was 258.8px) was still noticeable -- but the user wanted BOTH: no
   jump from the list, *and* the heading aligned with the sidebar on this page
   too, not just the card's outer edge.

   Solved by moving both halves to the same 220.8px target instead of moving
   only one of them to match the other:
   - .web-sidebar gets the same 172px the list routes use (matches
     body[data-path="address/list"]/"issues/list" above) instead of a value
     tuned to this page's own header.
   - main.container's margin-top drops from the generic detail-page 97px to
     59px, which -- given this Web Form template's fixed 113px internal offset
     from main.container's own top to .web-form-header's top (a 49px
     .breadcrumb-container trail plus the card's own padding) -- lands
     .web-form-header at the identical 220.8px the sidebar now sits at.
   Both confirmed live via javascript_tool injection before writing this: sidebar
   and header measured 220.8px/220.8px afterward, on both a real Address and a
   real Issue record. Deliberately NOT applied to /address/new or /issues/new,
   which already have their own separate, already-correct list-matching
   treatment (sidebar only, see above) confirmed with the user earlier. */
body[data-path^="address/"]:not([data-path$="/list"]):not([data-path="address/new"]) .web-sidebar,
body[data-path^="issues/"]:not([data-path$="/list"]):not([data-path="issues/new"]) .web-sidebar {
	margin-top: 172px !important;
}

body[data-path^="address/"]:not([data-path$="/list"]):not([data-path="address/new"]) main.container,
body[data-path^="issues/"]:not([data-path$="/list"]):not([data-path="issues/new"]) main.container {
	margin-top: 59px !important;
}

/* erpnext's shared single-record detail page (order.html -- Sales Order/Purchase
   Order/Quotation/Sales Invoice/... all render through this one core template) has
   a .page-breadcrumbs trail above main.container that list pages don't have,
   confirmed live via getBoundingClientRect: sidebar and main.container tops were
   65px apart with only the base 32px rule below applied. Same :has() scoping
   pattern as the two rules above, just keyed off a different page shape.

   2026-08-22: :not(:has(.website-list)) added after /home (a new page, built on
   templates/web.html like every other custom page in this app) turned out to also
   render a real .page-breadcrumbs element -- confirmed live -- despite being a
   card-grid list page, not a detail page. Nothing about that trail is actually
   detail-page-specific; it comes from the shared base template every custom page
   here extends, this is just the first page to combine it with .website-list.
   Before this exclusion, /home's sidebar landed at 97px instead of the 152px the
   plain :has(.website-list) rule above intends, because both rules tie at (0,3,0)
   specificity and this one happens to come later in the file, winning the tie by
   source order alone -- confirmed live via getComputedStyle, not assumed. Returns/
   Freight/Packaging Designs never hit this because they also carry
   .santek-list-filters, which pushes them onto the (0,4,0) 172px rule above,
   masking the same underlying tie rather than avoiding it. A true detail page
   never renders .website-list at all, so this exclusion can't misfire on any of
   this rule's real, intended targets (Order/Address/Issue/Return detail views). */
.row:has(.page-breadcrumbs):not(:has(.website-list)) .web-sidebar {
	margin-top: 97px;
}

/* ...and the card has to come up to meet it on this app's own detail pages.

   The rule above puts the sidebar at the universal detail-page position; it never
   touched main.container, which keeps the base 32px. On erpnext's own order.html
   that is already right (its template carries ~65px of markup above the card). On
   the four detail pages this app routes itself -- returns/detail,
   packaging-designs/detail, freight/detail, packing-declaration/detail, all built
   straight on templates/web.html with nothing above .page_content -- the card lands
   65px ABOVE the sidebar instead of level with it. Measured live, not estimated:
   card top 81px vs. sidebar top 146px, identical on packing-declaration/detail and
   on packaging-designs/detail, so this was never new to the newest page. Reported as
   "the card in backround is not inline with the sidebar".

   One value for the sidebar and the card, because both are children of the same
   .row: equal margin-top means equal top whatever each one contains, which is why a
   single number covers all four pages rather than needing per-page tuning. 172px
   rather than the sidebar's own 97px, so that a detail page's sidebar lands where a
   *list* page's sidebar already sits -- see the rule below for why that matters more
   than matching the neighbouring detail pages did.

   Scoped two ways on purpose. data-path$="/detail" is exactly this app's
   website_route_rules detail templates and nothing of erpnext's, whose paths carry
   the document name instead. The :has() half repeats the sidebar rule's own
   qualifier so the card can only move on a page where that rule actually put the
   sidebar at 97px -- the two values are one decision and should fail together, not
   drift apart.

   Same principle as the /address/<name> fix below: move the card to the sidebar, not
   the sidebar to the card, so the left nav stays where it is on every page. */
body[data-path$="/detail"] .row:has(.page-breadcrumbs):not(:has(.website-list)) .web-sidebar,
body[data-path$="/detail"] .row:has(.page-breadcrumbs):not(:has(.website-list)) main.container {
	margin-top: 172px;
}

/* The back link belongs above the card, not inside it.

   "maybe we can lower the card in the back and leave Packing Decleration back
   button out above it and lower the side bar accordingly too because sidebar moves
   between two pages" -- and the sidebar movement is the load-bearing half. A list
   page's sidebar sits at 221px (the 172px rule further up); a detail page's sat at
   146px, so walking from /packing-declaration into a shipment and back visibly
   shifted the whole left nav. 172px above puts both at 221px, so it now stays put
   across that navigation -- and the card comes with it, since both are children of
   the same .row and equal margins mean equal tops.

   That leaves the back link, which would otherwise be the first thing inside a card
   whose top edge is now 75px lower. Lifted out of the card entirely by absolute
   positioning against main.container (already position: relative, and overflow:
   visible, so nothing clips it): it sits on the page background above the card,
   where the list page's own <h1> sits, and the card's first in-flow child is the
   title again. -42px lands it at 180px, measured, with left: 32px matching
   main.container's own 32px padding so it lines up with the content below it.

   The title stays INSIDE the card -- it was briefly moved out with the link and put
   back on request. Only the back link lives out here, and that keeps this simple: it
   is one short line on every page, so it can never wrap into the navbar the way a
   record-name title could. (It was tried: /returns/<name>'s "<item name> — <serial>"
   at the inherited 3rem wraps to three lines and reaches 9px against a navbar ending
   at 49px.)

   `bottom: 100%` rather than a negative `top`: anchored to the card's top edge, so it
   sits directly above it whatever the card's own margin happens to be, and grows
   upward rather than down over the card if it ever does wrap.

   A class rather than :first-of-type: /freight/<name> renders a language switcher
   above its back link, so a positional selector would move the wrong element there. */
.santek-detail-back {
	position: absolute;
	bottom: 100%;
	left: 32px;
	right: 32px;
	margin: 0 0 14px;
}

/* /address/<name> (erpnext's core "addresses" Web Form) also renders a
   .page-breadcrumbs trail, matching the rule above, so its sidebar already lands at
   the same 97px-margin position as every other detail page's -- confirmed live via
   getBoundingClientRect, same absolute sidebarTop as order.html's. What's actually
   misaligned is main.container: this page's own Web Form template carries less
   markup/padding above the card than order.html's does, so the card itself sits
   65px higher here than it does on every other detail page, landing 65px above the
   sidebar instead of level with it (confirmed live: 80.8px vs. the 145.8px both the
   sidebar and order.html's own card sit at).

   An earlier version of this fix moved the *sidebar* down here instead, to
   80.8px, matching this one page's own card -- passing "aligned with its own card"
   but failing "consistent with where every other page's sidebar already sits",
   which is what was actually reported: the sidebar itself moving depending on which
   page you'd navigated in from. Pushing the *card* down to the universal 145.8px
   instead keeps the sidebar at the one position it's always at, on every detail
   page including this one, with the card brought to meet it rather than the other
   way around. body[data-path^="address/"] is set by frappe itself
   (base.html's data-path="{{ path | e }}") -- no JS needed to scope this.

   issues/ (erpnext's core "issues" Web Form -- /issues/list, /issues/new,
   /issues/<name>, /issues/<name>/edit) has the exact same gap for the exact same
   reason: another Web Form built on the same compact template, confirmed live at
   the identical 80.8px/145.8px split across all four of those routes, not just the
   one detail-page shape checked for Address.

   returns/ (this app's own www/returns/detail.html, not an erpnext core template)
   has the identical shape for the identical reason -- confirmed live via
   getBoundingClientRect: .row:has(.page-breadcrumbs) already puts the sidebar at
   the same universal 145.8px (its own breadcrumb trail matches that selector), but
   main.container sat at 80.8px, the same 65px short as Address/Issues, since this
   template's own markup above the timeline card is just as compact as those two
   Web Forms'. Body's data-path for this route is "returns/detail" (Frappe resolves
   it from the underlying page name, not the record in the URL), so the ^= prefix
   match still works the same way it does for address/issues.

   2026-08-22: /issues/list and /address/list were reported as misaligned relative
   to every other portal list page (Orders/Returns/Freight/...), the sidebar visibly
   sitting at a different height depending on which page you navigated in from --
   the same class of bug this file's own sidebar-ghosting history already documents.
   Root cause: the "confirmed live" note two paragraphs up checked the wrong thing.
   It verified that /issues/list's own sidebar and main.container agreed with EACH
   OTHER (both landing at 145.8px) -- genuine internal consistency, but never
   compared against how every *other* list page aligns its own sidebar (172px,
   via the real list-page rule below, landing at 220.8px -- a deliberately
   *different* value from main.container's own 80.8px, since list pages don't try
   to make the two match at all). This 97px rule's own reasoning is specific to the
   single-record *detail* template shape (a compact card with a `.page-breadcrumbs`
   trail) -- /issues/list and /address/list happen to share the "issues/"/"address/"
   data-path *prefix* with their own detail routes (Web Forms name their list route
   "<name>/list", right under the same prefix) but are a completely different page
   shape (a real list, no breadcrumbs, no single card), so this rule was never
   correctly scoped for them in the first place -- it only ever looked right because
   nothing had compared it against a real list page's own sidebar position before.
   /returns/list has no equivalent problem: that route's own data-path is plain
   "returns" (this app's own www/returns/index.py, not a Web Form's "<name>/list"
   convention), which never matched this prefix to begin with. Excluding "/list"
   here, confirmed live, lets main.container fall through to its real, unstyled
   default position -- 80.8px, exactly matching Orders' own -- with no replacement
   rule needed; the actual sidebar realignment these two routes need lives in the
   real list-page rule below instead. */
html[data-theme$="-dark"] body[data-path^="address/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-dark"] body[data-path^="issues/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-dark"] body[data-path^="returns/"] main.container,
html[data-theme$="-light"] body[data-path^="address/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-light"] body[data-path^="issues/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-light"] body[data-path^="returns/"] main.container {
	margin-top: 97px;
}

/* /address/new, /address/<name>, /issues/new, and /issues/<name> (Frappe's own
   generic document-edit-on-website "Web Form" render for these two core
   doctypes, not a page this app builds) render their own inner card already --
   the "Not Saved" badge, fields, Edit button (Address) / Comments section
   (Issue) -- confirmed live that main.container's default frosted-card
   treatment around it duplicated that, a card nested inside a card. Reported
   directly for Address first, then confirmed live (via javascript_tool
   injection on a real Issue detail page before writing this) that Issues has
   the identical gap for the identical reason, per this file's own earlier note
   on the margin-top rule above ("issues/ ... has the exact same gap for the
   exact same reason: another Web Form built on the same compact template").
   Stripped the outer card the same way #page-login/.error-page already are;
   padding is deliberately left alone (not that shape) since this page's own
   layout, like /cart's, was built assuming main.container's own 2rem padding
   stays in place to space the inner card away from the edge -- only the outer
   "box" itself goes away. Scoped to these two routes only, not the sibling
   returns/ detail route sharing the margin-top rule above, which keeps its own,
   deliberately-kept single-card design. */
html[data-theme$="-dark"] body[data-path^="address/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-dark"] body[data-path^="issues/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-light"] body[data-path^="address/"]:not([data-path$="/list"]) main.container,
html[data-theme$="-light"] body[data-path^="issues/"]:not([data-path$="/list"]) main.container {
	background: transparent !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
	border: none !important;
	box-shadow: none !important;
}

/* A zero-row transaction list (Orders/Invoices/Purchase Invoices/Supplier
   Quotation/Request for Quotation/Addresses/...) needs the exact same treatment as
   a populated one, not a shorter single card: core's list.html only ever emits the
   .website-list[data-doctype] wrapper when there's at least one row -- confirmed
   live via querySelector, not assumed -- so on a zero-row page (no data at all, or
   a status filter that matches nothing) the :has(.website-list) rule above never
   fires, and main.container was falling back to its default padded single-card
   look: a big card wrapping just the headline and an empty "Nothing to show" line.
   Explicitly asked to remove that, not just realign it -- an earlier pass here
   pushed the card down 97px to line its top edge up with the sidebar instead,
   which "fixed" the alignment but kept the card itself, which was never the actual
   ask. Stripped here the identical way :has(.website-list) already is, keyed on
   the same "filters present, list wrapper absent" signal the sidebar rule below
   uses, since every one of these pages shares the generic data-path="portal" and
   can't be scoped by body[data-path^=...] the way address/issues/returns' own
   compact-template fix above is. */
html[data-theme$="-dark"] main.container:has(.santek-list-filters):not(:has(.website-list)),
html[data-theme$="-light"] main.container:has(.santek-list-filters):not(:has(.website-list)) {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
	padding: 0 !important;
}

/* Sidebar counterpart: with main.container's own padding gone, this zero-row case
   needs the same 172px the populated .santek-list-filters + .website-list[data-
   doctype] combination already uses (see that rule's own comment above), not the
   152px base .website-list value -- every one of these pages has the identical
   h1 -> filters -> search -> (list or empty state) shape whether or not any row
   actually rendered. */
.row:has(.santek-list-filters):not(:has(.website-list)) .web-sidebar {
	margin-top: 172px;
}

/* Lines the sidebar card's top edge up with main.container's -- .page-content-wrapper
   (main.container's parent) carries its own top padding that .web-sidebar's column
   doesn't share, confirmed live via getBoundingClientRect (32px gap between the two
   columns' actual top edges before this).

   Sticky, not just aligned: confirmed live with 46 stress-test rows injected into a
   real Orders page that a plain statically-positioned sidebar just scrolls out of
   view once the content card grows past one screen, leaving a blank gap in its
   column for the rest of the page -- neither card was ever going to grow to match
   the other's height (the sidebar's own link list is a fixed length regardless of
   how many orders exist). Sticky keeps navigation reachable the whole time you're
   scrolling a long list, the standard pattern for this exact list+nav shape. Capped
   at the content card's own height (max-height: 100%, an ancestor with the matching
   height already exists via the shared .row) so it doesn't stick past a *short*
   content card either.

   isolation/will-change -- reported live on a real iPad (desktop-width layout,
   sidebar always visible, not the mobile drawer): a visible horizontal line
   appeared across the sidebar specifically when scrolling the page with it. Same
   bug class already found and fixed once this session for the mobile nav drawer --
   position: sticky (or fixed) combined with backdrop-filter (set above) is a
   documented WebKit compositing weak spot, where Safari can fail to keep the
   blurred backdrop correctly composited as a sticky element's offset changes
   during scroll, leaving a visible seam at the boundary of whatever region it last
   composited correctly. Backdrop-filter can't just be dropped here the way it was
   on the drawer -- unlike the drawer's near-opaque background, this sidebar's is
   deliberately translucent (var(--santek-card-bg), 8%/42% dark/light) specifically
   so the frosted-glass look works against the page's own colorful gradient; losing
   the blur would leave plain muted-gray text sitting directly on that gradient,
   unreadable wherever a saturated blob shows through (see this rule's own header
   comment above). isolation + will-change pins it to a stable compositing layer
   instead, the same mitigation used for the drawer, without touching the blur. */
html[data-theme$="-dark"] .web-sidebar,
html[data-theme$="-light"] .web-sidebar {
	margin-top: 32px;
	position: sticky;
	top: 16px;
	max-height: calc(100vh - 32px);
	overflow-y: auto;
	isolation: isolate;
	will-change: transform;
}

/* erpnext's shared order.html detail page (Sales Order/Purchase Order/Quotation/
   Sales Invoice/... all render through this one core template) -- each line item
   is its own .order-items div (a plain Bootstrap row with no card styling at all
   originally), reused as-is for the header row too, distinguished only by whether
   it actually contains an item (.order-item-name). Gives real line items the same
   per-row glass-card treatment as the list pages' .web-list-item, so a document's
   own item list reads consistently with everywhere else in the portal; the header
   row is left as a plain muted label row above the cards, not carded itself. */
/* color: core's own order.html sets an explicit `.order-items { color: #525252 }`
   (confirmed live by walking the DOM ancestor chain in getComputedStyle -- every
   descendant span, from .order-item-name through the qty/rate/amount columns,
   inherits that literal gray with no per-span rule of its own overriding it) --
   dark gray text on this theme's dark glass card read as almost no contrast at
   all ("items ... not visible correctly, no contrast"). Same specificity shape as
   every other override in this section (an html[data-theme] type+attribute
   selector plus two classes beats core's single plain class), so plain
   var(--text-color) is enough here, no !important needed. The header row
   (.order-items.order-item-header, not matched by :has(.order-item-name)) never
   had this problem -- confirmed live it already inherits the correctly-themed
   --text-muted from further up the tree, since core has no equivalent explicit
   color rule scoped to that class combination. */
html[data-theme$="-dark"] .order-items:has(.order-item-name),
html[data-theme$="-light"] .order-items:has(.order-item-name) {
	background: var(--santek-card-bg);
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border: 1px solid var(--santek-card-border);
	border-radius: 14px;
	padding: 14px 12px !important;
	margin-bottom: 12px;
	box-shadow:
		inset 0 1px 0 var(--santek-card-highlight),
		0 6px 14px -4px var(--santek-card-shadow);
	transition: transform 0.15s ease;
	align-items: center !important;
	color: var(--text-color);
}

html[data-theme$="-dark"] .order-items:has(.order-item-name):hover,
html[data-theme$="-light"] .order-items:has(.order-item-name):hover {
	transform: translateY(-2px);
}

.order-items.order-item-header {
	opacity: 0.6;
	font-size: 0.85em;
	padding: 0 12px 8px 12px !important;
}

/* The item thumbnail/placeholder box distorted into a tall, narrow rectangle the
   moment the row above got taller (card padding) -- it stretches via .h-100 to
   match whatever height its row happens to have, with no aspect-ratio of its own,
   and the "no image" placeholder inside it carries a *hardcoded inline*
   `min-height: 100px` (core markup, confirmed live via getAttribute("style")) that
   overrides a plain `height` on the same element regardless of specificity or
   !important -- min-height has to be overridden explicitly too, not just height. */
.order-image.h-100 {
	height: 56px !important;
	width: 56px !important;
	max-width: 56px;
	flex: 0 0 56px;
	border-radius: 8px;
}

.order-image.h-100 .no-image-cart-item {
	width: 56px !important;
	height: 56px !important;
	min-height: 56px !important;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* The "Actions" dropdown on this same detail page kept Bootstrap's stock
   .btn-secondary light-gray background regardless of theme, while this file's own
   global text-color override still applies to it -- confirmed live via
   getComputedStyle that dark theme rendered near-white text on a near-white
   background, an invisible button. Scoped to this page's own actions block, not a
   blanket .btn-secondary override that could affect buttons elsewhere.

   Unscoped from html[data-theme=...] originally, this rule and core's own
   `.btn.btn-secondary { background-color: var(--control-bg); color:
   var(--text-color); }` (frappe's desk-button styling, also loaded on portal pages)
   land at the identical (0,2,0) specificity -- two classes each -- so whichever one
   the bundler happens to place later in the cascade wins outright, no partial
   blend. Confirmed live via a full stylesheet scan
   (document.styleSheets/cssRules, matched against the real button with
   Element.matches) that core's rule was the one winning: its var(--control-bg)
   isn't a token this app redefines, so it resolved to core's own light desktop
   gray (rgb(243,243,243)) while var(--text-color) *is* one this app redefines --
   picking up this theme's near-white override -- combining into white text on a
   near-white pill, invisible regardless of which theme was active. Adding the
   html[data-theme] type+attribute selector this file uses everywhere else brings
   specificity to (0,2,1), a clean win with no !important needed; kept anyway on
   the color-bearing properties as insurance against build-order surprises, same
   belt-and-suspenders posture the rest of this file already uses against core. */
html[data-theme$="-dark"] .page-header-actions-block .btn-secondary,
html[data-theme$="-light"] .page-header-actions-block .btn-secondary {
	background-color: var(--santek-input-bg, rgba(255, 255, 255, 0.06)) !important;
	border: 1px solid var(--santek-card-border, rgba(255, 255, 255, 0.16)) !important;
	color: var(--text-color, #f4f6fb) !important;
}

html[data-theme$="-dark"] .page-header-actions-block .btn-secondary:hover,
html[data-theme$="-light"] .page-header-actions-block .btn-secondary:hover {
	background-color: var(--fg-color, rgba(255, 255, 255, 0.12)) !important;
}

/* /login's own card (Frappe core markup, not this app's): centered on the viewport
   (this Frappe version has no .page-card-container to center against -- confirmed
   live -- so .for-login centers itself), with the site chrome's palette carried
   through to its inputs/labels/button. It has NO box of its own any more: the frosted
   panel pinned to the viewport ran up under the language and theme selectors on a
   phone (the Santek ERP app shows this page) and collided with them, and this app's
   other pages already dropped their big card. The form floats on the brand background. */
html[data-theme$="-dark"] .for-login,
html[data-theme$="-light"] .for-login {
	position: fixed !important;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 2;
	width: calc(100% - 40px);
	max-width: 440px;
	max-height: calc(100vh - 40px);
	overflow: auto;
	scrollbar-width: none;
	background: transparent !important;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: 0;
	box-shadow: none !important;
}

/* Frappe's own core CSS gives .login-content.page-card (the inner div actually
   holding the form -- .for-login above is just its outer wrapper) a hardcoded solid
   white background. Left alone, that solid card sits on top of .for-login's glass
   and hides it entirely except for a thin sliver at the very edge -- confirmed live
   via getComputedStyle (background: rgb(255, 255, 255), not any of this file's
   variables) rather than assumed. Making it transparent is what actually lets the
   glass show through the whole card, not just its border. */
/* The wordmark above "Sign In" is about 4:1, not the square icon core sizes this slot for. */
html[data-theme$="-dark"] .for-login .page-card-head .app-logo,
html[data-theme$="-light"] .for-login .page-card-head .app-logo {
	display: block;
	width: min(240px, 80%);
	height: auto;
	max-height: none;
	margin: 0 0 56px;
}

html[data-theme$="-dark"] .login-content.page-card,
html[data-theme$="-light"] .login-content.page-card {
	background: transparent !important;
}

html[data-theme$="-dark"] .for-login .page-card-head h4,
html[data-theme$="-dark"] .for-login h4,
html[data-theme$="-light"] .for-login .page-card-head h4,
html[data-theme$="-light"] .for-login h4 {
	color: var(--text-color);
}

html[data-theme$="-dark"] .for-login .page-card-subtitle,
html[data-theme$="-dark"] .for-login .form-label,
html[data-theme$="-light"] .for-login .page-card-subtitle,
html[data-theme$="-light"] .for-login .form-label {
	color: var(--text-muted) !important;
}

html[data-theme$="-dark"] .for-login .btn-login,
html[data-theme$="-dark"] .for-login button[type="submit"],
html[data-theme$="-light"] .for-login .btn-login,
html[data-theme$="-light"] .for-login button[type="submit"] {
	background: color-mix(in srgb, var(--santek-accent) 85%, transparent) !important;
	backdrop-filter: var(--santek-btn-blur, blur(10px));
	-webkit-backdrop-filter: var(--santek-btn-blur, blur(10px));
	border-color: var(--santek-accent) !important;
	box-shadow: inset 0 1px 0 var(--santek-btn-glow, rgba(255, 255, 255, 0.35));
}

/* /returns' "File a New Return" button, same core-doesn't-know-this-theme's-
   tokens problem as .btn-secondary above -- reported directly by the user as
   hard to see on dark theme. Core's stock .btn-primary blue has no awareness of
   this app's accent palette; reuses the exact glass-button treatment already
   proven on /login's own submit button above rather than inventing a second
   pattern. Scoped to this class, not a blanket .btn-primary override -- also
   applied (via portal_web_form_list_enhancements.js's styleNewButton) to
   /issues/list's and /address/list's own native "New" buttons, per the user's
   direct request for the same blue treatment there instead of core's plain
   black .btn-primary. /packing-declaration's own two actions (open the form,
   submit the declaration) join the same list for the same reason -- they were
   reported live as "buttons are black not aligned with our other blue buttons",
   which is this exact rule not having been extended to a new page.
   /packaging-designs/<name>'s "Download All (ZIP)" is the same story again, spotted
   while checking that page for something else: a raw .btn-primary rendering solid
   black on a portal that never themed it. Four pages now share this one rule rather
   than four private copies, which is the whole point of it being a list. */
html[data-theme$="-dark"] .santek-returns-new-btn,
html[data-theme$="-light"] .santek-returns-new-btn,
html[data-theme$="-dark"] .santek-decl-btn,
html[data-theme$="-light"] .santek-decl-btn,
html[data-theme$="-dark"] .santek-pd-download-all-btn,
html[data-theme$="-light"] .santek-pd-download-all-btn {
	background: color-mix(in srgb, var(--santek-accent) 85%, transparent) !important;
	backdrop-filter: var(--santek-btn-blur, blur(10px));
	-webkit-backdrop-filter: var(--santek-btn-blur, blur(10px));
	border-color: var(--santek-accent) !important;
	box-shadow: inset 0 1px 0 var(--santek-btn-glow, rgba(255, 255, 255, 0.35));
}

html[data-theme$="-dark"] .santek-returns-new-btn:hover,
html[data-theme$="-light"] .santek-returns-new-btn:hover,
html[data-theme$="-dark"] .santek-decl-btn:hover,
html[data-theme$="-light"] .santek-decl-btn:hover,
html[data-theme$="-dark"] .santek-pd-download-all-btn:hover,
html[data-theme$="-light"] .santek-pd-download-all-btn:hover {
	background: var(--santek-accent) !important;
}

/* Persistent brand mark, top-left, on every portal page. Server-rendered now
   (events/website.py's brand_html, consumed by core's own .navbar-brand slot in
   navbar.html) instead of JS-inserted -- portal_theme_toggle.js used to build and
   insert a single <img> here on every page load, which raced first paint the same
   way the theme toggle/language picker did (see templates/includes/navbar/
   navbar_items.html's own header comment for the full "assets load in a different
   location for an instant" writeup). Not a position:fixed overlay at a guessed
   left:16px either, which drifted out of alignment with .navbar-brand's actual
   position (.navbar > .container is a centered, width-capped Bootstrap container,
   confirmed live via getBoundingClientRect: it starts well in from the true
   viewport edge at common widths). vertical-align: middle lines it up with the
   "Home" text next to it.

   Both light/dark variants ship inline in the initial HTML now instead of one
   <img> whose `src` JS swapped on every santek-theme-changed event -- shown/hidden
   by the same html[data-theme=...] convention every other themed rule in this file
   already uses, so there's nothing left to swap or insert client-side on a normal
   page. portal_theme_toggle.js's buildMark() still keeps a floating single-<img>,
   JS-swapped fallback for /login specifically, which has no .navbar-brand at all
   to render brand_html into (confirmed live -- nav.navbar doesn't exist there). */
#santek-portal-mark {
	display: inline-block;
	width: 22px;
	height: 22px;
	margin-right: 8px;
	vertical-align: middle;
}

.santek-portal-mark-img {
	display: block;
	width: 100%;
	height: 100%;
}

/* Core's own website.bundle.css ships ".navbar-brand img { display: inline-block;
   max-width: 150px; max-height: 22px; }" -- a class+tag selector that outranks the
   plain single-class rule above ((0,1,1) beats (0,1,0)), so the mark was actually
   rendering inline-block despite this file explicitly asking for block. Confirmed
   live via getComputedStyle + a stylesheet scan for the winning rule, not guessed:
   an inline-block replaced element gets baseline vertical-align by default, which
   left a few px of phantom space under the image inside its own 22px wrapper --
   the wrapper itself sat correctly on vertical-align: middle, but the image inside
   it was riding a few px lower than the wrapper's own box, reading as "the icon
   isn't aligned with the nav text next to it". Re-declaring display: block at
   higher specificity (two classes beats one class + one tag) removes the
   baseline-alignment quirk entirely rather than trying to offset around it with a
   margin/transform nudge. */
.navbar-brand .santek-portal-mark-img {
	display: block;
}

html[data-theme$="-dark"] .santek-portal-mark-img--light,
html[data-theme$="-light"] .santek-portal-mark-img--dark {
	display: none;
}

/* Fallback for pages with no .navbar-brand to live inside (e.g. /login) -- applies
   whether the fallback is the wrapper <span> (never reached on that page, kept for
   selector symmetry) or the single JS-built <img> buildMark() actually uses there. */
#santek-portal-mark.santek-portal-mark--floating {
	position: fixed;
	top: 16px;
	left: 16px;
	width: 28px;
	height: 28px;
	z-index: 1040;
	margin-right: 0;
}

/* /issues/list -- erpnext's core "issues" Web Form's own list view, rendered
   entirely client-side by Frappe's web_form.bundle.js (no page template of this
   app's own to swap out, unlike /addresses, which is a real generic-list route this
   app already controls -- see overrides/portal_list_filters.py). Asked to look like
   /addresses; decided live with the user to get there via a CSS-only reflow of the
   real <table> Frappe already renders, not by hooking into and partially rebuilding
   that internal, undocumented bundle's own DOM/data flow, which would be fragile
   against any future Frappe upgrade for comparatively little visual gain over this.
   Search-as-you-type is out of scope for the same reason: this table has no
   built-in free-text search to attach to, only the one Status filter field
   configured on the Web Form itself (already themed for free by the generic
   select/.form-control glass rules above -- nothing extra needed here for that).

   2026-08-22: the row-level reflow below shipped without also stripping the
   *outer* big-card look every other list page already loses -- main.container and
   .web-list-container (see the .website-list rule's own sibling fix above) were
   never in scope of that selector, since this page has neither .website-list nor
   .santek-list-filters. Confirmed live main.container still carried the default
   rgba(255,255,255,.08) card background behind these per-row cards until that was
   added.

   Sidebar alignment was believed to need no separate fix at the time -- wrong,
   corrected the same day once real filter pills/search chrome existed to compare
   against every other list page's own sidebar position with. See the detail-page
   97px rule's own dated note (above, near .row:has(.page-breadcrumbs)) for the full
   story: that rule's "confirmed live" check only verified /issues/list's sidebar
   and main.container agreed with *each other* (145.8px both), never that this
   matched how Orders/Returns/Freight/etc. actually align *their* sidebars (172px,
   landing at a deliberately different 220.8px, unrelated to where their own
   main.container sits). Fixed there, plus a new dedicated
   body[data-path="issues/list"]/body[data-path="address/list"] sidebar rule near
   the other 172px list-page rule -- not here, since this section only ever owned
   the row-level reflow and the big-card strip, not sidebar positioning.

   Confirmed live the same day: this section's row-level reflow and the big-card
   strip above were never actually Issues-specific in the first place -- they key
   off the generic .web-list-table/.web-list-container classes, so /address/list
   (erpnext's other core Web Form built on this exact same shape) already inherited
   both for free, with no separate rule needed. (Sidebar alignment did *not* carry
   over this cleanly the same way -- see the dated notes on the detail-page 97px
   rule and the 172px list-page rule for why that one needed its own dedicated fix
   instead of falling out of a shared class selector.) What Address *didn't* get
   automatically -- the filter-pill bar and search box -- lives in
   public/js/portal_web_form_list_enhancements.js
   (renamed from portal_issues_enhancements.js once Address became its second real
   caller), which builds a pill bar only when a native <select> exists in
   .web-list-filters (Address's own Web Form has none, confirmed live via an empty
   .web-list-filters innerHTML) and a search box unconditionally either way.

   Classic CSS table-to-card reflow: tr becomes the flex row that gets the actual
   glass-card treatment (background/border/blur/hover), matching every other
   per-row card elsewhere in this app almost exactly; td/th drop their table
   display so they lay out as flex children instead of table cells. thead is
   hidden outright rather than restyled -- "Sr. / Subject / Status / Raised By
   (Email) / Priority" as a second header row read as visual noise once each row
   is already a self-labeled card, the same call already made for Address's own
   row shape. */
.web-list-table table {
	display: block;
	width: 100%;
	border-collapse: collapse;
}

.web-list-table thead {
	display: none;
}

.web-list-table tbody {
	display: block;
	width: 100%;
}

html[data-theme$="-dark"] .web-list-table tbody tr,
html[data-theme$="-light"] .web-list-table tbody tr {
	display: flex;
	align-items: center;
	gap: 12px;
	background: var(--santek-card-bg);
	backdrop-filter: var(--santek-blur);
	-webkit-backdrop-filter: var(--santek-blur);
	border: 1px solid var(--santek-card-border);
	border-radius: 14px;
	padding: 14px 18px;
	margin-bottom: 12px;
	box-shadow:
		inset 0 1px 0 var(--santek-card-highlight),
		0 6px 14px -4px var(--santek-card-shadow);
	transition: transform 0.15s ease;
}

html[data-theme$="-dark"] .web-list-table tbody tr:hover,
html[data-theme$="-light"] .web-list-table tbody tr:hover {
	transform: translateY(-2px);
}

.web-list-table tbody td {
	display: block;
	border: none !important;
	padding: 0 !important;
}

/* Checkbox and Sr. columns are short, fixed-content -- flex:1 like every other
   column would leave a wide empty gap next to a single digit. Every remaining
   column (Subject, Status, Raised By, Priority, ...) shares the space evenly,
   whatever fields this Web Form happens to be configured to list. */
.web-list-table tbody td.list-col-checkbox {
	flex: 0 0 auto;
}

.web-list-table tbody td.list-col-serial {
	flex: 0 0 32px;
	color: var(--text-muted);
}

.web-list-table tbody td:not(.list-col-checkbox):not(.list-col-serial) {
	flex: 1 1 0;
	min-width: 0;
}

.web-list-table tbody td p.ellipsis {
	margin: 0;
}

/* The Status filter select + "New" button sit right above the cards now that the
   header row is gone -- lines them up on one row instead of the default stacked
   layout, matching the search-bar-then-list rhythm every other list page in this
   app already uses. */
.web-list-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: 12px;
}

.web-list-filters {
	margin-bottom: 20px;
}

/* Reduced-motion pass, 2026-08-22 audit -- neutralizes only `transform` on the
   three hover-lift rules above (.web-list-item.transaction-list-item/.mb-3,
   .order-items:has(.order-item-name), .web-list-table tbody tr), not a blanket
   `transition-duration: 0.01ms` kill on every animation site-wide -- that pattern
   destroys real feedback (e.g. it would also gut portal_page_transitions.js's
   opacity-based entrance, whose cleanup depends on a real transitionend firing;
   see that script's own reveal() for why a synchronous zero-duration transition
   there would leave stale classes behind instead of cleaning up). Hover-lift
   carries no information a user needs -- the row is still fully identifiable and
   clickable without the 2px rise -- so simply dropping the transform is a safe,
   complete "intentional alternative" here, not a partial fix. */
@media (prefers-reduced-motion: reduce) {
	html[data-theme$="-dark"] .web-list-item.transaction-list-item:hover,
	html[data-theme$="-light"] .web-list-item.transaction-list-item:hover,
	html[data-theme$="-dark"] .web-list-item.mb-3:hover,
	html[data-theme$="-light"] .web-list-item.mb-3:hover,
	html[data-theme$="-dark"] .order-items:has(.order-item-name):hover,
	html[data-theme$="-light"] .order-items:has(.order-item-name):hover,
	html[data-theme$="-dark"] .web-list-table tbody tr:hover,
	html[data-theme$="-light"] .web-list-table tbody tr:hover {
		transform: none !important;
	}
}

/* /me -- core Frappe's own "My Account" settings page. Reported live, dark theme
   only (light theme confirmed live to already look correct as-is, a plain white
   card on a light background reads fine there -- deliberately left untouched):
   two real, separate bugs, not one.

   1. main.container itself was rendering at the *exact* full viewport size here
      (confirmed live via getBoundingClientRect: 1513.6x1641.6, matching
      window.innerWidth/innerHeight exactly), not sized/centered to wrap the
      settings content the way this app's other single-card pages are. Its own
      default big-glass-card look (rgba(255,255,255,.08) + blur) was therefore
      painting across the *entire* page as a "fullscreen card" sitting behind the
      real content -- reported directly by the user pointing at the page's own
      raw HTML source. Stripped the same way list pages already strip it.
   2. Core's own real card here, .portal-container (confirmed live: solid opaque
      white background, core CSS, unaware this app has a dark theme at all -- this
      app's own text-color override already correctly applies rgb(244,246,251) to
      "Settings"/"Administrator", so the two together read as near-invisible
      white-on-white). Asked directly whether to restyle it as this app's own
      glass card or remove it outright -- removed: matches how a settings-style
      page with no meaningful visual grouping needs reads better as plain content
      on the page's own background than as a second nested card, once the
      fullscreen one behind it is already gone. */
html[data-theme$="-dark"] body[data-path="me"] main.container {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
}

html[data-theme$="-dark"] .portal-container {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
}

/* /new-return -- this app's own hand-rolled "File a New Return" form (not a Web
   Form, unlike address/new or issues/new -- there is no Frappe-native inner card
   here to preserve). Reported directly, 2026-08-28: this page still carried the
   same big frosted card already removed from /me, /cart, address/new, issues/new
   and the error/login pages, plus the same list-page sidebar mismatch already
   fixed for address/new and issues/new. Confirmed live before writing this: no
   element under main.container here carries its own translucent background (the
   form fields' own subtle input backgrounds are normal control styling, not a
   second card) -- so this matches /me's own treatment above (fully cardless,
   padding left alone), not address/issues' "strip the outer, keep the inner Web
   Form card" shape, since there is no inner card on this page to keep. */
html[data-theme$="-dark"] body[data-path="new-return"] main.container,
html[data-theme$="-light"] body[data-path="new-return"] main.container {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
}

/* /new-return is reached from /returns (sidebar at 220.8px, the real list-page
   position) via "File a New Return" -- same list <-> new-entry sidebar mismatch
   already fixed for /address/new and /issues/new (172px margin-top lands here at
   the identical 220.8px, confirmed live). */
body[data-path="new-return"] .web-sidebar {
	margin-top: 172px !important;
}

/* /repair-status -- the guest-facing "Check Repair Status" lookup form (magic-link
   deep link or the manual two-factor form), same hand-rolled shape as /new-return
   above: no Web Form, no inner card of its own, no .web-sidebar at all (guest
   pages don't render one -- confirmed live, no sidebar rule needed here unlike
   /new-return). Reported directly, same session as /new-return's fix: still
   carried the same big frosted main.container card. Confirmed live no element
   under main.container here carries a second translucent background either (the
   form fields and .santek-timeline-dot's own colors are normal control/status
   styling, not a card) -- same /me-style fully-cardless treatment, padding left
   alone. */
html[data-theme$="-dark"] body[data-path="repair-status"] main.container,
html[data-theme$="-light"] body[data-path="repair-status"] main.container {
	background: transparent !important;
	border: none !important;
	box-shadow: none !important;
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
}

/* /me's own Contact Info section -- reported directly: phone/email/address
   were all squeezed into a narrow column. Root cause: it reused core's
   .portal-section, which is `display: flex; justify-content: space-between;
   align-items: center` -- built for a one-line "Title ... one control" row
   (Theme: [switch]), not a real multi-field form. Given its own class
   (.santek-contact-section) instead of sharing .portal-section, so this
   section gets normal block layout with no fight over that flex behavior.
   (Address fields themselves were removed the same day -- core's own
   "/addresses" Web Form already covers that, more completely -- so this only
   ever needs to fit two fields side by side, not four.) */
.santek-contact-section {
	display: block;
	padding: 10px 0;
}

.santek-contact-form {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 14px 16px;
	max-width: 480px;
	margin-top: 10px;
}

.santek-contact-form .form-group {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0;
}

.santek-contact-form-actions {
	grid-column: 1 / -1;
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 4px;
}

.santek-contact-form-status {
	font-size: 0.85rem;
	color: var(--text-muted, #8d99a6);
}

.santek-contact-address-link {
	margin-top: 12px;
}

@media (max-width: 480px) {
	.santek-contact-form {
		grid-template-columns: 1fr;
	}
}

/* ---------------------------------------------------------------------------
   /warehouse runs full-bleed: no page panel, no header, no breadcrumbs.

   Every other portal page is a document you read, so the glass panel frames it
   correctly. This one is an instrument held in one hand mid-aisle, and the frame was
   costing about 70px of a 430px screen once its 32px padding and its own inset were
   counted -- enough to drop the mode grid from four buttons per row to three and push
   the primary action below the fold. Measured on a phone-sized viewport against the
   real page, not estimated.

   The breadcrumb trail and page title go with it. /warehouse is opened directly by
   someone starting a task and never navigated to from elsewhere in the portal, so a
   trail back to Home is height spent on a journey nobody makes.

   Scoped on body[data-path] like the other page-specific container overrides in this
   file, and prefixed with html[data-theme] so it outranks the theme's own
   main.container rules on specificity rather than relying on source order -- the two
   are otherwise equal, and a later edit reordering this file would silently undo it.
   --------------------------------------------------------------------------- */
html[data-theme] body[data-path="warehouse"] main.container {
	background: transparent;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	padding: 8px;
	max-width: 100%;
}

html[data-theme] body[data-path="warehouse"] .page-breadcrumbs,
html[data-theme] body[data-path="warehouse"] .page-header-wrapper,
html[data-theme] body[data-path="warehouse"] .page-header {
	display: none;
}
