FASE 7d: Lazy Loading + Minificación + Auto-serve minified
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
This commit is contained in:
385
pos/static/css/chat.min.css
vendored
Normal file
385
pos/static/css/chat.min.css
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
/* ==========================================================================
|
||||
NEXUS POS — AI Chat Widget
|
||||
Uses design system tokens from tokens.css
|
||||
========================================================================== */
|
||||
|
||||
/* ─── Floating Button ─── */
|
||||
|
||||
.chat-fab {
|
||||
position: fixed;
|
||||
bottom: 140px; /* above cart FAB and F-keys footer */
|
||||
right: var(--space-5);
|
||||
z-index: 8000;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: var(--radius-full);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: var(--shadow-lg);
|
||||
transition: transform var(--duration-fast) var(--ease-in-out),
|
||||
background var(--duration-fast) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-fab:hover {
|
||||
transform: scale(1.08);
|
||||
background: var(--color-primary-hover, #e5952f);
|
||||
}
|
||||
|
||||
.chat-fab.has-unread::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
/* ─── Chat Panel ─── */
|
||||
|
||||
.chat-panel {
|
||||
position: fixed;
|
||||
bottom: 200px;
|
||||
right: var(--space-5);
|
||||
z-index: 8001;
|
||||
width: 400px;
|
||||
height: 520px;
|
||||
max-height: calc(100vh - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl);
|
||||
overflow: hidden;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: transform var(--duration-normal) var(--ease-in-out),
|
||||
opacity var(--duration-normal) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-panel.open {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/* ─── Header ─── */
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-header h3 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat-header-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
padding: var(--space-1);
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.chat-header-close:hover { opacity: 1; }
|
||||
|
||||
/* ─── Messages Area ─── */
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* ─── Message Bubbles ─── */
|
||||
|
||||
.chat-msg {
|
||||
max-width: 85%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
line-height: 1.45;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.chat-msg.user {
|
||||
align-self: flex-end;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: var(--radius-sm, 4px);
|
||||
}
|
||||
|
||||
.chat-msg.ai {
|
||||
align-self: flex-start;
|
||||
background: var(--color-surface-2, rgba(255,255,255,0.06));
|
||||
color: var(--color-text-primary);
|
||||
border-bottom-left-radius: var(--radius-sm, 4px);
|
||||
}
|
||||
|
||||
/* ─── Typing Indicator ─── */
|
||||
|
||||
.chat-typing {
|
||||
align-self: flex-start;
|
||||
display: none;
|
||||
gap: 4px;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-surface-2, rgba(255,255,255,0.06));
|
||||
border-radius: var(--radius-lg);
|
||||
border-bottom-left-radius: var(--radius-sm, 4px);
|
||||
}
|
||||
|
||||
.chat-typing.visible { display: flex; }
|
||||
|
||||
.chat-typing span {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-text-muted);
|
||||
animation: chatBounce 1.2s infinite;
|
||||
}
|
||||
.chat-typing span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.chat-typing span:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes chatBounce {
|
||||
0%, 60%, 100% { transform: translateY(0); }
|
||||
30% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
/* ─── Part Result Cards ─── */
|
||||
|
||||
.chat-parts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-part-card {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
cursor: pointer;
|
||||
transition: border-color var(--duration-fast) var(--ease-in-out),
|
||||
background var(--duration-fast) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-part-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-bg-base);
|
||||
}
|
||||
|
||||
.chat-part-card .part-number {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-accent);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.chat-part-card .part-name {
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
color: var(--color-text-primary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.chat-part-card .part-stock {
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.chat-part-card .part-stock.in-stock {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
/* ─── Input Area ─── */
|
||||
|
||||
.chat-input-area {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
font-family: var(--font-body);
|
||||
resize: none;
|
||||
outline: none;
|
||||
min-height: 38px;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
.chat-input:focus {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.chat-input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.chat-send-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--radius-md);
|
||||
border: none;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--duration-fast) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
|
||||
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* ─── Camera Button (Photo identification) ─── */
|
||||
|
||||
.chat-cam-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--duration-fast) var(--ease-in-out),
|
||||
color var(--duration-fast) var(--ease-in-out),
|
||||
border-color var(--duration-fast) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-cam-btn:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.chat-msg-image img {
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ─── Mic Button (Voice Input) ─── */
|
||||
|
||||
.chat-mic-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--duration-fast) var(--ease-in-out),
|
||||
color var(--duration-fast) var(--ease-in-out),
|
||||
border-color var(--duration-fast) var(--ease-in-out);
|
||||
}
|
||||
|
||||
.chat-mic-btn:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.chat-mic-btn.listening {
|
||||
background: #f85149;
|
||||
border-color: #f85149;
|
||||
color: #fff;
|
||||
animation: micPulse 1.4s infinite;
|
||||
}
|
||||
|
||||
@keyframes micPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.4); }
|
||||
50% { box-shadow: 0 0 0 10px rgba(248, 81, 73, 0); }
|
||||
}
|
||||
|
||||
/* ─── Voice Toast ─── */
|
||||
|
||||
.chat-voice-toast {
|
||||
position: fixed;
|
||||
bottom: 160px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(10px);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
padding: 8px 18px;
|
||||
border-radius: var(--radius-md, 8px);
|
||||
font-size: 0.85rem;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-voice-toast.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
/* ─── Vehicle Info Banner ─── */
|
||||
|
||||
.chat-vehicle-banner {
|
||||
margin-top: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-accent);
|
||||
border-left: 3px solid var(--color-accent);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.chat-vehicle-banner strong {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* ─── Responsive ─── */
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.chat-panel {
|
||||
width: calc(100vw - var(--space-4));
|
||||
right: var(--space-2);
|
||||
height: 60vh;
|
||||
}
|
||||
}
|
||||
85
pos/static/css/common.min.css
vendored
Normal file
85
pos/static/css/common.min.css
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/* /home/Autopartes/pos/static/css/common.css */
|
||||
/* Theme variables — overridden by tenant theme */
|
||||
:root {
|
||||
--color-primary: #1a73e8;
|
||||
--color-secondary: #5f6368;
|
||||
--color-accent: #ff6b35;
|
||||
--color-bg: #ffffff;
|
||||
--color-surface: #f8f9fa;
|
||||
--color-text: #202124;
|
||||
--color-text-secondary: #5f6368;
|
||||
--color-border: #dadce0;
|
||||
--color-success: #34a853;
|
||||
--color-warning: #f9ab00;
|
||||
--color-error: #ea4335;
|
||||
--font-display: 'Sora', sans-serif;
|
||||
--font-body: 'Plus Jakarta Sans', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--radius: 8px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn:hover { background: var(--color-border); }
|
||||
.btn--primary { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
.btn--primary:hover { opacity: 0.9; }
|
||||
.btn--accent { background: var(--color-accent); color: white; border-color: var(--color-accent); }
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Catalog grid */
|
||||
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
|
||||
.catalog-card { cursor: pointer; transition: all 0.2s; }
|
||||
.catalog-card:hover { border-color: var(--color-primary); transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||
.stock-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
|
||||
.stock-badge--ok { background: #dcfce7; color: #166534; }
|
||||
.stock-badge--low { background: #fef9c3; color: #854d0e; }
|
||||
.stock-badge--zero { background: #fecaca; color: #991b1b; }
|
||||
|
||||
/* Cart sidebar */
|
||||
.cart-sidebar { position: fixed; right: 0; top: 0; bottom: 0; width: 360px; background: var(--color-surface); border-left: 1px solid var(--color-border); padding: 20px; overflow-y: auto; transform: translateX(100%); transition: transform 0.3s; z-index: 50; }
|
||||
.cart-sidebar.open { transform: translateX(0); }
|
||||
.cart-item { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--color-border); }
|
||||
.cart-total { font-family: var(--font-mono); font-size: 1.3rem; font-weight: 700; }
|
||||
|
||||
/* Search bar */
|
||||
.search-bar { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||
.search-bar input { flex: 1; padding: 10px 16px; border: 1px solid var(--color-border); border-radius: var(--radius); font-size: 1rem; }
|
||||
.search-bar input:focus { outline: none; border-color: var(--color-primary); }
|
||||
|
||||
/* Filter chips */
|
||||
.filter-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||
.chip { padding: 4px 12px; border-radius: 20px; border: 1px solid var(--color-border); font-size: 0.8rem; cursor: pointer; background: transparent; }
|
||||
.chip.active { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
|
||||
/* External availability */
|
||||
.external-results { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: var(--radius); padding: 16px; margin-top: 12px; }
|
||||
428
pos/static/css/onboarding.min.css
vendored
Normal file
428
pos/static/css/onboarding.min.css
vendored
Normal file
@@ -0,0 +1,428 @@
|
||||
/* ==========================================================================
|
||||
NEXUS POS — Onboarding Wizard
|
||||
Uses design system tokens (works with both industrial + modern themes)
|
||||
========================================================================== */
|
||||
|
||||
/* Overlay backdrop */
|
||||
.onboarding-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--overlay-backdrop);
|
||||
opacity: 0;
|
||||
animation: onb-fade-in var(--duration-normal) var(--ease-out) forwards;
|
||||
}
|
||||
|
||||
@keyframes onb-fade-in {
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Modal card */
|
||||
.onboarding-modal {
|
||||
width: 92vw;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transform: translateY(20px);
|
||||
animation: onb-slide-up var(--duration-normal) var(--ease-out) forwards;
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onboarding-modal {
|
||||
border-radius: 0;
|
||||
clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onboarding-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
@keyframes onb-slide-up {
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Step content area */
|
||||
.onboarding-body {
|
||||
padding: var(--space-8) var(--space-6);
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Step icon */
|
||||
.onb-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto var(--space-5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
background: var(--color-primary-muted);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-icon {
|
||||
border-radius: 0;
|
||||
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-icon {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
.onb-title {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
font-size: var(--text-h4);
|
||||
color: var(--color-text-primary);
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-2);
|
||||
letter-spacing: var(--tracking-snug);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-title {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.onb-desc {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
line-height: var(--leading-body-sm);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* Form fields inside wizard */
|
||||
.onb-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.onb-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.onb-label {
|
||||
font-size: var(--text-label);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-label {
|
||||
text-transform: uppercase;
|
||||
font-size: var(--text-caption);
|
||||
letter-spacing: var(--tracking-wider);
|
||||
}
|
||||
|
||||
.onb-input {
|
||||
height: 40px;
|
||||
padding: 0 var(--space-3);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-input {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-input {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.onb-input:focus {
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
|
||||
.onb-input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.onb-select {
|
||||
height: 40px;
|
||||
padding: 0 var(--space-3);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-select {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-select {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.onb-select:focus {
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
|
||||
/* Inline error */
|
||||
.onb-error {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-error);
|
||||
min-height: 18px;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
/* Success message in step */
|
||||
.onb-success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3);
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-success {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-success {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* Footer: buttons + progress */
|
||||
.onboarding-footer {
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
.onb-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.onb-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
padding: 0 var(--space-5);
|
||||
height: 40px;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-btn {
|
||||
border-radius: 0;
|
||||
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-btn {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.onb-btn--primary {
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
}
|
||||
|
||||
.onb-btn--primary:hover {
|
||||
background: var(--btn-primary-bg-hover);
|
||||
}
|
||||
|
||||
.onb-btn--primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.onb-btn--secondary {
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
border-color: var(--btn-secondary-border);
|
||||
}
|
||||
|
||||
.onb-btn--secondary:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
}
|
||||
|
||||
.onb-btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text-muted);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.onb-btn--ghost:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Progress dots */
|
||||
.onb-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.onb-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 2px solid var(--color-border-strong);
|
||||
background: transparent;
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-dot {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-dot {
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.onb-dot.is-active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.onb-dot.is-done {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Step counter label */
|
||||
.onb-step-label {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
/* "No mostrar de nuevo" checkbox row */
|
||||
.onb-dismiss-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
padding-top: var(--space-2);
|
||||
}
|
||||
|
||||
.onb-dismiss-row label {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.onb-dismiss-row input[type="checkbox"] {
|
||||
accent-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Final step link grid */
|
||||
.onb-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.onb-link-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-4) var(--space-3);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border);
|
||||
text-decoration: none;
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .onb-link-card {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .onb-link-card {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.onb-link-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.onb-link-card span.onb-link-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Step transition */
|
||||
.onb-step-enter {
|
||||
animation: onb-step-in var(--duration-normal) var(--ease-out) forwards;
|
||||
}
|
||||
|
||||
@keyframes onb-step-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.onboarding-modal {
|
||||
width: 96vw;
|
||||
max-height: 95vh;
|
||||
}
|
||||
.onboarding-body {
|
||||
padding: var(--space-6) var(--space-4);
|
||||
}
|
||||
.onboarding-footer {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
.onb-links {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
683
pos/static/css/pos-glass.min.css
vendored
Normal file
683
pos/static/css/pos-glass.min.css
vendored
Normal file
@@ -0,0 +1,683 @@
|
||||
/* ==========================================================================
|
||||
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;
|
||||
}
|
||||
}
|
||||
627
pos/static/css/tokens.min.css
vendored
Normal file
627
pos/static/css/tokens.min.css
vendored
Normal file
@@ -0,0 +1,627 @@
|
||||
/* ==========================================================================
|
||||
NEXUS AUTOPARTS — Design Tokens
|
||||
POS System for Auto Parts Stores
|
||||
Version: 1.0.0
|
||||
==========================================================================
|
||||
Themes:
|
||||
- [data-theme="industrial"] — Industrial Robusto (Dark)
|
||||
- [data-theme="modern"] — Técnico Moderno (Light)
|
||||
========================================================================== */
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
GOOGLE FONTS IMPORTS
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;700&family=Barlow+Condensed:wght@600;800&family=Poppins:wght@300;400;600;700&display=swap');
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
GLOBAL TOKENS — Theme-independent, shared across both themes
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
SEMANTIC COLORS — Status / Feedback (shared)
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-success: #22c55e;
|
||||
--color-success-light: #bbf7d0;
|
||||
--color-success-dark: #15803d;
|
||||
|
||||
--color-warning: #eab308;
|
||||
--color-warning-light: #fef08a;
|
||||
--color-warning-dark: #a16207;
|
||||
|
||||
--color-error: #ef4444;
|
||||
--color-error-light: #fecaca;
|
||||
--color-error-dark: #b91c1c;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
NEUTRAL SCALE — Grey ramp (50–900)
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-neutral-50: #fafafa;
|
||||
--color-neutral-100: #f5f5f5;
|
||||
--color-neutral-200: #e5e5e5;
|
||||
--color-neutral-300: #d4d4d4;
|
||||
--color-neutral-400: #a3a3a3;
|
||||
--color-neutral-500: #737373;
|
||||
--color-neutral-600: #525252;
|
||||
--color-neutral-700: #404040;
|
||||
--color-neutral-800: #262626;
|
||||
--color-neutral-900: #171717;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
SPACING — 4px base grid
|
||||
------------------------------------------------------------------------ */
|
||||
/* --space-N = N × 4px */
|
||||
|
||||
--space-1: 4px; /* 4px */
|
||||
--space-2: 8px; /* 8px */
|
||||
--space-3: 12px; /* 12px */
|
||||
--space-4: 16px; /* 16px */
|
||||
--space-5: 20px; /* 20px */
|
||||
--space-6: 24px; /* 24px */
|
||||
--space-7: 28px; /* 28px */
|
||||
--space-8: 32px; /* 32px */
|
||||
--space-9: 36px; /* 36px */
|
||||
--space-10: 40px; /* 40px */
|
||||
--space-11: 44px; /* 44px */
|
||||
--space-12: 48px; /* 48px */
|
||||
--space-14: 56px; /* 56px */
|
||||
--space-16: 64px; /* 64px */
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BORDER RADIUS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 20px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
TRANSITIONS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--transition-fast: all 0.10s ease;
|
||||
--transition-normal: all 0.20s ease;
|
||||
--transition-slow: all 0.40s ease;
|
||||
|
||||
/* Easing functions for fine-grained control */
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
--duration-fast: 100ms;
|
||||
--duration-normal: 200ms;
|
||||
--duration-slow: 400ms;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
Z-INDEX SCALE
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-modal: 1050;
|
||||
--z-toast: 1080;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BREAKPOINTS — Reference only (use in media queries, not calc())
|
||||
sm: 640px
|
||||
md: 768px
|
||||
lg: 1024px
|
||||
xl: 1280px
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
THEME A — Industrial Robusto (Dark)
|
||||
Usage: <html data-theme="industrial"> or <body data-theme="industrial">
|
||||
Style: Industrial, robust, high-contrast amber accents, clip-path diagonals
|
||||
========================================================================== */
|
||||
|
||||
[data-theme="industrial"] {
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
PRIMITIVE COLORS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-primary: #F5A623; /* Amber gold — main brand accent */
|
||||
--color-primary-hover: #e8951a; /* Darker amber on hover */
|
||||
--color-primary-active: #d4850f; /* Pressed state */
|
||||
--color-primary-muted: rgba(245, 166, 35, 0.15); /* Subtle tint */
|
||||
|
||||
--color-secondary: #333333; /* Mid-dark border / secondary bg */
|
||||
--color-secondary-hover: #444444;
|
||||
|
||||
--color-accent: #F5A623; /* Same as primary in this theme */
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BACKGROUNDS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-bg-base: #0d0d0d; /* Page / app shell background */
|
||||
--color-bg-elevated: #1a1a1a; /* Cards, panels, sidebars */
|
||||
--color-bg-overlay: #252525; /* Modals, dropdowns, tooltips */
|
||||
|
||||
/* Surface levels (for layered UI) */
|
||||
--color-surface-1: #1a1a1a; /* Lowest raised surface */
|
||||
--color-surface-2: #252525; /* Mid-level surface */
|
||||
--color-surface-3: #303030; /* Highest raised surface */
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
TEXT
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-text-primary: #FFFFFF;
|
||||
--color-text-secondary: #CCCCCC;
|
||||
--color-text-muted: #888888;
|
||||
--color-text-disabled: #555555;
|
||||
--color-text-inverse: #000000; /* Text on amber background */
|
||||
--color-text-accent: #F5A623;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BORDERS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-border: #333333;
|
||||
--color-border-strong: #555555;
|
||||
--color-border-accent: #F5A623;
|
||||
--color-border-focus: #F5A623;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
/* Primary button */
|
||||
--btn-primary-bg: #F5A623;
|
||||
--btn-primary-bg-hover: #e8951a;
|
||||
--btn-primary-bg-active: #d4850f;
|
||||
--btn-primary-text: #000000;
|
||||
--btn-primary-border: transparent;
|
||||
|
||||
/* Secondary button */
|
||||
--btn-secondary-bg: transparent;
|
||||
--btn-secondary-bg-hover: rgba(245, 166, 35, 0.10);
|
||||
--btn-secondary-text: #F5A623;
|
||||
--btn-secondary-border: #F5A623;
|
||||
|
||||
/* Ghost / Danger */
|
||||
--btn-ghost-bg: transparent;
|
||||
--btn-ghost-text: #CCCCCC;
|
||||
--btn-ghost-border: #333333;
|
||||
|
||||
--btn-danger-bg: #ef4444;
|
||||
--btn-danger-text: #FFFFFF;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
/* Font families */
|
||||
--font-heading: 'Barlow Condensed', 'Arial Narrow', sans-serif;
|
||||
--font-body: 'Barlow', 'Arial', sans-serif;
|
||||
--font-mono: 'Courier New', 'Consolas', monospace; /* prices / SKUs */
|
||||
|
||||
/* Font weights */
|
||||
--font-weight-light: 300; /* n/a in Barlow — falls to 400 */
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
|
||||
/* Heading weights (Barlow Condensed) */
|
||||
--heading-weight-primary: 800;
|
||||
--heading-weight-secondary: 600;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
SHADOWS / ELEVATION
|
||||
Tinted with amber to feel cohesive with the theme
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.60),
|
||||
0 1px 2px rgba(0, 0, 0, 0.40);
|
||||
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.60),
|
||||
0 2px 4px rgba(0, 0, 0, 0.40);
|
||||
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.70),
|
||||
0 4px 6px rgba(0, 0, 0, 0.50);
|
||||
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.80),
|
||||
0 10px 10px rgba(0, 0, 0, 0.50);
|
||||
|
||||
/* Accent glow — use on focused/highlighted elements */
|
||||
--shadow-accent: 0 0 0 3px rgba(245, 166, 35, 0.40);
|
||||
--shadow-focus: 0 0 0 3px rgba(245, 166, 35, 0.50);
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
MISC UI
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--scrollbar-track: #1a1a1a;
|
||||
--scrollbar-thumb: #444444;
|
||||
--scrollbar-thumb-hover: #F5A623;
|
||||
|
||||
--overlay-backdrop: rgba(0, 0, 0, 0.75);
|
||||
|
||||
/* Industrial clip-path angle (use in clip-path: polygon(...) utilities) */
|
||||
--clip-diagonal-angle: 6deg;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
THEME B — Técnico Moderno (Light)
|
||||
Usage: <html data-theme="modern"> or <body data-theme="modern">
|
||||
Style: Clean, modern, Poppins typography, subtle dot-grid background
|
||||
========================================================================== */
|
||||
|
||||
[data-theme="modern"] {
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
PRIMITIVE COLORS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-primary: #FF6B35; /* Orange — main brand accent */
|
||||
--color-primary-hover: #f05a22; /* Darker on hover */
|
||||
--color-primary-active: #dc4a12; /* Pressed state */
|
||||
--color-primary-muted: rgba(255, 107, 53, 0.10); /* Subtle tint */
|
||||
|
||||
--color-secondary: #1a1a2e; /* Deep navy — used for strong text */
|
||||
--color-secondary-hover: #252545;
|
||||
|
||||
--color-accent: #FF6B35; /* Same as primary in this theme */
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BACKGROUNDS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-bg-base: #FFFFFF; /* Page / app shell background */
|
||||
--color-bg-elevated: #F8F9FF; /* Cards, panels — very subtle blue */
|
||||
--color-bg-overlay: #FFFFFF; /* Modals, dropdowns */
|
||||
|
||||
/* Surface levels */
|
||||
--color-surface-1: #F8F9FF;
|
||||
--color-surface-2: #F0F2FF;
|
||||
--color-surface-3: #E8EBFF;
|
||||
|
||||
/* Dot-grid background pattern (apply via background-image on body/shell) */
|
||||
/* background-image: radial-gradient(circle, var(--dot-grid-color) 1px, transparent 1px); */
|
||||
/* background-size: var(--dot-grid-size) var(--dot-grid-size); */
|
||||
--dot-grid-color: rgba(26, 26, 46, 0.07);
|
||||
--dot-grid-size: 24px;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
TEXT
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-text-primary: #1a1a2e;
|
||||
--color-text-secondary: #4a4a6a;
|
||||
--color-text-muted: #8080a0;
|
||||
--color-text-disabled: #b0b0c8;
|
||||
--color-text-inverse: #FFFFFF; /* Text on orange background */
|
||||
--color-text-accent: #FF6B35;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BORDERS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--color-border: #e2e4f0;
|
||||
--color-border-strong: #c8cadc;
|
||||
--color-border-accent: #FF6B35;
|
||||
--color-border-focus: #FF6B35;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
/* Primary button */
|
||||
--btn-primary-bg: #FF6B35;
|
||||
--btn-primary-bg-hover: #f05a22;
|
||||
--btn-primary-bg-active: #dc4a12;
|
||||
--btn-primary-text: #FFFFFF;
|
||||
--btn-primary-border: transparent;
|
||||
|
||||
/* Secondary button */
|
||||
--btn-secondary-bg: transparent;
|
||||
--btn-secondary-bg-hover: rgba(255, 107, 53, 0.08);
|
||||
--btn-secondary-text: #FF6B35;
|
||||
--btn-secondary-border: #FF6B35;
|
||||
|
||||
/* Ghost / Danger */
|
||||
--btn-ghost-bg: transparent;
|
||||
--btn-ghost-text: #4a4a6a;
|
||||
--btn-ghost-border: #e2e4f0;
|
||||
|
||||
--btn-danger-bg: #ef4444;
|
||||
--btn-danger-text: #FFFFFF;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
/* Font families */
|
||||
--font-heading: 'Poppins', 'Segoe UI', sans-serif;
|
||||
--font-body: 'Poppins', 'Segoe UI', sans-serif;
|
||||
--font-mono: 'Courier New', 'Consolas', monospace; /* prices / SKUs */
|
||||
|
||||
/* Font weights */
|
||||
--font-weight-light: 300;
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800; /* falls to 700 in Poppins */
|
||||
|
||||
/* Heading weights (Poppins) */
|
||||
--heading-weight-primary: 700;
|
||||
--heading-weight-secondary: 600;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
SHADOWS / ELEVATION
|
||||
Softer, cooler tints for the light theme
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--shadow-sm: 0 1px 3px rgba(26, 26, 46, 0.08),
|
||||
0 1px 2px rgba(26, 26, 46, 0.05);
|
||||
|
||||
--shadow-md: 0 4px 6px rgba(26, 26, 46, 0.08),
|
||||
0 2px 4px rgba(26, 26, 46, 0.05);
|
||||
|
||||
--shadow-lg: 0 10px 15px rgba(26, 26, 46, 0.10),
|
||||
0 4px 6px rgba(26, 26, 46, 0.06);
|
||||
|
||||
--shadow-xl: 0 20px 25px rgba(26, 26, 46, 0.12),
|
||||
0 10px 10px rgba(26, 26, 46, 0.06);
|
||||
|
||||
/* Accent glow — use on focused/highlighted elements */
|
||||
--shadow-accent: 0 0 0 3px rgba(255, 107, 53, 0.25);
|
||||
--shadow-focus: 0 0 0 3px rgba(255, 107, 53, 0.30);
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
MISC UI
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
--scrollbar-track: #F8F9FF;
|
||||
--scrollbar-thumb: #c8cadc;
|
||||
--scrollbar-thumb-hover: #FF6B35;
|
||||
|
||||
--overlay-backdrop: rgba(26, 26, 46, 0.50);
|
||||
|
||||
/* No diagonal clip in modern theme — set to 0 for override-safe utilities */
|
||||
--clip-diagonal-angle: 0deg;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
TYPOGRAPHY SCALE — Token definitions
|
||||
Resolved at theme level because font families differ between themes.
|
||||
These tokens map to semantic roles and should be consumed directly.
|
||||
========================================================================== */
|
||||
|
||||
/* Shared scale values (dimensionless, theme-independent) */
|
||||
:root {
|
||||
|
||||
/* --- Type scale (font-size) --- */
|
||||
--text-h1: clamp(2.25rem, 5vw, 3.5rem); /* 36px → 56px */
|
||||
--text-h2: clamp(1.875rem, 4vw, 2.75rem); /* 30px → 44px */
|
||||
--text-h3: clamp(1.5rem, 3vw, 2.125rem); /* 24px → 34px */
|
||||
--text-h4: clamp(1.25rem, 2vw, 1.625rem); /* 20px → 26px */
|
||||
--text-h5: 1.125rem; /* 18px */
|
||||
--text-h6: 1rem; /* 16px */
|
||||
|
||||
--text-body-lg: 1.125rem; /* 18px */
|
||||
--text-body: 1rem; /* 16px */
|
||||
--text-body-sm: 0.875rem; /* 14px */
|
||||
--text-caption: 0.75rem; /* 12px */
|
||||
--text-label: 0.8125rem; /* 13px */
|
||||
--text-mono: 1rem; /* 16px — prices, SKUs */
|
||||
|
||||
/* --- Line heights --- */
|
||||
--leading-h1: 1.10;
|
||||
--leading-h2: 1.12;
|
||||
--leading-h3: 1.15;
|
||||
--leading-h4: 1.20;
|
||||
--leading-h5: 1.25;
|
||||
--leading-h6: 1.30;
|
||||
|
||||
--leading-body-lg: 1.65;
|
||||
--leading-body: 1.60;
|
||||
--leading-body-sm: 1.55;
|
||||
--leading-caption: 1.45;
|
||||
--leading-label: 1.40;
|
||||
--leading-mono: 1.50;
|
||||
|
||||
/* --- Letter spacing --- */
|
||||
--tracking-tight: -0.03em;
|
||||
--tracking-snug: -0.01em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-wide: 0.03em;
|
||||
--tracking-wider: 0.06em;
|
||||
--tracking-widest: 0.12em; /* Use for ALL-CAPS labels / badges */
|
||||
|
||||
}
|
||||
|
||||
/* Heading letter-spacing per theme */
|
||||
[data-theme="industrial"] {
|
||||
--heading-tracking-h1: -0.02em;
|
||||
--heading-tracking-h2: -0.02em;
|
||||
--heading-tracking-h3: -0.01em;
|
||||
--heading-tracking-h4: 0em;
|
||||
--heading-tracking-h5: 0.02em;
|
||||
--heading-tracking-h6: 0.04em;
|
||||
}
|
||||
|
||||
[data-theme="modern"] {
|
||||
--heading-tracking-h1: -0.03em;
|
||||
--heading-tracking-h2: -0.02em;
|
||||
--heading-tracking-h3: -0.01em;
|
||||
--heading-tracking-h4: 0em;
|
||||
--heading-tracking-h5: 0em;
|
||||
--heading-tracking-h6: 0.01em;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
COMPONENT SHORTHAND TOKENS
|
||||
Convenience aliases that combine multiple primitives. Components should
|
||||
reference these rather than the primitives above.
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
|
||||
/* --- Input / form fields --- */
|
||||
/* These are intentionally left as CSS variable references so they resolve
|
||||
correctly within whichever theme is active at runtime. */
|
||||
|
||||
/* (No :root overrides needed — components consume --color-* directly.) */
|
||||
|
||||
/* --- Focus ring --- */
|
||||
--focus-ring: 0 0 0 3px var(--shadow-focus, rgba(245,166,35,0.40));
|
||||
|
||||
/* --- Content max widths --- */
|
||||
--content-xs: 480px;
|
||||
--content-sm: 640px;
|
||||
--content-md: 768px;
|
||||
--content-lg: 1024px;
|
||||
--content-xl: 1280px;
|
||||
--content-full: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
UTILITY — Scrollbar styles (opt-in via class)
|
||||
========================================================================== */
|
||||
|
||||
.themed-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
}
|
||||
|
||||
.themed-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.themed-scrollbar::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-track);
|
||||
}
|
||||
|
||||
.themed-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-thumb);
|
||||
border-radius: var(--radius-full);
|
||||
border: 2px solid var(--scrollbar-track);
|
||||
}
|
||||
|
||||
.themed-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
UTILITY — Dot-grid background (Theme B helper)
|
||||
Apply class .bg-dot-grid to body or layout shell when using modern theme.
|
||||
========================================================================== */
|
||||
|
||||
[data-theme="modern"] .bg-dot-grid {
|
||||
background-color: var(--color-bg-base);
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
var(--dot-grid-color) 1px,
|
||||
transparent 1px
|
||||
);
|
||||
background-size: var(--dot-grid-size) var(--dot-grid-size);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
UTILITY — Industrial diagonal clip helpers (Theme A)
|
||||
========================================================================== */
|
||||
|
||||
[data-theme="industrial"] .clip-top-right {
|
||||
clip-path: polygon(0 0, calc(100% - 24px) 0, 100% 24px, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .clip-bottom-left {
|
||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 24px 100%, 0 calc(100% - 24px));
|
||||
}
|
||||
|
||||
[data-theme="industrial"] .clip-corner {
|
||||
clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
GLASSMORPHISM TOKENS
|
||||
========================================================================== */
|
||||
|
||||
[data-theme="industrial"] {
|
||||
--glass-bg: rgba(26, 26, 26, 0.70);
|
||||
--glass-bg-strong: rgba(26, 26, 26, 0.85);
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-blur: 16px;
|
||||
--glass-highlight: rgba(245, 166, 35, 0.06);
|
||||
|
||||
--glow-color: rgba(245, 166, 35, 0.40);
|
||||
--glow-color-soft: rgba(245, 166, 35, 0.15);
|
||||
--glow-color-strong: rgba(245, 166, 35, 0.60);
|
||||
|
||||
--gradient-accent: linear-gradient(135deg, #F5A623 0%, #e8951a 50%, #d4850f 100%);
|
||||
--gradient-text: linear-gradient(135deg, #F5A623 0%, #FFD080 50%, #F5A623 100%);
|
||||
|
||||
--canvas-grid-color: rgba(255, 255, 255, 0.06);
|
||||
--canvas-star-color: rgba(245, 166, 35, 0.30);
|
||||
--canvas-glow-color: rgba(245, 166, 35, 0.08);
|
||||
}
|
||||
|
||||
[data-theme="modern"] {
|
||||
--glass-bg: rgba(248, 249, 255, 0.70);
|
||||
--glass-bg-strong: rgba(248, 249, 255, 0.85);
|
||||
--glass-border: rgba(26, 26, 46, 0.08);
|
||||
--glass-blur: 16px;
|
||||
--glass-highlight: rgba(255, 107, 53, 0.04);
|
||||
|
||||
--glow-color: rgba(255, 107, 53, 0.35);
|
||||
--glow-color-soft: rgba(255, 107, 53, 0.12);
|
||||
--glow-color-strong: rgba(255, 107, 53, 0.55);
|
||||
|
||||
--gradient-accent: linear-gradient(135deg, #FF6B35 0%, #FF8F65 50%, #FF6B35 100%);
|
||||
--gradient-text: linear-gradient(135deg, #FF6B35 0%, #FF8F65 50%, #e85520 100%);
|
||||
|
||||
--canvas-grid-color: rgba(26, 26, 46, 0.05);
|
||||
--canvas-star-color: rgba(255, 107, 53, 0.20);
|
||||
--canvas-glow-color: rgba(255, 107, 53, 0.06);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
ANIMATION KEYFRAMES
|
||||
========================================================================== */
|
||||
|
||||
@keyframes nx-fade-up {
|
||||
from { opacity: 0; transform: translateY(24px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes nx-glow-pulse {
|
||||
0%, 100% { box-shadow: 0 0 20px var(--glow-color-soft); }
|
||||
50% { box-shadow: 0 0 40px var(--glow-color); }
|
||||
}
|
||||
|
||||
@keyframes nx-shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
END OF TOKENS FILE
|
||||
nexus-autoparts-design/tokens/tokens.css
|
||||
========================================================================== */
|
||||
1
pos/static/js/accounting.min.js
vendored
Normal file
1
pos/static/js/accounting.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/app-init.min.js
vendored
Normal file
1
pos/static/js/app-init.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e=localStorage.getItem("pos_token");if(e){try{var t=JSON.parse(atob(e.split(".")[1]));if(1e3*t.exp<Date.now())return localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee"),void(window.location.href="/pos/login")}catch(e){return localStorage.removeItem("pos_token"),void(window.location.href="/pos/login")}var o={};try{o=JSON.parse(localStorage.getItem("pos_employee")||"{}")}catch(e){}var n=o.name||t.name||"Usuario",a=o.role||t.role||"",r="function"==typeof window.t?window.t:function(e){return e},i={owner:r("role_owner"),admin:r("role_admin"),cashier:r("role_cashier"),warehouse:r("role_warehouse"),accountant:r("role_accountant")}[a]||a,c=n.split(" ").map((function(e){return e[0]})).join("").toUpperCase().substring(0,2);document.querySelectorAll(".sidebar__user-name").forEach((function(e){e.textContent=n})),document.querySelectorAll(".sidebar__user-role").forEach((function(e){e.textContent=i})),document.querySelectorAll(".sidebar__user-avatar, .sidebar__avatar").forEach((function(e){e.textContent=c})),document.querySelectorAll(".profile-info__name").forEach((function(e){e.textContent=n})),document.querySelectorAll(".profile-info__role").forEach((function(e){e.textContent=i})),document.querySelectorAll(".theme-bar__label").forEach((function(e){-1===e.textContent.indexOf("Usuario:")&&-1===e.textContent.indexOf("Sucursal")||(e.textContent="Sucursal Principal — "+n)})),document.querySelectorAll(".status-bar .user-name, .status-info span").forEach((function(e){var t=e.textContent;["Hugo M.","Hugo García","J. Ramírez","José Ramírez","Carlos M.","Admin"].forEach((function(o){-1!==t.indexOf(o)&&(e.textContent=t.replace(o,n))}))}));var l=window.location.pathname;document.querySelectorAll(".nav-item, .nav-link").forEach((function(e){e.classList.remove("is-active","active"),(e.getAttribute("href")||"")===l&&(e.classList.add("is-active"),e.classList.add("active"))})),window.posLogout=function(){localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee"),localStorage.removeItem("pos_tenant_id"),localStorage.removeItem("pos_cart"),window.location.href="/pos/login"},document.querySelectorAll('[data-action="logout"], .btn-logout, .logout-btn').forEach((function(e){e.addEventListener("click",(function(e){e.preventDefault(),posLogout()}))}));var s=localStorage.getItem("pos_theme");s||(s=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"industrial":"modern"),document.documentElement.setAttribute("data-theme",s),document.querySelectorAll(".theme-bar").forEach((function(e){e.style.display="none"})),window.posSetTheme=function(e){document.documentElement.setAttribute("data-theme",e),localStorage.setItem("pos_theme",e)},window.setTheme=window.posSetTheme,window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",(function(e){if(!localStorage.getItem("pos_theme")){var t=e.matches?"industrial":"modern";document.documentElement.setAttribute("data-theme",t)}})),setTimeout((function(){document.documentElement.setAttribute("data-theme",s)}),100),window.POS_USER={name:n,role:a,roleLabel:i,initials:c,token:e,tenantId:t.tenant_id,employeeId:t.employee_id,branchId:t.branch_id,permissions:t.permissions||[]}}else window.location.href="/pos/login"}();
|
||||
@@ -893,7 +893,7 @@
|
||||
}
|
||||
|
||||
var imgHtml = p.image_url
|
||||
? '<img src="' + esc(p.image_url) + '" alt="' + esc(p.name) + '">'
|
||||
? '<img src="' + esc(p.image_url) + '" alt="' + esc(p.name) + '" loading="lazy" decoding="async">'
|
||||
: '<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 7h8M8 12h8M8 17h5"/></svg>';
|
||||
|
||||
var tierClass = p.priority_tier === 1 ? ' part-card--tier1' : (p.priority_tier === 2 ? ' part-card--tier2' : '');
|
||||
@@ -997,7 +997,7 @@
|
||||
: '';
|
||||
|
||||
var imgHtml = p.image_url
|
||||
? '<img src="' + esc(p.image_url) + '" alt="' + esc(p.name) + '">'
|
||||
? '<img src="' + esc(p.image_url) + '" alt="' + esc(p.name) + '" loading="lazy" decoding="async">'
|
||||
: '<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 7h8M8 12h8M8 17h5"/></svg>';
|
||||
|
||||
// Local-mode extras: manufacturer badge + priority tier indicator
|
||||
@@ -1125,7 +1125,7 @@
|
||||
html += '<div class="detail-oem">' + esc(p.oem_part_number) + '</div>';
|
||||
html += '<div class="detail-name">' + esc(p.name) + '</div>';
|
||||
if (p.description) html += '<div class="detail-desc">' + esc(p.description) + '</div>';
|
||||
if (p.image_url) html += '<div style="margin-top:var(--space-3);text-align:center;"><img src="' + esc(p.image_url) + '" alt="" style="max-width:100%;max-height:200px;object-fit:contain;border-radius:var(--radius-sm);"></div>';
|
||||
if (p.image_url) html += '<div style="margin-top:var(--space-3);text-align:center;"><img src="' + esc(p.image_url) + '" alt="" loading="lazy" decoding="async" style="max-width:100%;max-height:200px;object-fit:contain;border-radius:var(--radius-sm);"></div>';
|
||||
html += '</div>';
|
||||
|
||||
// Local stock
|
||||
|
||||
1
pos/static/js/catalog.min.js
vendored
Normal file
1
pos/static/js/catalog.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/chat.min.js
vendored
Normal file
1
pos/static/js/chat.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/config.min.js
vendored
Normal file
1
pos/static/js/config.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/customers.min.js
vendored
Normal file
1
pos/static/js/customers.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/dashboard.min.js
vendored
Normal file
1
pos/static/js/dashboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/diagrams.min.js
vendored
Normal file
1
pos/static/js/diagrams.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/fleet.min.js
vendored
Normal file
1
pos/static/js/fleet.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/i18n.min.js
vendored
Normal file
1
pos/static/js/i18n.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -549,7 +549,7 @@
|
||||
// Product image section
|
||||
html += '<div style="text-align:center;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
if (data.image_url) {
|
||||
html += '<img src="' + esc(data.image_url) + '?t=' + Date.now() + '" alt="' + esc(data.name) + '" style="max-width:100%;max-height:220px;object-fit:contain;border-radius:var(--radius-sm);margin-bottom:8px;display:block;margin-left:auto;margin-right:auto;">';
|
||||
html += '<img src="' + esc(data.image_url) + '?t=' + Date.now() + '" alt="' + esc(data.name) + '" loading="lazy" decoding="async" style="max-width:100%;max-height:220px;object-fit:contain;border-radius:var(--radius-sm);margin-bottom:8px;display:block;margin-left:auto;margin-right:auto;">';
|
||||
html += '<div style="display:flex;gap:8px;justify-content:center;">';
|
||||
html += '<button class="btn btn--ghost btn--sm" onclick="uploadItemImage(' + data.id + ')">Cambiar imagen</button>';
|
||||
html += '<button class="btn btn--ghost btn--sm" style="color:var(--color-error);" onclick="deleteItemImage(' + data.id + ')">Eliminar imagen</button>';
|
||||
|
||||
1
pos/static/js/inventory.min.js
vendored
Normal file
1
pos/static/js/inventory.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/invoicing.min.js
vendored
Normal file
1
pos/static/js/invoicing.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/kiosk.min.js
vendored
Normal file
1
pos/static/js/kiosk.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e="pos_kiosk_mode";function n(){return window.matchMedia("(display-mode: standalone)").matches||!0===window.navigator.standalone}function t(){return void 0!==window.Capacitor&&window.Capacitor.isNativePlatform&&window.Capacitor.isNativePlatform()}function o(){var o=localStorage.getItem(e);return"true"===o||"false"!==o&&(n()||t())}var a=!1;function i(){if(!a){var e=document.documentElement,n=e.requestFullscreen||e.webkitRequestFullscreen||e.mozRequestFullScreen||e.msRequestFullscreen;n&&(n.call(e).catch((function(){})),a=!0)}}var c=null;async function r(){if("wakeLock"in navigator)try{(c=await navigator.wakeLock.request("screen")).addEventListener("release",(function(){c=null}))}catch(e){}}function u(){window.addEventListener("beforeunload",(function(e){o()&&(e.preventDefault(),e.returnValue="")})),document.addEventListener("contextmenu",(function(e){o()&&e.preventDefault()}));var e=["click","touchstart","keydown"];function n(){o()&&(i(),r()),e.forEach((function(e){document.removeEventListener(e,n)}))}e.forEach((function(e){document.addEventListener(e,n,{once:!1})})),function(){var e=localStorage.getItem("pos_token");if(e&&-1!==window.location.pathname.indexOf("/pos/login"))try{var n=e.split(".");if(3!==n.length)return;var t=JSON.parse(atob(n[1].replace(/-/g,"+").replace(/_/g,"/"))).exp;t&&1e3*t>Date.now()&&(window.location.href="/pos/")}catch(e){}}()}document.addEventListener("visibilitychange",(function(){"visible"===document.visibilityState&&o()&&!c&&r()})),window.NexusKiosk={isEnabled:o,isPWA:n,isCapacitor:t,enable:function(){localStorage.setItem(e,"true"),i(),r()},disable:function(){localStorage.setItem(e,"false"),c&&(c.release(),c=null),document.fullscreenElement&&document.exitFullscreen().catch((function(){}))},toggle:function(){return o()?window.NexusKiosk.disable():window.NexusKiosk.enable(),o()}},o()&&u(),window.addEventListener("storage",(function(n){n.key===e&&"true"===n.newValue&&u()}))}();
|
||||
1
pos/static/js/login.min.js
vendored
Normal file
1
pos/static/js/login.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var t="",e=document.querySelectorAll("#pinDots .pin-dot"),n=document.getElementById("loginError"),o=new URLSearchParams(window.location.search).get("tenant")||localStorage.getItem("pos_tenant_id"),a=localStorage.getItem("pos_device_id");function i(){e.forEach((function(e,n){e.classList.toggle("filled",n<t.length)}))}a||(a="dev-"+Date.now()+"-"+Math.random().toString(36).substr(2,9),localStorage.setItem("pos_device_id",a)),window.addDigit=function(e){t.length>=4||(t+=e,i(),n.textContent="",4===t.length&&submitPin())},window.clearPin=function(){t="",i(),n.textContent=""},window.submitPin=function(){4===t.length&&(n.textContent="",fetch("/pos/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tenant_id:parseInt(o),pin:t,device_id:a})}).then((function(t){return t.json().then((function(e){return{ok:t.ok,data:e}}))})).then((function(t){if(!t.ok)return n.textContent=t.data.error||"Error de autenticacion",void clearPin();localStorage.setItem("pos_token",t.data.token),localStorage.setItem("pos_employee",JSON.stringify(t.data.employee)),localStorage.setItem("pos_tenant_id",o),window.location.href="/pos/catalog"})).catch((function(){n.textContent="Error de conexion",clearPin()})))},document.addEventListener("keydown",(function(t){t.key>="0"&&t.key<="9"?addDigit(t.key):"Backspace"===t.key?clearPin():"Enter"===t.key&&submitPin()}));var r=localStorage.getItem("pos_token");r&&o&&(!function(t){try{var e=t.split(".");if(3!==e.length)return!1;var n=e[1].replace(/-/g,"+").replace(/_/g,"/"),o=JSON.parse(atob(n));return!!o.exp&&1e3*o.exp>Date.now()+3e4}catch(t){return!1}}(r)?(localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee")):window.location.href="/pos/catalog")}();
|
||||
1
pos/static/js/native-bridge.min.js
vendored
Normal file
1
pos/static/js/native-bridge.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";window.NexusNative={isNative:"undefined"!=typeof Capacitor,_scanStream:null,_scanVideo:null,async scanBarcode(){if(this.isNative)try{const{Camera:e}=await import("@capacitor/camera");return await e.getPhoto({quality:90,resultType:"base64"})}catch(e){return null}return"BarcodeDetector"in window?new Promise((async e=>{try{const t=await navigator.mediaDevices.getUserMedia({video:{facingMode:"environment",width:{ideal:1280},height:{ideal:720}}});this._scanStream=t;const a=document.createElement("div");a.id="barcode-scan-overlay",a.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;";const i=document.createElement("video");i.autoplay=!0,i.playsInline=!0,i.style.cssText="width:90%;max-width:500px;border-radius:12px;border:3px solid #F5A623;",i.srcObject=t,this._scanVideo=i;const o=document.createElement("p");o.textContent="Apunta al codigo de barras...",o.style.cssText="color:#fff;font-size:16px;margin-top:16px;font-family:sans-serif;";const s=document.createElement("button");s.textContent="Cancelar",s.style.cssText="margin-top:16px;padding:10px 24px;background:#F5A623;color:#000;border:none;border-radius:6px;font-size:15px;cursor:pointer;font-weight:bold;",s.onclick=()=>{this.stopScan(),a.remove(),e(null)},a.appendChild(i),a.appendChild(o),a.appendChild(s),document.body.appendChild(a);const n=new BarcodeDetector({formats:["ean_13","ean_8","code_128","code_39","qr_code","upc_a","upc_e"]}),r=async()=>{if(this._scanStream){try{const t=await n.detect(i);if(t.length>0){const i=t[0].rawValue;return o.textContent="Codigo detectado: "+i,o.style.color="#4CAF50",void setTimeout((()=>{this.stopScan(),a.remove(),e(i)}),400)}}catch(e){}requestAnimationFrame(r)}};i.onloadedmetadata=()=>{i.play(),requestAnimationFrame(r)}}catch(t){console.error("Camera access error:",t),alert("No se pudo acceder a la camara: "+t.message),e(null)}})):(alert("Tu navegador no soporta escaneo de codigos de barras. Usa Chrome 83+ o un dispositivo movil."),null)},stopScan(){this._scanStream&&(this._scanStream.getTracks().forEach((e=>e.stop())),this._scanStream=null),this._scanVideo=null;var e=document.getElementById("barcode-scan-overlay");e&&e.remove()},async registerPush(){if(!this.isNative)return null;try{const{PushNotifications:e}=await import("@capacitor/push-notifications");"granted"===(await e.requestPermissions()).receive&&await e.register(),e.addListener("registration",(e=>{console.log("Push token:",e.value)})),e.addListener("pushNotificationReceived",(e=>{console.log("Push received:",e)}))}catch(e){console.log("Push not available:",e)}},async vibrate(){if(this.isNative)try{const{Haptics:e,ImpactStyle:t}=await import("@capacitor/haptics");await e.impact({style:t.Light})}catch(e){}},async setupStatusBar(){if(this.isNative)try{const{StatusBar:e,Style:t}=await import("@capacitor/status-bar");await e.setStyle({style:t.Dark}),await e.setBackgroundColor({color:"#0d0d0d"})}catch(e){}}},window.NexusNative.isNative&&(window.NexusNative.setupStatusBar(),window.NexusNative.registerPush())}();
|
||||
1
pos/static/js/offline-banner.min.js
vendored
Normal file
1
pos/static/js/offline-banner.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var n=document.getElementById("offlineBanner"),e=document.getElementById("offlineBannerText");if(n&&e){var t=null;navigator.onLine||o(),window.addEventListener("offline",o),window.addEventListener("online",(function(){clearTimeout(t),n.className="banner banner--success",n.style.display="flex",n.style.animation="slideDown 0.35s ease-out forwards",e.innerHTML="<strong>Conexion restaurada</strong> — Sincronizando datos...",t=setTimeout((function(){n.style.animation="slideUp 0.3s ease-in forwards",n.addEventListener("animationend",(function e(){n.style.display="none",n.removeEventListener("animationend",e)}),{once:!0})}),3e3)}))}function o(){clearTimeout(t),n.className="banner banner--error",n.style.display="flex",n.style.animation="slideDown 0.35s ease-out forwards",e.innerHTML="<strong>Conexion perdida</strong> — Intentando reconectar..."}}();
|
||||
1
pos/static/js/onboarding.min.js
vendored
Normal file
1
pos/static/js/onboarding.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/pos-utils.min.js
vendored
Normal file
1
pos/static/js/pos-utils.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/pos.min.js
vendored
Normal file
1
pos/static/js/pos.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/printer.min.js
vendored
Normal file
1
pos/static/js/printer.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
window.NexusPrinter=function(){"use strict";let e=null,t=null,r=null,n=null,a=null;async function i(){try{"usb"===a&&e&&await e.close(),"serial"===a&&(n&&(n.releaseLock(),n=null),r&&(await r.close(),r=null))}catch(e){}e=null,t=null,a=null,localStorage.removeItem("nexus_printer")}async function o(r){if(!a)return!1;try{if("usb"===a&&e&&null!==t){const n=512;for(let a=0;a<r.length;a+=n)await e.transferOut(t,r.slice(a,a+n));return!0}if("serial"===a&&n)return await n.write(r),!0}catch(e){console.error("[NexusPrinter] send error:",e),await i()}return!1}function l(){try{localStorage.setItem("nexus_printer",a||"")}catch(e){}}return{connect:async function(){if("usb"in navigator)try{e=await navigator.usb.requestDevice({filters:[]}),await e.open(),null===e.configuration&&await e.selectConfiguration(1);const r=e.configuration.interfaces[0];await e.claimInterface(r.interfaceNumber);const n=r.alternates[0].endpoints.find((e=>"out"===e.direction&&"bulk"===e.type));if(n)return t=n.endpointNumber,a="usb",l(),{ok:!0,type:"usb",name:e.productName||"USB Printer"}}catch(t){e=null}if("serial"in navigator)try{return r=await navigator.serial.requestPort(),await r.open({baudRate:9600}),n=r.writable.getWriter(),a="serial",l(),{ok:!0,type:"serial",name:"Serial Printer"}}catch(e){r=null,n=null}return{ok:!1,error:"No printer connected. Use a browser that supports WebUSB or Web Serial (Chrome/Edge)."}},disconnect:i,isConnected:function(){return null!==a},sendRaw:o,printSale:async function(e,t){t=t||80;const r=localStorage.getItem("pos_token"),n=await fetch("/pos/api/sales/"+e+"/print",{method:"POST",headers:{Authorization:"Bearer "+r,"Content-Type":"application/json"},body:JSON.stringify({printer_type:"escpos_raw",width:t})});if(!n.ok)return console.error("[NexusPrinter] backend error",n.status),!1;const a=await n.arrayBuffer();return o(new Uint8Array(a))}}}();
|
||||
1
pos/static/js/push.min.js
vendored
Normal file
1
pos/static/js/push.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e={};try{e=JSON.parse(localStorage.getItem("pos_employee")||"{}")}catch(e){}var o=e.role||"";if("owner"===o||"admin"===o)if("serviceWorker"in navigator&&"PushManager"in window){var r=localStorage.getItem("pos_token");r&&("complete"===document.readyState?setTimeout(i,2e3):window.addEventListener("load",(function(){setTimeout(i,2e3)})))}else console.log("[Push] Browser does not support push notifications");function t(e){for(var o=(e+"=".repeat((4-e.length%4)%4)).replace(/-/g,"+").replace(/_/g,"/"),r=window.atob(o),t=new Uint8Array(r.length),i=0;i<r.length;++i)t[i]=r.charCodeAt(i);return t}async function i(){try{var e=await navigator.serviceWorker.register("/pos/static/sw-push.js",{scope:"/pos/"});console.log("[Push] Service worker registered");var o=await fetch("/pos/api/push/vapid-key",{headers:{Authorization:"Bearer "+r}});if(!o.ok)return void console.log("[Push] VAPID key not available:",o.status);var i=(await o.json()).public_key;if(!i)return;if("granted"!==await Notification.requestPermission())return void console.log("[Push] Permission denied");var a=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t(i)});(await fetch("/pos/api/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+r},body:JSON.stringify({subscription:a.toJSON()})})).ok&&console.log("[Push] Subscribed successfully")}catch(e){console.log("[Push] Setup error:",e)}}}();
|
||||
1
pos/static/js/reports.min.js
vendored
Normal file
1
pos/static/js/reports.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/sidebar.min.js
vendored
Normal file
1
pos/static/js/sidebar.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pos/static/js/sync-engine.min.js
vendored
Normal file
1
pos/static/js/sync-engine.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var n="sync_queue",e="inventory_cache",t="cached_parts",r=null;function o(){return new Promise((function(o,c){if(r)o(r);else{var i=indexedDB.open("nexus_pos_offline",2);i.onupgradeneeded=function(r){var o=r.target.result;if(o.objectStoreNames.contains(n)||o.createObjectStore(n,{keyPath:"id",autoIncrement:!0}),!o.objectStoreNames.contains(e)){var c=o.createObjectStore(e,{keyPath:"item_id"});c.createIndex("sku","sku",{unique:!1}),c.createIndex("name","name",{unique:!1})}if(!o.objectStoreNames.contains(t)){var i=o.createObjectStore(t,{keyPath:"part_number"});i.createIndex("name","name",{unique:!1}),i.createIndex("category","category",{unique:!1})}},i.onsuccess=function(n){r=n.target.result,o(r)},i.onerror=function(){c(i.error)}}}))}function c(){return o().then((function(e){return new Promise((function(t,r){var o=e.transaction(n,"readonly").objectStore(n).getAll();o.onsuccess=function(){t(o.result)},o.onerror=function(){r(o.error)}}))})).then((function(e){if(!e.length)return Promise.resolve({synced:0});var t=Promise.resolve(),r=0,c=0;return e.forEach((function(e){t=t.then((function(){var t={method:e.method,headers:{"Content-Type":"application/json"}};return e.body&&(t.body=JSON.stringify(e.body)),fetch(e.url,t).then((function(t){if(t.ok)return r++,i=e.id,o().then((function(e){return new Promise((function(t,r){var o=e.transaction(n,"readwrite");o.objectStore(n).delete(i),o.oncomplete=function(){t()},o.onerror=function(){r(o.error)}}))}));var i;c++})).catch((function(){c++}))}))})),t.then((function(){return{synced:r,failed:c,total:e.length}}))}))}window.addEventListener("online",(function(){console.log("[SyncEngine] Online — processing queue..."),c().then((function(n){n.synced>0&&console.log("[SyncEngine] Synced "+n.synced+" operations")})).catch((function(n){console.error("[SyncEngine] Queue processing error:",n)}))})),window.addEventListener("offline",(function(){console.log("[SyncEngine] Offline — operations will be queued")})),"serviceWorker"in navigator&&navigator.serviceWorker.addEventListener("message",(function(n){n.data&&"SYNC_REQUESTED"===n.data.type&&c()})),o().catch((function(n){console.error("[SyncEngine] Failed to open IndexedDB:",n)})),window.SyncEngine={queueOperation:function(e,t,r){return o().then((function(o){return new Promise((function(c,i){var a=o.transaction(n,"readwrite");a.objectStore(n).add({url:e,method:t,body:r||null,timestamp:Date.now()}),a.oncomplete=function(){c()},a.onerror=function(){i(a.error)}}))}))},processQueue:c,getQueueCount:function(){return o().then((function(e){return new Promise((function(t,r){var o=e.transaction(n,"readonly").objectStore(n).count();o.onsuccess=function(){t(o.result)},o.onerror=function(){r(o.error)}}))}))},isOnline:function(){return navigator.onLine},cacheInventory:function(){return fetch("/pos/api/sync/inventory").then((function(n){if(!n.ok)throw new Error("Sync inventory failed: "+n.status);return n.json()})).then((function(n){var t=n.items||[];return o().then((function(n){return new Promise((function(r,o){var c=n.transaction(e,"readwrite"),i=c.objectStore(e);i.clear(),t.forEach((function(n){i.put(n)})),c.oncomplete=function(){r(t.length)},c.onerror=function(){o(c.error)}}))}))}))},getCachedInventory:function(n){return o().then((function(t){return new Promise((function(r,o){var c=t.transaction(e,"readonly").objectStore(e).getAll();c.onsuccess=function(){var e=c.result;if(n){var t=n.toLowerCase(),o=e.filter((function(n){return n.sku&&-1!==n.sku.toLowerCase().indexOf(t)||n.name&&-1!==n.name.toLowerCase().indexOf(t)||n.barcode&&-1!==n.barcode.toLowerCase().indexOf(t)}));r(o)}else r(e)},c.onerror=function(){o(c.error)}}))}))},cacheTopParts:function(){return fetch("/pos/api/sync/top-parts").then((function(n){if(!n.ok)throw new Error("Sync top-parts failed: "+n.status);return n.json()})).then((function(n){var e=n.parts||[];return o().then((function(n){return new Promise((function(r,o){var c=n.transaction(t,"readwrite"),i=c.objectStore(t);i.clear(),e.forEach((function(n){i.put(n)})),c.oncomplete=function(){console.log("[SyncEngine] Cached "+e.length+" top parts"),r(e.length)},c.onerror=function(){o(c.error)}}))}))}))},searchCachedParts:function(n){return o().then((function(e){return new Promise((function(r,o){var c=e.transaction(t,"readonly").objectStore(t).getAll();c.onsuccess=function(){var e=c.result;if(n){var t=n.toLowerCase(),o=e.filter((function(n){return n.part_number&&-1!==n.part_number.toLowerCase().indexOf(t)||n.name&&-1!==n.name.toLowerCase().indexOf(t)||n.category&&-1!==n.category.toLowerCase().indexOf(t)||n.brand&&-1!==n.brand.toLowerCase().indexOf(t)}));r(o)}else r(e)},c.onerror=function(){o(c.error)}}))}))}}}();
|
||||
1
pos/static/js/whatsapp.min.js
vendored
Normal file
1
pos/static/js/whatsapp.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user