1222 lines
44 KiB
HTML
1222 lines
44 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>NEXUS AUTOPARTS — Catálogo OEM</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=Outfit:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
:root {
|
|
--bg: #0a0a0f;
|
|
--bg2: #12121a;
|
|
--card: #1a1a24;
|
|
--hover: #252532;
|
|
--border: #2a2a3a;
|
|
--accent: #ff6b35;
|
|
--accent-glow: rgba(255, 107, 53, 0.25);
|
|
--text: #ffffff;
|
|
--text2: #a0a0b0;
|
|
--success: #22c55e;
|
|
--info: #3b82f6;
|
|
}
|
|
|
|
body {
|
|
font-family: 'DM Sans', sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* --- Header --- */
|
|
.header {
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0;
|
|
z-index: 100;
|
|
background: rgba(18, 18, 26, 0.92);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0.7rem 2rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.7rem;
|
|
}
|
|
|
|
.logo-mark {
|
|
width: 40px; height: 40px;
|
|
background: linear-gradient(135deg, var(--accent), #ff4500);
|
|
border-radius: 10px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 1.3rem;
|
|
box-shadow: 0 3px 16px var(--accent-glow);
|
|
}
|
|
|
|
.logo-text {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-weight: 800;
|
|
font-size: 1.3rem;
|
|
background: linear-gradient(135deg, #fff, var(--accent));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.logo-sub {
|
|
font-size: 0.6rem;
|
|
color: var(--text2);
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.header-search {
|
|
position: relative;
|
|
width: 340px;
|
|
}
|
|
|
|
.header-search input {
|
|
width: 100%;
|
|
padding: 0.5rem 0.8rem 0.5rem 2.2rem;
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
color: var(--text);
|
|
font-family: 'DM Sans', sans-serif;
|
|
font-size: 0.85rem;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.header-search input:focus { border-color: var(--accent); }
|
|
.header-search input::placeholder { color: var(--text2); }
|
|
|
|
.header-search svg {
|
|
position: absolute;
|
|
left: 0.7rem; top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 16px; height: 16px;
|
|
color: var(--text2);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.search-drop {
|
|
position: absolute;
|
|
top: calc(100% + 4px);
|
|
left: 0; right: 0;
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
max-height: 320px;
|
|
overflow-y: auto;
|
|
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
|
|
display: none;
|
|
z-index: 200;
|
|
}
|
|
|
|
.search-drop.open { display: block; }
|
|
|
|
.search-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0.55rem 0.8rem;
|
|
border-bottom: 1px solid var(--border);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.search-item:last-child { border-bottom: none; }
|
|
.search-item:hover { background: var(--hover); }
|
|
|
|
.search-item .si-oem {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.search-item .si-name {
|
|
font-size: 0.8rem;
|
|
color: var(--text2);
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.search-item .si-cat {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
.badge-demo {
|
|
font-size: 0.65rem;
|
|
font-weight: 700;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
padding: 0.2rem 0.6rem;
|
|
border-radius: 5px;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
/* --- Main --- */
|
|
.main {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 4.5rem 1.5rem 2rem;
|
|
}
|
|
|
|
/* --- Breadcrumb --- */
|
|
.breadcrumb {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
margin-bottom: 1rem;
|
|
font-size: 0.8rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.breadcrumb span {
|
|
color: var(--text2);
|
|
}
|
|
|
|
.breadcrumb a {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.breadcrumb a:hover { text-decoration: underline; }
|
|
|
|
.breadcrumb .sep {
|
|
color: var(--border);
|
|
}
|
|
|
|
/* --- Section title --- */
|
|
.section-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-weight: 700;
|
|
font-size: 1.4rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.section-subtitle {
|
|
font-size: 0.85rem;
|
|
color: var(--text2);
|
|
margin-top: -0.6rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* --- Grid --- */
|
|
.grid {
|
|
display: grid;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.grid-brands {
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
}
|
|
|
|
.grid-models {
|
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
}
|
|
|
|
.grid-vehicles {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.grid-categories {
|
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
}
|
|
|
|
.grid-parts {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
/* --- Card --- */
|
|
.card {
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 0.8rem 1rem;
|
|
cursor: pointer;
|
|
transition: border-color 0.2s, transform 0.15s, background 0.2s;
|
|
}
|
|
|
|
.card:hover {
|
|
border-color: var(--accent);
|
|
background: var(--hover);
|
|
}
|
|
|
|
.card:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Brand card */
|
|
.card-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.card-brand .cb-name {
|
|
font-weight: 700;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.card-brand .cb-count {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
|
|
/* Model card */
|
|
.card-model {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
}
|
|
|
|
.card-model .cm-name {
|
|
font-weight: 700;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.card-model .cm-years {
|
|
font-size: 0.75rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
.card-model .cm-count {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
|
|
/* Vehicle row */
|
|
.card-vehicle {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.cv-main {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.cv-year {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
color: var(--accent);
|
|
min-width: 50px;
|
|
}
|
|
|
|
.cv-engine {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.cv-details {
|
|
font-size: 0.75rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
.cv-trim {
|
|
font-size: 0.7rem;
|
|
color: var(--info);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
padding: 0.15rem 0.5rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Category card */
|
|
.card-category {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.cc-name {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.cc-count {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
|
|
/* Group header */
|
|
.group-header {
|
|
padding: 0.6rem 0;
|
|
margin-top: 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.group-header .gh-name {
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.group-header .gh-count {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
/* Part row */
|
|
.part-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.5rem 0.6rem;
|
|
border-bottom: 1px solid rgba(42, 42, 58, 0.4);
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.part-row:hover {
|
|
background: var(--hover);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.part-oem {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
color: var(--accent);
|
|
min-width: 160px;
|
|
}
|
|
|
|
.part-name {
|
|
font-size: 0.85rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.part-pos {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
.part-qty {
|
|
font-size: 0.7rem;
|
|
color: var(--text2);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
min-width: 40px;
|
|
text-align: right;
|
|
}
|
|
|
|
/* --- Loading / Empty --- */
|
|
.loading, .empty {
|
|
text-align: center;
|
|
padding: 3rem 1rem;
|
|
color: var(--text2);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* --- Responsive --- */
|
|
@media (max-width: 768px) {
|
|
.header-search { display: none; }
|
|
.grid-brands { grid-template-columns: repeat(2, 1fr); }
|
|
.main { padding: 4rem 0.8rem 1.5rem; }
|
|
.part-oem { min-width: 120px; font-size: 0.8rem; }
|
|
}
|
|
|
|
/* --- Part row clickable --- */
|
|
.part-row {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* --- Modal overlay --- */
|
|
.modal-bg {
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(6px);
|
|
-webkit-backdrop-filter: blur(6px);
|
|
z-index: 500;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.25s;
|
|
}
|
|
|
|
.modal-bg.open {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.modal {
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
width: 600px;
|
|
max-width: 95vw;
|
|
max-height: 85vh;
|
|
overflow-y: auto;
|
|
transform: translateY(12px) scale(0.97);
|
|
transition: transform 0.25s;
|
|
}
|
|
|
|
.modal-bg.open .modal {
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
|
|
/* Modal header */
|
|
.modal-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
padding: 1.2rem 1.2rem 0.8rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.modal-head-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.modal-oem {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 700;
|
|
font-size: 1.2rem;
|
|
color: var(--accent);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.modal-part-name {
|
|
font-size: 0.95rem;
|
|
margin-top: 0.2rem;
|
|
}
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text2);
|
|
font-size: 1.4rem;
|
|
cursor: pointer;
|
|
padding: 0.2rem 0.4rem;
|
|
border-radius: 6px;
|
|
transition: background 0.15s, color 0.15s;
|
|
flex-shrink: 0;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.modal-close:hover {
|
|
background: var(--hover);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Modal detail fields */
|
|
.modal-fields {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.5rem;
|
|
padding: 0.8rem 1.2rem;
|
|
}
|
|
|
|
.mf {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.1rem;
|
|
}
|
|
|
|
.mf-label {
|
|
font-size: 0.65rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--text2);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mf-value {
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Modal sections */
|
|
.modal-section {
|
|
padding: 0.8rem 1.2rem;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.modal-section-title {
|
|
font-family: 'DM Sans', sans-serif;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--accent);
|
|
margin-bottom: 0.6rem;
|
|
}
|
|
|
|
/* Alternatives table */
|
|
.alt-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.alt-table th {
|
|
text-align: left;
|
|
padding: 0.35rem 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
color: var(--text2);
|
|
font-size: 0.7rem;
|
|
text-transform: uppercase;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.alt-table td {
|
|
padding: 0.4rem 0.5rem;
|
|
border-bottom: 1px solid rgba(42, 42, 58, 0.4);
|
|
}
|
|
|
|
.alt-table .alt-pn {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 600;
|
|
color: var(--info);
|
|
}
|
|
|
|
.alt-table .alt-mfr {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.alt-quality {
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
padding: 0.12rem 0.4rem;
|
|
border-radius: 4px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.alt-quality.premium { background: rgba(34, 197, 94, 0.15); color: var(--success); }
|
|
.alt-quality.economy { background: rgba(245, 158, 11, 0.15); color: #f59e0b; }
|
|
.alt-quality.oem { background: rgba(59, 130, 246, 0.15); color: var(--info); }
|
|
|
|
/* Cross-ref rows */
|
|
.xref-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.8rem;
|
|
padding: 0.35rem 0;
|
|
border-bottom: 1px solid rgba(42, 42, 58, 0.3);
|
|
}
|
|
|
|
.xref-row:last-child { border-bottom: none; }
|
|
|
|
.xref-number {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
color: var(--text);
|
|
}
|
|
|
|
.xref-type {
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
padding: 0.1rem 0.35rem;
|
|
border-radius: 3px;
|
|
background: rgba(255, 107, 53, 0.12);
|
|
color: var(--accent);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.xref-source {
|
|
font-size: 0.75rem;
|
|
color: var(--text2);
|
|
}
|
|
|
|
.modal-empty {
|
|
text-align: center;
|
|
padding: 1rem;
|
|
color: var(--text2);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.modal-loading {
|
|
text-align: center;
|
|
padding: 0.8rem;
|
|
color: var(--text2);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* --- Region Filter --- */
|
|
.region-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.region-label {
|
|
font-size: 0.75rem;
|
|
color: var(--text2);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
font-weight: 600;
|
|
margin-right: 0.3rem;
|
|
}
|
|
|
|
.region-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.35rem 0.75rem;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border);
|
|
background: var(--card);
|
|
color: var(--text2);
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
user-select: none;
|
|
}
|
|
|
|
.region-chip:hover {
|
|
border-color: var(--accent);
|
|
color: var(--text);
|
|
}
|
|
|
|
.region-chip.active {
|
|
background: rgba(255, 107, 53, 0.15);
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.region-chip .rc-flag {
|
|
font-size: 1rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* --- Animations --- */
|
|
@keyframes fadeUp {
|
|
from { opacity: 0; transform: translateY(6px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.card, .part-row, .group-header {
|
|
animation: fadeUp 0.3s ease both;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<div class="logo">
|
|
<div class="logo-mark">⚙️</div>
|
|
<div>
|
|
<div class="logo-text">NEXUS AUTOPARTS</div>
|
|
<div class="logo-sub">Catálogo de partes OEM</div>
|
|
</div>
|
|
</div>
|
|
<div class="header-search">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
|
<input id="search-input" type="text" placeholder="Buscar por número OEM o nombre..." autocomplete="off">
|
|
<div id="search-drop" class="search-drop"></div>
|
|
</div>
|
|
<span class="badge-demo">DEMO</span>
|
|
</header>
|
|
|
|
<main class="main">
|
|
<div id="breadcrumb" class="breadcrumb"></div>
|
|
<h1 id="title" class="section-title"></h1>
|
|
<p id="subtitle" class="section-subtitle"></p>
|
|
<div id="content"></div>
|
|
</main>
|
|
|
|
<!-- Part Detail Modal -->
|
|
<div id="modal-bg" class="modal-bg">
|
|
<div class="modal">
|
|
<div class="modal-head">
|
|
<div class="modal-head-info">
|
|
<div class="modal-oem" id="modal-oem"></div>
|
|
<div class="modal-part-name" id="modal-name"></div>
|
|
</div>
|
|
<button class="modal-close" id="modal-close">×</button>
|
|
</div>
|
|
<div id="modal-image" style="text-align:center;margin-bottom:1rem;display:none;">
|
|
<img id="modal-image-img" style="max-width:100%;max-height:300px;border-radius:8px;object-fit:contain;" />
|
|
</div>
|
|
<div class="modal-fields" id="modal-fields"></div>
|
|
<div class="modal-section" id="section-alts">
|
|
<div class="modal-section-title">Intercambios / Alternativas</div>
|
|
<div id="modal-alts"><div class="modal-loading">Cargando...</div></div>
|
|
</div>
|
|
<div class="modal-section" id="section-xrefs">
|
|
<div class="modal-section-title">Referencias cruzadas</div>
|
|
<div id="modal-xrefs"><div class="modal-loading">Cargando...</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
|
|
var API = '';
|
|
var state = { brand: null, model: null, mye_id: null, mye_label: null, category: null, category_name: null };
|
|
|
|
// Region filter: bitmask 1=MX, 2=US, 4=CA, 8=RW
|
|
var REGIONS = [
|
|
{ bit: 1, code: 'MX', label: 'M\u00e9xico', flag: '\uD83C\uDDF2\uD83C\uDDFD' },
|
|
{ bit: 2, code: 'US', label: 'USA', flag: '\uD83C\uDDFA\uD83C\uDDF8' },
|
|
{ bit: 4, code: 'CA', label: 'Canad\u00e1', flag: '\uD83C\uDDE8\uD83C\uDDE6' },
|
|
{ bit: 8, code: 'RW', label: 'Resto', flag: '\uD83C\uDF0E' }
|
|
];
|
|
|
|
// Load from localStorage or default MX+US (bitmask = 3)
|
|
var regionMask = parseInt(localStorage.getItem('nexus_region') || '3');
|
|
|
|
function esc(s) {
|
|
if (!s) return '';
|
|
var d = document.createElement('div');
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
// ============================================================
|
|
// Navigation
|
|
// ============================================================
|
|
|
|
function go(level, data) {
|
|
if (level === 'brands') {
|
|
state = { brand: null, model: null, mye_id: null, mye_label: null, category: null, category_name: null };
|
|
loadBrands();
|
|
} else if (level === 'models') {
|
|
state.brand = data;
|
|
state.model = null; state.mye_id = null; state.mye_label = null; state.category = null;
|
|
loadModels(data);
|
|
} else if (level === 'vehicles') {
|
|
state.model = data;
|
|
state.mye_id = null; state.mye_label = null; state.category = null;
|
|
loadVehicles(state.brand, data);
|
|
} else if (level === 'categories') {
|
|
state.mye_id = data.id;
|
|
state.mye_label = data.year + ' ' + data.engine;
|
|
state.category = null;
|
|
loadCategories(data.id);
|
|
} else if (level === 'parts') {
|
|
state.category = data.id;
|
|
state.category_name = data.name;
|
|
loadParts(state.mye_id, data.id, data.name);
|
|
}
|
|
updateBreadcrumb();
|
|
}
|
|
|
|
function updateBreadcrumb() {
|
|
var bc = document.getElementById('breadcrumb');
|
|
var parts = ['<a onclick="go(\'brands\')">Marcas</a>'];
|
|
if (state.brand) {
|
|
parts.push('<span class="sep">›</span>');
|
|
parts.push('<a onclick="go(\'models\',\'' + esc(state.brand) + '\')">' + esc(state.brand) + '</a>');
|
|
}
|
|
if (state.model) {
|
|
parts.push('<span class="sep">›</span>');
|
|
parts.push('<a onclick="go(\'vehicles\',\'' + esc(state.model) + '\')">' + esc(state.model) + '</a>');
|
|
}
|
|
if (state.mye_id) {
|
|
parts.push('<span class="sep">›</span>');
|
|
parts.push('<a onclick="go(\'categories\',{id:' + state.mye_id + ',year:\'\',engine:\'\'})">' + esc(state.mye_label) + '</a>');
|
|
}
|
|
if (state.category_name) {
|
|
parts.push('<span class="sep">›</span>');
|
|
parts.push('<span>' + esc(state.category_name) + '</span>');
|
|
}
|
|
bc.innerHTML = parts.join('');
|
|
}
|
|
|
|
// expose go to inline onclick
|
|
window.go = go;
|
|
|
|
// ============================================================
|
|
// Brands
|
|
// ============================================================
|
|
|
|
function buildRegionBar() {
|
|
var html = '<div class="region-bar">'
|
|
+ '<span class="region-label">Mercado:</span>';
|
|
REGIONS.forEach(function (r) {
|
|
var active = (regionMask & r.bit) ? ' active' : '';
|
|
html += '<div class="region-chip' + active + '" data-bit="' + r.bit + '" onclick="toggleRegion(' + r.bit + ')">'
|
|
+ '<span class="rc-flag">' + r.flag + '</span>'
|
|
+ r.label
|
|
+ '</div>';
|
|
});
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
window.toggleRegion = function (bit) {
|
|
regionMask ^= bit; // Toggle the bit
|
|
if (regionMask === 0) regionMask = bit; // Prevent all-off
|
|
localStorage.setItem('nexus_region', regionMask);
|
|
// Update chip styles
|
|
document.querySelectorAll('.region-chip').forEach(function (chip) {
|
|
var b = parseInt(chip.getAttribute('data-bit'));
|
|
if (regionMask & b) {
|
|
chip.classList.add('active');
|
|
} else {
|
|
chip.classList.remove('active');
|
|
}
|
|
});
|
|
// Reload brands
|
|
fetchBrands();
|
|
};
|
|
|
|
function loadBrands() {
|
|
var el = document.getElementById('content');
|
|
document.getElementById('title').textContent = 'Selecciona una marca';
|
|
document.getElementById('subtitle').innerHTML = buildRegionBar();
|
|
el.innerHTML = '<div class="loading">Cargando marcas...</div>';
|
|
fetchBrands();
|
|
}
|
|
|
|
function fetchBrands() {
|
|
var el = document.getElementById('content');
|
|
el.innerHTML = '<div class="loading">Cargando marcas...</div>';
|
|
|
|
fetch(API + '/api/brands?detailed=true®ion=' + regionMask)
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (brands) {
|
|
var sub = document.getElementById('subtitle');
|
|
// Keep region bar, update count text after it
|
|
var countEl = sub.querySelector('.region-count');
|
|
if (!countEl) {
|
|
var span = document.createElement('div');
|
|
span.className = 'region-count';
|
|
span.style.cssText = 'font-size:0.85rem; color:var(--text2); margin-top:0.3rem;';
|
|
sub.appendChild(span);
|
|
countEl = span;
|
|
}
|
|
countEl.textContent = brands.length + ' marcas disponibles';
|
|
|
|
el.className = 'grid grid-brands';
|
|
el.innerHTML = brands.map(function (b) {
|
|
return '<div class="card card-brand" onclick="go(\'models\',\'' + esc(b.name) + '\')">'
|
|
+ '<span class="cb-name">' + esc(b.name) + '</span>'
|
|
+ '<span class="cb-count">' + b.model_count + ' modelos</span>'
|
|
+ '</div>';
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// Models
|
|
// ============================================================
|
|
|
|
function loadModels(brand) {
|
|
var el = document.getElementById('content');
|
|
document.getElementById('title').textContent = brand;
|
|
el.innerHTML = '<div class="loading">Cargando modelos...</div>';
|
|
|
|
fetch(API + '/api/models?brand=' + encodeURIComponent(brand) + '&detailed=true')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (models) {
|
|
document.getElementById('subtitle').textContent = models.length + ' modelos';
|
|
el.className = 'grid grid-models';
|
|
el.innerHTML = models.map(function (m) {
|
|
var years = m.year_min === m.year_max ? m.year_min : m.year_min + ' - ' + m.year_max;
|
|
return '<div class="card card-model" onclick="go(\'vehicles\',\'' + esc(m.name) + '\')">'
|
|
+ '<span class="cm-name">' + esc(m.name) + '</span>'
|
|
+ '<span class="cm-years">' + years + '</span>'
|
|
+ '<span class="cm-count">' + m.vehicle_count + ' variantes</span>'
|
|
+ '</div>';
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// Vehicles (year + engine combos)
|
|
// ============================================================
|
|
|
|
function loadVehicles(brand, model) {
|
|
var el = document.getElementById('content');
|
|
document.getElementById('title').textContent = brand + ' ' + model;
|
|
el.innerHTML = '<div class="loading">Cargando variantes...</div>';
|
|
|
|
fetch(API + '/api/model-year-engine?brand=' + encodeURIComponent(brand) + '&model=' + encodeURIComponent(model) + '&per_page=100')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (res) {
|
|
var data = res.data || [];
|
|
document.getElementById('subtitle').textContent = data.length + ' variantes';
|
|
el.className = 'grid grid-vehicles';
|
|
if (data.length === 0) {
|
|
el.innerHTML = '<div class="empty">No se encontraron variantes para este modelo</div>';
|
|
return;
|
|
}
|
|
el.innerHTML = data.map(function (v) {
|
|
return '<div class="card card-vehicle" onclick="go(\'categories\',' + JSON.stringify(v).replace(/"/g, '"') + ')">'
|
|
+ '<div class="cv-main">'
|
|
+ '<span class="cv-year">' + v.year + '</span>'
|
|
+ '<div>'
|
|
+ '<div class="cv-engine">' + esc(v.engine) + '</div>'
|
|
+ '<div class="cv-details">' + esc(v.drivetrain || '') + (v.transmission ? ' · ' + esc(v.transmission) : '') + '</div>'
|
|
+ '</div>'
|
|
+ '</div>'
|
|
+ (v.trim_level ? '<span class="cv-trim">' + esc(v.trim_level) + '</span>' : '')
|
|
+ '</div>';
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// Categories
|
|
// ============================================================
|
|
|
|
function loadCategories(myeId) {
|
|
var el = document.getElementById('content');
|
|
document.getElementById('title').textContent = state.brand + ' ' + state.model + ' ' + state.mye_label;
|
|
el.innerHTML = '<div class="loading">Cargando categorías...</div>';
|
|
|
|
fetch(API + '/api/vehicles/' + myeId + '/categories')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (cats) {
|
|
if (cats.length === 0) {
|
|
document.getElementById('subtitle').textContent = '';
|
|
el.innerHTML = '<div class="empty">No hay partes registradas para este vehículo</div>';
|
|
return;
|
|
}
|
|
document.getElementById('subtitle').textContent = cats.length + ' categorías con partes';
|
|
el.className = 'grid grid-categories';
|
|
el.innerHTML = cats.map(function (c) {
|
|
return '<div class="card card-category" onclick="go(\'parts\',' + JSON.stringify({id: c.id, name: c.name}).replace(/"/g, '"') + ')">'
|
|
+ '<span class="cc-name">' + esc(c.name) + '</span>'
|
|
+ '</div>';
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// Parts
|
|
// ============================================================
|
|
|
|
function loadParts(myeId, categoryId, categoryName) {
|
|
var el = document.getElementById('content');
|
|
document.getElementById('title').textContent = categoryName;
|
|
el.innerHTML = '<div class="loading">Cargando partes...</div>';
|
|
|
|
fetch(API + '/api/vehicles/' + myeId + '/parts?category_id=' + categoryId + '&per_page=100')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (res) {
|
|
var parts = res.data || res;
|
|
if (parts.length === 0) {
|
|
document.getElementById('subtitle').textContent = '';
|
|
el.innerHTML = '<div class="empty">No hay partes en esta categoría</div>';
|
|
return;
|
|
}
|
|
|
|
document.getElementById('subtitle').textContent = parts.length + ' partes OEM';
|
|
el.className = 'grid grid-parts';
|
|
|
|
// Group by group_name
|
|
var groups = {};
|
|
var groupOrder = [];
|
|
parts.forEach(function (p) {
|
|
var g = p.group_name || 'Otros';
|
|
if (!groups[g]) { groups[g] = []; groupOrder.push(g); }
|
|
groups[g].push(p);
|
|
});
|
|
|
|
var html = '';
|
|
groupOrder.forEach(function (g) {
|
|
html += '<div class="group-header"><span class="gh-name">' + esc(g) + '</span>'
|
|
+ '<span class="gh-count">' + groups[g].length + '</span></div>';
|
|
groups[g].forEach(function (p) {
|
|
html += '<div class="part-row" data-part-id="' + p.id + '">'
|
|
+ '<span class="part-oem">' + esc(p.oem_part_number) + '</span>'
|
|
+ '<span class="part-name">' + esc(p.name || p.name_es || '') + '</span>'
|
|
+ (p.position ? '<span class="part-pos">' + esc(p.position) + '</span>' : '')
|
|
+ '<span class="part-qty">x' + (p.quantity_required || 1) + '</span>'
|
|
+ '</div>';
|
|
});
|
|
});
|
|
el.innerHTML = html;
|
|
|
|
// Attach click handlers to part rows
|
|
el.querySelectorAll('.part-row[data-part-id]').forEach(function (row) {
|
|
row.addEventListener('click', function () {
|
|
openPartModal(parseInt(row.getAttribute('data-part-id')));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// Part Detail Modal
|
|
// ============================================================
|
|
|
|
var modalBg = document.getElementById('modal-bg');
|
|
|
|
document.getElementById('modal-close').addEventListener('click', closeModal);
|
|
modalBg.addEventListener('click', function (e) {
|
|
if (e.target === modalBg) closeModal();
|
|
});
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && modalBg.classList.contains('open')) closeModal();
|
|
});
|
|
|
|
function closeModal() {
|
|
modalBg.classList.remove('open');
|
|
}
|
|
|
|
function openPartModal(partId) {
|
|
// Show modal immediately with loading state
|
|
document.getElementById('modal-oem').textContent = '...';
|
|
document.getElementById('modal-name').textContent = 'Cargando...';
|
|
document.getElementById('modal-fields').innerHTML = '';
|
|
document.getElementById('modal-image').style.display = 'none';
|
|
document.getElementById('modal-alts').innerHTML = '<div class="modal-loading">Cargando intercambios...</div>';
|
|
document.getElementById('modal-xrefs').innerHTML = '<div class="modal-loading">Cargando referencias...</div>';
|
|
modalBg.classList.add('open');
|
|
|
|
// Fetch part detail
|
|
fetch(API + '/api/parts/' + partId)
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (p) {
|
|
document.getElementById('modal-oem').textContent = p.oem_part_number || '';
|
|
document.getElementById('modal-name').textContent = p.name || p.name_es || '';
|
|
|
|
if (p.image_url) {
|
|
document.getElementById('modal-image-img').src = p.image_url;
|
|
document.getElementById('modal-image').style.display = 'block';
|
|
} else {
|
|
document.getElementById('modal-image').style.display = 'none';
|
|
}
|
|
|
|
var fields = '';
|
|
if (p.name_es) fields += '<div class="mf"><span class="mf-label">Nombre (ES)</span><span class="mf-value">' + esc(p.name_es) + '</span></div>';
|
|
if (p.category_name) fields += '<div class="mf"><span class="mf-label">Categor\u00eda</span><span class="mf-value">' + esc(p.category_name) + '</span></div>';
|
|
if (p.group_name) fields += '<div class="mf"><span class="mf-label">Grupo</span><span class="mf-value">' + esc(p.group_name) + '</span></div>';
|
|
if (p.description) fields += '<div class="mf" style="grid-column:1/-1"><span class="mf-label">Descripci\u00f3n</span><span class="mf-value">' + esc(p.description) + '</span></div>';
|
|
if (p.description_es) fields += '<div class="mf" style="grid-column:1/-1"><span class="mf-label">Descripci\u00f3n (ES)</span><span class="mf-value">' + esc(p.description_es) + '</span></div>';
|
|
|
|
document.getElementById('modal-fields').innerHTML = fields || '<div class="mf"><span class="mf-label">Categor\u00eda</span><span class="mf-value">' + esc(p.category_name || '') + '</span></div><div class="mf"><span class="mf-label">Grupo</span><span class="mf-value">' + esc(p.group_name || '') + '</span></div>';
|
|
});
|
|
|
|
// Fetch alternatives
|
|
fetch(API + '/api/parts/' + partId + '/alternatives')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (alts) {
|
|
var el = document.getElementById('modal-alts');
|
|
if (alts.length === 0) {
|
|
el.innerHTML = '<div class="modal-empty">No hay intercambios registrados para esta parte</div>';
|
|
return;
|
|
}
|
|
var html = '<table class="alt-table"><thead><tr>'
|
|
+ '<th>N\u00famero</th><th>Fabricante</th><th>Calidad</th><th>Precio</th><th>Garant\u00eda</th>'
|
|
+ '</tr></thead><tbody>';
|
|
alts.forEach(function (a) {
|
|
var qClass = (a.quality_tier || '').toLowerCase().indexOf('premium') >= 0 ? 'premium'
|
|
: (a.quality_tier || '').toLowerCase().indexOf('economy') >= 0 ? 'economy' : 'oem';
|
|
html += '<tr>'
|
|
+ '<td class="alt-pn">' + esc(a.part_number) + '</td>'
|
|
+ '<td class="alt-mfr">' + esc(a.manufacturer_name) + '</td>'
|
|
+ '<td>' + (a.quality_tier ? '<span class="alt-quality ' + qClass + '">' + esc(a.quality_tier) + '</span>' : '-') + '</td>'
|
|
+ '<td>' + (a.price_usd ? '$' + parseFloat(a.price_usd).toFixed(2) : '-') + '</td>'
|
|
+ '<td>' + (a.warranty_months ? a.warranty_months + ' meses' : '-') + '</td>'
|
|
+ '</tr>';
|
|
});
|
|
html += '</tbody></table>';
|
|
el.innerHTML = html;
|
|
});
|
|
|
|
// Fetch cross-references
|
|
fetch(API + '/api/parts/' + partId + '/cross-references')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (xrefs) {
|
|
var el = document.getElementById('modal-xrefs');
|
|
if (xrefs.length === 0) {
|
|
el.innerHTML = '<div class="modal-empty">No hay referencias cruzadas registradas</div>';
|
|
return;
|
|
}
|
|
el.innerHTML = xrefs.map(function (x) {
|
|
return '<div class="xref-row">'
|
|
+ '<span class="xref-number">' + esc(x.cross_reference_number) + '</span>'
|
|
+ (x.reference_type ? '<span class="xref-type">' + esc(x.reference_type) + '</span>' : '')
|
|
+ (x.source ? '<span class="xref-source">' + esc(x.source) + '</span>' : '')
|
|
+ '</div>';
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
// Expose for search results
|
|
window.openPartModal = openPartModal;
|
|
|
|
// ============================================================
|
|
// Global Search
|
|
// ============================================================
|
|
|
|
var searchTimer = null;
|
|
var searchInput = document.getElementById('search-input');
|
|
var searchDrop = document.getElementById('search-drop');
|
|
|
|
searchInput.addEventListener('input', function () {
|
|
clearTimeout(searchTimer);
|
|
var q = this.value.trim();
|
|
if (q.length < 2) { searchDrop.classList.remove('open'); return; }
|
|
|
|
searchTimer = setTimeout(function () {
|
|
fetch(API + '/api/search?q=' + encodeURIComponent(q) + '&limit=12')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (results) {
|
|
var items = results.parts || [];
|
|
if (!items.length) {
|
|
searchDrop.innerHTML = '<div style="padding:0.8rem;color:var(--text2);font-size:0.85rem">Sin resultados para "' + esc(q) + '"</div>';
|
|
} else {
|
|
searchDrop.innerHTML = items.slice(0, 10).map(function (p) {
|
|
return '<div class="search-item" data-part-id="' + p.id + '">'
|
|
+ '<div>'
|
|
+ '<span class="si-oem">' + esc(p.oem_part_number) + '</span>'
|
|
+ '<span class="si-name">' + esc(p.name || '') + '</span>'
|
|
+ '</div>'
|
|
+ '<span class="si-cat">' + esc(p.category_name || '') + '</span>'
|
|
+ '</div>';
|
|
}).join('');
|
|
searchDrop.querySelectorAll('.search-item[data-part-id]').forEach(function (item) {
|
|
item.addEventListener('mousedown', function (e) {
|
|
e.preventDefault();
|
|
var pid = parseInt(item.getAttribute('data-part-id'));
|
|
searchDrop.classList.remove('open');
|
|
searchInput.value = '';
|
|
openPartModal(pid);
|
|
});
|
|
});
|
|
}
|
|
searchDrop.classList.add('open');
|
|
});
|
|
}, 200);
|
|
});
|
|
|
|
searchInput.addEventListener('blur', function () {
|
|
setTimeout(function () { searchDrop.classList.remove('open'); }, 200);
|
|
});
|
|
|
|
searchInput.addEventListener('focus', function () {
|
|
if (searchDrop.innerHTML.trim()) searchDrop.classList.add('open');
|
|
});
|
|
|
|
// ============================================================
|
|
// Init
|
|
// ============================================================
|
|
|
|
go('brands');
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|