Cambios implementados: 1. Lazy loading de imágenes: - catalog.js: loading="lazy" decoding="async" en part cards y detail panel - inventory.js: lazy loading en imagen de detalle de item 2. Minificación de assets: - scripts/minify-assets.sh: minifica JS (terser) y CSS para POS y Dashboard - 25 archivos .min.js + 5 .min.css generados en pos/static/ - 14 archivos .min.js + 8 .min.css generados en dashboard/ 3. Nginx auto-serve minified: - try_files $1.min.js antes de servir .js original - try_files $1.min.css antes de servir .css original - Transparente para los templates HTML (cero cambios en HTML) 4. Cache warming script: - scripts/warm_vehicle_cache.py: pobla Redis con vehicle info por batches - Mitiga DISTINCT ON + 4 JOINs sobre 2B filas - Corre en background, procesa ~1.5M parts Tests: 73/73 pasando
684 lines
17 KiB
CSS
684 lines
17 KiB
CSS
/* ==========================================================================
|
||
POS-GLASS.CSS — Pixel-Perfect glassmorphism overlay for Nexus POS
|
||
Load AFTER tokens.css. Applies glass effects, glow, 3D buttons,
|
||
and animations to all POS pages without modifying inline styles.
|
||
========================================================================== */
|
||
|
||
/* ── Hidden scrollbar (global) ── */
|
||
html { scrollbar-width: none; }
|
||
html::-webkit-scrollbar { width: 0; }
|
||
|
||
/* ── Smooth font rendering ── */
|
||
body {
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
SIDEBAR — Glass treatment
|
||
========================================================================== */
|
||
|
||
.sidebar,
|
||
.pos-sidebar {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border-right: 1px solid var(--glass-border) !important;
|
||
}
|
||
|
||
.sidebar__logo {
|
||
position: relative;
|
||
}
|
||
|
||
.sidebar__logo-text {
|
||
position: relative;
|
||
}
|
||
|
||
/* Glow under logo text */
|
||
.sidebar__logo-text::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -4px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: var(--gradient-accent);
|
||
border-radius: 1px;
|
||
opacity: 0.4;
|
||
filter: blur(2px);
|
||
}
|
||
|
||
/* Nav items — hover glow */
|
||
.sidebar__nav a,
|
||
.sidebar__nav-item,
|
||
.sidebar .nav-item {
|
||
transition: all 0.25s var(--ease-out) !important;
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
.sidebar__nav a:hover,
|
||
.sidebar__nav-item:hover,
|
||
.sidebar .nav-item:hover {
|
||
box-shadow: 0 0 12px var(--glow-color-soft);
|
||
}
|
||
|
||
.sidebar__nav a.active,
|
||
.sidebar__nav-item.active,
|
||
.sidebar .nav-item.active {
|
||
box-shadow: 0 0 16px var(--glow-color-soft), inset 0 0 0 1px var(--glass-border);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
THEME BAR — Glass
|
||
========================================================================== */
|
||
|
||
.theme-bar {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
border-bottom: 1px solid var(--glass-border) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
CARDS — Glass with glow hover
|
||
========================================================================== */
|
||
|
||
.kpi-card,
|
||
.table-card,
|
||
.card,
|
||
.stat-card,
|
||
.chart-card,
|
||
.alert-card,
|
||
.config-card,
|
||
.fleet-card,
|
||
.report-card,
|
||
.invoice-card,
|
||
.customer-card,
|
||
.panel {
|
||
background: var(--glass-bg) !important;
|
||
backdrop-filter: blur(var(--glass-blur));
|
||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||
border: 1px solid var(--glass-border) !important;
|
||
transition: all 0.3s var(--ease-out) !important;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Accent top-line on hover */
|
||
.kpi-card::before,
|
||
.table-card::before,
|
||
.chart-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: var(--gradient-accent);
|
||
transform: scaleX(0);
|
||
transform-origin: left;
|
||
transition: transform 0.4s var(--ease-out);
|
||
z-index: 1;
|
||
}
|
||
|
||
.kpi-card:hover::before,
|
||
.table-card:hover::before,
|
||
.chart-card:hover::before {
|
||
transform: scaleX(1);
|
||
}
|
||
|
||
.kpi-card:hover,
|
||
.table-card:hover,
|
||
.card:hover,
|
||
.stat-card:hover,
|
||
.chart-card:hover,
|
||
.config-card:hover,
|
||
.fleet-card:hover,
|
||
.report-card:hover {
|
||
border-color: var(--color-border-accent) !important;
|
||
box-shadow: 0 4px 20px var(--glow-color-soft);
|
||
}
|
||
|
||
/* KPI card accent bar — add glow */
|
||
.kpi-card__accent-bar {
|
||
box-shadow: 0 0 8px var(--glow-color-soft);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
BUTTONS — 3D depth effect
|
||
========================================================================== */
|
||
|
||
/* Primary buttons */
|
||
.btn--primary,
|
||
button.primary,
|
||
.btn-primary,
|
||
input[type="submit"],
|
||
button[type="submit"] {
|
||
background: var(--gradient-accent) !important;
|
||
border: none !important;
|
||
box-shadow: 0 3px 0 var(--color-primary-active),
|
||
0 4px 10px var(--glow-color-soft) !important;
|
||
transition: all 0.25s var(--ease-out) !important;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.btn--primary:hover,
|
||
button.primary:hover,
|
||
.btn-primary:hover,
|
||
input[type="submit"]:hover,
|
||
button[type="submit"]:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 0 var(--color-primary-active),
|
||
0 8px 20px var(--glow-color) !important;
|
||
}
|
||
|
||
.btn--primary:active,
|
||
button.primary:active,
|
||
.btn-primary:active,
|
||
input[type="submit"]:active,
|
||
button[type="submit"]:active {
|
||
transform: translateY(1px);
|
||
box-shadow: 0 1px 0 var(--color-primary-active) !important;
|
||
}
|
||
|
||
/* Ghost / secondary buttons — glass */
|
||
.btn--ghost,
|
||
.btn--secondary,
|
||
.btn-secondary,
|
||
.btn-ghost,
|
||
button.secondary {
|
||
background: var(--glass-bg) !important;
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border: 1px solid var(--glass-border) !important;
|
||
transition: all 0.25s var(--ease-out) !important;
|
||
}
|
||
|
||
.btn--ghost:hover,
|
||
.btn--secondary:hover,
|
||
.btn-secondary:hover,
|
||
.btn-ghost:hover,
|
||
button.secondary:hover {
|
||
border-color: var(--color-border-accent) !important;
|
||
box-shadow: 0 0 16px var(--glow-color-soft);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
INPUTS — Glass with focus glow
|
||
========================================================================== */
|
||
|
||
input[type="text"],
|
||
input[type="number"],
|
||
input[type="email"],
|
||
input[type="password"],
|
||
input[type="search"],
|
||
input[type="tel"],
|
||
input[type="date"],
|
||
input[type="url"],
|
||
textarea,
|
||
select,
|
||
.search-input,
|
||
.filter-input {
|
||
background: var(--glass-bg) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
transition: all 0.25s var(--ease-out) !important;
|
||
}
|
||
|
||
input[type="text"]:focus,
|
||
input[type="number"]:focus,
|
||
input[type="email"]:focus,
|
||
input[type="password"]:focus,
|
||
input[type="search"]:focus,
|
||
input[type="tel"]:focus,
|
||
input[type="date"]:focus,
|
||
input[type="url"]:focus,
|
||
textarea:focus,
|
||
select:focus,
|
||
.search-input:focus,
|
||
.filter-input:focus {
|
||
border-color: var(--color-border-focus) !important;
|
||
box-shadow: 0 0 0 3px var(--glow-color-soft), 0 0 16px var(--glow-color-soft) !important;
|
||
outline: none;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
TABLES — Subtle glass rows
|
||
========================================================================== */
|
||
|
||
table thead th {
|
||
background: var(--glass-bg) !important;
|
||
backdrop-filter: blur(8px);
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-caption);
|
||
text-transform: uppercase;
|
||
letter-spacing: var(--tracking-wider);
|
||
}
|
||
|
||
table tbody tr {
|
||
transition: all 0.2s ease !important;
|
||
}
|
||
|
||
table tbody tr:hover {
|
||
background: var(--glass-highlight) !important;
|
||
box-shadow: inset 0 0 0 1px var(--glass-border);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
MODALS — Glass overlay + glass content
|
||
========================================================================== */
|
||
|
||
.modal-overlay,
|
||
.overlay,
|
||
.modal-backdrop {
|
||
backdrop-filter: blur(4px);
|
||
-webkit-backdrop-filter: blur(4px);
|
||
}
|
||
|
||
.modal,
|
||
.modal-content,
|
||
.modal-dialog,
|
||
.dialog {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(24px);
|
||
-webkit-backdrop-filter: blur(24px);
|
||
border: 1px solid var(--glass-border) !important;
|
||
box-shadow: 0 24px 48px rgba(0,0,0,0.3) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
TABS — Glass active state
|
||
========================================================================== */
|
||
|
||
.tab,
|
||
.tab-btn,
|
||
.tabs button {
|
||
transition: all 0.25s var(--ease-out) !important;
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
.tab.active,
|
||
.tab-btn.active,
|
||
.tabs button.active {
|
||
background: var(--color-primary-muted) !important;
|
||
box-shadow: 0 0 12px var(--glow-color-soft);
|
||
border-color: var(--color-border-accent) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
BADGES / TAGS — Subtle glow
|
||
========================================================================== */
|
||
|
||
.badge,
|
||
.tag,
|
||
.status-badge,
|
||
.pill {
|
||
backdrop-filter: blur(4px);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
SCROLL REVEAL — Available for any POS page that wants it
|
||
========================================================================== */
|
||
|
||
.nx-reveal {
|
||
opacity: 0;
|
||
transform: translateY(24px);
|
||
transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
|
||
}
|
||
.nx-reveal.is-visible {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
.nx-stagger > .nx-reveal:nth-child(1) { transition-delay: 0ms; }
|
||
.nx-stagger > .nx-reveal:nth-child(2) { transition-delay: 80ms; }
|
||
.nx-stagger > .nx-reveal:nth-child(3) { transition-delay: 160ms; }
|
||
.nx-stagger > .nx-reveal:nth-child(4) { transition-delay: 240ms; }
|
||
.nx-stagger > .nx-reveal:nth-child(5) { transition-delay: 320ms; }
|
||
.nx-stagger > .nx-reveal:nth-child(6) { transition-delay: 400ms; }
|
||
|
||
/* ==========================================================================
|
||
TOAST / NOTIFICATIONS — Glass
|
||
========================================================================== */
|
||
|
||
.toast,
|
||
.notification,
|
||
.snackbar,
|
||
.alert {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
border: 1px solid var(--glass-border) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
DROPDOWN / POPOVER — Glass
|
||
========================================================================== */
|
||
|
||
.dropdown-menu,
|
||
.popover,
|
||
.autocomplete-list,
|
||
.suggestion-list {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
border: 1px solid var(--glass-border) !important;
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.2) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
STATUS BAR (POS) — Glass
|
||
========================================================================== */
|
||
|
||
.status-bar,
|
||
.pos-status-bar {
|
||
background: var(--glass-bg-strong) !important;
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
border-bottom: 1px solid var(--glass-border) !important;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
LOADING SPINNER — Glow animation
|
||
========================================================================== */
|
||
|
||
.spinner,
|
||
.loading-spinner {
|
||
animation: nx-glow-pulse 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
ANIMATIONS — Available keyframes
|
||
========================================================================== */
|
||
|
||
@keyframes pos-fade-in {
|
||
from { opacity: 0; transform: translateY(12px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* Apply subtle entry animation to main content area */
|
||
.content,
|
||
.main-content,
|
||
main {
|
||
animation: pos-fade-in 0.4s var(--ease-out) both;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
DASHED BORDER ACCENTS (Pixel-Perfect style)
|
||
========================================================================== */
|
||
|
||
.section-divider,
|
||
hr {
|
||
border: none;
|
||
border-top: 1px dashed var(--glass-border);
|
||
margin: var(--space-4) 0;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
TABLET RESPONSIVE — Adaptive layout for 768px-1024px screens
|
||
Applied globally to all POS pages via pos-glass.css.
|
||
Targets iPad (768×1024), Android tablets (800×1280), and similar.
|
||
========================================================================== */
|
||
|
||
/* ── Tablet portrait (768-1023px) — sidebar collapses, grids reflow ── */
|
||
@media (max-width: 1023px) {
|
||
|
||
/* Sidebar collapses to an overlay drawer */
|
||
.sidebar,
|
||
.pos-sidebar {
|
||
position: fixed !important;
|
||
top: 0 !important;
|
||
left: 0 !important;
|
||
bottom: 0 !important;
|
||
z-index: var(--z-modal) !important;
|
||
transform: translateX(-100%) !important;
|
||
transition: transform 0.3s var(--ease-out) !important;
|
||
width: 260px !important;
|
||
}
|
||
|
||
.sidebar.open,
|
||
.pos-sidebar.open {
|
||
transform: translateX(0) !important;
|
||
box-shadow: 0 0 40px rgba(0,0,0,0.3) !important;
|
||
}
|
||
|
||
.sidebar-overlay {
|
||
display: none !important;
|
||
position: fixed !important;
|
||
inset: 0 !important;
|
||
z-index: calc(var(--z-modal) - 1) !important;
|
||
background: rgba(0,0,0,0.5) !important;
|
||
}
|
||
|
||
.sidebar-overlay.open {
|
||
display: block !important;
|
||
}
|
||
|
||
/* App shell: full width when sidebar is hidden */
|
||
.app-shell {
|
||
flex-direction: column !important;
|
||
}
|
||
|
||
.app-shell > main,
|
||
.app-shell > .main-content,
|
||
.app-shell > .content,
|
||
.main-content,
|
||
.content {
|
||
margin-left: 0 !important;
|
||
width: 100% !important;
|
||
}
|
||
|
||
/* Show hamburger button */
|
||
.hamburger-btn {
|
||
display: flex !important;
|
||
}
|
||
|
||
/* Touch-friendly targets — minimum 44px tap area */
|
||
button,
|
||
.btn,
|
||
.nav-card,
|
||
.tab-btn,
|
||
.tab,
|
||
.part-card,
|
||
.search-result-item,
|
||
table tbody tr,
|
||
.kpi-card {
|
||
min-height: 44px;
|
||
}
|
||
|
||
/* Larger text for readability on tablets */
|
||
.kpi-card__value {
|
||
font-size: 1.5rem !important;
|
||
}
|
||
|
||
/* Grid reflow: 2 columns instead of 3-4 */
|
||
.kpi-grid {
|
||
grid-template-columns: repeat(2, 1fr) !important;
|
||
}
|
||
|
||
.nav-grid {
|
||
grid-template-columns: repeat(2, 1fr) !important;
|
||
}
|
||
|
||
/* Tables: horizontal scroll wrapper on narrow screens */
|
||
.table-wrap,
|
||
.table-card {
|
||
overflow-x: auto !important;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
/* POS-specific: if the POS has a side panel (cart), stack vertically */
|
||
.pos-layout {
|
||
flex-direction: column !important;
|
||
}
|
||
|
||
.pos-layout .pos-cart,
|
||
.pos-layout .cart-panel {
|
||
width: 100% !important;
|
||
max-width: 100% !important;
|
||
height: auto !important;
|
||
max-height: 40vh !important;
|
||
}
|
||
|
||
/* Content headers: tighter padding */
|
||
.content-header,
|
||
.header,
|
||
.page-header {
|
||
padding: var(--space-3) var(--space-4) !important;
|
||
}
|
||
|
||
/* Search bar: full width */
|
||
.search-bar,
|
||
.search-wrapper {
|
||
width: 100% !important;
|
||
max-width: 100% !important;
|
||
}
|
||
|
||
/* Mode toggle: slightly larger buttons for touch */
|
||
.mode-toggle button {
|
||
padding: 6px 14px !important;
|
||
font-size: 12px !important;
|
||
}
|
||
|
||
/* Vehicle selector dropdowns: stack on smaller tablets */
|
||
.vehicle-selector__inner,
|
||
.vehicle-selector .vs-group {
|
||
flex-wrap: wrap !important;
|
||
}
|
||
|
||
.vehicle-selector .vs-arrow {
|
||
display: none !important;
|
||
}
|
||
|
||
.vehicle-selector .vs-select {
|
||
min-width: 130px !important;
|
||
}
|
||
}
|
||
|
||
/* ── Phone portrait (< 768px) — single column, max simplification ── */
|
||
@media (max-width: 767px) {
|
||
|
||
.sidebar {
|
||
width: 85vw !important;
|
||
max-width: 300px !important;
|
||
}
|
||
|
||
.kpi-grid,
|
||
.nav-grid,
|
||
.results-grid {
|
||
grid-template-columns: 1fr !important;
|
||
}
|
||
|
||
.kpi-card__value {
|
||
font-size: 1.3rem !important;
|
||
}
|
||
|
||
/* Stack the mode toggle buttons vertically if tight */
|
||
.mode-toggle {
|
||
flex-wrap: wrap !important;
|
||
}
|
||
|
||
/* Hide non-essential UI to save space */
|
||
.header__store-badge,
|
||
.vs-vin-divider {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Full-width modals */
|
||
.modal-content {
|
||
max-width: 95vw !important;
|
||
margin: var(--space-3) !important;
|
||
padding: var(--space-4) !important;
|
||
}
|
||
|
||
/* Tables: force readable font size */
|
||
table {
|
||
font-size: 12px !important;
|
||
}
|
||
|
||
table th,
|
||
table td {
|
||
padding: var(--space-2) var(--space-2) !important;
|
||
}
|
||
}
|
||
|
||
/* ── Landscape tablet (height < 600px with wide screen) ── */
|
||
@media (max-height: 600px) and (min-width: 768px) {
|
||
/* Reduce vertical padding for landscape tablet use */
|
||
.kpi-grid {
|
||
gap: var(--space-2) !important;
|
||
}
|
||
|
||
.dashboard,
|
||
.main-content,
|
||
.content {
|
||
padding: var(--space-3) !important;
|
||
}
|
||
}
|
||
|
||
/* ── Touch device hints ── */
|
||
@media (hover: none) and (pointer: coarse) {
|
||
/* Remove hover-only effects on touch devices — they cause sticky states */
|
||
.kpi-card:hover,
|
||
.nav-card:hover,
|
||
.part-card:hover,
|
||
.table-card:hover,
|
||
.card:hover {
|
||
transform: none !important;
|
||
}
|
||
|
||
/* Larger touch targets for interactive elements */
|
||
.sidebar__nav a,
|
||
.sidebar__nav-item,
|
||
.sidebar .nav-item {
|
||
padding: 12px 16px !important;
|
||
min-height: 48px !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
}
|
||
|
||
/* Scroll momentum on iOS */
|
||
.table-wrap,
|
||
.main-content,
|
||
.content,
|
||
.parts-grid,
|
||
.nav-grid {
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
/* Disable text selection on buttons (prevents accidental blue highlight on long tap) */
|
||
button,
|
||
.btn,
|
||
.nav-card,
|
||
.tab-btn {
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
}
|
||
}
|
||
|
||
|
||
/* ==========================================================================
|
||
PRINT — Disable glass effects for printing
|
||
========================================================================== */
|
||
|
||
@media print {
|
||
.sidebar,
|
||
.theme-bar,
|
||
.kpi-card,
|
||
.table-card,
|
||
.card,
|
||
.modal,
|
||
.modal-content,
|
||
table thead th,
|
||
input,
|
||
select,
|
||
textarea {
|
||
background: #fff !important;
|
||
backdrop-filter: none !important;
|
||
-webkit-backdrop-filter: none !important;
|
||
box-shadow: none !important;
|
||
border-color: #ccc !important;
|
||
color: #000 !important;
|
||
}
|
||
}
|