0% found this document useful (0 votes)
2 views141 pages

RetailPro Full Source Code

RetailPro is an online retail inventory and sales management system, complete with source code and files. It includes backend configuration, server setup, user authentication routes, and various frontend styles and scripts for different user roles. The project is developed by students under the guidance of a faculty member from the Department of IT at BIHER.

Uploaded by

sakthi balu001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views141 pages

RetailPro Full Source Code

RetailPro is an online retail inventory and sales management system, complete with source code and files. It includes backend configuration, server setup, user authentication routes, and various frontend styles and scripts for different user roles. The project is developed by students under the guidance of a faculty member from the Department of IT at BIHER.

Uploaded by

sakthi balu001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

RetailPro — Full Source Code

RetailPro
Online Retail Inventory & Sales Management System
Complete Source Code — All Files

Balasubramaniyam S (U22IT006)
Mohammed Sitthik A (U22IT026)
Tharuneeshwaran A S (U22IT049)

Guided by: Dr. R Yogesh Rajkumar M.E, Ph.D — Dept. of IT, BIHER

Page 1 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

Contents
1. backend/.env — Backend environment configuration
2. backend/[Link] — Express server entry point
3. backend/models/[Link] — MongoDB user schema
4. backend/routes/[Link] — Register and login API routes
5. css/[Link] — CSS design tokens
6. css/[Link] — Login/register page styles
7. css/[Link] — Shopkeeper dashboard styles
8. css/[Link] — Customer portal styles
9. [Link] — Login/register page HTML
10. pages/[Link] — Shopkeeper dashboard HTML
11. pages/[Link] — Customer portal HTML
12. js/[Link] — Login/register JavaScript
13. js/[Link] — Shopkeeper dashboard JavaScript
14. js/[Link] — Customer portal JavaScript

Page 2 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

1. backend / .env
📄 File: backend/.env
Lines of code: 3

PORT=5000
MONGO_URI=mongodb://localhost:27017/retailpro
JWT_SECRET=retailpro_super_secret_key_change_this_in_production_2026

Page 3 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

2. backend / [Link]
📄 File: backend/[Link]
Lines of code: 31

const express = require("express");


const mongoose = require("mongoose");
const cors = require("cors");
require("dotenv").config();

const app = express();

// Middleware
[Link](cors());
[Link]([Link]());

// Test route
[Link]("/", (req, res) => {
[Link]("Server running 🚀");
});

// DB connect
[Link]([Link].MONGO_URI)
.then(() => [Link]("MongoDB Connected ✅"))
.catch(err => [Link]("DB Error ❌", err));

// Routes
[Link]("/api/auth", require("./routes/auth"));

// Port
const PORT = [Link] || 5000;

// Start server
[Link](PORT, () => {
[Link](`Server running on port ${PORT}`);
});

Page 4 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

3. backend / models / [Link]


📄 File: backend/models/[Link]
Lines of code: 10

const mongoose = require("mongoose");

const UserSchema = new [Link]({


name: String,
email: String,
password: String,
role: String
});

[Link] = [Link]("User", UserSchema);

Page 5 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

4. backend / routes / [Link]


📄 File: backend/routes/[Link]
Lines of code: 58

const express = require("express");


const router = [Link]();
const User = require("../models/User");

// REGISTER
[Link]("/register", async (req, res) => {
try {
const { name, email, password, role } = [Link];

if (!name || !email || !password || !role) {


return [Link](400).json({ message: "All fields are required" });
}

const existingUser = await [Link]({ email });


if (existingUser) {
return [Link](400).json({ message: "Email already registered" });
}

const user = new User({ name, email, password, role });


await [Link]();

[Link](201).json({ message: "Registration successful" });


} catch (err) {
[Link](err);
[Link](500).json({ message: "Server error" });
}
});

// LOGIN
[Link]("/login", async (req, res) => {
try {
const { email, password, role } = [Link];

if (!email || !password || !role) {


return [Link](400).json({ message: "Email, password and role are required" });
}

const user = await [Link]({ email, password, role });

if (!user) {
return [Link](400).json({ message: "Invalid credentials" });
}

[Link]({
message: "Login successful",
user: {
name: [Link],
email: [Link],
role: [Link]
}
});
} catch (err) {
[Link](err);
[Link](500).json({ message: "Server error" });
}
});

[Link] = router;

Page 6 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

5. css / [Link]
📄 File: css/[Link]
Lines of code: 62

/* ============================================================
RetailPro — CSS Variables (Design Tokens)
File: css/[Link]
Used by: [Link], pages/[Link], pages/[Link]
============================================================ */

@import url('[Link]
family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');

:root {
/* ── Background layers ── */
--navy: #0D1117;
--navy2: #161B22;
--navy3: #1C2333;
--navy4: #21262D;

/* ── Borders ── */
--border: #30363D;

/* ── Indigo (Shopkeeper accent) ── */


--indigo: #5B63FE;
--indigo-l: #7B82FF;
--indigo-dim: rgba(91, 99, 254, 0.15);

/* ── Emerald (Customer accent) ── */


--emerald: #10D9A0;
--emerald-l: #34EDB8;
--emerald-dim: rgba(16, 217, 160, 0.12);

/* ── Semantic colours ── */
--rose: #FF4D6D;
--rose-dim: rgba(255, 77, 109, 0.12);

--amber: #F5A623;
--amber-dim: rgba(245, 166, 35, 0.10);

--sky: #38BDF8;
--sky-dim: rgba(56, 189, 248, 0.10);

--violet: #A78BFA;
--violet-dim: rgba(167, 139, 250, 0.12);

/* ── Text ── */
--text1: #F0F6FC;
--text2: #8B949E;
--text3: #484F58;

/* ── Typography ── */
--font: 'Sora', sans-serif;
--mono: 'JetBrains Mono', monospace;

/* ── Border radius ── */
--r4: 4px;
--r8: 8px;
--r12: 12px;
--r16: 16px;
--r20: 20px;

/* ── Shadows ── */
--shadow: 0 8px 32px rgba(0, 0, 0, 0.50);
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.60);

Page 7 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}

Page 8 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

6. css / [Link]
📄 File: css/[Link]
Lines of code: 653

/* ============================================================
RetailPro — Auth Page Styles
File: css/[Link]
Covers: reset, layout, left panel, right panel,
forms, tabs, role toggle, toast, responsive.
============================================================ */

/* ── Reset ─────────────────────────────────────────────── */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}

/* ── Base body ──────────────────────────────────────────── */


body {
font-family: var(--font);
background: var(--navy);
color: var(--text1);
min-height: 100vh;
display: flex;
align-items: stretch;
overflow: hidden;
}

/* ── Custom scrollbar ───────────────────────────────────── */


::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: var(--navy); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

/* ════════════════════════════════════════════════════════
LEFT PANEL
════════════════════════════════════════════════════════ */
.left-panel {
flex: 1;
background: var(--navy2);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 48px 52px;
position: relative;
overflow: hidden;
}

/* Ambient glow blobs */


.blob {
position: absolute;
border-radius: 50%;
filter: blur(80px);
pointer-events: none;
z-index: 0;
}
.blob-1 { width: 400px; height: 400px; background: rgba(91,99,254,0.08); top: -100px; left: -
100px; }
.blob-2 { width: 350px; height: 350px; background: rgba(16,217,160,0.06); bottom: -80px; right: -80px;
}
.blob-3 { width: 200px; height: 200px; background: rgba(245,166,35,0.05); top: 40%; left:
60%; }

Page 9 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

.left-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
height: 100%;
}

/* ── Brand ──────────────────────────────────────────────── */
.brand-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 56px;
}
.brand-logo {
width: 42px;
height: 42px;
background: linear-gradient(135deg, var(--indigo), var(--emerald));
border-radius: var(--r12);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
box-shadow: 0 4px 20px rgba(91,99,254,0.3);
flex-shrink: 0;
}
.brand-name {
font-size: 20px;
font-weight: 800;
color: var(--text1);
letter-spacing: -0.5px;
}
.brand-tag {
font-size: 11px;
color: var(--text2);
letter-spacing: 0.05em;
}

/* ── Hero eyebrow ───────────────────────────────────────── */


.hero-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--emerald-dim);
border: 1px solid rgba(16,217,160,0.2);
border-radius: 99px;
padding: 5px 14px;
font-size: 11px;
font-weight: 600;
color: var(--emerald);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 20px;
width: fit-content;
}

/* ── Hero title ─────────────────────────────────────────── */


.hero-title {
font-size: 38px;
font-weight: 800;
line-height: 1.15;
letter-spacing: -1px;
margin-bottom: 14px;
}
.hero-title .grad-indigo {
background: linear-gradient(135deg, var(--indigo-l), var(--sky));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;

Page 10 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}
.hero-title .grad-emerald {
background: linear-gradient(135deg, var(--emerald), var(--emerald-l));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-sub {
font-size: 14px;
color: var(--text2);
line-height: 1.7;
margin-bottom: 40px;
max-width: 440px;
}

/* ── Role preview cards (left panel) ───────────────────── */


.role-preview {
display: flex;
gap: 10px;
margin-bottom: 32px;
}
.rp-card {
flex: 1;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.07);
border-radius: var(--r12);
padding: 14px;
cursor: pointer;
transition: all 0.2s;
}
.rp-card:hover {
background: rgba(255,255,255,0.07);
}
.[Link]:hover,
.[Link] {
border-color: rgba(91,99,254,0.4);
background: var(--indigo-dim);
}
.[Link]:hover,
.[Link] {
border-color: rgba(16,217,160,0.4);
background: var(--emerald-dim);
}
.rp-icon { font-size: 24px; margin-bottom: 6px; }
.rp-label { font-size: 12px; font-weight: 700; color: var(--text1); margin-bottom: 3px; }
.rp-desc { font-size: 10px; color: var(--text2); }
.rp-badge {
display: inline-block;
margin-top: 6px;
padding: 2px 8px;
border-radius: 99px;
font-size: 10px;
font-weight: 700;
}
.[Link] .rp-badge { background: var(--indigo-dim); color: var(--indigo-l); }
.[Link] .rp-badge { background: var(--emerald-dim); color: var(--emerald); }

/* ── Feature cards ──────────────────────────────────────── */


.features {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 40px;
}
.feat-card {
display: flex;
align-items: center;
gap: 14px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: var(--r12);

Page 11 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
padding: 14px 16px;
transition: border 0.2s, background 0.2s;
}
.feat-card:hover {
background: rgba(255,255,255,0.05);
border-color: rgba(255,255,255,0.10);
}
.feat-icon {
width: 38px;
height: 38px;
border-radius: var(--r8);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.feat-title { font-size: 13px; font-weight: 600; color: var(--text1); margin-bottom: 2px; }
.feat-sub { font-size: 11px; color: var(--text2); }

/* ── Stats row ──────────────────────────────────────────── */


.stats-row { display: flex; gap: 24px; }
.stat-item { text-align: center; }
.stat-val {
font-size: 22px;
font-weight: 800;
font-family: var(--mono);
color: var(--text1);
}
.stat-lbl {
font-size: 10px;
color: var(--text2);
margin-top: 2px;
text-transform: uppercase;
letter-spacing: 0.06em;
}

/* ── Left footer / trust bar ────────────────────────────── */


.left-footer { margin-top: auto; }
.trust-row {
display: flex;
align-items: center;
gap: 16px;
font-size: 11px;
color: var(--text3);
}
.trust-item { display: flex; align-items: center; gap: 5px; }

/* ════════════════════════════════════════════════════════
RIGHT PANEL
════════════════════════════════════════════════════════ */
.right-panel {
width: 480px;
flex-shrink: 0;
background: var(--navy);
display: flex;
align-items: center;
justify-content: center;
padding: 32px 40px;
position: relative;
overflow-y: auto;
}

.auth-card {
width: 100%;
max-width: 400px;
animation: slideIn 0.4s cubic-bezier(0.34, 1.2, 0.64, 1);
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(16px); }

Page 12 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
to { opacity: 1; transform: translateY(0); }
}

/* ── Login / Register tab switcher ─────────────────────── */


.auth-tabs {
display: flex;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
padding: 4px;
margin-bottom: 28px;
}
.auth-tab {
flex: 1;
padding: 9px;
border-radius: var(--r8);
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: center;
color: var(--text2);
transition: all 0.2s;
border: none;
background: none;
font-family: var(--font);
}
.[Link]-login { background: var(--indigo); color: white; box-shadow: 0 2px 10px
rgba(91,99,254,0.3); }
.[Link]-register { background: var(--emerald); color: var(--navy); box-shadow: 0 2px 10px
rgba(16,217,160,0.3); }

/* ── Card headline / sub ────────────────────────────────── */


.card-headline {
font-size: 22px;
font-weight: 800;
letter-spacing: -0.5px;
margin-bottom: 4px;
}
.card-sub {
font-size: 12px;
color: var(--text2);
margin-bottom: 24px;
line-height: 1.5;
}

/* ── Role toggle (inside form) ──────────────────────────── */


.role-toggle {
display: flex;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
padding: 4px;
margin-bottom: 22px;
}
.role-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
padding: 9px 4px;
border-radius: var(--r8);
font-size: 12px;
font-weight: 600;
cursor: pointer;
color: var(--text2);
transition: all 0.2s;
border: none;
background: none;
font-family: var(--font);
}

Page 13 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.[Link]-sk {
background: var(--indigo-dim);
color: var(--indigo-l);
border: 1px solid rgba(91,99,254,0.3);
}
.[Link]-cu {
background: var(--emerald-dim);
color: var(--emerald);
border: 1px solid rgba(16,217,160,0.3);
}
.role-btn:not(.active-sk):not(.active-cu):hover {
background: rgba(255,255,255,0.04);
}

/* ── Form groups & inputs ───────────────────────────────── */


.form-group {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 14px;
}
.form-label {
font-size: 11px;
font-weight: 600;
color: var(--text2);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.input-wrap { position: relative; }

.form-input {
width: 100%;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r8);
padding: 11px 14px;
font-size: 13px;
color: var(--text1);
font-family: var(--font);
outline: none;
transition: border 0.2s, box-shadow 0.2s;
}
.form-input:focus {
border-color: var(--indigo);
box-shadow: 0 0 0 3px rgba(91,99,254,0.12);
}
.[Link]:focus {
border-color: var(--emerald);
box-shadow: 0 0 0 3px rgba(16,217,160,0.10);
}
.form-input::placeholder { color: var(--text3); }
.[Link] { border-color: var(--rose); }

/* Inline icon inside input */


.input-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
cursor: pointer;
color: var(--text3);
transition: color 0.15s;
user-select: none;
}
.input-icon:hover { color: var(--text2); }

/* Validation messages */
.error-msg {
font-size: 11px;
color: var(--rose);

Page 14 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
display: none;
margin-top: 4px;
}
.[Link] { display: block; }

.success-msg {
font-size: 11px;
color: var(--emerald);
display: none;
margin-top: 4px;
}
.[Link] { display: block; }

/* ── Password strength meter ────────────────────────────── */


.strength-bar {
display: flex;
gap: 4px;
margin-top: 8px;
}
.strength-seg {
flex: 1;
height: 3px;
border-radius: 99px;
background: var(--navy4);
transition: background 0.3s;
}
.[Link] { background: var(--rose); }
.[Link] { background: var(--amber); }
.[Link] { background: var(--sky); }
.[Link] { background: var(--emerald); }
.strength-label {
font-size: 10px;
color: var(--text2);
margin-top: 5px;
}

/* ── Two-column grid ────────────────────────────────────── */


.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}

/* ── Forgot password ────────────────────────────────────── */


.forgot-row {
display: flex;
justify-content: flex-end;
margin-top: -8px;
margin-bottom: 14px;
}
.forgot-link {
font-size: 12px;
color: var(--indigo-l);
cursor: pointer;
transition: color 0.15s;
background: none;
border: none;
font-family: var(--font);
padding: 0;
}
.forgot-link:hover { text-decoration: underline; }

/* ── Submit button ──────────────────────────────────────── */


.submit-btn {
width: 100%;
padding: 13px;
border: none;
border-radius: var(--r12);
font-size: 14px;
font-weight: 700;
cursor: pointer;

Page 15 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
font-family: var(--font);
transition: all 0.2s;
margin-top: 4px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.[Link] {
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
color: white;
}
.[Link]:hover {
box-shadow: 0 6px 24px rgba(91,99,254,0.45);
transform: translateY(-1px);
}
.[Link] {
background: linear-gradient(135deg, var(--emerald), var(--emerald-l));
color: var(--navy);
}
.[Link]:hover {
box-shadow: 0 6px 24px rgba(16,217,160,0.40);
transform: translateY(-1px);
}
.submit-btn:disabled {
opacity: 0.65;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}

/* ── Switch link ────────────────────────────────────────── */


.switch-link {
text-align: center;
font-size: 12px;
color: var(--text2);
margin-top: 16px;
}
.switch-link a {
color: var(--indigo-l);
font-weight: 600;
cursor: pointer;
transition: color 0.15s;
text-decoration: none;
}
.switch-link a:hover { text-decoration: underline; }
.[Link] a { color: var(--emerald); }

/* ── Terms note ─────────────────────────────────────────── */


.terms-note {
font-size: 10px;
color: var(--text3);
text-align: center;
margin-top: 14px;
line-height: 1.5;
}
.terms-note a { color: var(--text2); cursor: pointer; }
.terms-note a:hover { color: var(--text1); }

/* ── Agree checkbox ─────────────────────────────────────── */


.agree-row {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 14px;
}
.agree-check {
width: 16px;
height: 16px;
border-radius: 4px;
background: var(--navy3);

Page 16 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
border: 1.5px solid var(--border);
flex-shrink: 0;
margin-top: 2px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
transition: all 0.15s;
user-select: none;
}
.[Link]-sk { background: var(--indigo); border-color: var(--indigo); color: white;
}
.[Link]-cu { background: var(--emerald); border-color: var(--emerald); color: var(--
navy); }
.agree-text {
font-size: 11px;
color: var(--text2);
line-height: 1.5;
}
.agree-text a { color: var(--indigo-l); cursor: pointer; }

/* ── Form panel show / hide + animation ─────────────────── */


.form-panel { display: none; }
.[Link] {
display: block;
animation: fadeUp 0.3s ease;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}

/* ════════════════════════════════════════════════════════
TOAST NOTIFICATIONS
════════════════════════════════════════════════════════ */
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 300;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
padding: 12px 16px;
box-shadow: var(--shadow);
min-width: 280px;
pointer-events: auto;
animation: toastIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
transition: opacity 0.3s, transform 0.3s;
}
@keyframes toastIn {
from { opacity: 0; transform: translateX(40px); }
to { opacity: 1; transform: translateX(0); }
}
.[Link] {
opacity: 0;
transform: translateX(40px);
}
.toast-icon { font-size: 16px; flex-shrink: 0; }

Page 17 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.toast-msg { font-size: 13px; color: var(--text1); font-weight: 500; }

/* ════════════════════════════════════════════════════════
RESPONSIVE
════════════════════════════════════════════════════════ */

/* Tablet — hide left branding panel */


@media (max-width: 900px) {
.left-panel { display: none; }
.right-panel { width: 100%; padding: 32px 24px; }
body { overflow-y: auto; }
}

/* Mobile — stack two-col fields and reduce padding */


@media (max-width: 480px) {
.right-panel { padding: 24px 16px; }
.two-col { grid-template-columns: 1fr; }

.auth-card { max-width: 100%; }

.card-headline { font-size: 20px; }

.toast-container {
bottom: 12px;
right: 12px;
left: 12px;
}
.toast { min-width: unset; width: 100%; }
}

Page 18 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

7. css / [Link]
📄 File: css/[Link]
Lines of code: 1571

/* ============================================================
RetailPro — Shopkeeper Dashboard Styles
File: css/[Link]
Requires: css/[Link] (imported first)

Sections:
1. Reset & Base
2. Layout Shell (sidebar + main)
3. Sidebar
4. Top Navbar
5. Content area
6. KPI Cards
7. Charts section
8. Tables
9. Badges & Pills
10. Forms & Inputs (drawer / modal)
11. Modal / Drawer
12. Buttons
13. Toast
14. Empty States
15. Specific page sections
— Products — Inventory — Orders
— Customers — Analytics — Settings
16. Utilities
17. Responsive (tablet → mobile)
============================================================ */

/* ════════════════════════════════════════════════════════
1. RESET & BASE
════════════════════════════════════════════════════════ */
*,
*::before,
*::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }

body {
font-family: var(--font);
background: var(--navy);
color: var(--text1);
min-height: 100vh;
overflow: hidden;
}

::-webkit-scrollbar { width: 5px; height: 5px; }


::-webkit-scrollbar-track { background: var(--navy); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--navy4); }

a { color: inherit; text-decoration: none; }


button { cursor: pointer; font-family: var(--font); }
input, select, textarea { font-family: var(--font); }
img { max-width: 100%; display: block; }

/* ════════════════════════════════════════════════════════
2. LAYOUT SHELL
════════════════════════════════════════════════════════ */
#app {
display: flex;
height: 100vh;

Page 19 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
overflow: hidden;
}

/* ════════════════════════════════════════════════════════
3. SIDEBAR
════════════════════════════════════════════════════════ */
#sidebar {
width: 240px;
flex-shrink: 0;
background: var(--navy2);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
position: relative;
z-index: 30;
}
#[Link] { width: 64px; }

/* Brand */
.sb-brand {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 18px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
overflow: hidden;
}
.sb-logo-icon {
width: 38px;
height: 38px;
border-radius: var(--r12);
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
box-shadow: 0 4px 16px rgba(91,99,254,0.3);
}
.sb-brand-text { overflow: hidden; white-space: nowrap; }
.sb-brand-name { font-size: 15px; font-weight: 800; color: var(--text1); letter-spacing: -0.4px; }
.sb-brand-sub { font-size: 10px; color: var(--text3); font-weight: 600; letter-spacing: 1.2px; text-
transform: uppercase; margin-top: 1px; }

/* Nav */
.sb-nav {
flex: 1;
padding: 12px 8px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1px;
}
.sb-section-label {
font-size: 9.5px;
font-weight: 800;
color: var(--text3);
letter-spacing: 1.6px;
text-transform: uppercase;
padding: 8px 11px 4px;
white-space: nowrap;
overflow: hidden;
}
.sb-divider { height: 1px; background: var(--border); margin: 6px 4px; }

.sb-item {
display: flex;
align-items: center;

Page 20 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
gap: 11px;
padding: 10px 11px;
border-radius: var(--r8);
border: none;
border-left: 2.5px solid transparent;
background: transparent;
color: var(--text3);
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
width: 100%;
text-align: left;
transition: all 0.15s;
white-space: nowrap;
overflow: hidden;
position: relative;
}
.sb-item:hover { background: var(--navy3); color: var(--text2); }
.[Link] {
background: var(--indigo-dim);
color: var(--indigo-l);
border-left-color: var(--indigo);
}
.sb-item svg { flex-shrink: 0; width: 17px; height: 17px; }
.sb-item-label { overflow: hidden; text-overflow: ellipsis; }
.sb-item-badge {
margin-left: auto;
background: var(--rose-dim);
color: var(--rose);
border-radius: 99px;
font-size: 10px;
font-weight: 800;
padding: 1px 7px;
flex-shrink: 0;
}

/* Sidebar user footer */


.sb-user {
padding: 13px 14px;
border-top: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
overflow: hidden;
flex-shrink: 0;
}
.sb-avatar {
width: 34px;
height: 34px;
border-radius: 9px;
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 13px;
color: #fff;
flex-shrink: 0;
}
.sb-user-info { overflow: hidden; white-space: nowrap; }
.sb-user-name { font-size: 13px; font-weight: 700; color: var(--text1); }
.sb-user-role { font-size: 10.5px; color: var(--text3); }

/* Collapsed sidebar — hide labels */


#[Link] .sb-brand-text,
#[Link] .sb-section-label,
#[Link] .sb-item-label,
#[Link] .sb-item-badge,
#[Link] .sb-user-info { display: none; }
#[Link] .sb-item { justify-content: center; padding: 10px; border-left: none; }

Page 21 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
#[Link] .[Link] { border-left: none; border-bottom: none; background: var(--indigo-
dim); }
#[Link] .sb-brand { padding: 20px 13px; justify-content: center; }
#[Link] .sb-user { justify-content: center; }

/* ════════════════════════════════════════════════════════
4. TOP NAVBAR
════════════════════════════════════════════════════════ */
#navbar {
height: 56px;
background: var(--navy2);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 12px;
padding: 0 20px;
flex-shrink: 0;
}

.nb-toggle {
width: 34px;
height: 34px;
background: var(--navy3);
border: 1px solid var(--border);
border-radius: var(--r8);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text2);
flex-shrink: 0;
transition: all 0.15s;
}
.nb-toggle:hover { background: var(--navy4); }

.nb-search-wrap {
flex: 1;
max-width: 360px;
position: relative;
}
.nb-search-icon {
position: absolute;
left: 11px;
top: 50%;
transform: translateY(-50%);
color: var(--text3);
pointer-events: none;
}
.nb-search {
width: 100%;
background: var(--navy3);
border: 1px solid var(--border);
border-radius: var(--r8);
padding: 8px 12px 8px 36px;
font-size: 13px;
color: var(--text1);
outline: none;
transition: border 0.2s, box-shadow 0.2s;
}
.nb-search:focus {
border-color: var(--indigo);
box-shadow: 0 0 0 3px var(--indigo-dim);
}
.nb-search::placeholder { color: var(--text3); }

.nb-right {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;

Page 22 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}

.nb-date {
font-size: 11.5px;
color: var(--text3);
font-weight: 500;
white-space: nowrap;
}
.nb-vdiv { width: 1px; height: 18px; background: var(--border); }

/* Icon button */
.nb-ic-btn {
width: 34px;
height: 34px;
background: var(--navy3);
border: 1px solid var(--border);
border-radius: var(--r8);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text2);
position: relative;
transition: all 0.15s;
flex-shrink: 0;
}
.nb-ic-btn:hover { background: var(--navy4); }

/* Notification dot */
.notif-dot {
position: absolute;
top: 6px;
right: 7px;
width: 7px;
height: 7px;
background: var(--rose);
border-radius: 50%;
border: 1.5px solid var(--navy2);
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}

/* Notification panel dropdown */


#notif-panel {
position: absolute;
top: 44px;
right: 0;
width: 310px;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
z-index: 200;
display: none;
box-shadow: var(--shadow-lg);
overflow: hidden;
}
#[Link] { display: block; animation: fadeDown 0.2s ease; }
@keyframes fadeDown {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
.notif-hd {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}

Page 23 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.notif-hd-title { font-size: 13.5px; font-weight: 800; }
.notif-mark-all {
font-size: 11px;
color: var(--indigo-l);
background: none;
border: none;
cursor: pointer;
font-weight: 600;
}
.notif-mark-all:hover { text-decoration: underline; }

.notif-item {
display: flex;
gap: 11px;
align-items: flex-start;
padding: 12px 16px;
border-bottom: 1px solid rgba(48,54,61,0.5);
transition: background 0.12s;
cursor: pointer;
}
.notif-item:hover { background: var(--navy3); }
.[Link] { background: rgba(91,99,254,0.04); }
.notif-ic {
width: 30px;
height: 30px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
font-size: 14px;
}
.notif-body { flex: 1; }
.notif-text { font-size: 12.5px; color: var(--text2); line-height: 1.45; }
.notif-time { font-size: 10.5px; color: var(--text3); margin-top: 3px; }
.notif-unread-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--indigo);
flex-shrink: 0;
margin-top: 5px;
}

/* Quick-add button */
.btn-quick-add {
display: flex;
align-items: center;
gap: 6px;
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
border: none;
border-radius: var(--r8);
color: #fff;
font-size: 13px;
font-weight: 700;
padding: 7px 15px;
cursor: pointer;
box-shadow: 0 3px 12px rgba(91,99,254,0.3);
transition: all 0.2s;
white-space: nowrap;
flex-shrink: 0;
}
.btn-quick-add:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(91,99,254,0.45);
}

/* ════════════════════════════════════════════════════════
5. MAIN & CONTENT AREA

Page 24 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
════════════════════════════════════════════════════════ */
#main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}

#content {
flex: 1;
overflow-y: auto;
padding: 24px 22px;
}

/* Page sections */
.page-section { display: none; }
.[Link] { display: block; animation: fadeUp 0.28s ease; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}

/* Page header */
.page-hd { margin-bottom: 22px; }
.page-title { font-size: 22px; font-weight: 900; color: var(--text1); letter-spacing: -0.5px; }
.page-sub { font-size: 13px; color: var(--text3); margin-top: 4px; }

.section-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 18px;
flex-wrap: wrap;
}

/* ════════════════════════════════════════════════════════
6. KPI CARDS
════════════════════════════════════════════════════════ */
.kpi-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 22px;
}

.kpi-card {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r16);
padding: 18px;
position: relative;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.kpi-card:hover {
transform: translateY(-2px);
border-color: var(--indigo);
box-shadow: 0 8px 30px rgba(91,99,254,0.12);
}
.kpi-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2.5px;
border-radius: var(--r16) var(--r16) 0 0;
}
.[Link]::before { background: linear-gradient(90deg, var(--indigo), var(--indigo-l)); }

Page 25 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.[Link]::before { background: linear-gradient(90deg, var(--emerald), var(--emerald-l)); }
.[Link]::before { background: var(--amber); }
.[Link]::before { background: var(--rose); }
.[Link]::before { background: var(--sky); }

.kpi-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.kpi-label {
font-size: 10.5px;
font-weight: 800;
color: var(--text3);
letter-spacing: 0.6px;
text-transform: uppercase;
}
.kpi-icon-wrap {
width: 34px;
height: 34px;
border-radius: var(--r8);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.[Link] { background: var(--indigo-dim); }
.[Link] { background: var(--emerald-dim); }
.[Link] { background: var(--amber-dim); }
.[Link] { background: var(--rose-dim); }
.[Link] { background: var(--sky-dim); }

.kpi-value {
font-size: 28px;
font-weight: 900;
color: var(--text1);
letter-spacing: -1.5px;
line-height: 1;
margin-bottom: 10px;
font-variant-numeric: tabular-nums;
font-family: var(--mono);
}
.kpi-footer {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.kpi-change {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
font-weight: 700;
padding: 2px 7px;
border-radius: 99px;
}
.[Link] { background: var(--emerald-dim); color: var(--emerald); }
.[Link] { background: var(--rose-dim); color: var(--rose); }
.[Link] { background: var(--amber-dim); color: var(--amber); }
.kpi-note { font-size: 11px; color: var(--text3); }

/* ── Grid variants ── */
.g2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.g3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
.g4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 14px; }
.g-main-side { display: grid; grid-template-columns: 1fr 300px; gap: 16px; }
.g-main-wide { display: grid; grid-template-columns: 1fr 340px; gap: 16px; }

Page 26 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
/* ════════════════════════════════════════════════════════
7. CARDS & CHARTS
════════════════════════════════════════════════════════ */
.card {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r16);
padding: 20px;
transition: border-color 0.2s;
}
.card:hover { border-color: rgba(48,54,61,0.8); }

.card-title { font-size: 15px; font-weight: 800; color: var(--text1); letter-spacing: -0.2px; }


.card-sub { font-size: 12px; color: var(--text3); margin-top: 3px; }

.chart-wrap {
position: relative;
width: 100%;
height: 220px;
}

.time-tabs {
display: flex;
gap: 5px;
}
.time-tab {
padding: 5px 13px;
border-radius: var(--r8);
border: none;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 0.15s;
background: var(--navy3);
color: var(--text3);
}
.[Link] { background: var(--indigo); color: #fff; }

/* Best seller bars */


.bs-row { margin-bottom: 15px; }
.bs-label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 12.5px;
}
.bs-rank { color: var(--indigo-l); font-weight: 800; margin-right: 6px; }
.bs-name { color: var(--text1); font-weight: 600; flex: 1; }
.bs-sold { color: var(--text3); font-size: 11.5px; }
.bs-bar { height: 5px; background: var(--navy4); border-radius: 99px; overflow: hidden; }
.bs-fill { height: 100%; border-radius: 99px; transition: width 1.2s ease; }
.bs-revenue { font-size: 11.5px; color: var(--text3); text-align: right; margin-top: 3px; }

/* Category donut legend */


.cat-row { margin-bottom: 13px; }
.cat-label-row {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 13px;
}
.cat-bar { height: 7px; background: var(--navy4); border-radius: 99px; overflow: hidden; }
.cat-fill { height: 100%; border-radius: 99px; }

/* ════════════════════════════════════════════════════════
8. TABLES
════════════════════════════════════════════════════════ */
.tbl-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }

Page 27 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
thead th {
padding: 9px 13px;
font-size: 10.5px;
font-weight: 800;
color: var(--text3);
letter-spacing: 0.7px;
text-transform: uppercase;
border-bottom: 1px solid var(--border);
text-align: left;
white-space: nowrap;
}
thead th:first-child { padding-left: 0; }

tbody tr {
border-bottom: 1px solid rgba(48,54,61,0.4);
cursor: pointer;
transition: background 0.12s;
}
tbody tr:hover { background: rgba(255,255,255,0.015); }
tbody tr:last-child { border-bottom: none; }

tbody td {
padding: 12px 13px;
font-size: 13.5px;
color: var(--text1);
vertical-align: middle;
}
tbody td:first-child { padding-left: 0; }

.td-mono { font-family: var(--mono); font-size: 12.5px; color: var(--indigo-l); font-weight: 600; }


.td-muted { color: var(--text3); font-size: 12.5px; }
.td-bold { font-weight: 800; }
.td-emoji { font-size: 20px; line-height: 1; }
.td-name { font-weight: 700; font-size: 13.5px; }
.td-sub { font-size: 11px; color: var(--text3); margin-top: 2px; font-family: var(--mono); }

/* Action buttons in table */


.tbl-actions { display: flex; gap: 5px; }
.act-btn {
width: 28px;
height: 28px;
border: none;
border-radius: var(--r8);
background: var(--navy3);
color: var(--text2);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
flex-shrink: 0;
}
.act-btn:hover { background: var(--navy4); }
.[Link]:hover { background: var(--indigo-dim); color: var(--indigo-l); }
.[Link]:hover { background: var(--rose-dim); color: var(--rose); }
.[Link]:hover { background: var(--sky-dim); color: var(--sky); }
.[Link]:hover { background: var(--amber-dim); color: var(--amber); }

/* ════════════════════════════════════════════════════════
9. BADGES & PILLS
════════════════════════════════════════════════════════ */
.badge {
display: inline-flex;
align-items: center;
padding: 2.5px 9px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.2px;
white-space: nowrap;

Page 28 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}
.badge-green { background: var(--emerald-dim); color: var(--emerald); border: 1px solid
rgba(16,217,160,0.2); }
.badge-amber { background: var(--amber-dim); color: var(--amber); border: 1px solid
rgba(245,166,35,0.2); }
.badge-red { background: var(--rose-dim); color: var(--rose); border: 1px solid
rgba(255,77,109,0.2); }
.badge-blue { background: var(--indigo-dim); color: var(--indigo-l); border: 1px solid
rgba(91,99,254,0.2); }
.badge-sky { background: var(--sky-dim); color: var(--sky); border: 1px solid
rgba(56,189,248,0.2); }
.badge-gray { background: var(--navy4); color: var(--text3); border: 1px solid var(--
border); }

/* Category chip */
.chip {
background: var(--navy3);
border-radius: 5px;
padding: 2px 8px;
font-size: 11px;
color: var(--text3);
}

/* ════════════════════════════════════════════════════════
10. FORMS & INPUTS
════════════════════════════════════════════════════════ */
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 16px;
}
.form-label {
font-size: 11px;
font-weight: 700;
color: var(--text2);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.input-wrap { position: relative; }

.form-input, .form-select {
width: 100%;
background: var(--navy3);
border: 1px solid var(--border);
border-radius: var(--r8);
padding: 10px 13px;
font-size: 13.5px;
color: var(--text1);
outline: none;
transition: border 0.2s, box-shadow 0.2s;
}
.form-input:focus, .form-select:focus {
border-color: var(--indigo);
box-shadow: 0 0 0 3px var(--indigo-dim);
}
.form-input::placeholder { color: var(--text3); }
.[Link] { border-color: var(--rose); box-shadow: 0 0 0 3px var(--rose-dim); }

.form-select { cursor: pointer; }


.form-select option { background: var(--navy3); }

.form-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: var(--text3);
pointer-events: none;

Page 29 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.f-error {
font-size: 11px;
color: var(--rose);
display: none;
}
.[Link] { display: block; }

/* Form section box */


.f-box {
background: var(--navy3);
border: 1px solid var(--border);
border-radius: 11px;
padding: 16px;
margin-bottom: 16px;
}
.f-box-title {
font-size: 10.5px;
font-weight: 800;
color: var(--indigo-l);
letter-spacing: 1.2px;
text-transform: uppercase;
margin-bottom: 14px;
}

/* Profit margin auto-display */


.margin-chip {
display: inline-block;
padding: 5px 12px;
border-radius: var(--r8);
font-size: 12px;
font-weight: 700;
margin-top: 8px;
}
.[Link] { background: var(--emerald-dim); color: var(--emerald); }
.[Link] { background: var(--amber-dim); color: var(--amber); }
.[Link] { background: var(--rose-dim); color: var(--rose); }

/* Upload area */
.upload-area {
border: 2px dashed var(--border);
border-radius: 11px;
padding: 22px 16px;
text-align: center;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
margin-bottom: 16px;
}
.upload-area:hover { border-color: var(--indigo); background: var(--indigo-dim); }
.upload-emoji { font-size: 30px; margin-bottom: 6px; }
.upload-txt { font-size: 12.5px; color: var(--text3); }

/* Status toggle */
.status-toggle { display: flex; gap: 8px; }
.status-btn {
flex: 1;
padding: 9px;
border-radius: var(--r8);
border: 1px solid var(--border);
background: transparent;
color: var(--text3);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.[Link]-green {
border-color: var(--emerald);

Page 30 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
background: var(--emerald-dim);
color: var(--emerald);
}
.[Link]-gray {
border-color: var(--border);
background: var(--navy4);
color: var(--text2);
}

/* Pay mode buttons */


.pay-modes { display: flex; gap: 7px; flex-wrap: wrap; }
.pay-btn {
flex: 1;
min-width: 60px;
padding: 8px 5px;
border-radius: var(--r8);
border: 1px solid var(--border);
background: transparent;
color: var(--text3);
font-size: 12px;
font-weight: 600;
cursor: pointer;
text-align: center;
transition: all 0.15s;
}
.[Link] {
border-color: var(--indigo);
background: var(--indigo-dim);
color: var(--indigo-l);
}

/* Operating hours toggle */


.toggle-switch {
width: 38px;
height: 20px;
background: var(--navy4);
border-radius: 99px;
position: relative;
cursor: pointer;
transition: background 0.2s;
border: none;
flex-shrink: 0;
}
.[Link] { background: var(--indigo); }
.toggle-switch::after {
content: '';
position: absolute;
width: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
top: 3px;
left: 3px;
transition: left 0.2s;
}
.[Link]::after { left: 21px; }

/* ════════════════════════════════════════════════════════
11. MODAL / DRAWER
════════════════════════════════════════════════════════ */
#modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 100;
display: none;
backdrop-filter: blur(2px);
}
#[Link] { display: block; }

Page 31 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
#drawer {
position: fixed;
right: 0;
top: 0;
bottom: 0;
width: 440px;
background: var(--navy2);
border-left: 1px solid var(--border);
z-index: 101;
overflow-y: auto;
transform: translateX(100%);
transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
}
#[Link] { transform: translateX(0); }

.drawer-hd {
padding: 18px 22px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.drawer-title { font-size: 16px; font-weight: 800; color: var(--text1); }
.drawer-sub { font-size: 12px; color: var(--text3); margin-top: 2px; }
.drawer-close {
width: 30px;
height: 30px;
background: var(--navy3);
border: none;
border-radius: var(--r8);
color: var(--text3);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.drawer-close:hover { background: var(--navy4); color: var(--text1); }

.drawer-body { padding: 22px; flex: 1; overflow-y: auto; }

.drawer-ft {
padding: 14px 22px;
border-top: 1px solid var(--border);
display: flex;
gap: 10px;
flex-shrink: 0;
}

/* Confirm modal (delete) */


#confirm-modal {
position: fixed;
inset: 0;
z-index: 200;
display: none;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.65);
backdrop-filter: blur(2px);
}
#[Link] {
display: flex;
animation: fadeUp 0.2s ease;
}
.confirm-box {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r16);

Page 32 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
padding: 28px 28px 22px;
max-width: 360px;
width: 100%;
text-align: center;
box-shadow: var(--shadow-lg);
}
.confirm-icon { font-size: 40px; margin-bottom: 14px; }
.confirm-title { font-size: 17px; font-weight: 800; margin-bottom: 8px; }
.confirm-msg { font-size: 13px; color: var(--text3); margin-bottom: 22px; line-height: 1.5; }
.confirm-btns { display: flex; gap: 10px; }

/* ════════════════════════════════════════════════════════
12. BUTTONS
════════════════════════════════════════════════════════ */
.btn-primary {
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
border: none;
border-radius: var(--r12);
color: #fff;
font-size: 13.5px;
font-weight: 700;
padding: 10px 20px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 7px;
box-shadow: 0 3px 12px rgba(91,99,254,0.3);
transition: all 0.2s;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 6px 22px rgba(91,99,254,0.45);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}

.btn-secondary {
background: var(--navy3);
border: 1px solid var(--border);
border-radius: var(--r12);
color: var(--text2);
font-size: 13px;
font-weight: 600;
padding: 10px 18px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.15s;
}
.btn-secondary:hover { background: var(--navy4); color: var(--text1); }

.btn-ghost {
background: transparent;
border: 1px solid var(--border);
border-radius: var(--r12);
color: var(--text3);
font-size: 13px;
font-weight: 600;
padding: 10px 18px;
cursor: pointer;
transition: all 0.15s;
}
.btn-ghost:hover { border-color: var(--indigo); color: var(--indigo-l); }

.btn-danger {
background: var(--rose-dim);

Page 33 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
border: 1px solid rgba(255,77,109,0.25);
border-radius: var(--r12);
color: var(--rose);
font-size: 13.5px;
font-weight: 700;
padding: 10px 20px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 7px;
transition: all 0.15s;
}
.btn-danger:hover { background: rgba(255,77,109,0.2); }

.btn-full { width: 100%; justify-content: center; }


.btn-sm { padding: 6px 13px; font-size: 12px; border-radius: var(--r8); }

/* ════════════════════════════════════════════════════════
13. TOAST
════════════════════════════════════════════════════════ */
.toast-container {
position: fixed;
bottom: 22px;
right: 22px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 500;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
padding: 12px 16px;
min-width: 280px;
box-shadow: var(--shadow);
pointer-events: auto;
animation: toastIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
transition: opacity 0.3s, transform 0.3s;
}
@keyframes toastIn {
from { opacity: 0; transform: translateX(40px); }
to { opacity: 1; transform: translateX(0); }
}
.[Link] { opacity: 0; transform: translateX(40px); }
.toast-icon { font-size: 16px; flex-shrink: 0; }
.toast-msg { font-size: 13px; color: var(--text1); font-weight: 500; }

/* ════════════════════════════════════════════════════════
14. EMPTY STATES
════════════════════════════════════════════════════════ */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 52px 20px;
text-align: center;
}
.empty-icon { font-size: 52px; margin-bottom: 14px; }
.empty-title { font-size: 16px; font-weight: 800; color: var(--text1); margin-bottom: 6px; }
.empty-sub { font-size: 13px; color: var(--text3); max-width: 280px; line-height: 1.6; margin-
bottom: 20px; }

/* Filter tabs */

Page 34 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.filter-tabs {
display: flex;
gap: 7px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.filter-tab {
padding: 6.5px 14px;
border-radius: var(--r8);
border: 1px solid var(--border);
background: transparent;
color: var(--text3);
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.15s;
}
.filter-tab:hover { border-color: var(--navy4); color: var(--text2); }
.[Link] {
border-color: var(--indigo);
background: var(--indigo-dim);
color: var(--indigo-l);
}
.filter-tab .cnt {
background: var(--navy3);
border-radius: 4px;
padding: 1px 6px;
font-size: 10.5px;
}

/* Progress bar */
.prog-bar { height: 5px; background: var(--navy4); border-radius: 99px; overflow: hidden; }
.prog-fill { height: 100%; border-radius: 99px; transition: width 1.2s ease; }
.prog-bar-lg { height: 7px; background: var(--navy4); border-radius: 99px; overflow: hidden; }

/* ════════════════════════════════════════════════════════
15. PAGE-SPECIFIC SECTIONS
════════════════════════════════════════════════════════ */

/* ── Inventory health row ── */


.inv-health-row {
display: flex;
align-items: center;
gap: 14px;
padding: 13px 0;
border-bottom: 1px solid rgba(48,54,61,0.4);
}
.inv-health-row:last-child { border-bottom: none; }
.inv-emoji { font-size: 24px; flex-shrink: 0; }
.inv-info { flex: 1; }
.inv-name { font-weight: 700; font-size: 14px; }
.inv-meta { font-size: 11.5px; color: var(--text3); margin-top: 2px; }
.inv-stock { text-align: center; flex-shrink: 0; }
.inv-qty { font-size: 22px; font-weight: 900; font-family: var(--mono); }
.inv-qty-lbl{ font-size: 10.5px; color: var(--text3); }

/* Inventory stat mini cards */


.inv-stat-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.inv-stat {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);

Page 35 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
padding: 15px;
text-align: center;
}
.inv-stat-val { font-size: 26px; font-weight: 900; font-family: var(--mono); }
.inv-stat-lbl { font-size: 10.5px; color: var(--text3); margin-top: 4px; text-transform: uppercase;
letter-spacing: 0.5px; }

/* ── Invoice preview ── */
.invoice-preview {
background: #fff;
color: #1e293b;
border-radius: var(--r16);
padding: 26px;
font-size: 13px;
line-height: 1.6;
}
.inv-logo { font-size: 17px; font-weight: 900; color: #1a1a2e; margin-bottom: 3px; }
.inv-address { font-size: 11.5px; color: #64748b; margin-bottom: 18px; }
.inv-hd-row { display: flex; justify-content: space-between; margin-bottom: 14px; }
.inv-bill-to .lbl { font-size: 10px; color: #64748b; font-weight: 700; text-transform: uppercase;
letter-spacing: 1px; }
.inv-divider { border: none; border-top: 1px solid #e2e8f0; margin: 10px 0; }
.inv-summary-row {
display: flex;
justify-content: space-between;
font-size: 12.5px;
color: #64748b;
margin-bottom: 5px;
}
.inv-total-row {
display: flex;
justify-content: space-between;
font-size: 17px;
font-weight: 900;
color: #1a1a2e;
border-top: 2px solid var(--indigo);
padding-top: 10px;
margin-top: 6px;
}
.inv-pay-chip {
margin-top: 14px;
padding: 7px 12px;
background: #f1f5f9;
border-radius: 8px;
font-size: 12px;
font-weight: 700;
color: var(--indigo);
text-align: center;
}

/* ── Analytics ── */
.analytics-kpi {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r12);
padding: 18px;
border-left: 3px solid;
}

/* ── Settings sections ── */
.settings-panel {
background: var(--navy2);
border: 1px solid var(--border);
border-radius: var(--r16);
padding: 22px;
margin-bottom: 16px;
}
.settings-panel-title {
font-size: 14px;
font-weight: 800;
margin-bottom: 18px;

Page 36 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
color: var(--text1);
}
.profile-photo-wrap {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: var(--navy3);
border-radius: var(--r12);
margin-bottom: 20px;
}
.profile-photo {
width: 60px;
height: 60px;
border-radius: 14px;
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
box-shadow: 0 4px 16px rgba(91,99,254,0.25);
}
.profile-name { font-size: 15px; font-weight: 800; }
.profile-role { font-size: 12px; color: var(--text3); margin-top: 2px; }
.profile-change-btn {
margin-top: 6px;
background: none;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--indigo-l);
cursor: pointer;
padding: 3px 11px;
font-size: 11px;
font-weight: 700;
}

.notif-pref-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 11px 0;
border-bottom: 1px solid rgba(48,54,61,0.4);
}
.notif-pref-row:last-child { border-bottom: none; }
.notif-pref-label { font-size: 13.5px; color: var(--text1); }

.op-hours-row {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
background: var(--navy3);
border-radius: var(--r8);
margin-bottom: 9px;
flex-wrap: wrap;
gap: 8px;
}
.op-day-label { font-size: 13px; font-weight: 600; min-width: 110px; }

/* ── Location / map box ── */


.map-box {
background: var(--navy3);
border: 2px dashed var(--border);
border-radius: var(--r16);
height: 240px;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;

Page 37 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
justify-content: center;
margin-bottom: 16px;
}
.map-grid-v, .map-grid-h { position: absolute; background: var(--border); opacity: 0.5; }
.map-grid-v { width: 1px; height: 100%; }
.map-grid-h { height: 1px; width: 100%; }
.map-pin-wrap {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.map-pin {
width: 44px;
height: 44px;
background: linear-gradient(135deg, var(--indigo), var(--indigo-l));
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 16px rgba(91,99,254,0.4);
}
.map-pin-inner {
width: 16px;
height: 16px;
background: #fff;
border-radius: 50%;
transform: rotate(45deg);
}

/* ── Ledger ── */
.ledger-credit { color: var(--emerald); font-weight: 700; }
.ledger-debit { color: var(--rose); font-weight: 700; }

/* Expense donut */
.expense-summary-box {
background: linear-gradient(135deg, rgba(91,99,254,0.07), rgba(123,130,255,0.07));
border: 1px solid rgba(91,99,254,0.15);
border-radius: var(--r16);
padding: 22px;
text-align: center;
margin-bottom: 16px;
}

/* Stat divider rows (tally summary) */


.tally-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid rgba(48,54,61,0.4);
}
.tally-row:last-child { border-bottom: none; }

/* ════════════════════════════════════════════════════════
16. UTILITIES
════════════════════════════════════════════════════════ */
.mb4 { margin-bottom: 4px; }
.mb8 { margin-bottom: 8px; }
.mb12 { margin-bottom: 12px; }
.mb16 { margin-bottom: 16px; }
.mb20 { margin-bottom: 20px; }
.mb24 { margin-bottom: 24px; }
.mt4 { margin-top: 4px; }
.mt8 { margin-top: 8px; }
.mt12 { margin-top: 12px; }
.mt16 { margin-top: 16px; }

Page 38 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

.flex { display: flex; }


.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.gap4 { gap: 4px; }
.gap8 { gap: 8px; }
.gap10 { gap: 10px; }
.gap12 { gap: 12px; }
.gap16 { gap: 16px; }
.flex1 { flex: 1; }
.shrink0 { flex-shrink: 0; }
.wrap { flex-wrap: wrap; }

.text-muted { color: var(--text3); }


.text-mono { font-family: var(--mono); }
.fw700 { font-weight: 700; }
.fw800 { font-weight: 800; }
.fw900 { font-weight: 900; }
.fs11 { font-size: 11px; }
.fs12 { font-size: 12px; }
.fs13 { font-size: 13px; }
.fs14 { font-size: 14px; }
.uppercase { text-transform: uppercase; letter-spacing: 0.06em; }

.w100 { width: 100%; }


.text-center { text-align: center; }
.text-right { text-align: right; }

.indigo-text { color: var(--indigo-l); }


.emerald-text { color: var(--emerald); }
.amber-text { color: var(--amber); }
.rose-text { color: var(--rose); }
.sky-text { color: var(--sky); }

/* Mobile sidebar overlay */


#sb-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
z-index: 29;
}
#[Link] { display: block; }

/* Stat item (analytics) */


.stat-item-sm {
display: flex;
flex-direction: column;
gap: 3px;
}
.stat-val-sm {
font-size: 22px;
font-weight: 900;
font-family: var(--mono);
letter-spacing: -1px;
}
.stat-lbl-sm {
font-size: 10.5px;
color: var(--text3);
text-transform: uppercase;
letter-spacing: 0.5px;
}

/* Mobile bottom nav */


#mob-nav {
display: none;
position: fixed;
bottom: 0;

Page 39 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
left: 0;
right: 0;
background: var(--navy2);
border-top: 1px solid var(--border);
padding: 6px 0 max(6px, env(safe-area-inset-bottom));
z-index: 40;
}
.mob-nav-inner {
display: flex;
justify-content: space-around;
}
.mob-nav-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 5px 10px;
border-radius: var(--r8);
background: none;
border: none;
color: var(--text3);
cursor: pointer;
font-size: 9.5px;
font-weight: 700;
transition: all 0.15s;
min-width: 52px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.mob-nav-item svg { width: 20px; height: 20px; }
.[Link] { color: var(--indigo-l); }

/* ════════════════════════════════════════════════════════
17. RESPONSIVE
════════════════════════════════════════════════════════ */

/* ── Laptop/Desktop ≥ 1100px — full layout ── */


@media (min-width: 1100px) {
.kpi-grid { grid-template-columns: repeat(4, 1fr); }
}

/* ── Tablet landscape 900–1100px ── */


@media (max-width: 1100px) {
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
.g4 { grid-template-columns: repeat(2, 1fr); }
.g-main-side { grid-template-columns: 1fr; }
.g-main-wide { grid-template-columns: 1fr; }
.g3 { grid-template-columns: 1fr 1fr; }
.inv-stat-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ── Tablet portrait 768–900px ── */


@media (max-width: 900px) {
/* Sidebar moves off-screen, controlled by JS */
#sidebar {
position: fixed;
top: 0; bottom: 0; left: 0;
transform: translateX(-100%);
width: 230px !important;
transition: transform 0.28s cubic-bezier(0.4,0,0.2,1);
z-index: 31;
}
#[Link]-open { transform: translateX(0); }

.nb-date { display: none; }


.nb-vdiv { display: none; }
.mob-menu-show { display: flex !important; }

#mob-nav { display: block; }


#content { padding-bottom: 76px; }

Page 40 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

.g2 { grid-template-columns: 1fr; }


.g3 { grid-template-columns: 1fr 1fr; }
}

/* ── Mobile ≤ 640px ── */
@media (max-width: 640px) {
.kpi-grid { grid-template-columns: 1fr 1fr; }
.g3 { grid-template-columns: 1fr; }
.g4 { grid-template-columns: 1fr 1fr; }
.two-col { grid-template-columns: 1fr; }
#content { padding: 14px 14px; }
.card { padding: 15px; }
.kpi-card { padding: 14px; }
.kpi-value { font-size: 23px; }
#drawer { width: 100%; border-left: none; }
.toast-container { bottom: 12px; right: 12px; left: 12px; }
.toast { min-width: unset; width: 100%; }
thead th:nth-child(n+4):not(:last-child) { display: none; }
tbody td:nth-child(n+4):not(:last-child) { display: none; }
.btn-quick-add span { display: none; }
.btn-quick-add { padding: 7px 10px; }
.nb-search-wrap { max-width: 140px; }
.inv-stat-grid { grid-template-columns: 1fr 1fr; }
}

/* ── Very small ≤ 400px ── */


@media (max-width: 400px) {
.kpi-grid { grid-template-columns: 1fr; }
}

Page 41 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

8. css / [Link]
📄 File: css/[Link]
Lines of code: 945

/* ============================================================
RetailPro — Customer Dashboard CSS
File: RetailPro/css/[Link]
Matches existing dark theme: Sora font, navy palette,
emerald accent, indigo secondary.
============================================================ */

@import url('[Link]
family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');

/* ── Reset ─────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Design Tokens ─────────────────────────────────────── */


:root {
--sw: 240px;
--navy: #0D1117; --navy2: #161B22; --navy3: #1C2333; --navy4: #21262D;
--border: #30363D;
--indigo: #5B63FE; --indigo-l: #7B82FF; --indigo-dim: rgba(91,99,254,.15);
--emerald: #10D9A0; --emerald-l: #34EDB8; --emerald-dim: rgba(16,217,160,.12);
--rose: #FF4D6D; --rose-dim: rgba(255,77,109,.12);
--amber: #F5A623; --amber-dim: rgba(245,166,35,.12);
--sky: #38BDF8; --sky-dim: rgba(56,189,248,.12);
--violet: #A78BFA; --violet-dim: rgba(167,139,250,.12);
--text1: #F0F6FC; --text2: #8B949E; --text3: #484F58;
--font: 'Sora', sans-serif; --mono: 'JetBrains Mono', monospace;
--r4: 4px; --r8: 8px; --r12: 12px; --r16: 16px; --r20: 20px;
--shadow: 0 8px 32px rgba(0,0,0,.4);
--shadow-lg: 0 20px 60px rgba(0,0,0,.5);
}

/* ── Base ───────────────────────────────────────────────── */
body {
font-family: var(--font);
background: var(--navy);
color: var(--text1);
display: flex;
min-height: 100vh;
font-size: 14px;
line-height: 1.5;
}
a { text-decoration: none; color: inherit; }
button { cursor: pointer; font-family: var(--font); }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: var(--navy); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

/* ════════════════════════════════════════════
SIDEBAR
════════════════════════════════════════════ */
.sidebar {
width: var(--sw); background: var(--navy2); border-right: 1px solid var(--border);
display: flex; flex-direction: column; position: fixed; top: 0; left: 0; bottom: 0; z-index: 200;
transition: transform .3s;
}
.sidebar-brand {
padding: 20px 16px 16px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 10px;
}
.brand-logo {
width: 34px; height: 34px; background: linear-gradient(135deg,var(--emerald),var(--sky));

Page 42 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
border-radius: var(--r8); display: flex; align-items: center; justify-content: center;
font-size: 16px; box-shadow: 0 4px 12px rgba(16,217,160,.35); flex-shrink: 0;
}
.brand-text .name { font-size: 14px; font-weight: 700; color: var(--text1); }
.brand-text .tagline { font-size: 10px; color: var(--text2); margin-top: 1px; }

.section-label {
font-size: 10px; font-weight: 600; color: var(--text3);
text-transform: uppercase; letter-spacing: .1em; padding: 18px 16px 7px;
}
.nav-item {
display: flex; align-items: center; gap: 10px; padding: 10px 14px;
margin: 1px 8px; border-radius: var(--r8); color: var(--text2);
font-size: 13px; font-weight: 500; cursor: pointer; transition: all .15s; position: relative;
}
.nav-item:hover { background: var(--navy3); color: var(--text1); }
.[Link] { background: var(--emerald-dim); color: var(--emerald); }
.[Link]::before {
content: ''; position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
width: 3px; height: 20px; background: var(--emerald); border-radius: 0 2px 2px 0;
}
.nav-icon {
width: 32px; height: 32px; border-radius: var(--r8);
display: flex; align-items: center; justify-content: center; font-size: 15px;
background: rgba(255,255,255,.04); flex-shrink: 0; transition: all .15s;
}
.[Link] .nav-icon { background: var(--emerald-dim); }
.nav-badge {
margin-left: auto; background: var(--rose); color: white;
font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 99px;
}
.nav-count {
margin-left: auto; background: var(--emerald); color: var(--navy);
font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 99px;
}

.sidebar-footer { margin-top: auto; padding: 12px 8px; border-top: 1px solid var(--border); }
.user-card {
display: flex; align-items: center; gap: 10px; padding: 10px;
border-radius: var(--r8); cursor: pointer; transition: background .15s;
}
.user-card:hover { background: var(--navy3); }
.user-avatar {
width: 34px; height: 34px; border-radius: 50%;
background: linear-gradient(135deg,var(--emerald),var(--sky));
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 700; color: var(--navy); flex-shrink: 0;
}
.user-info .uname { font-size: 13px; font-weight: 600; color: var(--text1); }
.user-info .uemail { font-size: 11px; color: var(--text2); }
.logout-btn {
display: flex; align-items: center; gap: 8px; padding: 8px 10px; margin-top: 4px;
border-radius: var(--r8); color: var(--rose); font-size: 12px; font-weight: 500;
cursor: pointer; transition: background .15s;
}
.logout-btn:hover { background: var(--rose-dim); }

/* Mobile sidebar overlay */


.sidebar-overlay {
display: none; position: fixed; inset: 0; background: rgba(0,0,0,.6);
z-index: 199; backdrop-filter: blur(2px);
}
.[Link] { display: block; }
.hamburger {
display: none; width: 36px; height: 36px; border: 1px solid var(--border);
background: var(--navy3); border-radius: var(--r8); align-items: center;
justify-content: center; font-size: 16px; cursor: pointer; transition: all .15s;
}
.hamburger:hover { border-color: var(--emerald); }

/* ════════════════════════════════════════════

Page 43 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
TOPBAR
════════════════════════════════════════════ */
.main { margin-left: var(--sw); flex: 1; display: flex; flex-direction: column; min-height: 100vh; }
.topbar {
background: var(--navy2); border-bottom: 1px solid var(--border);
padding: 0 24px; height: 60px; display: flex; align-items: center; gap: 12px;
position: sticky; top: 0; z-index: 100;
}
.topbar-title { font-size: 15px; font-weight: 700; }
.topbar-sub { font-size: 11px; color: var(--text2); margin-left: 3px; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.search-topbar {
display: flex; align-items: center; gap: 8px;
background: var(--navy3); border: 1px solid var(--border);
border-radius: var(--r12); padding: 8px 14px; width: 240px; transition: border .2s;
}
.search-topbar:focus-within { border-color: var(--emerald); }
.search-topbar input { background: none; border: none; outline: none; color: var(--text1); font-
family: var(--font); font-size: 13px; flex: 1; }
.search-topbar input::placeholder { color: var(--text2); }

.icon-btn {
width: 36px; height: 36px; background: var(--navy3); border: 1px solid var(--border);
border-radius: var(--r8); display: flex; align-items: center; justify-content: center;
cursor: pointer; transition: all .15s; font-size: 15px; position: relative;
}
.icon-btn:hover { border-color: var(--emerald); background: var(--emerald-dim); }
.notif-dot {
position: absolute; top: 6px; right: 6px; width: 7px; height: 7px;
background: var(--rose); border-radius: 50%; border: 2px solid var(--navy2);
}
.cart-topbtn {
display: flex; align-items: center; gap: 7px; padding: 7px 14px;
background: var(--emerald-dim); border: 1px solid rgba(16,217,160,.3);
border-radius: var(--r8); cursor: pointer; transition: all .15s;
font-size: 12px; font-weight: 600; color: var(--emerald);
}
.cart-topbtn:hover { background: var(--emerald); color: var(--navy); }
.cart-count {
background: var(--rose); color: white; border-radius: 99px;
padding: 1px 6px; font-size: 10px; font-weight: 700;
}

/* ════════════════════════════════════════════
PAGE SYSTEM
════════════════════════════════════════════ */
.page { display: none; }
.[Link] { display: block; animation: fadeIn .3s ease; }
@keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
.page-content { padding: 24px 28px; }

/* ════════════════════════════════════════════
SHARED COMPONENTS
════════════════════════════════════════════ */

/* Section headers */
.sec-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;
}
.sec-title { font-size: 15px; font-weight: 700; }
.sec-sub { font-size: 12px; color: var(--text2); margin-top: 2px; }
.view-all-btn {
font-size: 12px; color: var(--emerald); background: none; border: none;
cursor: pointer; font-weight: 500; display: flex; align-items: center; gap: 4px;
}
.view-all-btn:hover { text-decoration: underline; }

/* Chips / filters */
.filter-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.chip {
padding: 6px 14px; border-radius: 99px; font-size: 12px; font-weight: 500;

Page 44 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
background: var(--navy3); border: 1px solid var(--border); color: var(--text2);
cursor: pointer; transition: all .15s; white-space: nowrap;
}
.[Link] { background: var(--emerald-dim); border-color: rgba(16,217,160,.4); color: var(--
emerald); }
.chip:hover:not(.active) { border-color: var(--text3); color: var(--text1); }

/* Cards */
.card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); overflow: hidden;
}
.card-p { padding: 18px 20px; }

/* Badges */
.badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 10px; border-radius: 99px; font-size: 11px; font-weight: 600;
}
.badge-emerald { background: var(--emerald-dim); color: var(--emerald); }
.badge-rose { background: var(--rose-dim); color: var(--rose); }
.badge-amber { background: var(--amber-dim); color: var(--amber); }
.badge-sky { background: var(--sky-dim); color: var(--sky); }
.badge-violet { background: var(--violet-dim); color: var(--violet); }

/* Buttons */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
padding: 8px 16px; border-radius: var(--r8); font-size: 13px; font-weight: 600;
border: none; cursor: pointer; transition: all .15s; font-family: var(--font);
}
.btn-primary { background: var(--emerald); color: var(--navy); }
.btn-primary:hover { background: var(--emerald-l); box-shadow: 0 4px 16px rgba(16,217,160,.35);
transform: translateY(-1px); }
.btn-secondary { background: var(--navy3); color: var(--text2); border: 1px solid var(--border); }
.btn-secondary:hover { color: var(--text1); border-color: var(--text3); }
.btn-ghost { background: transparent; color: var(--text2); border: 1px solid var(--border); }
.btn-ghost:hover { color: var(--text1); }
.btn-sm { padding: 5px 12px; font-size: 11px; }
.btn-icon { width: 32px; height: 32px; padding: 0; border-radius: var(--r8); }

/* Stars */
.stars { color: var(--amber); font-size: 12px; }
.star-count { font-size: 11px; color: var(--text2); margin-left: 4px; }

/* ════════════════════════════════════════════
HOME PAGE — HERO BANNER
════════════════════════════════════════════ */
.hero-banner {
background: linear-gradient(120deg,#0d1f3c 0%,#0f2040 40%,#0d1b38 100%);
border: 1px solid var(--border); border-radius: var(--r20);
padding: 28px 32px; margin-bottom: 22px; position: relative; overflow: hidden;
}
.hero-banner::before {
content: ''; position: absolute; right: -60px; top: -60px;
width: 280px; height: 280px;
background: radial-gradient(circle,rgba(16,217,160,.12) 0%,transparent 65%); border-radius: 50%;
}
.hero-banner::after {
content: ''; position: absolute; left: 30%; bottom: -80px;
width: 200px; height: 200px;
background: radial-gradient(circle,rgba(91,99,254,.1) 0%,transparent 65%); border-radius: 50%;
}
.hero-content { position: relative; z-index: 1; }
.hero-greeting { font-size: 11px; font-weight: 600; color: var(--emerald); text-transform: uppercase;
letter-spacing: .1em; margin-bottom: 6px; }
.hero-title { font-size: 24px; font-weight: 700; line-height: 1.25; margin-bottom: 8px; }
.hero-title span { color: var(--emerald); }
.hero-sub { font-size: 13px; color: var(--text2); margin-bottom: 18px; }
.hero-search {
display: flex; align-items: center; gap: 10px;

Page 45 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.12);
border-radius: var(--r12); padding: 11px 16px; max-width: 500px;
backdrop-filter: blur(10px); transition: border .2s;
}
.hero-search:focus-within { border-color: var(--emerald); }
.hero-search input { background: none; border: none; outline: none; color: var(--text1); font-family:
var(--font); font-size: 14px; flex: 1; }
.hero-search input::placeholder { color: rgba(255,255,255,.35); }
.hero-search-btn {
background: var(--emerald); color: var(--navy); border: none;
border-radius: var(--r8); padding: 7px 16px; font-size: 12px; font-weight: 700;
cursor: pointer; font-family: var(--font); white-space: nowrap; transition: all .15s;
}
.hero-search-btn:hover { box-shadow: 0 4px 16px rgba(16,217,160,.4); }
.hero-chips { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.hero-chip {
padding: 5px 12px; border-radius: 99px; font-size: 11px; font-weight: 500;
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
color: rgba(255,255,255,.6); cursor: pointer; transition: all .15s;
}
.hero-chip:hover, .[Link] { background: var(--emerald-dim); border-color:
rgba(16,217,160,.4); color: var(--emerald); }
.hero-stats {
position: absolute; right: 32px; top: 50%; transform: translateY(-50%);
display: flex; flex-direction: column; gap: 10px; z-index: 1;
}
.hero-stat {
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.08);
border-radius: var(--r12); padding: 12px 16px; text-align: center;
backdrop-filter: blur(8px); min-width: 90px;
}
.hero-stat-val { font-size: 20px; font-weight: 700; font-family: var(--mono); }
.hero-stat-lbl { font-size: 10px; color: var(--text2); margin-top: 2px; }

/* ════════════════════════════════════════════
CATEGORY PILLS
════════════════════════════════════════════ */
.cat-scroll { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; margin-bottom: 20px; }
.cat-scroll::-webkit-scrollbar { height: 3px; }
.cat-pill {
display: flex; flex-direction: column; align-items: center; gap: 5px;
padding: 12px 16px; border-radius: var(--r12);
background: var(--navy2); border: 1px solid var(--border);
cursor: pointer; transition: all .2s; flex-shrink: 0; min-width: 75px;
}
.cat-pill:hover, .[Link] {
border-color: rgba(16,217,160,.4); background: var(--emerald-dim); color: var(--emerald);
}
.cat-pill-icon { font-size: 22px; }
.cat-pill-label { font-size: 11px; font-weight: 500; color: var(--text2); white-space: nowrap; }
.cat-pill:hover .cat-pill-label, .[Link] .cat-pill-label { color: var(--emerald); }

/* ════════════════════════════════════════════
MAP SECTION
════════════════════════════════════════════ */
.map-section { margin-bottom: 24px; }
.map-layout { display: grid; grid-template-columns: 1fr 340px; gap: 16px; }

.map-container {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); overflow: hidden; position: relative; height: 480px;
}
#map { width: 100%; height: 100%; }

/* Fake/demo map background when API not loaded */


.map-demo-bg {
width: 100%; height: 100%;
background: linear-gradient(135deg,#0d1b2a 0%,#0f2235 40%,#0a1628 100%);
position: relative; overflow: hidden;
}
.map-grid {

Page 46 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
position: absolute; inset: 0;
background-image:
linear-gradient(rgba(16,217,160,.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(16,217,160,.04) 1px, transparent 1px);
background-size: 40px 40px;
}
.map-road-h {
position: absolute; height: 3px; background: rgba(255,255,255,.08);
border-radius: 2px;
}
.map-road-v {
position: absolute; width: 3px; background: rgba(255,255,255,.08);
border-radius: 2px;
}
.map-marker {
position: absolute; transform: translate(-50%,-100%);
display: flex; flex-direction: column; align-items: center; cursor: pointer; z-index: 5;
}
.marker-pin {
width: 38px; height: 38px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 16px rgba(0,0,0,.4); transition: transform .2s;
}
.marker-pin span { transform: rotate(45deg); font-size: 16px; }
.map-marker:hover .marker-pin { transform: rotate(-45deg) scale(1.15); }
.marker-label {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r8); padding: 4px 8px; font-size: 10px; font-weight: 600;
white-space: nowrap; margin-bottom: 4px; color: var(--text1);
box-shadow: 0 2px 8px rgba(0,0,0,.4);
opacity: 0; transition: opacity .2s; pointer-events: none;
}
.map-marker:hover .marker-label { opacity: 1; }
.marker-dot { width: 8px; height: 8px; background: var(--text2); border-radius: 50%; }

.map-controls {
position: absolute; top: 12px; right: 12px;
display: flex; flex-direction: column; gap: 6px; z-index: 10;
}
.map-btn {
width: 34px; height: 34px; background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r8); display: flex; align-items: center; justify-content: center;
font-size: 14px; cursor: pointer; transition: all .15s; color: var(--text1);
}
.map-btn:hover { border-color: var(--emerald); background: var(--emerald-dim); }

.map-overlay-search {
position: absolute; top: 12px; left: 12px; right: 60px; z-index: 10;
display: flex; gap: 8px;
}
.map-search-box {
flex: 1; display: flex; align-items: center; gap: 8px;
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r8); padding: 8px 12px;
}
.map-search-box input { background: none; border: none; outline: none; color: var(--text1); font-
family: var(--font); font-size: 13px; flex: 1; }
.map-search-box input::placeholder { color: var(--text2); }

.map-filter-bar {
position: absolute; bottom: 12px; left: 12px; right: 12px; z-index: 10;
display: flex; gap: 8px; overflow-x: auto;
}
.map-filter-bar::-webkit-scrollbar { display: none; }
.map-chip {
padding: 5px 12px; border-radius: 99px; font-size: 11px; font-weight: 600;
background: var(--navy2); border: 1px solid var(--border); color: var(--text2);
cursor: pointer; white-space: nowrap; transition: all .15s;
}
.[Link] { background: var(--emerald); color: var(--navy); border-color: var(--emerald); }
.map-chip:hover:not(.active) { border-color: var(--text3); color: var(--text1); }

Page 47 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

.user-location-dot {
position: absolute; width: 14px; height: 14px; border-radius: 50%;
background: var(--sky); border: 2px solid white;
box-shadow: 0 0 0 6px rgba(56,189,248,.2);
animation: ping 2s ease-in-out infinite;
}
@keyframes ping {
0%,100% { box-shadow: 0 0 0 6px rgba(56,189,248,.2); }
50% { box-shadow: 0 0 0 12px rgba(56,189,248,.05); }
}

/* Shop list beside map */


.shop-list-panel {
display: flex; flex-direction: column; gap: 10px;
height: 480px; overflow-y: auto;
}
.shop-list-panel::-webkit-scrollbar { width: 4px; }

.shop-list-card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r12); padding: 14px; cursor: pointer;
transition: all .2s; position: relative;
}
.shop-list-card:hover, .[Link] {
border-color: var(--emerald); transform: translateX(2px);
}
.[Link] { background: var(--emerald-dim); }
.slc-header { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.slc-icon { font-size: 26px; flex-shrink: 0; }
.slc-name { font-size: 13px; font-weight: 700; margin-bottom: 2px; }
.slc-cat { font-size: 11px; color: var(--text2); }
.slc-meta { display: flex; gap: 8px; align-items: center; font-size: 11px; color: var(--text2);
margin-bottom: 8px; flex-wrap: wrap; }
.slc-dist { display: flex; align-items: center; gap: 3px; }
.slc-open { font-weight: 600; }
.[Link] { color: var(--emerald); }
.[Link] { color: var(--rose); }
.slc-actions { display: flex; gap: 6px; }
.slc-btn {
flex: 1; padding: 5px 8px; border-radius: var(--r8); font-size: 11px; font-weight: 600;
border: 1px solid var(--border); background: var(--navy3); color: var(--text2);
cursor: pointer; transition: all .15s; font-family: var(--font);
}
.[Link] { background: var(--emerald); color: var(--navy); border-color: var(--emerald); }
.slc-btn:hover:not(.primary) { color: var(--text1); }
.slc-fav-btn {
width: 30px; height: 30px; border-radius: var(--r8);
background: var(--navy3); border: 1px solid var(--border);
display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 14px; transition: all .15s; flex-shrink: 0;
}
.slc-fav-btn:hover, .[Link] { background: var(--rose-dim); color: var(--rose); border-
color: rgba(255,77,109,.3); }
.[Link] { color: var(--rose); }

/* ════════════════════════════════════════════
SHOP GRID CARDS
════════════════════════════════════════════ */
.shops-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 16px; margin-bottom: 24px; }

.shop-card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); overflow: hidden; transition: all .2s; cursor: pointer;
}
.shop-card:hover { border-color: var(--emerald); transform: translateY(-2px); box-shadow: 0 8px 30px
rgba(0,0,0,.35); }

.shop-card-cover {
height: 100px; position: relative; overflow: hidden;
display: flex; align-items: center; justify-content: center; font-size: 42px;

Page 48 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}
.shop-card-cover-badge {
position: absolute; top: 8px; right: 8px;
padding: 3px 9px; border-radius: 99px; font-size: 10px; font-weight: 700;
backdrop-filter: blur(8px); background: rgba(0,0,0,.5);
}
.shop-card-body { padding: 14px; }
.shop-card-name { font-size: 14px; font-weight: 700; margin-bottom: 3px; }
.shop-card-meta { font-size: 11px; color: var(--text2); display: flex; flex-direction: column; gap:
3px; margin-bottom: 10px; }
.shop-card-meta-row { display: flex; align-items: center; gap: 5px; }
.shop-card-footer { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.shop-card-rating { display: flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600;
color: var(--amber); }
.shop-card-actions { display: flex; gap: 5px; }
.sca-btn {
padding: 5px 10px; border-radius: var(--r8); font-size: 11px; font-weight: 600;
cursor: pointer; transition: all .15s; font-family: var(--font); border: none;
}
.sca-primary { background: var(--emerald-dim); color: var(--emerald); }
.sca-primary:hover { background: var(--emerald); color: var(--navy); }
.sca-heart { background: var(--navy3); border: 1px solid var(--border); color: var(--text2); }
.sca-heart:hover, .[Link] { color: var(--rose); border-color: rgba(255,77,109,.3); background:
var(--rose-dim); }

/* ════════════════════════════════════════════
PRODUCT CARDS GRID
════════════════════════════════════════════ */
.products-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 14px; margin-bottom:
24px; }

.prod-card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); overflow: hidden; transition: all .2s; position: relative;
}
.prod-card:hover { border-color: var(--indigo); transform: translateY(-2px); box-shadow: 0 8px 24px
rgba(0,0,0,.3); }

.prod-thumb {
height: 110px; display: flex; align-items: center; justify-content: center;
font-size: 46px; position: relative; overflow: hidden;
}
.prod-stock-tag {
position: absolute; top: 8px; left: 8px;
padding: 3px 8px; border-radius: 99px; font-size: 10px; font-weight: 700;
}
.prod-wish-btn {
position: absolute; top: 8px; right: 8px; width: 26px; height: 26px;
border-radius: 50%; background: rgba(0,0,0,.5); border: none; cursor: pointer;
display: flex; align-items: center; justify-content: center; font-size: 12px;
transition: all .2s;
}
.prod-wish-btn:hover, .[Link] { background: var(--rose-dim); }
.prod-offer-tag {
position: absolute; bottom: 8px; left: 8px;
background: var(--rose); color: white;
padding: 2px 7px; border-radius: var(--r4); font-size: 10px; font-weight: 700;
}

.prod-body { padding: 12px; }


.prod-shop { font-size: 10px; color: var(--text2); margin-bottom: 3px; }
.prod-name { font-size: 13px; font-weight: 600; margin-bottom: 4px; line-height: 1.3; }
.prod-price-row { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
.prod-price { font-size: 15px; font-weight: 700; font-family: var(--mono); color: var(--emerald); }
.prod-old-price { font-size: 11px; color: var(--text3); text-decoration: line-through; font-family:
var(--mono); }
.prod-footer { display: flex; justify-content: space-between; align-items: center; }
.prod-rating { font-size: 11px; color: var(--amber); }
.prod-add-btn {
width: 30px; height: 30px; border-radius: 50%; background: var(--emerald);
border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;

Page 49 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
font-size: 16px; color: var(--navy); font-weight: 700; transition: all .2s;
}
.prod-add-btn:hover { transform: scale(1.15); box-shadow: 0 4px 12px rgba(16,217,160,.4); }
.[Link] { background: var(--indigo); }
.prod-add-btn:disabled { background: var(--navy4); color: var(--text3); cursor: not-allowed;
transform: none; }

/* ════════════════════════════════════════════
SHOP DETAIL MODAL
════════════════════════════════════════════ */
.modal-overlay {
display: none; position: fixed; inset: 0;
background: rgba(0,0,0,.7); backdrop-filter: blur(6px);
z-index: 500; align-items: center; justify-content: center; padding: 20px;
}
.[Link] { display: flex; }

.shop-modal {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r20); width: 100%; max-width: 780px; max-height: 88vh;
overflow-y: auto; animation: modalIn .3s cubic-bezier(.34,1.2,.64,1);
}
@keyframes modalIn { from{opacity:0;transform:scale(.94) translateY(20px)}
to{opacity:1;transform:scale(1) translateY(0)} }

.modal-header {
padding: 20px 24px; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
position: sticky; top: 0; background: var(--navy2); z-index: 10;
}
.modal-close {
width: 32px; height: 32px; border-radius: var(--r8);
background: var(--navy3); border: 1px solid var(--border);
display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 16px; color: var(--text2); transition: all .15s;
}
.modal-close:hover { color: var(--text1); background: var(--navy4); }
.modal-body { padding: 24px; }

.shop-modal-hero {
display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px;
}
.shop-modal-info { }
.shop-modal-icon-lg { font-size: 56px; margin-bottom: 12px; }
.shop-modal-name { font-size: 20px; font-weight: 800; margin-bottom: 6px; }
.shop-modal-meta { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--
text2); }
.shop-modal-meta-row { display: flex; align-items: center; gap: 7px; }
.shop-modal-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.shop-tag { padding: 4px 10px; border-radius: 99px; font-size: 10px; font-weight: 600; background:
var(--navy3); border: 1px solid var(--border); color: var(--text2); }

.shop-modal-actions { display: flex; flex-direction: column; gap: 8px; }


.shop-modal-stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom:
12px; }
.shop-modal-stat { background: var(--navy3); border: 1px solid var(--border); border-radius: var(--
r8); padding: 10px; }
.sms-label { font-size: 10px; color: var(--text2); text-transform: uppercase; letter-spacing: .06em;
margin-bottom: 3px; }
.sms-val { font-size: 16px; font-weight: 700; font-family: var(--mono); }

.modal-section-title { font-size: 13px; font-weight: 700; margin-bottom: 12px; padding-bottom: 8px;


border-bottom: 1px solid var(--border); }
.modal-products-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-bottom:
20px; }

/* Enquiry form inside modal */


.enquiry-form { display: flex; flex-direction: column; gap: 10px; }
.enquiry-form textarea {
background: var(--navy3); border: 1px solid var(--border); border-radius: var(--r8);
padding: 10px 14px; font-size: 13px; color: var(--text1); font-family: var(--font);

Page 50 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
resize: vertical; min-height: 80px; outline: none; transition: border .2s;
}
.enquiry-form textarea:focus { border-color: var(--emerald); }

/* ════════════════════════════════════════════
CART PAGE
════════════════════════════════════════════ */
.cart-layout { display: grid; grid-template-columns: 1fr 300px; gap: 20px; }
.cart-section { background: var(--navy2); border: 1px solid var(--border); border-radius: var(--r16);
overflow: hidden; }
.cart-header { padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; justify-
content: space-between; align-items: center; }
.cart-header-title { font-size: 14px; font-weight: 700; }

.cart-item {
display: flex; align-items: center; gap: 13px;
padding: 14px 18px; border-bottom: 1px solid rgba(48,54,61,.5);
transition: background .15s;
}
.cart-item:hover { background: rgba(255,255,255,.01); }
.cart-item:last-child { border-bottom: none; }
.cart-img {
width: 52px; height: 52px; border-radius: var(--r8);
background: var(--navy3); border: 1px solid var(--border);
display: flex; align-items: center; justify-content: center; font-size: 26px; flex-shrink: 0;
}
.cart-item-name { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
.cart-item-shop { font-size: 11px; color: var(--text2); }
.cart-item-price { font-size: 14px; font-weight: 700; font-family: var(--mono); color: var(--emerald);
margin-top: 4px; }
.qty-ctrl { display: flex; align-items: center; gap: 7px; }
.qty-btn {
width: 28px; height: 28px; border-radius: var(--r8);
background: var(--navy3); border: 1px solid var(--border);
cursor: pointer; font-size: 14px; font-weight: 700; color: var(--text1);
display: flex; align-items: center; justify-content: center; transition: all .15s;
}
.qty-btn:hover { border-color: var(--emerald); color: var(--emerald); }
.qty-val { font-family: var(--mono); font-size: 14px; font-weight: 600; min-width: 20px; text-align:
center; }
.rm-btn {
width: 28px; height: 28px; border-radius: var(--r8);
background: var(--rose-dim); border: 1px solid rgba(255,77,109,.2);
cursor: pointer; font-size: 13px; color: var(--rose);
display: flex; align-items: center; justify-content: center; transition: all .15s;
}
.rm-btn:hover { background: var(--rose); color: white; }

.cart-summary { background: var(--navy2); border: 1px solid var(--border); border-radius: var(--r16);


padding: 18px; position: sticky; top: 76px; }
.sum-row { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 8px; }
.sum-row .label { color: var(--text2); }
.sum-row .val { font-family: var(--mono); font-weight: 600; }
.sum-divider { height: 1px; background: var(--border); margin: 12px 0; }
.sum-total .label { font-size: 15px; font-weight: 700; }
.sum-total .val { font-size: 17px; font-weight: 700; font-family: var(--mono); color: var(--
emerald); }
.checkout-btn {
width: 100%; padding: 12px; background: var(--emerald); color: var(--navy);
border: none; border-radius: var(--r12); font-size: 14px; font-weight: 700;
cursor: pointer; font-family: var(--font); margin-top: 14px; transition: all .2s;
}
.checkout-btn:hover { box-shadow: 0 6px 20px rgba(16,217,160,.4); transform: translateY(-1px); }
.promo-row { display: flex; gap: 7px; margin-top: 10px; }
.promo-input {
flex: 1; background: var(--navy3); border: 1px solid var(--border);
border-radius: var(--r8); padding: 8px 11px; font-size: 12px; color: var(--text1);
font-family: var(--font); outline: none; transition: border .2s;
}
.promo-input:focus { border-color: var(--emerald); }
.promo-btn {

Page 51 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
padding: 8px 12px; border-radius: var(--r8); font-size: 11px; font-weight: 700;
background: var(--navy3); color: var(--text2); border: 1px solid var(--border);
cursor: pointer; transition: all .15s;
}
.promo-btn:hover { color: var(--emerald); border-color: rgba(16,217,160,.4); }

/* Compare bar */
.compare-bar {
position: fixed; bottom: 0; left: var(--sw); right: 0;
background: var(--navy2); border-top: 1px solid var(--border);
padding: 12px 24px; display: none; align-items: center; gap: 16px;
z-index: 90;
}
.[Link] { display: flex; }
.compare-items { display: flex; gap: 10px; flex: 1; }
.compare-item-slot {
width: 100px; height: 52px; border: 1px dashed var(--border);
border-radius: var(--r8); display: flex; align-items: center; justify-content: center;
font-size: 11px; color: var(--text3); position: relative;
}
.[Link] { border-style: solid; border-color: var(--emerald); background: var(--
emerald-dim); }
.compare-item-slot .rm { position: absolute; top: -6px; right: -6px; width: 16px; height: 16px;
border-radius: 50%; background: var(--rose); color: white; font-size: 10px; display: flex; align-
items: center; justify-content: center; cursor: pointer; }

/* ════════════════════════════════════════════
ORDERS PAGE
════════════════════════════════════════════ */
.order-stats-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; margin-bottom:
20px; }
.order-stat-card {
background: var(--navy2); border: 1px solid var(--border); border-radius: var(--r12);
padding: 14px 16px; display: flex; align-items: center; gap: 12px;
}
.osi { width: 36px; height: 36px; border-radius: var(--r8); display: flex; align-items: center;
justify-content: center; font-size: 17px; }
.osv { font-size: 20px; font-weight: 700; font-family: var(--mono); }
.osl { font-size: 11px; color: var(--text2); }

.order-card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); margin-bottom: 12px; overflow: hidden;
}
.order-card-header {
padding: 14px 18px; display: flex; align-items: center; gap: 12px;
border-bottom: 1px solid rgba(48,54,61,.5);
}
.order-shop-icon {
width: 38px; height: 38px; border-radius: var(--r8);
background: var(--navy3); border: 1px solid var(--border);
display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0;
}
.order-id { font-family: var(--mono); font-size: 12px; color: var(--indigo-l); }
.order-meta { font-size: 11px; color: var(--text2); }
.order-amount { font-family: var(--mono); font-size: 16px; font-weight: 700; color: var(--emerald);
margin-left: auto; }

.order-items-row { padding: 10px 18px; display: flex; gap: 8px; flex-wrap: wrap; }
.order-item-chip {
display: flex; align-items: center; gap: 5px; padding: 4px 10px;
background: var(--navy3); border: 1px solid var(--border);
border-radius: var(--r8); font-size: 11px; color: var(--text2);
}

.order-timeline { padding: 12px 18px; border-top: 1px solid rgba(48,54,61,.5); display: flex; align-
items: center; }
.timeline-step { display: flex; flex-direction: column; align-items: center; flex: 1; position:
relative; }
.timeline-step:not(:last-child)::after {
content: ''; position: absolute; top: 11px; left: 50%;

Page 52 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
width: 100%; height: 2px; background: var(--border); z-index: 0;
}
.[Link]:not(:last-child)::after { background: var(--emerald); }
.timeline-dot {
width: 22px; height: 22px; border-radius: 50%; z-index: 1;
display: flex; align-items: center; justify-content: center; font-size: 10px;
background: var(--navy4); border: 2px solid var(--border); margin-bottom: 5px;
}
.[Link] .timeline-dot { background: var(--emerald); border-color: var(--emerald); color:
var(--navy); }
.[Link] .timeline-dot { background: var(--amber); border-color: var(--amber); color:
var(--navy); animation: pulse 1.5s ease-in-out infinite; }
@keyframes pulse { 0%,100%{box-shadow:0 0 0 0 rgba(245,166,35,.4)} 50%{box-shadow:0 0 0 6px
rgba(245,166,35,.0)} }
.timeline-label { font-size: 10px; color: var(--text2); text-align: center; }
.[Link] .timeline-label { color: var(--emerald); }
.[Link] .timeline-label { color: var(--amber); }

.order-actions { padding: 10px 18px; display: flex; gap: 8px; border-top: 1px solid rgba(48,54,61,.5);
}

/* ════════════════════════════════════════════
WISHLIST PAGE
════════════════════════════════════════════ */
.wishlist-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 14px; }

/* ════════════════════════════════════════════
NOTIFICATIONS PANEL
════════════════════════════════════════════ */
.notif-drawer {
position: fixed; top: 0; right: -380px; width: 380px; bottom: 0;
background: var(--navy2); border-left: 1px solid var(--border);
z-index: 300; transition: right .3s ease; display: flex; flex-direction: column;
}
.[Link] { right: 0; }
.notif-drawer-header {
padding: 20px; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
}
.notif-drawer-body { flex: 1; overflow-y: auto; }
.notif-item {
padding: 14px 20px; border-bottom: 1px solid rgba(48,54,61,.5);
display: flex; gap: 12px; align-items: flex-start; transition: background .15s; cursor: pointer;
}
.notif-item:hover { background: rgba(255,255,255,.02); }
.[Link] { background: rgba(16,217,160,.04); }
.notif-icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; }
.notif-title { font-size: 13px; font-weight: 600; margin-bottom: 3px; }
.notif-body { font-size: 12px; color: var(--text2); line-height: 1.5; }
.notif-time { font-size: 10px; color: var(--text3); margin-top: 4px; }
.notif-unread-dot { width: 7px; height: 7px; background: var(--emerald); border-radius: 50%; margin-
top: 6px; flex-shrink: 0; }

/* ════════════════════════════════════════════
PROFILE PAGE
════════════════════════════════════════════ */
.profile-layout { display: grid; grid-template-columns: 280px 1fr; gap: 20px; }
.profile-sidebar-card {
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r16); padding: 24px; text-align: center;
position: sticky; top: 76px;
}
.profile-avatar-lg {
width: 72px; height: 72px; border-radius: 50%;
background: linear-gradient(135deg,var(--emerald),var(--sky));
display: flex; align-items: center; justify-content: center;
font-size: 28px; font-weight: 700; color: var(--navy);
margin: 0 auto 14px;
}
.profile-name { font-size: 18px; font-weight: 700; margin-bottom: 4px; }
.profile-email { font-size: 12px; color: var(--text2); margin-bottom: 16px; }

Page 53 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.profile-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
.profile-stat { background: var(--navy3); border: 1px solid var(--border); border-radius: var(--r8);
padding: 10px; }
.profile-stat-val { font-size: 18px; font-weight: 700; font-family: var(--mono); }
.profile-stat-lbl { font-size: 10px; color: var(--text2); }
.profile-menu-item {
display: flex; align-items: center; gap: 10px; padding: 10px 12px;
border-radius: var(--r8); cursor: pointer; transition: background .15s;
font-size: 13px; color: var(--text2); font-weight: 500;
}
.profile-menu-item:hover, .[Link] { background: var(--navy3); color: var(--text1); }

.profile-main { display: flex; flex-direction: column; gap: 16px; }


.profile-section { background: var(--navy2); border: 1px solid var(--border); border-radius: var(--
r16); overflow: hidden; }
.profile-section-header { padding: 16px 20px; border-bottom: 1px solid var(--border); font-size: 14px;
font-weight: 700; }
.profile-section-body { padding: 20px; }

.form-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }


.form-group2 { display: flex; flex-direction: column; gap: 5px; }
.[Link] { grid-column: 1/-1; }
.form-label2 { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase;
letter-spacing: .06em; }
.form-input2 {
background: var(--navy3); border: 1px solid var(--border); border-radius: var(--r8);
padding: 10px 13px; font-size: 13px; color: var(--text1); font-family: var(--font);
outline: none; transition: border .2s, box-shadow .2s; width: 100%;
}
.form-input2:focus { border-color: var(--emerald); box-shadow: 0 0 0 3px rgba(16,217,160,.1); }
.form-input2::placeholder { color: var(--text3); }

/* Address card */
.address-card {
background: var(--navy3); border: 1px solid var(--border);
border-radius: var(--r12); padding: 14px; position: relative;
}
.[Link] { border-color: rgba(16,217,160,.4); background: var(--emerald-dim); }
.address-default-badge { font-size: 10px; font-weight: 700; color: var(--emerald); margin-bottom: 6px;
}

/* ════════════════════════════════════════════
DATA TABLE
════════════════════════════════════════════ */
.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.data-table th {
padding: 10px 16px; font-size: 11px; font-weight: 600; color: var(--text2);
text-transform: uppercase; letter-spacing: .06em;
background: var(--navy3); border-bottom: 1px solid var(--border); text-align: left;
}
.data-table td { padding: 14px 16px; border-bottom: 1px solid rgba(48,54,61,.5); }
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: rgba(255,255,255,.015); }
.td-muted { color: var(--text2); font-size: 12px; }

/* ════════════════════════════════════════════
RECENTLY VIEWED / HORIZONTAL SCROLL
════════════════════════════════════════════ */
.h-scroll { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 6px; }
.h-scroll::-webkit-scrollbar { height: 4px; }
.h-scroll-item { flex-shrink: 0; }

/* ════════════════════════════════════════════
TOAST
════════════════════════════════════════════ */
.toast-container {
position: fixed; bottom: 24px; right: 24px;
display: flex; flex-direction: column; gap: 8px; z-index: 999; pointer-events: none;
}
.toast {
display: flex; align-items: center; gap: 10px;

Page 54 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
background: var(--navy2); border: 1px solid var(--border);
border-radius: var(--r12); padding: 12px 16px; box-shadow: var(--shadow);
min-width: 280px; pointer-events: auto;
animation: toastIn .3s cubic-bezier(.34,1.56,.64,1); transition: opacity .3s, transform .3s;
}
@keyframes toastIn { from{opacity:0;transform:translateX(40px)}
to{opacity:1;transform:translateX(0)} }
.[Link] { opacity: 0; transform: translateX(40px); }
.toast-msg { font-size: 13px; color: var(--text1); font-weight: 500; }

/* ════════════════════════════════════════════
MOBILE BOTTOM NAV
════════════════════════════════════════════ */
.mobile-bottom-nav {
display: none; position: fixed; bottom: 0; left: 0; right: 0;
background: var(--navy2); border-top: 1px solid var(--border);
z-index: 150; padding: 8px 0 10px;
}
.mbn-items { display: flex; justify-content: space-around; }
.mbn-item {
display: flex; flex-direction: column; align-items: center; gap: 3px;
padding: 5px 16px; cursor: pointer; transition: all .15s; position: relative;
}
.mbn-icon { font-size: 20px; }
.mbn-label { font-size: 10px; color: var(--text2); font-weight: 500; }
.[Link] .mbn-label { color: var(--emerald); }
.mbn-badge {
position: absolute; top: 2px; right: 8px; background: var(--rose);
color: white; font-size: 9px; font-weight: 700; padding: 1px 5px; border-radius: 99px;
}

/* ════════════════════════════════════════════
RESPONSIVE BREAKPOINTS
════════════════════════════════════════════ */

/* Large tablets / small laptop */


@media (max-width: 1200px) {
.shops-grid { grid-template-columns: repeat(2,1fr); }
.products-grid { grid-template-columns: repeat(3,1fr); }
.map-layout { grid-template-columns: 1fr; }
.shop-list-panel { height: auto; max-height: 260px; flex-direction: row; overflow-x: auto;
overflow-y: hidden; }
.shop-list-card { min-width: 240px; }
}

/* Tablets */
@media (max-width: 900px) {
:root { --sw: 0px; }
.sidebar { transform: translateX(-240px); --sw: 240px; }
.[Link]-open { transform: translateX(0); }
.main { margin-left: 0; }
.hamburger { display: flex; }
.search-topbar { width: 180px; }
.hero-stats { display: none; }
.products-grid { grid-template-columns: repeat(2,1fr); }
.cart-layout { grid-template-columns: 1fr; }
.cart-summary { position: static; }
.profile-layout { grid-template-columns: 1fr; }
.profile-sidebar-card { position: static; }
.order-stats-grid { grid-template-columns: repeat(2,1fr); }
.form-grid2 { grid-template-columns: 1fr; }
.shop-modal-hero { grid-template-columns: 1fr; }
.modal-products-grid { grid-template-columns: repeat(2,1fr); }
.notif-drawer { width: 100%; right: -100%; }
}

/* Mobile */
@media (max-width: 600px) {
.page-content { padding: 16px; }
.shops-grid { grid-template-columns: 1fr; }
.products-grid { grid-template-columns: repeat(2,1fr); }

Page 55 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
.wishlist-grid { grid-template-columns: repeat(2,1fr); }
.hero-banner { padding: 20px; }
.hero-title { font-size: 20px; }
.topbar { padding: 0 14px; }
.topbar-sub, .search-topbar { display: none; }
.mobile-bottom-nav { display: block; }
.main { padding-bottom: 60px; }
.compare-bar { left: 0; }
.order-stats-grid { grid-template-columns: repeat(2,1fr); }
.two-col { grid-template-columns: 1fr !important; }
.map-container { height: 320px; }
.hero-stats { display: none; }
}

Page 56 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

9. [Link]
📄 File: [Link]
Lines of code: 444

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="RetailPro — Login and Register for Shopkeepers and Customers">
<title>RetailPro — Sign In / Register</title>

<!-- 1. Design tokens (CSS variables + Google Fonts import) -->


<link rel="stylesheet" href="css/[Link]">
<!-- 2. Auth page styles -->
<link rel="stylesheet" href="css/[Link]">
</head>
<body>

<!-- ════════════════════════════════════════════════════════
LEFT PANEL — Branding & Feature Highlights
════════════════════════════════════════════════════════ -->

<div class="left-panel">

<!-- Ambient glow blobs (purely decorative) -->


<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>

<div class="left-content">

<!-- Brand -->


<div class="brand-row">
<div class="brand-logo">🛍</div>
<div>
<div class="brand-name">RetailPro</div>
<div class="brand-tag">Online Retail Inventory System</div>
</div>
</div>

<!-- Hero -->


<div class="hero-eyebrow">✦ &nbsp;Trusted by 500+ shops in Tamil Nadu</div>
<div class="hero-title">
One platform.<br>
<span class="grad-indigo">Shopkeepers</span> &amp;<br>
<span class="grad-emerald">Customers.</span>
</div>
<p class="hero-sub">
Manage your inventory, track sales, and connect with customers —
all in one place. Whether you run a shop or shop from one,
RetailPro makes it effortless.
</p>

<!-- Role preview cards -->


<div class="role-preview">
<div class="rp-card shopkeeper" id="rp-sk">
<div class="rp-icon">🏪</div>
<div class="rp-label">Shopkeeper</div>
<div class="rp-desc">Manage products, orders &amp; revenue</div>
<div class="rp-badge">Admin Dashboard</div>
</div>
<div class="rp-card customer" id="rp-cu">

Page 57 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="rp-icon">🛒</div>
<div class="rp-label">Customer</div>
<div class="rp-desc">Find shops, buy products nearby</div>
<div class="rp-badge">Customer Portal</div>
</div>
</div>

<!-- Feature cards -->


<div class="features">
<div class="feat-card">
<div class="feat-icon" style="background:var(--indigo-dim)">📦</div>
<div>
<div class="feat-title">Real-time Inventory</div>
<div class="feat-sub">Live stock tracking across all your products</div>
</div>
</div>
<div class="feat-card">
<div class="feat-icon" style="background:var(--emerald-dim)">📊</div>
<div>
<div class="feat-title">Sales Analytics</div>
<div class="feat-sub">Revenue, orders &amp; profit insights at a glance</div>
</div>
</div>
<div class="feat-card">
<div class="feat-icon" style="background:var(--sky-dim)">📍</div>
<div>
<div class="feat-title">Nearby Shop Discovery</div>
<div class="feat-sub">Customers find your store within 5 km radius</div>
</div>
</div>
</div>

<!-- Stats -->


<div class="stats-row">
<div class="stat-item">
<div class="stat-val">500+</div>
<div class="stat-lbl">Shops Registered</div>
</div>
<div class="stat-item">
<div class="stat-val">12k+</div>
<div class="stat-lbl">Products Listed</div>
</div>
<div class="stat-item">
<div class="stat-val">48k+</div>
<div class="stat-lbl">Orders Placed</div>
</div>
<div class="stat-item">
<div class="stat-val">4.9⭐</div>
<div class="stat-lbl">Avg Rating</div>
</div>
</div>

<!-- Trust bar -->


<div class="left-footer" style="margin-top:32px">
<div class="trust-row">
<div class="trust-item">🔒 SSL Secured</div>
<div class="trust-item">⚡ 99.9% Uptime</div>
<div class="trust-item">🇮🇳 Made for India</div>
<div class="trust-item">✅ GDPR Compliant</div>
</div>
</div>

</div><!-- /left-content -->


</div><!-- /left-panel -->

<!-- ════════════════════════════════════════════════════════
RIGHT PANEL — Auth Forms
════════════════════════════════════════════════════════ -->
<div class="right-panel">
<div class="auth-card" id="auth-card">

Page 58 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

<!-- Tab switcher -->


<div class="auth-tabs">
<button class="auth-tab active-login"
id="tab-login"
onclick="showTab('login')"
aria-label="Switch to Sign In">
Sign In
</button>
<button class="auth-tab"
id="tab-register"
onclick="showTab('register')"
aria-label="Switch to Create Account">
Create Account
</button>
</div>

<!-- ══════════════════════════════════
LOGIN FORM
══════════════════════════════════ -->
<div class="form-panel active" id="panel-login" role="region" aria-label="Login">

<h2 class="card-headline" id="login-headline">Welcome back 👋</h2>


<p class="card-sub" id="login-sub">
Sign in as <strong style="color:var(--indigo-l)">Shopkeeper</strong>
to access your dashboard.
</p>

<!-- Role toggle -->


<div class="role-toggle" role="group" aria-label="Select your role">
<button class="role-btn active-sk"
id="login-sk-btn"
onclick="setLoginRole('sk')"
aria-pressed="true">
🏪 Shopkeeper
</button>
<button class="role-btn"
id="login-cu-btn"
onclick="setLoginRole('cu')"
aria-pressed="false">
🛒 Customer
</button>
</div>

<!-- Email -->


<div class="form-group">
<label class="form-label" for="login-email">Email Address</label>
<div class="input-wrap">
<input class="form-input"
id="login-email"
type="email"
placeholder="you@[Link]"
autocomplete="email"
required>
<span class="input-icon" aria-hidden="true">✉</span>
</div>
<span class="error-msg" id="login-email-err" role="alert">
Please enter a valid email address.
</span>
</div>

<!-- Password -->


<div class="form-group">
<label class="form-label" for="login-pass">Password</label>
<div class="input-wrap">
<input class="form-input"
id="login-pass"
type="password"
placeholder="Your password"
autocomplete="current-password"

Page 59 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
required>
<span class="input-icon"
onclick="togglePass('login-pass', this)"
title="Show / hide password"
role="button"
aria-label="Toggle password visibility">👁</span>
</div>
<span class="error-msg" id="login-pass-err" role="alert">
Password must be at least 6 characters.
</span>
</div>

<!-- Forgot password -->


<div class="forgot-row">
<button class="forgot-link"
type="button"
onclick="showToast('Password reset link sent to your email!','info')">
Forgot password?
</button>
</div>

<!-- Submit -->


<button class="submit-btn sk"
id="login-btn"
type="button"
onclick="handleLogin()">
<span id="login-btn-text">Sign In as Shopkeeper →</span>
</button>

<!-- Switch to Register -->


<p class="switch-link" id="login-switch">
Don't have an account?
<a onclick="showTab('register')" role="button">Create one free</a>
</p>

<!-- Terms note -->


<p class="terms-note">
By signing in you agree to our
<a href="#" onclick="return false">Terms of Service</a> and
<a href="#" onclick="return false">Privacy Policy</a>
</p>

</div><!-- /panel-login -->

<!-- ══════════════════════════════════
REGISTER FORM
══════════════════════════════════ -->
<div class="form-panel" id="panel-register" role="region" aria-label="Register">

<h2 class="card-headline" id="reg-headline">Open your store 🏪</h2>


<p class="card-sub" id="reg-sub">
Join as <strong style="color:var(--indigo-l)">Shopkeeper</strong>
— get your store online in minutes.
</p>

<!-- Role toggle -->


<div class="role-toggle" role="group" aria-label="Select your role">
<button class="role-btn active-sk"
id="reg-sk-btn"
onclick="setRegRole('sk')"
aria-pressed="true">
🏪 Shopkeeper
</button>
<button class="role-btn"
id="reg-cu-btn"
onclick="setRegRole('cu')"
aria-pressed="false">
🛒 Customer
</button>
</div>

Page 60 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

<!-- Name + Phone (two-column) -->


<div class="two-col">
<div class="form-group">
<label class="form-label" for="reg-name">Full Name</label>
<div class="input-wrap">
<input class="form-input"
id="reg-name"
type="text"
placeholder="Raj Kumar"
autocomplete="name"
required>
</div>
<span class="error-msg" id="reg-name-err" role="alert">Name is required.</span>
</div>
<div class="form-group">
<label class="form-label" for="reg-phone">Phone</label>
<div class="input-wrap">
<input class="form-input"
id="reg-phone"
type="tel"
placeholder="+91 98765 43210"
autocomplete="tel"
required>
</div>
<span class="error-msg" id="reg-phone-err" role="alert">Enter a valid phone
number.</span>
</div>
</div>

<!-- Email -->


<div class="form-group">
<label class="form-label" for="reg-email">Email Address</label>
<div class="input-wrap">
<input class="form-input"
id="reg-email"
type="email"
placeholder="you@[Link]"
autocomplete="email"
required>
<span class="input-icon" aria-hidden="true">✉</span>
</div>
<span class="error-msg" id="reg-email-err" role="alert">Please enter a valid
email.</span>
</div>

<!-- Store Name — Shopkeeper only -->


<div class="form-group" id="store-name-group">
<label class="form-label" for="reg-store">Store Name</label>
<div class="input-wrap">
<input class="form-input"
id="reg-store"
type="text"
placeholder="e.g. Raj General Store">
<span class="input-icon" aria-hidden="true">🏪</span>
</div>
<span class="error-msg" id="reg-store-err" role="alert">
Store name is required for shopkeepers.
</span>
</div>

<!-- Delivery Address — Customer only -->


<div class="form-group" id="address-group" style="display:none">
<label class="form-label" for="reg-address">Delivery Address</label>
<div class="input-wrap">
<input class="form-input"
id="reg-address"
type="text"
placeholder="Your street address">
<span class="input-icon" aria-hidden="true">📍</span>
</div>

Page 61 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>

<!-- Password + Confirm (two-column) -->


<div class="two-col">
<!-- Password -->
<div class="form-group">
<label class="form-label" for="reg-pass">Password</label>
<div class="input-wrap">
<input class="form-input"
id="reg-pass"
type="password"
placeholder="Min 8 characters"
autocomplete="new-password"
oninput="checkStrength([Link])"
required>
<span class="input-icon"
onclick="togglePass('reg-pass', this)"
role="button"
aria-label="Toggle password visibility">👁</span>
</div>
<!-- Strength meter -->
<div class="strength-bar" aria-label="Password strength indicator">
<div class="strength-seg" id="seg1"></div>
<div class="strength-seg" id="seg2"></div>
<div class="strength-seg" id="seg3"></div>
<div class="strength-seg" id="seg4"></div>
</div>
<div class="strength-label" id="strength-lbl" aria-live="polite"></div>
<span class="error-msg" id="reg-pass-err" role="alert">
Password must be at least 8 characters.
</span>
</div>

<!-- Confirm Password -->


<div class="form-group">
<label class="form-label" for="reg-confirm">Confirm Password</label>
<div class="input-wrap">
<input class="form-input"
id="reg-confirm"
type="password"
placeholder="Repeat password"
autocomplete="new-password"
required>
<span class="input-icon"
onclick="togglePass('reg-confirm', this)"
role="button"
aria-label="Toggle confirm password visibility">👁</span>
</div>
<span class="error-msg" id="reg-confirm-err" role="alert">Passwords do not
match.</span>
<span class="success-msg" id="reg-confirm-ok" role="status">✓ Passwords match</span>
</div>
</div>

<!-- Agree checkbox -->


<div class="agree-row">
<div class="agree-check checked-sk"
id="agree-box"
onclick="toggleAgree()"
role="checkbox"
aria-checked="true"
tabindex="0"
onkeydown="if([Link]==='Enter'||[Link]===' ')toggleAgree()">

</div>
<p class="agree-text">
I agree to the
<a href="#" onclick="return false">Terms of Service</a> and
<a href="#" onclick="return false">Privacy Policy</a>.
I confirm all information is accurate.
</p>

Page 62 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>

<!-- Submit -->


<button class="submit-btn sk"
id="reg-btn"
type="button"
onclick="handleRegister()">
<span id="reg-btn-text">Create Shopkeeper Account →</span>
</button>

<!-- Switch to Login -->


<p class="switch-link" id="reg-switch">
Already have an account?
<a onclick="showTab('login')" role="button">Sign in here</a>
</p>

</div><!-- /panel-register -->

</div><!-- /auth-card -->


</div><!-- /right-panel -->

<!-- Toast notification container -->


<div class="toast-container" id="toasts" aria-live="polite" aria-atomic="true"></div>

<!-- JavaScript (loaded last — DOM is ready) -->


<script src="js/[Link]"></script>

</body>
</html>

Page 63 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

10. pages / [Link]


📄 File: pages/[Link]
Lines of code: 1182

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="RetailPro — Shopkeeper Dashboard"/>
<title>RetailPro — Shopkeeper Dashboard</title>

<!-- 1. Design tokens (CSS variables + Sora font) -->


<link rel="stylesheet" href="../css/[Link]"/>
<!-- 2. Dashboard styles -->
<link rel="stylesheet" href="../css/[Link]"/>
<!-- 3. [Link] -->
<script src="[Link]
</head>
<body>

<!-- ════════════════════════════════════════════════════════
APP SHELL
════════════════════════════════════════════════════════ -->
<div id="app">

<!-- ── MOBILE SIDEBAR OVERLAY ── -->


<div id="sb-overlay" onclick="closeMobSidebar()"></div>

<!-- ════════════════════════════════════════════════════
SIDEBAR
════════════════════════════════════════════════════ -->
<aside id="sidebar" role="navigation" aria-label="Main navigation">

<!-- Brand -->


<div class="sb-brand">
<div class="sb-logo-icon" aria-hidden="true">🛍</div>
<div class="sb-brand-text">
<div class="sb-brand-name">RetailPro</div>
<div class="sb-brand-sub">Shopkeeper</div>
</div>
</div>

<!-- Navigation -->


<nav class="sb-nav" id="sb-nav">

<div class="sb-section-label">Main</div>

<button class="sb-item active" data-page="dashboard" onclick="navigate('dashboard')" aria-


label="Dashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
<span class="sb-item-label">Dashboard</span>
</button>

<button class="sb-item" data-page="products" onclick="navigate('products')" aria-


label="Products">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010
2.82z"/>
<circle cx="7" cy="7" r="1" fill="currentColor"/>

Page 64 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</svg>
<span class="sb-item-label">Products</span>
</button>

<button class="sb-item" data-page="inventory" onclick="navigate('inventory')" aria-


label="Inventory">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001
1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12"
y2="12"/>
</svg>
<span class="sb-item-label">Inventory</span>
<span class="sb-item-badge" id="inv-badge" style="display:none">0</span>
</button>

<div class="sb-divider"></div>
<div class="sb-section-label">Finance</div>

<button class="sb-item" data-page="orders" onclick="navigate('orders')" aria-label="Sales


and Orders">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<line x1="12" y1="1" x2="12" y2="23"/>
<path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
</svg>
<span class="sb-item-label">Sales &amp; Orders</span>
</button>

<button class="sb-item" data-page="invoices" onclick="navigate('invoices')" aria-


label="Invoices">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
</svg>
<span class="sb-item-label">Invoices</span>
</button>

<button class="sb-item" data-page="tally" onclick="navigate('tally')" aria-label="Tally">


<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
<span class="sb-item-label">Tally / Accounts</span>
</button>

<div class="sb-divider"></div>
<div class="sb-section-label">Insights</div>

<button class="sb-item" data-page="analytics" onclick="navigate('analytics')" aria-


label="Analytics">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
<span class="sb-item-label">Analytics</span>
</button>

<div class="sb-divider"></div>
<div class="sb-section-label">Shop</div>

<button class="sb-item" data-page="settings" onclick="navigate('settings')" aria-


label="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>

Page 65 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<path d="M19.07 4.93a10 10 0 010 14.14M4.93 4.93a10 10 0 000 14.14"/>
</svg>
<span class="sb-item-label">Settings</span>
</button>

<button class="sb-item" data-page="location" onclick="navigate('location')" aria-label="Shop


Location">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
<span class="sb-item-label">Shop Location</span>
</button>

<div class="sb-divider"></div>

<button class="sb-item" onclick="logout()" style="color:var(--rose)" aria-label="Sign out">


<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
<span class="sb-item-label">Sign Out</span>
</button>

</nav>

<!-- User footer -->


<div class="sb-user">
<div class="sb-avatar" id="sb-avatar">R</div>
<div class="sb-user-info">
<div class="sb-user-name" id="sb-user-name">Shop Owner</div>
<div class="sb-user-role">Shopkeeper · Pro</div>
</div>
</div>
</aside><!-- /sidebar -->

<!-- ════════════════════════════════════════════════════
MAIN AREA
════════════════════════════════════════════════════ -->
<div id="main">

<!-- ── TOP NAVBAR ── -->


<header id="navbar" role="banner">

<!-- Mobile menu toggle (hidden on desktop) -->


<button class="nb-toggle" id="mob-menu-btn" onclick="openMobSidebar()"
style="display:none" aria-label="Open navigation menu">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>

<!-- Desktop collapse toggle -->


<button class="nb-toggle" onclick="toggleSidebar()" aria-label="Toggle sidebar" id="desktop-
toggle">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="15" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>

<!-- Search -->

Page 66 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="nb-search-wrap" role="search">
<svg class="nb-search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input class="nb-search" id="nb-search" type="search"
placeholder="Search products, orders, customers…"
oninput="handleGlobalSearch([Link])"
aria-label="Global search"/>
</div>

<!-- Right section -->


<div class="nb-right">
<!-- Shop name (desktop) -->
<span class="nb-date" id="nb-shop-name">Rajan General Stores</span>
<div class="nb-vdiv"></div>
<span class="nb-date">Sat, 28 Mar 2026</span>
<div class="nb-vdiv"></div>

<!-- Notifications -->


<div style="position:relative">
<button class="nb-ic-btn" id="notif-btn" onclick="toggleNotifPanel()" aria-
label="Notifications" aria-haspopup="true">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.8" stroke-linecap="round">
<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 01-3.46 0"/>
</svg>
<span class="notif-dot" id="notif-dot-indicator" aria-hidden="true"></span>
</button>

<!-- Notifications panel -->


<div id="notif-panel" role="dialog" aria-label="Notifications">
<div class="notif-hd">
<span class="notif-hd-title">Notifications</span>
<button class="notif-mark-all" onclick="markAllRead()">Mark all read</button>
</div>

<div class="notif-item unread" onclick="navigate('inventory')">


<div class="notif-ic" style="background:var(--amber-dim)">⚠️</div>
<div class="notif-body">
<div class="notif-text">Sunflower Oil 1L — only 9 units left</div>
<div class="notif-time">10 min ago</div>
</div>
<div class="notif-unread-dot"></div>
</div>

<div class="notif-item unread" onclick="navigate('inventory')">


<div class="notif-ic" style="background:var(--amber-dim)">⚠️</div>
<div class="notif-body">
<div class="notif-text">Salt 1kg — only 4 units left</div>
<div class="notif-time">1 hr ago</div>
</div>
<div class="notif-unread-dot"></div>
</div>

<div class="notif-item unread" onclick="navigate('orders')">


<div class="notif-ic" style="background:var(--emerald-dim)">✅</div>
<div class="notif-body">
<div class="notif-text">Invoice #1042 generated successfully</div>
<div class="notif-time">30 min ago</div>
</div>
<div class="notif-unread-dot"></div>
</div>

<div class="notif-item" onclick="navigate('analytics')">


<div class="notif-ic" style="background:var(--indigo-dim)">📊</div>
<div class="notif-body">
<div class="notif-text">Weekly revenue report is ready to view</div>
<div class="notif-time">2 hrs ago</div>
</div>

Page 67 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>

<div class="notif-item">
<div class="notif-ic" style="background:var(--sky-dim)">💡</div>
<div class="notif-body">
<div class="notif-text">Tip: Basmati Rice sells 3× more on weekends!</div>
<div class="notif-time">This morning</div>
</div>
</div>
</div>
</div><!-- /notif wrap -->

<!-- Quick add -->


<button class="btn-quick-add" onclick="openProductDrawer(null)" aria-label="Add new
product">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
<span>Add Product</span>
</button>

</div><!-- /nb-right -->


</header><!-- /navbar -->

<!-- ════════════════════════════════════════════════════
CONTENT
════════════════════════════════════════════════════ -->
<div id="content" role="main">

<!-- ══════════════════════════════════════════════════
DASHBOARD
══════════════════════════════════════════════════ -->
<section class="page-section active" id="page-dashboard" aria-label="Dashboard">

<div class="page-hd">
<div class="page-title">Good Morning, Rajan! 👋</div>
<div class="page-sub">Here's your store performance — <strong style="color:var(--
indigo-l)" id="dash-shop-name">Rajan General Stores</strong></div>
</div>

<!-- KPI Cards -->


<div class="kpi-grid">
<div class="kpi-card indigo" onclick="navigate('orders')" role="button" aria-
label="Today's revenue">
<div class="kpi-top">
<div class="kpi-label">Today's Revenue</div>
<div class="kpi-icon-wrap indigo">💰</div>
</div>
<div class="kpi-value" id="kpi-revenue">₹0</div>
<div class="kpi-footer">
<span class="kpi-change up">↑ 12%</span>
<span class="kpi-note">vs yesterday</span>
</div>
</div>

<div class="kpi-card emerald" onclick="navigate('inventory')" role="button" aria-


label="Total stock">
<div class="kpi-top">
<div class="kpi-label">Total Stock</div>
<div class="kpi-icon-wrap emerald">📦</div>
</div>
<div class="kpi-value" id="kpi-stock">0</div>
<div class="kpi-footer">
<span class="kpi-change up">↑ Stocked</span>
<span class="kpi-note">items in store</span>
</div>
</div>

Page 68 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="kpi-card sky" onclick="navigate('orders')" role="button" aria-
label="Orders today">
<div class="kpi-top">
<div class="kpi-label">Orders Today</div>
<div class="kpi-icon-wrap sky">🛒</div>
</div>
<div class="kpi-value" id="kpi-orders">0</div>
<div class="kpi-footer">
<span class="kpi-change up">↑ 5 more</span>
<span class="kpi-note">vs yesterday</span>
</div>
</div>

<div class="kpi-card amber" onclick="navigate('inventory')" role="button" aria-


label="Low stock alerts">
<div class="kpi-top">
<div class="kpi-label">Low Stock Alert</div>
<div class="kpi-icon-wrap amber">⚠️</div>
</div>
<div class="kpi-value" id="kpi-lowstock">0</div>
<div class="kpi-footer">
<span class="kpi-change warn">Action needed</span>
</div>
</div>
</div>

<!-- Revenue chart + Best sellers -->


<div class="g-main-wide mb16">

<div class="card">
<div class="section-row">
<div>
<div class="card-title">Revenue Overview</div>
<div class="card-sub" id="chart-subtitle">Loading…</div>
</div>
<div class="time-tabs">
<button class="time-tab active"
onclick="switchChart('weekly',this)">Weekly</button>
<button class="time-tab"
onclick="switchChart('monthly',this)">Monthly</button>
</div>
</div>
<div class="chart-wrap"><canvas id="revenueChart"></canvas></div>
</div>

<div class="card">
<div class="card-title mb8">🏆 Best Sellers</div>
<div class="card-sub mb16">Top performers this month</div>
<div id="best-sellers-list"></div>
</div>
</div>

<!-- Recent transactions -->


<div class="card">
<div class="section-row">
<div class="card-title">Recent Transactions</div>
<button class="btn-ghost btn-sm" onclick="navigate('orders')">View All →</button>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>Invoice</th><th>Customer</th><th>Date</th>
<th>Items</th><th>Amount</th><th>Status</th><th></th>
</tr>
</thead>
<tbody id="dash-txn-tbody"></tbody>
</table>
</div>
</div>

Page 69 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</section><!-- /dashboard -->

<!-- ══════════════════════════════════════════════════
PRODUCTS
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-products" aria-label="Products">

<div class="page-hd">
<div class="section-row">
<div>
<div class="page-title">Products</div>
<div class="page-sub" id="prod-count-sub">Loading…</div>
</div>
<button class="btn-primary" onclick="openProductDrawer(null)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Add Product
</button>
</div>
</div>

<!-- Filter tabs -->


<div class="filter-tabs" role="tablist" aria-label="Filter products">
<button class="filter-tab active" data-filter="all"
onclick="filterProducts('all',this)" role="tab">All <span class="cnt"
id="cnt-all">0</span></button>
<button class="filter-tab" data-filter="active"
onclick="filterProducts('active',this)" role="tab">Active <span class="cnt" id="cnt-
active">0</span></button>
<button class="filter-tab" data-filter="low"
onclick="filterProducts('low',this)" role="tab">Low Stock <span class="cnt"
id="cnt-low">0</span></button>
<button class="filter-tab" data-filter="out"
onclick="filterProducts('out',this)" role="tab">Out of Stock <span class="cnt" id="cnt-
out">0</span></button>
</div>

<div class="card">
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th></th><th>Product</th><th>Category</th>
<th>Price</th><th>Stock</th><th>Margin</th>
<th>Status</th><th>Actions</th>
</tr>
</thead>
<tbody id="prod-tbody"></tbody>
</table>
</div>
</div>

</section><!-- /products -->

<!-- ══════════════════════════════════════════════════
INVENTORY
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-inventory" aria-label="Inventory">

<div class="page-hd">
<div class="page-title">Inventory Tracking</div>
<div class="page-sub">Monitor stock levels and manage product availability</div>
</div>

<!-- Stat mini cards -->


<div class="inv-stat-grid" id="inv-stats"></div>

Page 70 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<!-- Low stock alerts -->
<div class="card mb16">
<div class="section-row mb16" style="margin-bottom:16px">
<div class="flex items-center gap8">
<span style="font-size:18px">⚠️</span>
<div class="card-title">Low Stock Alerts</div>
<span class="badge badge-amber">Action Needed</span>
</div>
</div>
<div id="low-stock-list"></div>
</div>

<!-- Full stock table -->


<div class="card">
<div class="card-title mb16">All Products — Stock Health</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>Product</th><th>Category</th><th>Stock</th>
<th>Threshold</th><th>Health</th><th>Status</th><th></th>
</tr>
</thead>
<tbody id="inv-tbody"></tbody>
</table>
</div>
</div>

</section><!-- /inventory -->

<!-- ══════════════════════════════════════════════════
ORDERS / SALES
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-orders" aria-label="Sales and Orders">

<div class="page-hd">
<div class="page-title">Sales &amp; Orders</div>
<div class="page-sub">Track all transactions and revenue performance</div>
</div>

<!-- Summary KPIs -->


<div class="g3 mb16">
<div class="card" style="border-top:2.5px solid var(--indigo)">
<div class="kpi-label mb8">Total Revenue</div>
<div style="font-size:26px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">₹2,84,000</div>
<div class="mt8"><span class="kpi-change up">↑ 18% vs last month</span></div>
</div>
<div class="card" style="border-top:2.5px solid var(--emerald)">
<div class="kpi-label mb8">Total Orders</div>
<div style="font-size:26px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">712</div>
<div class="mt8"><span class="kpi-change up">↑ 9% vs last month</span></div>
</div>
<div class="card" style="border-top:2.5px solid var(--sky)">
<div class="kpi-label mb8">Avg. Order Value</div>
<div style="font-size:26px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">₹399</div>
<div class="mt8"><span class="kpi-change up">↑ 8% vs last month</span></div>
</div>
</div>

<!-- Orders table -->


<div class="card">
<div class="section-row">
<div class="card-title">All Transactions</div>
<button class="btn-secondary btn-sm" onclick="showToast('📥 Exporting
CSV…','info')">
📥 Export CSV
</button>

Page 71 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>

<div class="filter-tabs" id="order-filter-tabs">


<button class="filter-tab active" onclick="filterOrders('all',this)">All</button>
<button class="filter-tab" onclick="filterOrders('paid',this)">Paid</button>
<button class="filter-tab"
onclick="filterOrders('pending',this)">Pending</button>
</div>

<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>Invoice</th><th>Customer</th><th>Date</th>
<th>Items</th><th>Amount</th><th>Status</th><th>Actions</th>
</tr>
</thead>
<tbody id="orders-tbody"></tbody>
</table>
</div>
</div>

</section><!-- /orders -->

<!-- ══════════════════════════════════════════════════
INVOICES
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-invoices" aria-label="Invoices">

<div class="page-hd">
<div class="section-row">
<div>
<div class="page-title">Invoices &amp; Billing</div>
<div class="page-sub">Generate, manage and print professional invoices</div>
</div>
<div class="flex gap8">
<button class="btn-primary btn-sm" id="inv-tab-create"
onclick="switchInvTab('create')">+ Create Invoice</button>
<button class="btn-secondary btn-sm" id="inv-tab-history"
onclick="switchInvTab('history')">📋 History</button>
</div>
</div>
</div>

<!-- Create invoice -->


<div id="inv-create-panel">
<div class="g-main-wide">

<div class="card">
<div class="card-title mb4">New Invoice</div>
<div class="card-sub mb16" id="inv-number">Invoice #1043 · 28 March 2026</div>

<div class="form-group">
<label class="form-label" for="inv-customer">Customer Name / Phone</label>
<input class="form-input" id="inv-customer" placeholder="Walk-in or search
name…"
oninput="updateInvPreview()"/>
</div>

<!-- Line items table -->


<div class="tbl-wrap mb12">
<table>
<thead>
<tr><th>Product</th><th>Qty</th><th>Price</th><th>Total</th><th></
th></tr>
</thead>
<tbody id="inv-items-tbody"></tbody>
</table>
</div>
<button onclick="addInvItem()"

Page 72 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
style="width:100%;padding:9px;border-radius:var(--r8);border:1px dashed
var(--border);
background:transparent;color:var(--indigo-l);cursor:pointer;font-
size:13px;font-weight:600;margin-bottom:18px">
+ Add Item
</button>

<div class="two-col mb16">


<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="inv-discount">Discount (%)</label>
<input class="form-input" id="inv-discount" type="number" min="0"
max="100" value="0"
oninput="updateInvPreview()"/>
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="inv-gst">GST</label>
<select class="form-select" id="inv-gst" onchange="updateInvPreview()">
<option value="0">No GST (0%)</option>
<option value="5" selected>GST 5%</option>
<option value="12">GST 12%</option>
<option value="18">GST 18%</option>
</select>
</div>
</div>

<div class="form-group">
<label class="form-label">Payment Mode</label>
<div class="pay-modes" id="pay-modes">
<button class="pay-btn active" onclick="setInvPayMode(this,'Cash')">💵
Cash</button>
<button class="pay-btn" onclick="setInvPayMode(this,'UPI')">📱
UPI</button>
<button class="pay-btn" onclick="setInvPayMode(this,'Card')">💳
Card</button>
<button class="pay-btn" onclick="setInvPayMode(this,'Credit')">🏦
Credit</button>
</div>
</div>

<div class="flex gap10 mt16">


<button class="btn-secondary btn-full" onclick="showToast('📝 Draft
saved!','info')">Save Draft</button>
<button class="btn-primary btn-full" onclick="generateInvoice()">
Generate &amp; Print
</button>
</div>
</div>

<!-- Live preview -->


<div>
<div class="invoice-preview">
<div class="inv-logo">🏪 Rajan General Stores</div>
<div class="inv-address">
No.12, Gandhi Nagar, Chennai – 600020<br/>
Ph: +91 98765 43210 · GST: 22AAAAA0000A1Z5
</div>
<div class="inv-hd-row">
<div class="inv-bill-to">
<div class="lbl">BILL TO</div>
<div id="inv-prev-customer" style="font-weight:700;font-
size:14px;color:#1e293b;margin-top:2px">Walk-in Customer</div>
</div>
<div style="text-align:right">
<div class="lbl">INVOICE</div>
<div style="font-weight:700;font-size:14px;color:#1e293b">#1043</div>
<div style="font-size:11px;color:#64748b;margin-top:2px">28 Mar
2026</div>
</div>
</div>

Page 73 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<table style="width:100%;border-collapse:collapse;margin-bottom:12px"
id="inv-prev-items">
<thead>
<tr style="border-bottom:1px solid #e2e8f0">
<th style="text-align:left;padding:4px 0;font-
size:10px;color:#64748b;font-weight:700">PRODUCT</th>
<th style="text-align:center;font-size:10px;color:#64748b;font-
weight:700">QTY</th>
<th style="text-align:right;font-size:10px;color:#64748b;font-
weight:700">TOTAL</th>
</tr>
</thead>
<tbody id="inv-prev-tbody"></tbody>
</table>

<hr class="inv-divider"/>
<div id="inv-prev-totals"></div>
<div class="inv-pay-chip" id="inv-prev-paymode">Payment: Cash</div>
<div style="margin-top:14px;font-size:11px;color:#94a3b8;text-
align:center">Thank you for shopping with us! 🙏</div>
</div>
</div>

</div>
</div><!-- /inv-create-panel -->

<!-- Invoice history -->


<div id="inv-history-panel" style="display:none">
<div class="card">
<div class="card-title mb16">Invoice History</div>
<div class="tbl-wrap">
<table>
<thead>

<tr><th>Invoice</th><th>Customer</th><th>Date</th><th>Amount</th><th>Status</
th><th>Actions</th></tr>
</thead>
<tbody id="inv-history-tbody"></tbody>
</table>
</div>
</div>
</div>

</section><!-- /invoices -->

<!-- ══════════════════════════════════════════════════
ANALYTICS
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-analytics" aria-label="Analytics">

<div class="page-hd">
<div class="page-title">Analytics &amp; Reports</div>
<div class="page-sub">Deep insights into your business growth and trends</div>
</div>

<!-- Summary KPIs -->


<div class="g4 mb16">
<div class="analytics-kpi" style="border-left-color:var(--indigo)">
<div class="kpi-label mb8">This Month Rev.</div>
<div style="font-size:24px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">₹2,84,000</div>
<div class="mt8"><span class="kpi-change up">↑ 18%</span></div>
</div>
<div class="analytics-kpi" style="border-left-color:var(--emerald)">
<div class="kpi-label mb8">Orders</div>
<div style="font-size:24px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">712</div>
<div class="mt8"><span class="kpi-change up">↑ 9%</span></div>
</div>
<div class="analytics-kpi" style="border-left-color:var(--sky)">

Page 74 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="kpi-label mb8">New Customers</div>
<div style="font-size:24px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">43</div>
<div class="mt8"><span class="kpi-change up">↑ 15%</span></div>
</div>
<div class="analytics-kpi" style="border-left-color:var(--amber)">
<div class="kpi-label mb8">Avg. Basket</div>
<div style="font-size:24px;font-weight:900;letter-spacing:-1px;font-family:var(--
mono)">₹399</div>
<div class="mt8"><span class="kpi-change up">↑ 8%</span></div>
</div>
</div>

<div class="g2 mb16">


<div class="card">
<div class="card-title mb4">Monthly Revenue Trend</div>
<div class="card-sub mb16">Full year — 2026</div>
<div class="chart-wrap"><canvas id="analyticsChart"></canvas></div>
</div>
<div class="card">
<div class="card-title mb4">Category Breakdown</div>
<div class="card-sub mb16">Revenue share this month</div>
<div id="cat-breakdown"></div>
</div>
</div>

<div class="card flex justify-between items-center wrap gap12">


<div>
<div class="card-title">Export Reports</div>
<div class="card-sub">Download business data for accounting or review</div>
</div>
<div class="flex gap10 wrap">
<button class="btn-secondary btn-sm" onclick="showToast('📥 Downloading
CSV…','info')">📥 CSV</button>
<button class="btn-secondary btn-sm" onclick="showToast('📄 Generating
PDF…','info')">📄 PDF</button>
<button class="btn-primary btn-sm" onclick="showToast('📧 Report
emailed!','success')">📧 Email Report</button>
</div>
</div>

</section><!-- /analytics -->

<!-- ══════════════════════════════════════════════════
TALLY / ACCOUNTS
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-tally" aria-label="Tally and Accounts">

<div class="page-hd">
<div class="page-title">Tally &amp; Accounts</div>
<div class="page-sub">Financial ledger, income tracking and expense management</div>
</div>

<div class="g-main-wide mb16">


<div class="flex-col gap16">

<!-- Summary -->


<div class="g2">
<div class="card" style="border-top:2.5px solid var(--emerald)">
<div class="kpi-label" style="color:var(--emerald);margin-bottom:8px">Total
Income</div>
<div style="font-size:26px;font-weight:900;color:var(--emerald);font-
family:var(--mono)">₹2,84,000</div>
<div class="card-sub mt8">712 orders · March 2026</div>
</div>
<div class="card" style="border-top:2.5px solid var(--rose)">
<div class="kpi-label" style="color:var(--rose);margin-bottom:8px">Total
Expenses</div>
<div style="font-size:26px;font-weight:900;color:var(--rose);font-
family:var(--mono)">₹45,000</div>

Page 75 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="card-sub mt8">Rent + Salary + Bills</div>
</div>
</div>

<!-- Net profit -->


<div class="expense-summary-box">
<div class="card-sub mb8">NET PROFIT — March 2026</div>
<div style="font-size:38px;font-weight:900;color:var(--indigo-l);font-
family:var(--mono);letter-spacing:-2px">₹2,39,000</div>
<div class="mt12"><span class="badge badge-green" style="font-size:12px">✅
Healthy · 84.2% Margin</span></div>
</div>

<!-- Ledger -->


<div class="card">
<div class="card-title mb16">Transaction Ledger</div>
<div class="tbl-wrap">
<table>
<thead>

<tr><th>Date</th><th>Description</th><th>Credit</th><th>Debit</th><th>Balance</th></tr>
</thead>
<tbody id="ledger-tbody"></tbody>
</table>
</div>
</div>
</div>

<!-- Right column -->


<div class="flex-col gap16">
<!-- Add expense -->
<div class="card">
<div class="card-title mb16">Add Expense</div>
<div class="form-group">
<label class="form-label" for="exp-desc">Description</label>
<input class="form-input" id="exp-desc" placeholder="e.g. Electricity
Bill"/>
</div>
<div class="form-group">
<label class="form-label" for="exp-amount">Amount (₹)</label>
<input class="form-input" id="exp-amount" type="number" min="1"
placeholder="0"/>
</div>
<div class="form-group">
<label class="form-label" for="exp-cat">Category</label>
<select class="form-select" id="exp-cat">
<option>Rent</option>
<option>Staff Salary</option>
<option>Electricity</option>
<option>Inventory Restock</option>
<option>Transport</option>
<option>Miscellaneous</option>
</select>
</div>
<button class="btn-primary btn-full" onclick="addExpense()">Record
Expense</button>
</div>

<!-- Expense breakdown -->


<div class="card">
<div class="card-title mb16">Expense Breakdown</div>
<div id="expense-breakdown"></div>
</div>
</div>
</div>

</section><!-- /tally -->

<!-- ══════════════════════════════════════════════════
SETTINGS

Page 76 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-settings" aria-label="Settings">

<div class="page-hd">
<div class="page-title">Settings</div>
<div class="page-sub">Manage your shop profile, hours and preferences</div>
</div>

<div class="g2 mb16">

<!-- Left column -->


<div class="flex-col gap16">

<div class="settings-panel">
<div class="settings-panel-title">Shop Information</div>

<div class="profile-photo-wrap">
<div class="profile-photo" id="s-user-initial">R</div>
<div>
<div class="profile-name" id="s-user-name">Rajan Kumar</div>
<div class="profile-role">Shopkeeper · RetailPro Pro</div>
<button class="profile-change-btn" onclick="showToast('📷 Upload feature
coming soon!','info')">Change Photo</button>
</div>
</div>

<div class="two-col">
<div class="form-group">
<label class="form-label" for="s-shop-name">Shop Name</label>
<input class="form-input" id="s-shop-name" placeholder="Your shop name"/>
</div>
<div class="form-group">
<label class="form-label" for="s-tagline">Tagline</label>
<input class="form-input" id="s-tagline" placeholder="Your tagline"/>
</div>
</div>
<div class="two-col">
<div class="form-group">
<label class="form-label" for="s-gst">GST Number</label>
<input class="form-input" id="s-gst" placeholder="22AAAAA0000A1Z5"/>
</div>
<div class="form-group">
<label class="form-label" for="s-phone">Phone</label>
<input class="form-input" id="s-phone" type="tel" placeholder="+91 98765
43210"/>
</div>
</div>
<div class="two-col">
<div class="form-group">
<label class="form-label" for="s-email">Email</label>
<input class="form-input" id="s-email" type="email"
placeholder="shop@[Link]"/>
</div>
<div class="form-group">
<label class="form-label" for="s-upi">UPI ID</label>
<input class="form-input" id="s-upi" placeholder="shop@upi"/>
</div>
</div>
</div>

<div class="settings-panel">
<div class="settings-panel-title">Account &amp; Security</div>
<div class="form-group">
<label class="form-label" for="s-curr-pass">Current Password</label>
<input class="form-input" id="s-curr-pass" type="password"
placeholder="••••••••"/>
</div>
<div class="two-col">
<div class="form-group">
<label class="form-label" for="s-new-pass">New Password</label>

Page 77 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<input class="form-input" id="s-new-pass" type="password"
placeholder="Min 8 chars"/>
</div>
<div class="form-group">
<label class="form-label" for="s-confirm-pass">Confirm</label>
<input class="form-input" id="s-confirm-pass" type="password"
placeholder="Repeat"/>
</div>
</div>
<button class="btn-primary btn-sm" onclick="showToast('🔒 Password
updated!','success')">Update Password</button>
</div>
</div>

<!-- Right column -->


<div class="flex-col gap16">

<div class="settings-panel">
<div class="settings-panel-title">Operating Hours</div>
<div id="op-hours-config"></div>
</div>

<div class="settings-panel">
<div class="settings-panel-title">Notification Preferences</div>
<div id="notif-prefs-list"></div>
</div>

</div>
</div><!-- /g2 -->

<div class="flex justify-between items-center wrap gap10">


<button class="btn-ghost" onclick="showToast('↩ Changes discarded','warn')">Discard
Changes</button>
<button class="btn-primary" onclick="saveSettings()">Save All Changes</button>
</div>

</section><!-- /settings -->

<!-- ══════════════════════════════════════════════════
LOCATION
══════════════════════════════════════════════════ -->
<section class="page-section" id="page-location" aria-label="Shop Location">

<div class="page-hd">
<div class="page-title">Shop Location</div>
<div class="page-sub">Set your location to help customers discover your shop on the
map</div>
</div>

<div class="g2">

<div class="card">
<div class="card-title mb4">Pin Your Location</div>
<div class="card-sub mb16">Drag the marker or search to set your exact shop
location</div>

<!-- Map placeholder -->


<div class="map-box mb16">
<!-- Grid lines (decorative) -->
<div class="map-grid-v" style="left:20%"></div><div class="map-grid-v"
style="left:40%"></div>
<div class="map-grid-v" style="left:60%"></div><div class="map-grid-v"
style="left:80%"></div>
<div class="map-grid-h" style="top:25%"></div><div class="map-grid-h"
style="top:50%"></div>
<div class="map-grid-h" style="top:75%"></div>
<!-- Pin -->
<div class="map-pin-wrap">
<div class="map-pin"><div class="map-pin-inner"></div></div>

Page 78 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div style="font-size:13.5px;font-weight:700;color:var(--text1);margin-
top:10px" id="map-shop-label">Rajan General Stores</div>
<div style="font-size:12px;color:var(--text3)" id="map-coords">13.0827° N ·
80.2707° E</div>
</div>
<!-- Zoom -->
<div style="position:absolute;top:10px;right:10px;display:flex;flex-
direction:column;gap:4px">
<button onclick="showToast('🔍 Zoom in','info')"
style="width:28px;height:28px;background:var(--navy2);border:1px solid var(--border);border-
radius:6px;color:var(--text2);cursor:pointer;font-size:14px">+</button>
<button onclick="showToast('🔍 Zoom out','info')"
style="width:28px;height:28px;background:var(--navy2);border:1px solid var(--border);border-
radius:6px;color:var(--text2);cursor:pointer;font-size:14px">−</button>
</div>
</div>

<div class="form-group">
<label class="form-label" for="loc-address">Address</label>
<input class="form-input" id="loc-address" placeholder="Your shop address"/>
</div>
<div class="two-col mb16">
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="loc-lat">Latitude</label>
<input class="form-input" id="loc-lat" placeholder="13.0827"/>
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="loc-lng">Longitude</label>
<input class="form-input" id="loc-lng" placeholder="80.2707"/>
</div>
</div>

<div class="flex gap10">


<button class="btn-secondary flex1" onclick="showToast('📍 Using current
location…','info')">📍 Use My Location</button>
<button class="btn-primary" onclick="saveLocation()">Save Location</button>
</div>
</div>

<div class="flex-col gap16">


<div class="card">
<div class="card-title mb4">Nearby Competitors</div>
<div class="card-sub mb16">Shops in your area using RetailPro</div>
<div id="nearby-list">
<div class="tally-row"><span style="font-size:13.5px">🏪 Ramesh
Stores</span><span class="card-sub">0.3 km away</span></div>
<div class="tally-row"><span style="font-size:13.5px">🛒 Velan
Supermart</span><span class="card-sub">0.7 km away</span></div>
<div class="tally-row"><span style="font-size:13.5px">🏬 City
Provisions</span><span class="card-sub">1.1 km away</span></div>
</div>
</div>
<div class="card" style="background:var(--indigo-dim);border-
color:rgba(91,99,254,0.2)">
<div style="font-size:24px;margin-bottom:10px"></div>
<div class="card-title mb8">Customer Discovery</div>
<div class="card-sub">When customers search for shops within 5 km, your store
will appear in their results with your location pin.</div>
<div class="mt16"><span class="badge badge-blue">📍 Active on Map</span></div>
</div>
</div>

</div>
</section><!-- /location -->

</div><!-- /content -->


</div><!-- /main -->
</div><!-- /app -->

Page 79 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<!-- ════════════════════════════════════════════════════════
PRODUCT DRAWER (Add / Edit)
════════════════════════════════════════════════════════ -->
<div id="modal-overlay" onclick="closeDrawer()" aria-hidden="true"></div>

<div id="drawer" role="dialog" aria-labelledby="drawer-title" aria-modal="true">

<div class="drawer-hd">
<div>
<div class="drawer-title" id="drawer-title">Add New Product</div>
<div class="drawer-sub" id="drawer-sub">Fill in the details for the new product.</div>
</div>
<button class="drawer-close" onclick="closeDrawer()" aria-label="Close drawer">✕</button>
</div>

<div class="drawer-body">

<!-- Image upload -->


<div class="upload-area" onclick="showToast('📷 Image upload coming soon!','info')" role="button"
tabindex="0"
onkeydown="if([Link]==='Enter')showToast('📷 Image upload coming soon!','info')">
<div class="upload-emoji" id="d-img-preview">📦</div>
<div class="upload-txt">Click to upload product image</div>
<div class="upload-txt" style="font-size:11px;margin-top:3px">JPG, PNG, WEBP · Max 2MB</div>
</div>

<!-- Name -->


<div class="form-group">
<label class="form-label" for="d-name">Product Name *</label>
<input class="form-input" id="d-name" placeholder="e.g. Basmati Rice 5kg"/>
<span class="f-error" id="d-name-err">Product name is required.</span>
</div>

<!-- SKU + Category -->


<div class="two-col">
<div class="form-group">
<label class="form-label" for="d-cat">Category</label>
<select class="form-select" id="d-cat">
<option>Grains</option><option>Oils</option><option>Pulses</option>
<option>Spices</option><option>Essentials</option><option>Packaged</option>
<option>Beverages</option><option>Dairy</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="d-sku">SKU / Barcode</label>
<input class="form-input" id="d-sku" placeholder="GR-001"/>
</div>
</div>

<!-- Pricing -->


<div class="f-box">
<div class="f-box-title">Pricing</div>
<div class="two-col">
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="d-price">Selling Price (₹) *</label>
<input class="form-input" id="d-price" type="number" min="1" placeholder="0"
oninput="calcMargin()"/>
<span class="f-error" id="d-price-err">Enter a valid selling price.</span>
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="d-cost">Cost Price (₹)</label>
<input class="form-input" id="d-cost" type="number" min="0" placeholder="0"
oninput="calcMargin()"/>
</div>
</div>
<div id="margin-display"></div>
</div>

<!-- Inventory -->


<div class="f-box">
<div class="f-box-title">Inventory</div>

Page 80 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="two-col">
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="d-stock">Stock Quantity</label>
<input class="form-input" id="d-stock" type="number" min="0" placeholder="0"/>
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="d-threshold">Low Stock Alert (units)</label>
<input class="form-input" id="d-threshold" type="number" min="1" placeholder="10"
value="10"/>
</div>
</div>
</div>

<!-- Status -->


<div class="form-group">
<label class="form-label">Status</label>
<div class="status-toggle">
<button class="status-btn active-green" id="status-btn-active"
onclick="setProductStatus('active')">✔ Active</button>
<button class="status-btn" id="status-btn-inactive"
onclick="setProductStatus('inactive')">⏸ Inactive</button>
</div>
</div>

</div><!-- /drawer-body -->

<div class="drawer-ft">
<button class="btn-ghost flex1" onclick="closeDrawer()">Cancel</button>
<button class="btn-primary" style="flex:2;justify-content:center" onclick="saveProduct()">Save
Product</button>
</div>

</div><!-- /drawer -->

<!-- ════════════════════════════════════════════════════════
CONFIRM MODAL (Delete)
════════════════════════════════════════════════════════ -->
<div id="confirm-modal" role="alertdialog" aria-labelledby="confirm-title" aria-modal="true">
<div class="confirm-box">
<div class="confirm-icon"></div>
<div class="confirm-title" id="confirm-title">Delete Product</div>
<p class="confirm-msg" id="confirm-msg">Are you sure? This cannot be undone.</p>
<div class="confirm-btns">
<button class="btn-ghost flex1" onclick="closeConfirm()">Cancel</button>
<button class="btn-danger flex1" onclick="doConfirm()">Yes, Delete</button>
</div>
</div>
</div>

<!-- ════════════════════════════════════════════════════════
MOBILE BOTTOM NAV
════════════════════════════════════════════════════════ -->
<nav id="mob-nav" aria-label="Mobile navigation">
<div class="mob-nav-inner">
<button class="mob-nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
Home
</button>
<button class="mob-nav-item" data-page="products" onclick="navigate('products')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/>
<circle cx="7" cy="7" r="1" fill="currentColor"/>
</svg>
Products

Page 81 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</button>
<button class="mob-nav-item" data-page="orders" onclick="navigate('orders')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round">
<line x1="12" y1="1" x2="12" y2="23"/>
<path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
</svg>
Sales
</button>
<button class="mob-nav-item" data-page="invoices" onclick="navigate('invoices')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
Invoice
</button>
<button class="mob-nav-item" data-page="settings" onclick="navigate('settings')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-
linecap="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.07 4.93a10 10 0 010 14.14M4.93 4.93a10 10 0 000 14.14"/>
</svg>
Settings
</button>
</div>
</nav>

<!-- ════════════════════════════════════════════════════════
TOAST CONTAINER
════════════════════════════════════════════════════════ -->
<div class="toast-container" id="toasts" aria-live="polite" aria-atomic="true"></div>

<!-- ════════════════════════════════════════════════════════
SCRIPTS
════════════════════════════════════════════════════════ -->
<script src="../js/[Link]"></script>

</body>
</html>

Page 82 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

11. pages / [Link]


📄 File: pages/[Link]
Lines of code: 609

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RetailPro — Customer Dashboard</title>

<!-- ═══════════════════════════════════════════════════
GOOGLE MAPS API KEY — ADD YOUR KEY HERE
Steps:
1. Go to [Link]
2. Create a project → Enable "Maps JavaScript API"
and "Places API"
3. Generate an API key
4. Replace YOUR_GOOGLE_MAPS_API_KEY below with it
5. Save and reload — the real map replaces the demo grid
═══════════════════════════════════════════════════ -->
<!-- <script src="[Link]
key=YOUR_GOOGLE_MAPS_API_KEY&libraries=places&callback=initGoogleMap" async defer></script> -->

<link rel="stylesheet" href="../css/[Link]">


</head>
<body>

<!-- SIDEBAR OVERLAY (mobile) -->


<div class="sidebar-overlay" onclick="closeSidebar()"></div>

<!-- ═══ SIDEBAR ═══ -->


<aside class="sidebar">
<div class="sidebar-brand">
<div class="brand-logo">🛒</div>
<div class="brand-text">
<div class="name">RetailPro</div>
<div class="tagline">Customer Portal</div>
</div>
</div>
<div class="section-label">Explore</div>
<div class="nav-item active" data-page="home" onclick="switchPage('home');closeSidebar()"><div
class="nav-icon">🏠</div>Discover</div>
<div class="nav-item" data-page="shops" onclick="switchPage('shops');closeSidebar()"><div
class="nav-icon">🗺</div>Nearby Shops</div>
<div class="nav-item" data-page="products"
onclick="switchPage('products');closeSidebar()"><div class="nav-icon">📦</div>Browse Products</div>
<div class="section-label">Shopping</div>
<div class="nav-item" data-page="cart" onclick="switchPage('cart');closeSidebar()">
<div class="nav-icon">🛒</div>My Cart
<span class="nav-count cart-badge" id="sidebar-cart-count" style="display:none">0</span>
</div>
<div class="nav-item" data-page="wishlist" onclick="switchPage('wishlist');closeSidebar()"><div
class="nav-icon">❤️</div>Wishlist</div>
<div class="nav-item" data-page="orders" onclick="switchPage('orders');closeSidebar()"><div
class="nav-icon">📋</div>My Orders</div>
<div class="section-label">Account</div>
<div class="nav-item" data-page="profile" onclick="switchPage('profile');closeSidebar()"><div
class="nav-icon">👤</div>My Profile</div>
<div class="sidebar-footer">
<div class="user-card" onclick="switchPage('profile')">
<div class="user-avatar" id="cu-avatar">S</div>
<div class="user-info">
<div class="uname" id="cu-name">Customer</div>
<div class="uemail" id="cu-email">loading…</div>

Page 83 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>
</div>
<div class="logout-btn" onclick="doLogout()">⎋ &nbsp;Logout</div>
</div>
</aside>

<!-- ═══ MAIN ═══ -->


<main class="main">

<!-- TOPBAR -->


<div class="topbar">
<button class="hamburger" onclick="toggleSidebar()">☰</button>
<div style="display:flex;flex-direction:column">
<span class="topbar-title" id="topbar-title">Discover</span>
<span class="topbar-sub" id="topbar-sub">📍 Chengalpattu, TN</span>
</div>
<div class="topbar-right">
<div class="search-topbar">
<span>🔍</span>
<input type="text" id="topbar-search" placeholder="Search shops, products…">
</div>
<div class="icon-btn" onclick="openNotifDrawer()" title="Notifications">
🔔<div class="notif-dot notif-badge"></div>
</div>
<div class="cart-topbtn" onclick="switchPage('cart')">
🛒 Cart
<span class="cart-count cart-badge" id="topbar-cart-count" style="display:none">0</span>
</div>
</div>
</div>

<!-- ████ PAGE: HOME ████ -->


<div class="page active" id="page-home">
<div class="page-content">

<!-- Hero -->


<div class="hero-banner">
<div class="hero-content">
<div class="hero-greeting" id="hero-greeting">👋 Hello, Customer!</div>
<h1 class="hero-title">Find what you need,<br><span>delivered nearby.</span></h1>
<p class="hero-sub">Browse 48 shops and 1,200+ products near Chengalpattu.</p>
<div class="hero-search">
<span style="font-size:18px">🔍</span>
<input type="text" id="hero-search-input" placeholder="Search for rice, milk,
furniture, electronics…">
<button class="hero-search-btn" onclick="heroSearch()">Search</button>
</div>
<div class="hero-chips">
<span class="hero-chip active">🌾 Grocery</span>
<span class="hero-chip">⚡ Electronics</span>
<span class="hero-chip">🪑 Furniture</span>
<span class="hero-chip">🌀 Appliances</span>
<span class="hero-chip">🌿 Organic</span>
<span class="hero-chip">💊 Pharmacy</span>
</div>
</div>
<div class="hero-stats">
<div class="hero-stat"><div class="hero-stat-val">48</div><div class="hero-stat-
lbl">Shops Nearby</div></div>
<div class="hero-stat"><div class="hero-stat-val">1.2k</div><div class="hero-stat-
lbl">Products</div></div>
<div class="hero-stat"><div class="hero-stat-val">4.8⭐</div><div class="hero-stat-
lbl">Avg Rating</div></div>
</div>
</div>

<!-- Categories -->


<div class="cat-scroll">
<div class="cat-pill active" data-cat="All"><div class="cat-pill-icon">🏪</div><div
class="cat-pill-label">All</div></div>

Page 84 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="cat-pill" data-cat="Grocery"><div class="cat-pill-icon">🌾</div><div
class="cat-pill-label">Grocery</div></div>
<div class="cat-pill" data-cat="Electronics"><div class="cat-pill-icon">⚡</div><div
class="cat-pill-label">Electronics</div></div>
<div class="cat-pill" data-cat="Furniture"><div class="cat-pill-icon">🪑</div><div
class="cat-pill-label">Furniture</div></div>
<div class="cat-pill" data-cat="Appliances"><div class="cat-pill-icon">🌀</div><div
class="cat-pill-label">Appliances</div></div>
<div class="cat-pill" data-cat="Organic"><div class="cat-pill-icon">🌿</div><div
class="cat-pill-label">Organic</div></div>
<div class="cat-pill" data-cat="Pharmacy"><div class="cat-pill-icon">💊</div><div
class="cat-pill-label">Pharmacy</div></div>
</div>

<!-- Nearby Shops -->


<div class="sec-header">
<div><div class="sec-title">Nearby Shops</div><div class="sec-sub">48 shops within 5
km</div></div>
<button class="view-all-btn" onclick="switchPage('shops')">View all →</button>
</div>
<div class="shops-grid" id="shops-grid-home"></div>

<!-- Trending -->


<div class="sec-header" style="margin-top:4px">
<div><div class="sec-title">Trending Now 🔥</div><div class="sec-sub">Most popular
products this week</div></div>
<button class="view-all-btn" onclick="switchPage('products')">View all →</button>
</div>
<div class="products-grid" id="trending-grid"></div>

<!-- Offers -->


<div class="sec-header">
<div><div class="sec-title">Hot Offers 💸</div><div class="sec-sub">Limited-time discounts
near you</div></div>
</div>
<div class="products-grid" id="offers-grid"></div>

<!-- Recently Viewed -->


<div class="sec-header">
<div><div class="sec-title">Recently Viewed</div></div>
</div>
<div class="h-scroll" id="recently-viewed"></div>

</div>
</div>

<!-- ████ PAGE: NEARBY SHOPS + MAP ████ -->


<div class="page" id="page-shops">
<div class="page-content">

<div class="sec-header">
<div><div class="sec-title">Shops Near You 📍</div><div class="sec-sub">Chengalpattu,
Tamil Nadu</div></div>
</div>

<!-- ═══════════════════════════════════════════════
MAP SECTION
— When Google Maps API key is added (see <head>),
the real interactive map loads in #map div.
— Without the key, the beautiful demo map shows.
═══════════════════════════════════════════════ -->
<div class="map-layout" style="margin-bottom:24px">
<div class="map-container">
<!-- Real Google Map renders here when API key is added -->
<div id="map" style="width:100%;height:100%;display:none"></div>

<!-- Demo map (shown when no API key) -->


<div class="map-demo-bg" id="map-demo">
<div class="map-grid"></div>
<!-- Map markers are injected by [Link] → renderMapMarkers() -->

Page 85 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>

<div class="map-overlay-search">
<div class="map-search-box">
<span>🔍</span>
<input type="text" id="map-search" placeholder="Search on map…"
oninput="handleMapSearch([Link])">
</div>
</div>
<div class="map-controls">
<div class="map-btn" onclick="showToast('Zoom in','info')" title="Zoom in">+</div>
<div class="map-btn" onclick="showToast('Zoom out','info')" title="Zoom
out">−</div>
<div class="map-btn" onclick="showToast('Centering on your location…','info')"
title="My location">◎</div>
</div>
<div class="map-filter-bar">
<div class="map-chip active"
onclick="setMapFilter(this,'category','All')">All</div>
<div class="map-chip" onclick="setMapFilter(this,'category','Grocery')">🌾
Grocery</div>
<div class="map-chip" onclick="setMapFilter(this,'category','Electronics')">⚡
Electronics</div>
<div class="map-chip" onclick="setMapFilter(this,'category','Furniture')">🪑
Furniture</div>
<div class="map-chip" onclick="setMapFilter(this,'category','Appliances')">🌀
Appliances</div>
<div class="map-chip" onclick="toggleOpenFilter(this)">● Open Now</div>
</div>
</div>
<div class="shop-list-panel" id="shop-list-panel"></div>
</div>

<!-- Distance / Rating filters -->


<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px">
<div style="display:flex;gap:6px;flex-wrap:wrap" id="dist-filter">
<div class="chip active" onclick="filterChip(this,'dist-
filter');setMapFilter(this,'distance','All')">📍 All Distance</div>
<div class="chip" onclick="filterChip(this,'dist-
filter');setMapFilter(this,'distance',1)">≤ 1 km</div>
<div class="chip" onclick="filterChip(this,'dist-
filter');setMapFilter(this,'distance',2)">≤ 2 km</div>
<div class="chip" onclick="filterChip(this,'dist-
filter');setMapFilter(this,'distance',5)">≤ 5 km</div>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap" id="rating-filter">
<div class="chip active" onclick="filterChip(this,'rating-
filter');setMapFilter(this,'rating','All')">⭐ Any Rating</div>
<div class="chip" onclick="filterChip(this,'rating-
filter');setMapFilter(this,'rating',4.5)">4.5+</div>
<div class="chip" onclick="filterChip(this,'rating-
filter');setMapFilter(this,'rating',4)">4.0+</div>
</div>
</div>

<div class="sec-header">
<div><div class="sec-title">All Nearby Shops</div><div class="sec-sub" id="nearby-
count">Loading…</div></div>
</div>
<div class="shops-grid" id="nearby-shops-grid"></div>

</div>
</div>

<!-- ████ PAGE: PRODUCTS ████ -->


<div class="page" id="page-products">
<div class="page-content">
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:18px" id="prod-cat-filter">
<div class="chip active" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('All')">All</div>

Page 86 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Grocery')">Grocery</div>
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Electronics')">Electronics</div>
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Furniture')">Furniture</div>
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Appliances')">Appliances</div>
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Organic')">Organic</div>
<div class="chip" onclick="filterChip(this,'prod-cat-
filter');filterProductsByCategory('Pharmacy')">Pharmacy</div>
</div>
<div class="sec-header"><div><div class="sec-title">New Arrivals 🆕</div></div></div>
<div class="products-grid" id="new-arrivals-grid"></div>
<div class="sec-header" style="margin-top:8px"><div><div class="sec-title">All
Products</div></div></div>
<div class="products-grid" id="all-products-grid"></div>
</div>
</div>

<!-- ████ PAGE: CART ████ -->


<div class="page" id="page-cart">
<div class="page-content">
<div class="cart-layout">
<div>
<div class="cart-section">
<div class="cart-header">
<div class="cart-header-title">My Cart</div>
<button class="btn btn-ghost btn-sm" onclick="clearCart()">Clear All</button>
</div>
<div id="cart-body"></div>
</div>
</div>
<div class="cart-summary">
<div style="font-size:14px;font-weight:700;margin-bottom:14px">Order Summary</div>
<div class="sum-row"><span class="label">Subtotal</span><span class="val" id="cart-
subtotal">₹0</span></div>
<div class="sum-row"><span class="label">Delivery Fee</span><span
class="val">₹30</span></div>
<div class="sum-row"><span class="label">Discount</span><span class="val" id="cart-
discount" style="color:var(--emerald)">−₹0</span></div>
<div class="sum-divider"></div>
<div class="sum-row sum-total"><span class="label">Total</span><span class="val"
id="cart-total">₹0</span></div>
<button class="checkout-btn" onclick="checkout()">Proceed to Checkout →</button>
<div class="promo-row">
<input class="promo-input" id="promo-input" placeholder="Promo code (try SAVE10)">
<button class="promo-btn" onclick="applyPromo()">Apply</button>
</div>
<div style="font-size:11px;color:var(--text2);text-align:center;margin-top:10px">🔒
Secure checkout · Free returns</div>
</div>
</div>
</div>
</div>

<!-- ████ PAGE: WISHLIST ████ -->


<div class="page" id="page-wishlist">
<div class="page-content">
<div class="sec-header">
<div><div class="sec-title">My Wishlist ❤️</div><div class="sec-sub">Products saved for
later</div></div>
<button class="view-all-btn" onclick="switchPage('products')">Browse more →</button>
</div>
<div class="wishlist-grid" id="wishlist-container"></div>
<div class="sec-header" style="margin-top:28px">
<div><div class="sec-title">Favourite Shops</div><div class="sec-sub">Shops you
follow</div></div>

Page 87 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>
<div class="shops-grid" id="fav-shops-container"></div>
</div>
</div>

<!-- ████ PAGE: ORDERS ████ -->


<div class="page" id="page-orders">
<div class="page-content">
<div class="order-stats-grid">
<div class="order-stat-card"><div class="osi"
style="background:var(--sky-dim)">📦</div><div><div class="osv">12</div><div class="osl">Total
Orders</div></div></div>
<div class="order-stat-card"><div class="osi"
style="background:var(--emerald-dim)">✅</div><div><div class="osv" style="color:var(--
emerald)">9</div><div class="osl">Delivered</div></div></div>
<div class="order-stat-card"><div class="osi"
style="background:var(--amber-dim)">⏳</div><div><div class="osv"
style="color:var(--amber)">2</div><div class="osl">Pending</div></div></div>
<div class="order-stat-card"><div class="osi"
style="background:var(--violet-dim)">💰</div><div><div class="osv" style="color:var(--
violet)">₹6.4k</div><div class="osl">Total Spent</div></div></div>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px" id="order-filter">
<div class="chip active" onclick="filterChip(this,'order-
filter');filterOrders('All')">All Orders</div>
<div class="chip" onclick="filterChip(this,'order-
filter');filterOrders('Delivered')">Delivered</div>
<div class="chip" onclick="filterChip(this,'order-
filter');filterOrders('Processing')">Processing</div>
<div class="chip" onclick="filterChip(this,'order-
filter');filterOrders('Pending')">Pending</div>
</div>
<div id="orders-list"></div>
</div>
</div>

<!-- ████ PAGE: PROFILE ████ -->


<div class="page" id="page-profile">
<div class="page-content">
<div class="profile-layout">
<div>
<div class="profile-sidebar-card">
<div class="profile-avatar-lg" id="profile-av">S</div>
<div class="profile-name" id="profile-name">Customer</div>
<div class="profile-email" id="profile-email">loading…</div>
<div class="profile-stats-grid">
<div class="profile-stat"><div class="profile-stat-val">12</div><div
class="profile-stat-lbl">Orders</div></div>
<div class="profile-stat"><div class="profile-stat-val">₹6.4k</div><div
class="profile-stat-lbl">Spent</div></div>
<div class="profile-stat"><div class="profile-stat-val"
id="wish-count">0</div><div class="profile-stat-lbl">Wishlist</div></div>
<div class="profile-stat"><div class="profile-stat-val"
id="fav-count">0</div><div class="profile-stat-lbl">Fav Shops</div></div>
</div>
<div class="profile-menu-item active"><span>👤</span> Personal Info</div>
<div class="profile-menu-item" onclick="switchPage('orders')"><span>📋</span> My
Orders</div>
<div class="profile-menu-item" onclick="switchPage('wishlist')"><span>❤️</span>
Wishlist</div>
<div class="profile-menu-item" onclick="openNotifDrawer()"><span>🔔</span>
Notifications</div>
<div class="profile-menu-item" style="color:var(--rose);margin-top:6px"
onclick="doLogout()"><span>⎋</span> Logout</div>
</div>
</div>
<div class="profile-main">
<!-- Personal Info -->
<div class="profile-section">

Page 88 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="profile-section-header">Personal Information</div>
<div class="profile-section-body">
<div class="form-grid2">
<div class="form-group2"><label class="form-label2">Full Name</label><input
class="form-input2" id="edit-name" placeholder="Your name"></div>
<div class="form-group2"><label class="form-label2">Phone</label><input
class="form-input2" id="edit-phone" placeholder="+91 98765 43210"></div>
<div class="form-group2 full"><label class="form-label2">Email</label><input
class="form-input2" id="edit-email" placeholder="you@[Link]"></div>
</div>
<button class="btn btn-primary" style="margin-top:14px"
onclick="saveProfile()">Save Changes →</button>
</div>
</div>
<!-- Saved Addresses -->
<div class="profile-section">
<div class="profile-section-header" style="display:flex;justify-content:space-
between;align-items:center">
Saved Addresses
<button class="btn btn-ghost btn-sm" onclick="showToast('Add address form —
connect to your backend DB','info')">+ Add New</button>
</div>
<div class="profile-section-body">
<div style="display:flex;flex-direction:column;gap:10px">
<div class="address-card default">
<div class="address-default-badge">✓ Default Address</div>
<div style="font-size:13px;font-weight:600;margin-bottom:3px">Home</div>
<div style="font-size:12px;color:var(--text2)">42 Gandhi Nagar,
Chengalpattu, Tamil Nadu 603001</div>
<div style="display:flex;gap:8px;margin-top:10px">
<button class="btn btn-ghost btn-sm" onclick="showToast('Edit
address','info')">✏ Edit</button>
<button class="btn btn-ghost btn-sm" onclick="showToast('Address
removed','error')">🗑 Remove</button>
</div>
</div>
<div class="address-card">
<div
style="font-size:13px;font-weight:600;margin-bottom:3px">Office</div>
<div style="font-size:12px;color:var(--text2)">15 IT Park Road, OMR,
Chennai 600119</div>
<div style="display:flex;gap:8px;margin-top:10px">
<button class="btn btn-ghost btn-sm" onclick="showToast('Set as
default','success')">📍 Set Default</button>
<button class="btn btn-ghost btn-sm" onclick="showToast('Edit
address','info')">✏ Edit</button>
</div>
</div>
</div>
</div>
</div>
<!-- Price Alerts -->
<div class="profile-section">
<div class="profile-section-header">Price &amp; Stock Alerts 🔔</div>
<div class="profile-section-body">
<div style="display:flex;flex-direction:column;gap:10px">
<div style="background:var(--navy3);border:1px solid var(--border);border-
radius:var(--r8);padding:12px;display:flex;align-items:center;gap:12px">
<span style="font-size:22px">📱</span>
<div style="flex:1">
<div style="font-size:13px;font-weight:600">iPhone 15 128GB</div>
<div style="font-size:11px;color:var(--text2)">Alert when price drops
below ₹70,000</div>
</div>
<button class="btn btn-ghost btn-sm" onclick="showToast('Alert
removed','info')">Remove</button>
</div>
<div style="background:var(--navy3);border:1px solid var(--border);border-
radius:var(--r8);padding:12px;display:flex;align-items:center;gap:12px">
<span style="font-size:22px">🛋</span>
<div style="flex:1">

Page 89 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div style="font-size:13px;font-weight:600">Premium Sofa 3+1+1</div>
<div style="font-size:11px;color:var(--text2)">Alert when back in
stock</div>
</div>
<button class="btn btn-ghost btn-sm" onclick="showToast('Alert
removed','info')">Remove</button>
</div>
</div>
<button class="btn btn-secondary" style="margin-top:12px;width:100%"
onclick="switchPage('products')">+ Set more alerts</button>
</div>
</div>
<!-- Change Password -->
<div class="profile-section">
<div class="profile-section-header">Change Password</div>
<div class="profile-section-body">
<div class="form-grid2">
<div class="form-group2 full"><label class="form-label2">Current
Password</label><input class="form-input2" id="cur-pass" type="password"
placeholder="••••••••"></div>
<div class="form-group2"><label class="form-label2">New
Password</label><input class="form-input2" id="new-pass" type="password"
placeholder="••••••••"></div>
<div class="form-group2"><label class="form-label2">Confirm</label><input
class="form-input2" id="conf-pass" type="password" placeholder="••••••••"></div>
</div>
<button class="btn btn-ghost" style="margin-top:14px"
onclick="changePassword()">Update Password</button>
</div>
</div>
</div>
</div>
</div>
</div>

</main>

<!-- NOTIFICATIONS DRAWER -->


<div id="notif-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-
index:299" onclick="closeNotifDrawer()"></div>
<div class="notif-drawer" id="notif-drawer">
<div class="notif-drawer-header">
<div style="font-size:15px;font-weight:700">Notifications</div>
<div style="display:flex;gap:8px;align-items:center">
<button class="btn btn-ghost btn-sm" onclick="markAllRead()">Mark all read</button>
<div class="modal-close" onclick="closeNotifDrawer()">✕</div>
</div>
</div>
<div class="notif-drawer-body" id="notif-body"></div>
</div>

<!-- SHOP DETAIL MODAL -->


<div class="modal-overlay" id="shop-modal-overlay">
<div class="shop-modal">
<div class="modal-header">
<div style="font-size:16px;font-weight:700">Shop Details</div>
<div class="modal-close" onclick="closeShopModal()">✕</div>
</div>
<div class="modal-body" id="shop-modal-content"></div>
</div>
</div>

<!-- COMPARE MODAL -->


<div class="modal-overlay" id="compare-modal">
<div class="shop-modal" style="max-width:700px">
<div class="modal-header">
<div style="font-size:16px;font-weight:700">Compare Products ⇌</div>

Page 90 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="modal-close" onclick="[Link]('compare-
modal').[Link]('open')">✕</div>
</div>
<div class="modal-body" id="compare-modal-body"></div>
</div>
</div>

<!-- COMPARE BAR -->


<div class="compare-bar" id="compare-bar">
<div style="font-size:13px;font-weight:600;white-space:nowrap">⇌ Compare:</div>
<div class="compare-items" id="compare-slots"></div>
<button class="btn btn-primary" onclick="openCompareModal()">Compare Now</button>
<button class="btn btn-ghost"
onclick="[Link]=[];saveState();renderCompareBar()">Clear</button>
</div>

<!-- MOBILE BOTTOM NAV -->


<nav class="mobile-bottom-nav">
<div class="mbn-items">
<div class="mbn-item active" data-page="home" onclick="switchPage('home')"><div class="mbn-
icon">🏠</div><div class="mbn-label">Home</div></div>
<div class="mbn-item" data-page="shops" onclick="switchPage('shops')"><div class="mbn-
icon">🗺</div><div class="mbn-label">Shops</div></div>
<div class="mbn-item" data-page="cart" onclick="switchPage('cart')">
<div class="mbn-icon">🛒</div><div class="mbn-label">Cart</div>
<div class="mbn-badge cart-badge" style="display:none">0</div>
</div>
<div class="mbn-item" data-page="wishlist" onclick="switchPage('wishlist')"><div class="mbn-
icon">❤️</div><div class="mbn-label">Wishlist</div></div>
<div class="mbn-item" data-page="profile" onclick="switchPage('profile')"><div class="mbn-
icon">👤</div><div class="mbn-label">Profile</div></div>
</div>
</nav>

<!-- TOAST CONTAINER -->


<div class="toast-container" id="toasts"></div>

<!-- ═══════════════════════════════════════════════════
SCRIPTS
[Link] handles all: auth guard, shops, map,
cart, wishlist, orders, profile, notifications.
═══════════════════════════════════════════════════ -->
<script src="../js/[Link]"></script>
<script>
/* ── Extra inline helpers ── */

/* filterChip: used by category/filter chip rows */


function filterChip(el, groupId) {
[Link](groupId)?.querySelectorAll('.chip').forEach(c =>
[Link]('active'));
[Link]('active');
}

/* filterProductsByCategory: delegates to [Link] renderProducts */


function filterProductsByCategory(cat) {
const filtered = cat === 'All' ? PRODUCTS : [Link](p => [Link] === cat);
renderProducts(filtered, 'new-arrivals-grid');
renderProducts(filtered, 'all-products-grid');
}

/* filterOrders: delegates to [Link] renderOrders */


function filterOrders(status) {
if (typeof renderOrders === 'function') renderOrders(status);
}

/* handleMapSearch: filter map markers by shop name */


function handleMapSearch(q) {

Page 91 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
if (typeof renderMapMarkers === 'function') {
const filtered = q ? [Link](s => [Link]().includes([Link]())) : SHOPS;
renderMapMarkers(filtered);
}
}

/* changePassword: validates and saves (localStorage for now) */


function changePassword() {
const cur = [Link]('cur-pass').value;
const nw = [Link]('new-pass').value;
const conf = [Link]('conf-pass').value;
if (!cur || !nw || !conf) { showToast('Please fill all password fields.', 'warn'); return; }
if ([Link] < 8) { showToast('New password must be at least 8 characters.', 'warn');
return; }
if (nw !== conf) { showToast('New passwords do not match.', 'error'); return; }
/* In a real backend: POST /api/auth/change-password */
showToast('✅ Password updated successfully!', 'success');
[Link]('cur-pass').value = '';
[Link]('new-pass').value = '';
[Link]('conf-pass').value = '';
}

/* ── initGoogleMap: called by Google Maps API callback ──


Activated automatically when you un-comment the API
script in <head> and replace YOUR_GOOGLE_MAPS_API_KEY */
function initGoogleMap() {
const mapEl = [Link]('map');
const demoEl = [Link]('map-demo');
if (!mapEl || typeof google === 'undefined') return;

/* Hide demo, show real map */


[Link] = 'none';
[Link] = 'block';

const center = { lat: 12.6924, lng: 79.9618 }; /* Chengalpattu, TN */


const gmap = new [Link](mapEl, {
center, zoom: 14,
styles: [ /* Dark map theme */
{ elementType:'geometry', stylers:[{ color:'#0d1117' }] },
{ elementType:'[Link]',stylers:[{ color:'#8b949e' }] },
{ featureType:'road', elementType:'geometry', stylers:[{ color:'#1c2333' }] },
{ featureType:'water', elementType:'geometry', stylers:[{ color:'#0d1117' }] },
{ featureType:'poi', stylers:[{ visibility:'off' }] },
],
});

/* Drop a marker for each shop */


[Link](shop => {
if (![Link] || ![Link]) return;
const marker = new [Link]({
position : { lat: [Link], lng: [Link] },
map : gmap,
title : [Link],
icon : {
path : [Link],
fillColor : '#10D9A0',
fillOpacity: 1,
strokeColor: '#0D1117',
strokeWeight: 2,
scale : 10,
},
});
[Link]('click', () => openShopModal([Link]));
});
}

/* ── Post-load init ── */
[Link]('load', () => {
const u = [Link]([Link]('rp_user') || '{}');

/* Sync profile avatar initial */

Page 92 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const av = [Link]('profile-av');
if (av && [Link]) [Link] = [Link][0].toUpperCase();

/* Sync wishlist / fav counts in profile stats */


if (typeof appState !== 'undefined') {
const wc = [Link]('wish-count');
const fc = [Link]('fav-count');
if (wc) [Link] = [Link];
if (fc) [Link] = [Link];
const nc = [Link]('nearby-count');
if (nc) [Link] = [Link] + ' shops near you';
}
});
</script>

</body>
</html>

Page 93 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

12. js / [Link]
📄 File: js/[Link]
Lines of code: 566

/* ============================================================
RetailPro — Auth Page JavaScript
File: js/[Link]

Covers:
- Tab switching (Login ↔ Register)
- Login role (Shopkeeper / Customer)
- Register role (Shopkeeper / Customer)
- Password visibility toggle
- Password strength meter
- Confirm-password live check
- Agree checkbox
- Form validation (login + register)
- Submit handlers (login + register)
- Toast notifications
- Session storage (localStorage)
- Auto-redirect if already logged in
- Role-based redirect after login/register
============================================================ */

'use strict';

/* ── State ───────────────────────────────────────────────── */
let loginRole = 'sk'; // 'sk' | 'cu'
let regRole = 'sk';
let agreedToTerms = true;

/* ── Shorthand helpers ───────────────────────────────────── */


const $ = id => [Link](id);
const $$ = sel => [Link](sel);

/* ════════════════════════════════════════════════════════════
SESSION (localStorage — no backend needed)
Key: 'rp_user'
Shape: { name, email, phone, role, store, address, loggedIn }
════════════════════════════════════════════════════════════ */
const Session = {
KEY: 'rp_user',
save(data) { [Link]([Link], [Link](data)); },
get() { try { return [Link]([Link]([Link])); } catch { return null; } },
clear() { [Link]([Link]); },
isLoggedIn() { const u = [Link](); return !!(u && [Link]); },
};

/* Auto-redirect if already logged in */


(function autoRedirect() {
if (![Link]()) return;
const u = [Link]();
const dest = [Link] === 'sk'
? 'pages/[Link]'
: 'pages/[Link]';
[Link](dest);
})();

/* ════════════════════════════════════════════════════════════
TAB SWITCHER (Login ↔ Register)
════════════════════════════════════════════════════════════ */
function showTab(tab) {
const tLogin = $('tab-login');

Page 94 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const tReg = $('tab-register');
const pLogin = $('panel-login');
const pReg = $('panel-register');

[Link]('active-login', 'active-register');
[Link]('active-login', 'active-register');

if (tab === 'login') {


[Link]('active-login');
[Link]('active');
[Link]('active');
} else {
[Link]('active-register');
[Link]('active');
[Link]('active');
}
}

/* ════════════════════════════════════════════════════════════
LOGIN ROLE SWITCHER
════════════════════════════════════════════════════════════ */
function setLoginRole(role) {
loginRole = role;

const skBtn = $('login-sk-btn');


const cuBtn = $('login-cu-btn');
const headline = $('login-headline');
const sub = $('login-sub');
const loginBtn = $('login-btn');
const btnText = $('login-btn-text');
const switchLink = $('login-switch');
const emailInput = $('login-email');
const passInput = $('login-pass');

[Link]('active-sk', 'active-cu');
[Link]('active-sk', 'active-cu');

/* highlight the matching role-preview card on the left panel */


const rpSk = $('rp-sk');
const rpCu = $('rp-cu');
if (rpSk) [Link]('active');
if (rpCu) [Link]('active');

if (role === 'sk') {


[Link]('active-sk');
[Link] = 'Welcome back 👋';
[Link] = 'Sign in as <strong style="color:var(--indigo-l)">Shopkeeper</strong> to
access your dashboard.';
[Link] = 'submit-btn sk';
[Link] = 'Sign In as Shopkeeper →';
[Link] = 'switch-link';
[Link]('a').[Link] = 'var(--indigo-l)';
[Link]('emerald');
[Link]('emerald');
if (rpSk) [Link]('active');
} else {
[Link]('active-cu');
[Link] = 'Good to see you 🛒';
[Link] = 'Sign in as <strong style="color:var(--emerald)">Customer</strong> to shop
nearby stores.';
[Link] = 'submit-btn cu';
[Link] = 'Sign In as Customer →';
[Link] = 'switch-link cu';
[Link]('a').[Link] = 'var(--emerald)';
[Link]('emerald');
[Link]('emerald');
if (rpCu) [Link]('active');
}
}

Page 95 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

/* ════════════════════════════════════════════════════════════
REGISTER ROLE SWITCHER
════════════════════════════════════════════════════════════ */
function setRegRole(role) {
regRole = role;

const skBtn = $('reg-sk-btn');


const cuBtn = $('reg-cu-btn');
const headline = $('reg-headline');
const sub = $('reg-sub');
const regBtn = $('reg-btn');
const btnText = $('reg-btn-text');
const storeGroup = $('store-name-group');
const addrGroup = $('address-group');
const agreeBox = $('agree-box');
const switchLink = $('reg-switch');

[Link]('active-sk', 'active-cu');
[Link]('active-sk', 'active-cu');

const allInputs = $$('#panel-register .form-input');

if (role === 'sk') {


[Link]('active-sk');
[Link] = 'Open your store 🏪';
[Link] = 'Join as <strong style="color:var(--indigo-l)">Shopkeeper</strong> — get
your store online in minutes.';
[Link] = 'submit-btn sk';
[Link] = 'Create Shopkeeper Account →';
[Link] = 'flex';
[Link] = 'column';
[Link] = '6px';
[Link] = 'none';
[Link] = 'agree-check checked-sk';
[Link] = '✓';
[Link] = 'switch-link';
[Link](i => [Link]('emerald'));
} else {
[Link]('active-cu');
[Link] = 'Start shopping 🛒';
[Link] = 'Join as <strong style="color:var(--emerald)">Customer</strong> — discover
shops near you.';
[Link] = 'submit-btn cu';
[Link] = 'Create Customer Account →';
[Link] = 'none';
[Link] = 'flex';
[Link] = 'column';
[Link] = '6px';
[Link] = 'agree-check checked-cu';
[Link] = '✓';
[Link] = 'switch-link cu';
[Link](i => [Link]('emerald'));
}

if (!agreedToTerms) {
[Link] = 'agree-check';
[Link] = '';
}
}

/* ════════════════════════════════════════════════════════════
PASSWORD — toggle visibility
════════════════════════════════════════════════════════════ */
function togglePass(inputId, iconEl) {
const input = $(inputId);
if ([Link] === 'password') {
[Link] = 'text';
[Link] = '🙈';
} else {

Page 96 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = 'password';
[Link] = '👁';
}
}

/* ════════════════════════════════════════════════════════════
PASSWORD — strength meter
════════════════════════════════════════════════════════════ */
function checkStrength(val) {
const segs = ['seg1', 'seg2', 'seg3', 'seg4'];
const lbl = $('strength-lbl');

[Link](id => { $(id).className = 'strength-seg'; });

if (!val) { [Link] = ''; return; }

let score = 0;
if ([Link] >= 8) score++;
if (/[A-Z]/.test(val)) score++;
if (/[0-9]/.test(val)) score++;
if (/[^A-Za-z0-9]/.test(val)) score++;

const levels = [
{ cls: 'weak', txt: 'Weak 😬', color: 'var(--rose)' },
{ cls: 'ok', txt: 'Fair 🙂', color: 'var(--amber)' },
{ cls: 'good', txt: 'Good 👍', color: 'var(--sky)' },
{ cls: 'strong', txt: 'Strong 💪', color: 'var(--emerald)' },
];

const level = levels[score - 1] || levels[0];


for (let i = 0; i < score; i++) {
$(segs[i]).[Link]([Link]);
}
[Link] = [Link];
[Link] = [Link];
}

/* ════════════════════════════════════════════════════════════
CONFIRM PASSWORD — live check
════════════════════════════════════════════════════════════ */
function initConfirmCheck() {
const confirmEl = $('reg-confirm');
if (!confirmEl) return;
[Link]('input', function () {
const pass = $('reg-pass').value;
const confirmOk = $('reg-confirm-ok');
const confirmErr = $('reg-confirm-err');

if (![Link]) {
[Link]('show');
[Link]('show');
return;
}

if ([Link] === pass) {


[Link]('show');
[Link]('show');
[Link]('error');
} else {
[Link]('show');
[Link]('show');
[Link]('error');
}
});
}

/* ════════════════════════════════════════════════════════════
AGREE CHECKBOX

Page 97 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
════════════════════════════════════════════════════════════ */
function toggleAgree() {
agreedToTerms = !agreedToTerms;
const box = $('agree-box');
if (agreedToTerms) {
[Link] = '✓';
[Link] = regRole === 'sk' ? 'agree-check checked-sk' : 'agree-check checked-cu';
} else {
[Link] = '';
[Link] = 'agree-check';
}
}

/* ════════════════════════════════════════════════════════════
VALIDATION HELPERS
════════════════════════════════════════════════════════════ */
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function validatePhone(phone) {
return /^[\d\s\+\-]{7,15}$/.test([Link]());
}

function showFieldError(errId, show) {


const errEl = $(errId);
if (!errEl) return;
const parent = [Link]('.form-group');
const wrap = parent ? [Link]('.input-wrap') : null;
const input = wrap ? [Link]('.form-input') : null;

if (show) {
[Link]('show');
if (input) [Link]('error');
} else {
[Link]('show');
if (input) [Link]('error');
}
}

/* ════════════════════════════════════════════════════════════
HANDLE LOGIN SUBMIT
Saves session → redirects to the correct dashboard
════════════════════════════════════════════════════════════ */
function handleLogin() {
const email = $('login-email').[Link]();
const pass = $('login-pass').value;
let valid = true;

if (!validateEmail(email)) {
showFieldError('login-email-err', true);
valid = false;
} else {
showFieldError('login-email-err', false);
}

if ([Link] < 6) {
showFieldError('login-pass-err', true);
valid = false;
} else {
showFieldError('login-pass-err', false);
}

if (!valid) return;

const btn = $('login-btn');


const txt = $('login-btn-text');
[Link] = true;
[Link] = '⟳ Signing in…';

Page 98 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

/* Make API call to backend */


fetch('[Link] {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: [Link]({
email,
password: pass,
role: loginRole
})
})
.then(res => [Link]())
.then(data => {
if (![Link]) {
throw new Error([Link] || 'Login failed');
}

/* Save session with role */


[Link]({
...[Link],
loggedIn: true,
});

[Link] = false;
[Link] = loginRole === 'sk'
? 'Sign In as Shopkeeper →'
: 'Sign In as Customer →';

showToast(
loginRole === 'sk'
? '✅ Welcome back, Shopkeeper! Redirecting…'
: '✅ Welcome back! Redirecting to your portal…',
'success'
);

/* Role-based redirect */
setTimeout(() => {
[Link] = loginRole === 'sk'
? 'pages/[Link]'
: 'pages/[Link]';
}, 1200);
})
.catch(err => {
[Link] = false;
[Link] = loginRole === 'sk'
? 'Sign In as Shopkeeper →'
: 'Sign In as Customer →';
showToast(`❌ ${[Link]}`, 'error');
});
}

/* ════════════════════════════════════════════════════════════
HANDLE REGISTER SUBMIT
Saves session → redirects to the correct dashboard
════════════════════════════════════════════════════════════ */
function handleRegister() {
const name = $('reg-name').[Link]();
const phone = $('reg-phone').[Link]();
const email = $('reg-email').[Link]();
const pass = $('reg-pass').value;
const confirm = $('reg-confirm').value;
const store = $('reg-store').[Link]();
const address = $('reg-address').[Link]();
let valid = true;

if ([Link] < 2) {
showFieldError('reg-name-err', true); valid = false;
} else { showFieldError('reg-name-err', false); }

Page 99 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

if (!validatePhone(phone)) {
showFieldError('reg-phone-err', true); valid = false;
} else { showFieldError('reg-phone-err', false); }

if (!validateEmail(email)) {
showFieldError('reg-email-err', true); valid = false;
} else { showFieldError('reg-email-err', false); }

if ([Link] < 8) {
showFieldError('reg-pass-err', true); valid = false;
} else { showFieldError('reg-pass-err', false); }

if (pass !== confirm) {


showFieldError('reg-confirm-err', true); valid = false;
} else { showFieldError('reg-confirm-err', false); }

if (regRole === 'sk' && [Link] < 2) {


showFieldError('reg-store-err', true); valid = false;
} else { showFieldError('reg-store-err', false); }

if (!agreedToTerms) {
showToast('Please agree to the Terms of Service to continue.', 'error');
valid = false;
}

if (!valid) return;

const btn = $('reg-btn');


const txt = $('reg-btn-text');
[Link] = true;
[Link] = '⟳ Creating account…';

/* Make API call to backend */


fetch('[Link] {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: [Link]({
name,
email,
password: pass,
role: regRole
})
})
.then(res => [Link]())
.then(data => {
if ([Link] === 'Email already registered') {
throw new Error('Email already registered');
}
if (![Link] || [Link] !== 'Registration successful') {
throw new Error([Link] || 'Registration failed');
}

/* Save session with role and extra profile data */


[Link]({
name,
email,
phone,
role: regRole, // 'sk' or 'cu'
store: regRole === 'sk' ? store : null,
address: regRole === 'cu' ? address : null,
loggedIn: true,
});

[Link] = false;
[Link] = regRole === 'sk'
? 'Create Shopkeeper Account →'
: 'Create Customer Account →';

Page 100 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const firstName = [Link](' ')[0];
showToast(`🎉 Account created! Welcome, ${firstName}!`, 'success');

/* Role-based redirect */
setTimeout(() => {
[Link] = regRole === 'sk'
? 'pages/[Link]'
: 'pages/[Link]';
}, 1400);
})
.catch(err => {
[Link] = false;
[Link] = regRole === 'sk'
? 'Create Shopkeeper Account →'
: 'Create Customer Account →';
showToast(`❌ ${[Link]}`, 'error');
});
}

/* ════════════════════════════════════════════════════════════
TOAST NOTIFICATIONS
════════════════════════════════════════════════════════════ */
function showToast(msg, type = 'info') {
const container = $('toasts');
if (!container) return;

const colors = {
success: 'var(--emerald)',
error: 'var(--rose)',
info: 'var(--sky)',
warn: 'var(--amber)',
};
const icons = {
success: '✅',
error: '❌',
info: '💡',
warn: '⚠️',
};

const toast = [Link]('div');


[Link] = 'toast';
[Link] = `3px solid ${colors[type] || 'var(--indigo)'}`;
[Link] = `
<span class="toast-icon">${icons[type] || 'ℹ️'}</span>
<span class="toast-msg">${msg}</span>
`;

[Link](toast);

setTimeout(() => {
[Link]('out');
setTimeout(() => [Link](), 350);
}, 4000);
}

/* ════════════════════════════════════════════════════════════
INITIALISE (runs once DOM is ready)
════════════════════════════════════════════════════════════ */
[Link]('DOMContentLoaded', () => {
setLoginRole('sk');
setRegRole('sk');

const rpSk = $('rp-sk');


if (rpSk) [Link]('active');

initConfirmCheck();

/* Expose to inline onclick handlers */


[Link] = showTab;

Page 101 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = setLoginRole;
[Link] = setRegRole;
[Link] = togglePass;
[Link] = checkStrength;
[Link] = toggleAgree;
[Link] = handleLogin;
[Link] = handleRegister;
[Link] = showToast;
});

Page 102 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

13. js / [Link]
📄 File: js/[Link]
Lines of code: 1314

/* ============================================================
RetailPro — Shopkeeper Dashboard JavaScript
File: js/[Link]

Covers:
- Session guard (redirect if not logged in as sk)
- Data layer (localStorage-backed store)
- Sidebar (collapse, mobile open/close)
- Navigation (page switching)
- Global search (live filter)
- Notifications (panel open/close, mark all read)
- Dashboard (KPI counters, charts, best-sellers, recent txns)
- Products (CRUD, search, filter, drawer form, validation)
- Inventory (stock health, restock modal)
- Orders/Sales (table, filter by status)
- Analytics (charts, category breakdown)
- Tally/Accounts (ledger, add expense)
- Invoices (create, line items, live preview, history)
- Settings (profile update, operating hours, notifications)
- Location (map placeholder interactions)
- Toast (typed notifications)
- Confirm modal (delete confirmation)
- Logout
- Charts ([Link] wrappers)
- Responsive helpers
============================================================ */

'use strict';

/* ════════════════════════════════════════════════════════
HELPERS
════════════════════════════════════════════════════════ */
const $ = id => [Link](id);
const $$ = sel => [Link](sel);
const fmt = n => '₹' + [Link](n).toLocaleString('en-IN');
const uid = () => [Link]().toString(36).slice(2, 10);
const now = () => new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short',
year:'numeric' });

/* ════════════════════════════════════════════════════════
SESSION (mirrors [Link] Session object)
════════════════════════════════════════════════════════ */
const Session = {
KEY: 'rp_user',
get() { try { return [Link]([Link]([Link])); } catch { return null; } },
clear() { [Link]([Link]); },
isLoggedIn() { const u = [Link](); return !!(u && [Link]); },
};

/* Guard — redirect to login if not a logged-in shopkeeper */


(function guard() {
const u = [Link]();
if (!u || ![Link] || [Link] !== 'sk') {
[Link] = '../[Link]';
}
})();

/* ════════════════════════════════════════════════════════
DATA STORE (localStorage-backed)

Page 103 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
════════════════════════════════════════════════════════ */
const DB = {
KEY: 'rp_sk_data',

defaults() {
const user = [Link]();
return {
shop: {
name: user?.store || 'Rajan General Stores',
tagline: 'Your Neighbourhood Store',
gst: '22AAAAA0000A1Z5',
phone: '+91 98765 43210',
email: user?.email || 'shop@[Link]',
upi: 'rajan@upi',
address: 'No.12, Gandhi Nagar, Chennai – 600020',
lat: '13.0827',
lng: '80.2707',
hours: { 'Mon–Sat': { open:'08:00', close:'21:00', active:true }, Sunday:
{ open:'09:00', close:'18:00', active:false } },
notifs: { lowStock:true, newOrder:true, weeklyReport:true, tips:false },
},
products: [
{ id:'p1', name:'Basmati Rice 5kg', cat:'Grains', price:320, cost:240, stock:83,
threshold:20, status:'active', sku:'GR-001', emoji:'🌾' },
{ id:'p2', name:'Sunflower Oil 1L', cat:'Oils', price:158, cost:120, stock:9,
threshold:15, status:'low', sku:'OL-002', emoji:'🫒' },
{ id:'p3', name:'Toor Dal 1kg', cat:'Pulses', price:148, cost:110, stock:0,
threshold:10, status:'out', sku:'PL-003', emoji:'🫘' },
{ id:'p4', name:'Aashirvaad Atta 5kg', cat:'Grains', price:275, cost:210, stock:45,
threshold:15, status:'active', sku:'GR-004', emoji:'🌾' },
{ id:'p5', name:'Salt 1kg', cat:'Spices', price:28, cost:18, stock:4,
threshold:20, status:'low', sku:'SP-005', emoji:'🧂' },
{ id:'p6', name:'Sugar 1kg', cat:'Essentials',price:45, cost:32, stock:6,
threshold:15, status:'low', sku:'ES-006', emoji:'🍚' },
{ id:'p7', name:'Maggi Noodles 70g', cat:'Packaged', price:14, cost:10, stock:210,
threshold:50, status:'active', sku:'PK-007', emoji:'🍜' },
{ id:'p8', name:"Parle-G Biscuits", cat:'Packaged', price:10, cost:7, stock:340,
threshold:100,status:'active', sku:'PK-008', emoji:'🍪' },
],
orders: [
{ id:'#1042', customer:'Mohan Kumar', date:'27 Mar 2026', items:3, amount:1200,
status:'paid', payMode:'Cash' },
{ id:'#1041', customer:'Walk-in', date:'27 Mar 2026', items:1, amount:840,
status:'paid', payMode:'UPI' },
{ id:'#1040', customer:'Priya Sharma', date:'27 Mar 2026', items:5, amount:2100,
status:'paid', payMode:'Cash' },
{ id:'#1039', customer:'Raj Patel', date:'26 Mar 2026', items:2, amount:476,
status:'paid', payMode:'Card' },
{ id:'#1038', customer:'Walk-in', date:'26 Mar 2026', items:4, amount:1380,
status:'paid', payMode:'Cash' },
{ id:'#1037', customer:'Suresh V.', date:'26 Mar 2026', items:2, amount:616,
status:'pending', payMode:'Credit'},
{ id:'#1036', customer:'Anita Devi', date:'25 Mar 2026', items:1, amount:158,
status:'paid', payMode:'UPI' },
{ id:'#1035', customer:'Walk-in', date:'25 Mar 2026', items:6, amount:2780,
status:'paid', payMode:'Cash' },
],
ledger: [
{ id:uid(), date:'27 Mar', desc:'Invoice #1042 — Mohan Kumar', credit:1200,
debit:null },
{ id:uid(), date:'27 Mar', desc:'Invoice #1041 — Walk-in', credit:840,
debit:null },
{ id:uid(), date:'27 Mar', desc:'Electricity Bill', credit:null,
debit:800 },
{ id:uid(), date:'26 Mar', desc:'Invoice #1040 — Priya Sharma', credit:2100,
debit:null },
{ id:uid(), date:'26 Mar', desc:'Staff Salary — Advance', credit:null,
debit:5000 },
{ id:uid(), date:'25 Mar', desc:'Invoice #1038 — Walk-in', credit:1380,
debit:null },

Page 104 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
{ id:uid(), date:'25 Mar', desc:'Restock — Sunflower Oil', credit:null,
debit:6000 },
],
invNextId: 1043,
};
},

load() {
try {
const raw = [Link]([Link]);
return raw ? [Link](raw) : [Link]();
} catch { return [Link](); }
},

save(data) {
[Link]([Link], [Link](data));
},
};

/* Working copy */
let state = [Link]();

function persist() { [Link](state); }

/* Product status compute */


function computeStatus(p) {
if ([Link] === 0) return 'out';
if ([Link] < [Link]) return 'low';
return 'active';
}

/* ════════════════════════════════════════════════════════
WEEKLY / MONTHLY CHART DATA
════════════════════════════════════════════════════════ */
const weeklyData = [
{ label:'Mon', val:12400, orders:31 },
{ label:'Tue', val:18200, orders:46 },
{ label:'Wed', val:15800, orders:40 },
{ label:'Thu', val:22100, orders:56 },
{ label:'Fri', val:19600, orders:49 },
{ label:'Sat', val:28900, orders:73 },
{ label:'Sun', val:14280, orders:38 },
];
const monthlyData = [
{ label:'Jan', val:242000 }, { label:'Feb', val:218000 }, { label:'Mar', val:284000 },
{ label:'Apr', val:196000 }, { label:'May', val:310000 }, { label:'Jun', val:289000 },
{ label:'Jul', val:334000 }, { label:'Aug', val:298000 }, { label:'Sep', val:267000 },
{ label:'Oct', val:312000 }, { label:'Nov', val:356000 }, { label:'Dec', val:421000 },
];

let chartMode = 'weekly';


let revenueChart = null;
let analyticsChart = null;

/* ════════════════════════════════════════════════════════
TOAST (reuses [Link] pattern)
════════════════════════════════════════════════════════ */
function showToast(msg, type = 'info') {
const container = $('toasts');
const colors = { success:'var(--emerald)', error:'var(--rose)', info:'var(--sky)', warn:'var(--
amber)' };
⚠️
const icons = { success:'', error:'', info:'💡', warn:'⚠️' };

const el = [Link]('div');
[Link] = 'toast';
[Link] = `3px solid ${colors[type] || [Link]}`;
[Link] = `<span class="toast-icon">${icons[type] || [Link]}</span><span class="toast-
msg">${msg}</span>`;
[Link](el);

Page 105 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

setTimeout(() => { [Link]('out'); setTimeout(() => [Link](), 350); }, 4000);


}

/* ════════════════════════════════════════════════════════
CONFIRM MODAL
════════════════════════════════════════════════════════ */
let _confirmCb = null;

function openConfirm(title, msg, cb) {


$('confirm-title').textContent = title;
$('confirm-msg').textContent = msg;
_confirmCb = cb;
$('confirm-modal').[Link]('open');
}
function closeConfirm() { $('confirm-modal').[Link]('open'); _confirmCb = null; }
function doConfirm() { if (_confirmCb) _confirmCb(); closeConfirm(); }

/* ════════════════════════════════════════════════════════
SIDEBAR
════════════════════════════════════════════════════════ */
let sidebarCollapsed = false;

function toggleSidebar() {
sidebarCollapsed = !sidebarCollapsed;
const sb = $('sidebar');
[Link]('collapsed', sidebarCollapsed);
}

function openMobSidebar() {
$('sidebar').[Link]('mob-open');
$('sb-overlay').[Link]('open');
}
function closeMobSidebar() {
$('sidebar').[Link]('mob-open');
$('sb-overlay').[Link]('open');
}

/* ════════════════════════════════════════════════════════
PAGE NAVIGATION
════════════════════════════════════════════════════════ */
let currentPage = 'dashboard';

function navigate(page) {
if (currentPage === page) return;
currentPage = page;

/* Hide all page sections */


$$('.page-section').forEach(s => [Link]('active'));
const target = $('page-' + page);
if (target) [Link]('active');

/* Update sidebar active item */


$$('.sb-item').forEach(i => [Link]('active'));
const sbItem = [Link](`.sb-item[data-page="${page}"]`);
if (sbItem) [Link]('active');

/* Update mobile bottom nav */


$$('.mob-nav-item').forEach(i => [Link]('active'));
const mobItem = [Link](`.mob-nav-item[data-page="${page}"]`);
if (mobItem) [Link]('active');

/* Update page title in navbar */


const titles = {
dashboard:'Dashboard', products:'Products', inventory:'Inventory',
orders:'Sales & Orders', analytics:'Analytics', tally:'Tally / Accounts',
invoices:'Invoices', settings:'Settings', location:'Shop Location',
};

Page 106 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const el = $('nb-page-title');
if (el) [Link] = titles[page] || '';

/* Close mobile sidebar */


closeMobSidebar();

/* Scroll content to top */


const content = $('content');
if (content) [Link] = 0;

/* Render the page */


renderPage(page);
}

function renderPage(page) {
const map = {
dashboard : renderDashboard,
products : renderProducts,
inventory : renderInventory,
orders : renderOrders,
analytics : renderAnalytics,
tally : renderTally,
invoices : renderInvoices,
settings : renderSettings,
location : renderLocation,
};
if (map[page]) map[page]();
}

/* ════════════════════════════════════════════════════════
GLOBAL SEARCH
════════════════════════════════════════════════════════ */
function handleGlobalSearch(q) {
if (![Link]()) return;
const lower = [Link]();

/* If on products page, filter products live */


if (currentPage === 'products') {
const filtered = [Link](p =>
[Link]().includes(lower) ||
[Link]().includes(lower) ||
[Link]().includes(lower)
);
renderProductTable(filtered);
return;
}

/* Otherwise navigate to products */


navigate('products');
setTimeout(() => {
const filtered = [Link](p =>
[Link]().includes(lower) ||
[Link]().includes(lower) ||
[Link]().includes(lower)
);
renderProductTable(filtered);
}, 50);
}

/* Clear search restores full list */


$('nb-search')?.addEventListener('input', function () {
if (![Link]() && currentPage === 'products') renderProducts();
else if ([Link]()) handleGlobalSearch([Link]);
});

/* ════════════════════════════════════════════════════════
NOTIFICATIONS PANEL
════════════════════════════════════════════════════════ */
function toggleNotifPanel() {

Page 107 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
$('notif-panel').[Link]('open');
}
function markAllRead() {
$$('.[Link]').forEach(i => [Link]('unread'));
$$('.notif-unread-dot').forEach(d => [Link]());
$('notif-dot-indicator')?.remove();
showToast('All notifications marked as read.', 'info');
}

/* Close on outside click */


[Link]('click', e => {
const panel = $('notif-panel');
const btn = $('notif-btn');
if (!panel || !btn) return;
if (![Link]([Link]) && ![Link]([Link])) {
[Link]('open');
}
});

/* ════════════════════════════════════════════════════════
DASHBOARD
════════════════════════════════════════════════════════ */
function renderDashboard() {
/* KPIs */
const totalStock = [Link]((s, p) => s + [Link], 0);
const lowCount = [Link](p => [Link] === 'low' || [Link] === 'out').length;
animateNum($('kpi-revenue'), 14280, '₹');
animateNum($('kpi-stock'), totalStock);
animateNum($('kpi-orders'), 38);
animateNum($('kpi-lowstock'), lowCount);

/* Best sellers */
const bsData = [
{ name:"Parle-G Biscuits", sold:520, revenue:5200, pct:100 },
{ name:'Basmati Rice 5kg', sold:340, revenue:28400, pct:65 },
{ name:'Maggi Noodles 70g', sold:410, revenue:5740, pct:79 },
{ name:'Sunflower Oil 1L', sold:240, revenue:19200, pct:46 },
{ name:'Aashirvaad Atta 5kg', sold:180, revenue:24750, pct:35 },
];
const colors = ['var(--indigo)', 'var(--emerald)', 'var(--sky)', 'var(--amber)', 'var(--indigo-
l)'];
const bsEl = $('best-sellers-list');
if (bsEl) {
[Link] = [Link]((b, i) => `
<div class="bs-row">
<div class="bs-label">
<span><span class="bs-rank">#${i+1}</span><span
class="bs-name">${[Link]}</span></span>
<span class="bs-sold">${[Link]} sold</span>
</div>
<div class="bs-bar"><div class="bs-fill" style="width:${[Link]}%;background:$
{colors[i]}"></div></div>
<div class="bs-revenue">${fmt([Link])}</div>
</div>`).join('');
}

/* Recent transactions */
const tbody = $('dash-txn-tbody');
if (tbody) {
[Link] = [Link](0, 5).map(o => `
<tr>
<td class="td-mono">${[Link]}</td>
<td class="td-bold">${[Link]}</td>
<td class="td-muted">${[Link]}</td>
<td class="td-muted">${[Link]} item${[Link] > 1 ? 's' : ''}</td>
<td class="td-bold">${fmt([Link])}</td>
<td>${statusBadge([Link])}</td>
<td>
<div class="tbl-actions">

Page 108 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<button class="act-btn view" onclick="showToast('Viewing ${[Link]}','info')"
title="View">👁</button>
</div>
</td>
</tr>`).join('');
}

/* Chart subtitle */
const sub = $('chart-subtitle');
if (sub) [Link] = chartMode === 'weekly'
? '₹1,41,300 this week · ' + [Link]((s,d)=>s+[Link],0) + ' orders'
: '₹33,27,000 this year';

buildRevenueChart();
}

/* Animated counter */
function animateNum(el, target, prefix = '', suffix = '') {
if (!el) return;
let start = 0, dur = 900, step = 14;
const inc = target / (dur / step);
const t = setInterval(() => {
start += inc;
if (start >= target) {
[Link] = prefix + [Link](target).toLocaleString('en-IN') + suffix;
clearInterval(t);
} else {
[Link] = prefix + [Link](start).toLocaleString('en-IN') + suffix;
}
}, step);
}

/* ════════════════════════════════════════════════════════
[Link] WRAPPERS
════════════════════════════════════════════════════════ */
const CHART_DEFAULTS = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#161B22',
borderColor: '#30363D',
borderWidth: 1,
titleColor: '#8B949E',
bodyColor: '#F0F6FC',
padding: 10,
},
},
scales: {
x: {
grid: { color: 'rgba(48,54,61,0.5)', drawBorder: false },
ticks: { color: '#484F58', font: { size: 11, family: "'Sora', sans-serif" } },
},
y: {
grid: { color: 'rgba(48,54,61,0.5)', drawBorder: false },
ticks: {
color: '#484F58',
font: { size: 11, family: "'Sora', sans-serif" },
callback: v => '₹' + (v >= 1000 ? [Link](v/1000) + 'k' : v),
},
},
},
};

function buildRevenueChart() {
const canvas = $('revenueChart');
if (!canvas) return;
if (revenueChart) { [Link](); revenueChart = null; }

Page 109 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const data = chartMode === 'weekly' ? weeklyData : monthlyData;

revenueChart = new Chart(canvas, {


type: 'line',
data: {
labels: [Link](d => [Link]),
datasets: [{
data: [Link](d => [Link]),
borderColor: '#5B63FE',
borderWidth: 2.5,
backgroundColor: ctx => {
const g = [Link](0, 0, 0, 220);
[Link](0, 'rgba(91,99,254,0.25)');
[Link](1, 'rgba(91,99,254,0.02)');
return g;
},
fill: true,
tension: 0.42,
pointBackgroundColor: '#5B63FE',
pointRadius: 4,
pointHoverRadius: 6,
pointBorderColor: '#161B22',
pointBorderWidth: 2,
}],
},
options: { ...CHART_DEFAULTS },
});
}

function switchChart(mode, el) {


chartMode = mode;
$$('.time-tab').forEach(b => [Link]('active'));
if (el) [Link]('active');
buildRevenueChart();
const sub = $('chart-subtitle');
if (sub) [Link] = mode === 'weekly'
? '₹1,41,300 this week · ' + [Link]((s,d)=>s+[Link],0) + ' orders'
: '₹33,27,000 this year';
}

function buildAnalyticsChart() {
const canvas = $('analyticsChart');
if (!canvas) return;
if (analyticsChart) { [Link](); analyticsChart = null; }

analyticsChart = new Chart(canvas, {


type: 'bar',
data: {
labels: [Link](d => [Link]),
datasets: [{
data: [Link](d => [Link]),
backgroundColor: [Link]((_,i) => i === 2 ? '#5B63FE' : 'rgba(91,99,254,0.22)'),
borderRadius: 6,
borderSkipped: false,
}],
},
options: { ...CHART_DEFAULTS },
});
}

/* ════════════════════════════════════════════════════════
STATUS BADGE HELPER
════════════════════════════════════════════════════════ */
function statusBadge(s) {
const map = {
active: ['badge-green', 'Active'],
low: ['badge-amber', 'Low Stock'],
out: ['badge-red', 'Out of Stock'],
paid: ['badge-green', 'Paid'],
pending: ['badge-amber', 'Pending'],

Page 110 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
draft: ['badge-gray', 'Draft'],
};
const [cls, label] = map[s] || ['badge-gray', s];
return `<span class="badge ${cls}">${label}</span>`;
}

/* ════════════════════════════════════════════════════════
PRODUCTS
════════════════════════════════════════════════════════ */
let prodFilter = 'all';
let editingProdId = null;

function renderProducts() {
updateProdFilterCounts();
renderProductTable(filteredProds());
}

function filteredProds() {
if (prodFilter === 'all') return [Link];
return [Link](p => [Link] === prodFilter);
}

function updateProdFilterCounts() {
const all = [Link];
const active = [Link](p => [Link] === 'active').length;
const low = [Link](p => [Link] === 'low').length;
const out = [Link](p => [Link] === 'out').length;
const cnt = id => { const el = $(id); if (el) [Link] = [Link] === 'all' ? all :
[Link] === 'active' ? active : [Link] === 'low' ? low : out; };
['cnt-all','cnt-active','cnt-low','cnt-out'].forEach(cnt);
$('prod-count-sub').textContent = `${all} total products in your store`;
}

function filterProducts(f, el) {


prodFilter = f;
$$('.filter-tab').forEach(t => [Link]('active'));
if (el) [Link]('active');
renderProductTable(filteredProds());
}

function renderProductTable(list) {
const tbody = $('prod-tbody');
if (!tbody) return;

if (![Link]) {
[Link] = `<tr><td colspan="9">
<div class="empty-state">
<div class="empty-icon">📦</div>
<div class="empty-title">No products found</div>
<div class="empty-sub">Try a different filter or add a new product.</div>
<button class="btn-primary btn-sm" onclick="openProductDrawer(null)">+ Add
Product</button>
</div>
</td></tr>`;
return;
}

[Link] = [Link](p => {


const m = [Link] && [Link] ? [Link]((([Link] - [Link]) / [Link]) * 100) : 0;
const marginCls = m >= 25 ? 'emerald-text' : m >= 15 ? 'amber-text' : 'rose-text';
return `
<tr>
<td class="td-emoji">${[Link]}</td>
<td>
<div class="td-name">${[Link]}</div>
<div class="td-sub">${[Link]}</div>
</td>
<td><span class="chip">${[Link]}</span></td>
<td class="td-bold">${fmt([Link])}</td>

Page 111 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<td style="font-weight:800;color:${[Link]===0?'var(--rose)':[Link]<[Link]?'var(--
amber)':'var(--text1)'}">${[Link]}</td>
<td class="${marginCls} fw700">${m}%</td>
<td>${statusBadge([Link])}</td>
<td>
<div class="tbl-actions">
<button class="act-btn edit" onclick="openProductDrawer('${[Link]}')"
title="Edit">✏️
</button>
<button class="act-btn del" onclick="confirmDeleteProduct('${[Link]}')" title="Delete">
</button>
</div>
</td>
</tr>`;
}).join('');
}

/* ── Product Drawer ── */
function openProductDrawer(id) {
editingProdId = id;
const p = id ? [Link](x => [Link] === id) : null;

$('drawer-title').textContent = p ? 'Edit Product' : 'Add New Product';


$('drawer-sub').textContent = p ? 'Update product details below.' : 'Fill in the details for the
new product.';

$('d-name').value = p?.name || '';


$('d-sku').value = p?.sku || '';
$('d-price').value = p?.price || '';
$('d-cost').value = p?.cost || '';
$('d-stock').value = p?.stock || '';
$('d-threshold').value = p?.threshold || 10;
$('d-cat').value = p?.cat || 'Grains';
$('d-img-preview').textContent = p?.emoji || '📦';

setProductStatus(p?.status === 'inactive' ? 'inactive' : 'active');


calcMargin();
clearFormErrors();

$('modal-overlay').[Link]('open');
$('drawer').[Link]('open');
}

function closeDrawer() {
$('modal-overlay').[Link]('open');
$('drawer').[Link]('open');
editingProdId = null;
}

let drawerStatus = 'active';


function setProductStatus(s) {
drawerStatus = s;
const a = $('status-btn-active');
const b = $('status-btn-inactive');
if (!a || !b) return;
if (s === 'active') {
[Link] = 'status-btn active-green';
[Link] = 'status-btn';
} else {
[Link] = 'status-btn active-gray';
[Link] = 'status-btn';
}
}

function calcMargin() {
const price = parseFloat($('d-price')?.value) || 0;
const cost = parseFloat($('d-cost')?.value) || 0;
const el = $('margin-display');
if (!el) return;
if (price && cost) {
const m = [Link](((price - cost) / price) * 100);
const cls = m >= 25 ? 'good' : m >= 15 ? 'ok' : 'low';

Page 112 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = `<span class="margin-chip ${cls}">Margin: ${m}% · Net ${fmt(price -
cost)}/unit</span>`;
} else {
[Link] = '';
}
}

function clearFormErrors() {
$$('.[Link]').forEach(e => [Link]('show'));
$$('.[Link]').forEach(e => [Link]('error'));
}

function showFieldErr(errId) {
const el = $(errId);
if (!el) return;
[Link]('show');
[Link]('.form-group')?.querySelector('.form-input')?.[Link]('error');
}

function saveProduct() {
const name = $('d-name').[Link]();
const price = parseFloat($('d-price').value) || 0;
const cost = parseFloat($('d-cost').value) || 0;
const stock = parseInt($('d-stock').value) || 0;
const thr = parseInt($('d-threshold').value)|| 10;
const cat = $('d-cat').value;
const sku = $('d-sku').[Link]() || 'PRD-' + [Link]().toString().slice(-6);

clearFormErrors();
let valid = true;
if (!name) { showFieldErr('d-name-err'); valid = false; }
if (!price || price < 1) { showFieldErr('d-price-err'); valid = false; }
if (!valid) return;

const catEmoji = { Grains:'🌾', Oils:'🫒', Pulses:'🫘', Spices:'🧂', Essentials:'🍚', Packaged:'🍪',


Beverages:'🥤', Dairy:'🥛' };
const emoji = catEmoji[cat] || '📦';
const stock_val = drawerStatus === 'inactive' ? (parseInt($('d-stock').value) || 0) : stock;
const status = drawerStatus === 'inactive' ? 'inactive' : computeStatus({ stock: stock_val,
threshold: thr });

if (editingProdId) {
const idx = [Link](p => [Link] === editingProdId);
if (idx > -1) [Link][idx] = { ...[Link][idx], name, price, cost, stock:
stock_val, threshold: thr, cat, sku, status, emoji };
showToast('✅ Product updated successfully!', 'success');
} else {
[Link]({ id: 'p' + uid(), name, price, cost, stock: stock_val, threshold: thr, cat,
sku, status, emoji });
showToast('✅ Product added successfully!', 'success');
}

persist();
closeDrawer();
renderProducts();
updateInventoryBadge();
}

function confirmDeleteProduct(id) {
const p = [Link](x => [Link] === id);
if (!p) return;
openConfirm('Delete Product', `Are you sure you want to delete "${[Link]}"? This action cannot be
undone.`, () => {
[Link] = [Link](x => [Link] !== id);
persist();
renderProducts();
showToast(' Product deleted.', 'warn');
});
}

/* Update sidebar low-stock badge */

Page 113 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
function updateInventoryBadge() {
const n = [Link](p => [Link] === 'low' || [Link] === 'out').length;
const el = $('inv-badge');
if (el) [Link] = n || '';
if (el) [Link] = n ? '' : 'none';
}

/* ════════════════════════════════════════════════════════
INVENTORY
════════════════════════════════════════════════════════ */
function renderInventory() {
const total = [Link];
const well = [Link](p => [Link] === 'active').length;
const low = [Link](p => [Link] === 'low').length;
const out = [Link](p => [Link] === 'out').length;

/* Mini stat cards */


const statsEl = $('inv-stats');
if (statsEl) {
[Link] = [
['Total SKUs', total, 'var(--indigo-l)'],
['Well Stocked', well, 'var(--emerald)'],
['Low Stock', low, 'var(--amber)'],
['Out of Stock', out, 'var(--rose)'],
].map(([l, v, c]) => `
<div class="inv-stat" style="border-top:2.5px solid ${c}">
<div class="inv-stat-val" style="color:${c}">${v}</div>
<div class="inv-stat-lbl">${l}</div>
<div class="prog-bar" style="margin-top:8px">
<div class="prog-fill"
style="width:${[Link](v/total*100)}%;background:${c}"></div>
</div>
</div>`).join('');
}

/* Low-stock alerts */
const alerts = [Link](p => [Link] === 'low' || [Link] === 'out');
const alertEl = $('low-stock-list');
if (alertEl) {
[Link] = [Link]
? [Link](p => `
<div class="inv-health-row">
<div class="inv-emoji">${[Link]}</div>
<div class="inv-info">
<div class="inv-name">${[Link]}</div>
<div class="inv-meta">SKU: ${[Link]} · Threshold: ${[Link]} units</div>
<div class="prog-bar-lg" style="margin-top:7px;max-width:180px">
<div class="prog-fill"
style="width:${[Link](100,[Link]([Link]/[Link]*100))}%;background:$
{[Link]===0?'var(--rose)':'var(--amber)'}"></div>
</div>
</div>
<div class="inv-stock">
<div class="inv-qty" style="color:${[Link]===0?'var(--rose)':'var(--amber)'}">$
{[Link]}</div>
<div class="inv-qty-lbl">units left</div>
</div>
${statusBadge([Link])}
<button class="btn-primary btn-sm" onclick="restockProduct('${[Link]}')">+
Restock</button>
</div>`).join('')
: `<div class="empty-state" style="padding:28px">
<div class="empty-icon">✅</div>
<div class="empty-title">All products well stocked</div>
</div>`;
}

/* Full inventory table */


const tbody = $('inv-tbody');
if (tbody) {

Page 114 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = [Link](p => {
const health = [Link] === 0 ? 0 : [Link](100, [Link]([Link] / ([Link] * 5) *
100));
const col = [Link] === 0 ? 'var(--rose)' : [Link] < [Link] ? 'var(--amber)' : 'var(--
emerald)';
return `
<tr>
<td><span style="font-size:20px;margin-right:8px">${[Link]}</span><span class="fw600">$
{[Link]}</span></td>
<td class="td-muted">${[Link]}</td>
<td style="font-weight:900;font-size:16px;color:${col};font-family:var(--mono)">$
{[Link]}</td>
<td class="td-muted">${[Link]}</td>
<td style="min-width:100px">
<div class="prog-bar-lg">
<div class="prog-fill" style="width:${health}%;background:${col}"></div>
</div>
</td>
<td>${statusBadge([Link])}</td>
<td>
<button class="act-btn edit" onclick="restockProduct('${[Link]}')"
title="Restock">📥</button>
</td>
</tr>`;
}).join('');
}
}

function restockProduct(id) {
const p = [Link](x => [Link] === id);
if (!p) return;
const qty = parseInt(prompt(`Restock "${[Link]}"\nEnter quantity to add:`, 50));
if (isNaN(qty) || qty <= 0) return;
[Link] += qty;
[Link] = computeStatus(p);
persist();
renderInventory();
updateInventoryBadge();
showToast(`✅ Restocked ${[Link]} +${qty} units`, 'success');
}

/* ════════════════════════════════════════════════════════
ORDERS / SALES
════════════════════════════════════════════════════════ */
let orderFilter = 'all';

function renderOrders() {
const tbody = $('orders-tbody');
if (!tbody) return;

const list = orderFilter === 'all'


? [Link]
: [Link](o => [Link] === orderFilter);

if (![Link]) {
[Link] = `<tr><td colspan="7"><div class="empty-state"><div
class="empty-icon">🧾</div><div class="empty-title">No orders found</div></div></td></tr>`;
return;
}

[Link] = [Link](o => `


<tr>
<td class="td-mono">${[Link]}</td>
<td class="td-bold">${[Link]}</td>
<td class="td-muted">${[Link]}</td>
<td class="td-muted">${[Link]} item${[Link]>1?'s':''}</td>
<td class="td-bold">${fmt([Link])}</td>
<td>${statusBadge([Link])}</td>
<td>
<div class="tbl-actions">

Page 115 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<button class="act-btn view" onclick="showToast('Viewing ${[Link]}','info')"
title="View">👁</button>
<button class="act-btn print" onclick="showToast('Printing ${[Link]}…','info')"
title="Print"></button>
</div>
</td>
</tr>`).join('');
}

function filterOrders(f, el) {


orderFilter = f;
$$('#order-filter-tabs .filter-tab').forEach(t => [Link]('active'));
if (el) [Link]('active');
renderOrders();
}

/* ════════════════════════════════════════════════════════
ANALYTICS
════════════════════════════════════════════════════════ */
function renderAnalytics() {
buildAnalyticsChart();
const cats = [
['Grains', 38, 'var(--indigo)'],
['Packaged', 25, 'var(--sky)'],
['Oils', 18, 'var(--emerald)'],
['Pulses', 12, 'var(--amber)'],
['Others', 7, 'var(--text3)'],
];
const el = $('cat-breakdown');
if (el) {
[Link] = [Link](([l, pct, c]) => `
<div class="cat-row">
<div class="cat-label-row">
<span style="font-size:13px;color:var(--text1)">${l}</span>
<span style="font-size:13px;font-weight:700;color:${c}">${pct}%</span>
</div>
<div class="cat-bar">
<div class="cat-fill" style="width:${pct}%;background:${c}"></div>
</div>
</div>`).join('');
}
}

/* ════════════════════════════════════════════════════════
TALLY / ACCOUNTS
════════════════════════════════════════════════════════ */
function renderTally() {
const total = [Link]((s, l) => s + ([Link] || 0) - ([Link] || 0), 0);

/* Ledger table */
const tbody = $('ledger-tbody');
if (tbody) {
let running = 0;
[Link] = [Link](l => {
running += ([Link] || 0) - ([Link] || 0);
return `
<tr>
<td class="td-muted">${[Link]}</td>
<td style="font-size:13px">${[Link]}</td>
<td class="ledger-credit">${[Link] ? fmt([Link]) : '—'}</td>
<td class="ledger-debit">${[Link] ? fmt([Link]) : '—'}</td>
<td style="font-weight:700;color:${running>=0?'var(--emerald)':'var(--rose)'}; font-
family:var(--mono)">${fmt([Link](running))}</td>
</tr>`;
}).join('');
}

/* Expense breakdown */
const expenseData = [

Page 116 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
{ label:'Staff Salary', val:22000, color:'var(--amber)' },
{ label:'Rent', val:18000, color:'var(--rose)' },
{ label:'Electricity', val:3200, color:'var(--sky)' },
{ label:'Miscellaneous',val:1800, color:'var(--text3)' },
];
const expTotal = [Link]((s,e) => s+[Link], 0);
const expEl = $('expense-breakdown');
if (expEl) {
[Link] = [Link](e => `
<div style="margin-bottom:14px">
<div style="display:flex;justify-content:space-between;margin-bottom:5px">
<span style="font-size:13px">${[Link]}</span>
<span style="font-weight:700;color:${[Link]}">${fmt([Link])}</span>
</div>
<div class="prog-bar">
<div class="prog-fill" style="width:${[Link]([Link]/expTotal*100)}%;background:$
{[Link]}"></div>
</div>
</div>`).join('');
}
}

function addExpense() {
const desc = $('exp-desc').[Link]();
const amount = parseFloat($('exp-amount').value);
const cat = $('exp-cat').value;

if (!desc) { showToast('⚠️ Enter expense description.', 'warn'); return; }


if (!amount || amount < 1) { showToast('⚠️ Enter a valid amount.', 'warn'); return; }

[Link]({
id: uid(),
date: now(),
desc: `${cat} — ${desc}`,
credit: null,
debit: amount,
});
persist();
renderTally();
$('exp-desc').value = '';
$('exp-amount').value = '';
showToast(`✅ Expense recorded: ${fmt(amount)}`, 'success');
}

/* ════════════════════════════════════════════════════════
INVOICES
════════════════════════════════════════════════════════ */
let invItems = [{ productId: [Link][0]?.id || 'p1', qty: 1 }];
let invPayMode = 'Cash';
let invTab = 'create';

function renderInvoices() {
renderInvItems();
updateInvPreview();
renderInvHistory();
}

function switchInvTab(tab) {
invTab = tab;
$('inv-create-panel').[Link] = tab === 'create' ? 'block' : 'none';
$('inv-history-panel').[Link] = tab === 'history' ? 'block' : 'none';
$('inv-tab-create').className = tab === 'create' ? 'btn-primary btn-sm' : 'btn-secondary btn-
sm';
$('inv-tab-history').className = tab === 'history' ? 'btn-primary btn-sm' : 'btn-secondary btn-
sm';
}

function renderInvItems() {
const tbody = $('inv-items-tbody');
if (!tbody) return;

Page 117 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = [Link]((item, i) => {
const p = [Link](x => [Link] === [Link]) || [Link][0];
return `
<tr>
<td style="min-width:160px">
<select class="form-select" style="padding:6px 9px;font-size:12.5px"
onchange="invChangeProduct(${i},[Link])">
${[Link](pr => `<option value="${[Link]}" ${[Link] === [Link] ?
'selected' : ''}>${[Link]} ${[Link]}</option>`).join('')}
</select>
</td>
<td>
<input type="number" min="1" value="${[Link]}" class="form-input"
style="width:60px;padding:6px 8px;text-align:center"
onchange="invChangeQty(${i},[Link])"/>
</td>
<td style="white-space:nowrap;font-size:13px">${fmt(p?.price || 0)}</td>
<td style="font-weight:700;white-space:nowrap">${fmt((p?.price || 0) * [Link])}</td>
<td>
${[Link] > 1
? `<button class="act-btn del" onclick="removeInvItem(${i})"
title="Remove">✕</button>`
: ''}
</td>
</tr>`;
}).join('');
updateInvPreview();
}

function addInvItem() { [Link]({ productId: [Link][0]?.id || 'p1', qty: 1 });


renderInvItems(); }
function removeInvItem(i) { [Link](i, 1); renderInvItems(); }
function invChangeProduct(i,id){ invItems[i].productId = id; renderInvItems(); }
function invChangeQty(i, v) { invItems[i].qty = [Link](1, parseInt(v) || 1);
updateInvPreview(); }

function setInvPayMode(el, mode) {


invPayMode = mode;
$$('.pay-btn').forEach(b => [Link]('active'));
if (el) [Link]('active');
updateInvPreview();
}

function updateInvPreview() {
const subtotal = [Link]((s, it) => {
const p = [Link](x => [Link] === [Link]) || [Link][0];
return s + (p?.price || 0) * [Link];
}, 0);
const gstPct = parseFloat($('inv-gst')?.value || 5);
const discPct = parseFloat($('inv-discount')?.value || 0);
const gst = [Link](subtotal * gstPct / 100);
const disc = [Link](subtotal * discPct / 100);
const total = subtotal + gst - disc;
const cust = $('inv-customer')?.value || 'Walk-in Customer';

const prev = $('inv-prev-customer');


if (prev) [Link] = cust || 'Walk-in Customer';

const pt = $('inv-prev-tbody');
if (pt) {
[Link] = [Link](it => {
const p = [Link](x => [Link] === [Link]) || [Link][0];
return `<tr>
<td style="padding:4px 0;font-size:12px">${p?.emoji} ${p?.name}</td>
<td style="text-align:center;color:#64748b;font-size:12px">×${[Link]}</td>
<td style="text-align:right;font-size:12.5px;font-weight:700;color:#1e293b">$
{fmt((p?.price||0)*[Link])}</td>
</tr>`;
}).join('');
}

Page 118 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const ptot = $('inv-prev-totals');
if (ptot) {
[Link] = `
<div class="inv-summary-row"><span>Subtotal</span><span>${fmt(subtotal)}</span></div>
${gstPct > 0 ? `<div class="inv-summary-row"><span>GST (${gstPct}%)</span><span>$
{fmt(gst)}</span></div>` : ''}
${disc > 0 ? `<div class="inv-summary-row" style="color:var(--rose)"><span>Discount ($
{discPct}%)</span><span>−${fmt(disc)}</span></div>` : ''}
<div class="inv-total-row"><span>TOTAL</span><span>${fmt(total)}</span></div>`;
}

const ppm = $('inv-prev-paymode');


if (ppm) [Link] = 'Payment: ' + invPayMode;
}

function generateInvoice() {
const cust = $('inv-customer')?.value?.trim() || 'Walk-in';
if (![Link]) { showToast('⚠️ Add at least one product.', 'warn'); return; }

const subtotal = [Link]((s, it) => {


const p = [Link](x => [Link] === [Link]) || [Link][0];
return s + (p?.price || 0) * [Link];
}, 0);
const gstPct = parseFloat($('inv-gst')?.value || 5);
const discPct = parseFloat($('inv-discount')?.value || 0);
const total = subtotal + [Link](subtotal * gstPct / 100) - [Link](subtotal * discPct /
100);
const invId = '#' + [Link]++;

const totalItems = [Link]((s, it) => s + [Link], 0);


[Link]({ id: invId, customer: cust, date: now(), items: totalItems, amount: total,
status: 'paid', payMode: invPayMode });
[Link]({ id: uid(), date: now(), desc: `Invoice ${invId} — ${cust}`, credit: total,
debit: null });
persist();

showToast(` Invoice ${invId} generated! Total: ${fmt(total)}`, 'success');


invItems = [{ productId: [Link][0]?.id || 'p1', qty: 1 }];
if ($('inv-customer')) $('inv-customer').value = '';
renderInvItems();
}

function renderInvHistory() {
const tbody = $('inv-history-tbody');
if (!tbody) return;
[Link] = [Link](o => `
<tr>
<td class="td-mono">${[Link]}</td>
<td class="td-bold">${[Link]}</td>
<td class="td-muted">${[Link]}</td>
<td class="td-bold">${fmt([Link])}</td>
<td>${statusBadge([Link])}</td>
<td>
<div class="tbl-actions">
<button class="act-btn view" onclick="showToast('Viewing ${[Link]}','info')"
title="View">👁</button>
<button class="act-btn print" onclick="showToast('Printing ${[Link]}…','info')"
title="Print"></button>
</div>
</td>
</tr>`).join('');
}

/* ════════════════════════════════════════════════════════
SETTINGS
════════════════════════════════════════════════════════ */
function renderSettings() {
const s = [Link];

/* Populate inputs */

Page 119 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const set = (id, val) => { const el = $(id); if (el) [Link] = val || ''; };
set('s-shop-name', [Link]);
set('s-tagline', [Link]);
set('s-gst', [Link]);
set('s-phone', [Link]);
set('s-email', [Link]);
set('s-upi', [Link]);

/* User info */
const user = [Link]();
const nameEl = $('s-user-name');
if (nameEl) [Link] = user?.name || [Link];
const initEl = $('s-user-initial');
if (initEl) [Link] = (user?.name || [Link] || 'R')[0].toUpperCase();

/* Operating hours */
const hoursEl = $('op-hours-config');
if (hoursEl) {
[Link] = [Link]([Link]).map(([day, v]) => `
<div class="op-hours-row">
<span class="op-day-label">${day}</span>
<input type="time" class="form-input" value="${[Link]}" style="width:110px;padding:7px
9px"
onchange="updateHours('${day}','open',[Link])"/>
<span style="font-size:12px;color:var(--text3)">to</span>
<input type="time" class="form-input" value="${[Link]}" style="width:110px;padding:7px
9px"
onchange="updateHours('${day}','close',[Link])"/>
<button class="toggle-switch ${[Link] ? 'on' : ''}"
id="toggle-${[Link](/[–\s]/g,'_')}"
onclick="toggleHours('${day}')"></button>
</div>`).join('');
}

/* Notification prefs */
const prefs = [
['lowStock', 'Low Stock Alerts', [Link]],
['newOrder', 'New Order Notifications', [Link]],
['weeklyReport', 'Weekly Revenue Report', [Link]],
['tips', 'Smart Sales Tips', [Link]],
];
const nEl = $('notif-prefs-list');
if (nEl) {
[Link] = [Link](([k, label, on]) => `
<div class="notif-pref-row">
<span class="notif-pref-label">${label}</span>
<button class="toggle-switch ${on ? 'on' : ''}" id="pref-${k}"
onclick="toggleNotifPref('${k}')"></button>
</div>`).join('');
}
}

function saveSettings() {
[Link] = $('s-shop-name')?.value?.trim() || [Link];
[Link] = $('s-tagline')?.value?.trim() || [Link];
[Link] = $('s-gst')?.value?.trim() || [Link];
[Link] = $('s-phone')?.value?.trim() || [Link];
[Link] = $('s-email')?.value?.trim() || [Link];
[Link] = $('s-upi')?.value?.trim() || [Link];
persist();
showToast('✅ Settings saved successfully!', 'success');
}

function updateHours(day, field, val) {


if ([Link][day]) [Link][day][field] = val;
}
function toggleHours(day) {
if ([Link][day]) {
[Link][day].active = ![Link][day].active;
const btn = $('toggle-' + [Link](/[–\s]/g,'_'));
if (btn) [Link]('on', [Link][day].active);

Page 120 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
}
}
function toggleNotifPref(key) {
[Link][key] = ![Link][key];
const btn = $('pref-' + key);
if (btn) [Link]('on', [Link][key]);
}

/* ════════════════════════════════════════════════════════
LOCATION
════════════════════════════════════════════════════════ */
function renderLocation() {
const s = [Link];
const set = (id, val) => { const el = $(id); if (el) [Link] = val || ''; };
set('loc-address', [Link]);
set('loc-lat', [Link]);
set('loc-lng', [Link]);
}

function saveLocation() {
[Link] = $('loc-address')?.value?.trim() || [Link];
[Link] = $('loc-lat')?.value?.trim() || [Link];
[Link] = $('loc-lng')?.value?.trim() || [Link];
persist();
showToast('📍 Shop location saved!', 'success');
}

/* ════════════════════════════════════════════════════════
LOGOUT
════════════════════════════════════════════════════════ */
function logout() {
openConfirm('Sign Out', 'Are you sure you want to sign out?', () => {
[Link]();
showToast('👋 Signed out. Redirecting…', 'info');
setTimeout(() => { [Link] = '../[Link]'; }, 1200);
});
}

/* ════════════════════════════════════════════════════════
RESPONSIVE HELPER
════════════════════════════════════════════════════════ */
function handleResize() {
const mobMenuBtn = $('mob-menu-btn');
if (!mobMenuBtn) return;
if ([Link] <= 900) {
[Link] = 'flex';
/* If sidebar was desktop-collapsed, reset for mobile */
if (!sidebarCollapsed) {
$('sidebar').[Link]('collapsed');
}
} else {
[Link] = 'none';
$('sidebar').[Link]('mob-open');
$('sb-overlay').[Link]('open');
}
}
[Link]('resize', handleResize);

/* ════════════════════════════════════════════════════════
INITIALISE
════════════════════════════════════════════════════════ */
[Link]('DOMContentLoaded', () => {

/* Populate navbar user info from session */


const user = [Link]();
const nameShortEl = $('nb-user-name');
if (nameShortEl) [Link] = user?.name || 'Shop Owner';

Page 121 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const shopNameEl = $('nb-shop-name');
if (shopNameEl) [Link] = [Link];
const sbUserNameEl = $('sb-user-name');
if (sbUserNameEl) [Link] = user?.name || 'Shop Owner';
const sbAvatarEl = $('sb-avatar');
if (sbAvatarEl) [Link] = (user?.name || 'R')[0].toUpperCase();

/* Initial inventory badge */


updateInventoryBadge();

/* Render first page */


navigate('dashboard');

/* Handle resize for responsive */


handleResize();

/* Expose to global (for inline onclick handlers) */


[Link] = navigate;
[Link] = toggleSidebar;
[Link] = openMobSidebar;
[Link] = closeMobSidebar;
[Link] = toggleNotifPanel;
[Link] = markAllRead;
[Link] = handleGlobalSearch;
[Link] = switchChart;
[Link] = openProductDrawer;
[Link] = closeDrawer;
[Link] = setProductStatus;
[Link] = calcMargin;
[Link] = saveProduct;
[Link]= confirmDeleteProduct;
[Link] = filterProducts;
[Link] = restockProduct;
[Link] = filterOrders;
[Link] = addExpense;
[Link] = switchInvTab;
[Link] = addInvItem;
[Link] = removeInvItem;
[Link] = invChangeProduct;
[Link] = invChangeQty;
[Link] = setInvPayMode;
[Link] = updateInvPreview;
[Link] = generateInvoice;
[Link] = saveSettings;
[Link] = updateHours;
[Link] = toggleHours;
[Link] = toggleNotifPref;
[Link] = saveLocation;
[Link] = logout;
[Link] = closeConfirm;
[Link] = doConfirm;
[Link] = showToast;
});

Page 122 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code

14. js / [Link]
📄 File: js/[Link]
Lines of code: 1217

/* ============================================================
RetailPro — Customer Dashboard JavaScript
File: RetailPro/js/[Link]
All features: auth guard, map, shops, cart, wishlist,
compare, notifications, orders, profile, localStorage state.
============================================================ */
'use strict';

/* ═══════════════════════════════════════════════════════════
1. SESSION & AUTH GUARD
═══════════════════════════════════════════════════════════ */
const Session = {
KEY: 'rp_user',
get() { try { return [Link]([Link]([Link])); } catch { return null; } },
clear(){ [Link]([Link]); }
};

let currentUser = null;

function initAuth() {
const u = [Link]();
if (!u || ![Link] || [Link] !== 'cu') {
[Link] = '../[Link]';
return false;
}
currentUser = u;
// Populate UI with user data
const init = ([Link] || [Link] || 'C')[0].toUpperCase();
qs('#cu-avatar').textContent = init;
qs('#cu-name').textContent = [Link] || 'Customer';
qs('#cu-email').textContent = [Link] || '';
qs('#profile-name').textContent = [Link] || 'Customer';
qs('#profile-email').textContent = [Link] || '';
qs('#edit-name').value = [Link] || '';
qs('#edit-email').value = [Link] || '';
qs('#edit-phone').value = [Link] || '';
qs('#hero-greeting').textContent = `👋 Hello, ${([Link] || 'Customer').split(' ')[0]}!`;
qs('#topbar-sub').textContent = '📍 Chengalpattu, TN · 48 shops nearby';
return true;
}

function doLogout() {
[Link]();
showToast('Logged out. See you soon!', 'info');
setTimeout(() => { [Link] = '../[Link]'; }, 900);
}

/* ═══════════════════════════════════════════════════════════
2. STATE — in-memory + localStorage
═══════════════════════════════════════════════════════════ */
const $ = id => [Link](id);
const qs = sel => [Link](sel);
const qsa = sel => [Link](sel);

const State = {
KEY: 'rp_customer_state',
load() {
try { return [Link]([Link]([Link])) || [Link](); }
catch { return [Link](); }
},
save(data) {

Page 123 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
try { [Link]([Link], [Link](data)); } catch {}
},
defaults() {
return {
cart: [
{ id:'rice', name:'Basmati Rice 5kg', shop:'Raj General Store', emoji:'🌾', price:480,
qty:1 },
{ id:'oil', name:'Sunflower Oil 1L', shop:'Fresh Mart', emoji:'🫙', price:165,
qty:2 },
{ id:'butter', name:'Amul Butter 500g', shop:'Raj General Store', emoji:'🧈', price:280,
qty:1 },
],
wishlist: ['prod-1','prod-3','prod-7'],
favShops: ['shop-1','shop-4'],
compareList: [],
recentlyViewed: ['prod-2','prod-5','prod-1'],
notifications: 4,
followedShops: ['shop-1'],
priceAlerts: [],
};
}
};

let appState = [Link]();

function saveState() { [Link](appState); }

/* ═══════════════════════════════════════════════════════════
3. SHOP DATA
═══════════════════════════════════════════════════════════ */
const SHOPS = [
{
id:'shop-1', name:'Raj General Store', cat:'Grocery & Essentials', emoji:'🏪',
rating:4.8, reviews:120, dist:0.4, distLabel:'0.4 km',
open:true, address:'12 Main Street, Chengalpattu',
phone:'+91 98765 43210', since:'2019',
tags:['Grocery','Dairy','Spices','Beverages'],
products: ['Rice', 'Oil', 'Salt', 'Butter', 'Tea', 'Biscuits'],
priceRange:'₹20 – ₹500',
coverBg:'linear-gradient(135deg,#1a2a1a,#0d1f0d)',
desc:'Your neighbourhood go-to store for all daily essentials. Freshness guaranteed.',
hours:'8:00 AM – 9:00 PM', delivery:true,
},
{
id:'shop-2', name:'TechZone Electronics', cat:'Electronics & Gadgets', emoji:'⚡',
rating:4.6, reviews:86, dist:1.2, distLabel:'1.2 km',
open:true, address:'45 North Street, Chengalpattu',
phone:'+91 87654 32109', since:'2021',
tags:['Electronics','Mobiles','Laptops','Accessories'],
products: ['iPhone 15', 'Samsung Galaxy', 'Laptop', 'Earbuds', 'Chargers'],
priceRange:'₹299 – ₹1,20,000',
coverBg:'linear-gradient(135deg,#1a1a2a,#0d0d20)',
desc:'Premium electronics with genuine warranty and expert advice.',
hours:'10:00 AM – 8:00 PM', delivery:true,
},
{
id:'shop-3', name:'Bismillah Wood Furniture', cat:'Furniture & Home', emoji:'🪑',
rating:4.9, reviews:201, dist:2.1, distLabel:'2.1 km',
open:false, address:'10 Bazulla Road, Chengalpattu',
phone:'+91 76543 21098', since:'2015',
tags:['Furniture','Sofa','Wardrobe','Dining'],
products: ['Sofa Set', 'Wardrobe', 'Dining Table', 'Bed Frame', 'Study Table'],
priceRange:'₹4,999 – ₹85,000',
coverBg:'linear-gradient(135deg,#2a1a0d,#1f120a)',
desc:'Premium handcrafted furniture. Teak, rosewood, and engineered wood options.',
hours:'9:00 AM – 6:00 PM', delivery:true,
},
{
id:'shop-4', name:'CoolBreeze Appliances', cat:'Home Appliances', emoji:'🌀',
rating:4.5, reviews:72, dist:2.9, distLabel:'2.9 km',
open:true, address:'8 Temple Road, Chengalpattu',

Page 124 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
phone:'+91 65432 10987', since:'2018',
tags:['AC','Refrigerator','Washing Machine','Microwave'],
products: ['Split AC 1.5T', 'Double Door Fridge', 'Front Load Washer', 'Microwave Oven'],
priceRange:'₹8,000 – ₹95,000',
coverBg:'linear-gradient(135deg,#0d1f2a,#0a1520)',
desc:'Authorised dealer for LG, Samsung, Voltas, and Whirlpool.',
hours:'10:00 AM – 7:00 PM', delivery:true,
},
{
id:'shop-5', name:'Green Basket Organic', cat:'Organic & Fresh', emoji:'🌿',
rating:4.7, reviews:156, dist:3.4, distLabel:'3.4 km',
open:true, address:'3 Gandhi Nagar, Chengalpattu',
phone:'+91 54321 09876', since:'2020',
tags:['Organic','Fresh Produce','Fruits','Vegetables'],
products: ['Organic Rice', 'Fresh Vegetables', 'Exotic Fruits', 'Herbal Products'],
priceRange:'₹30 – ₹800',
coverBg:'linear-gradient(135deg,#1a2a1a,#102010)',
desc:'100% organic and chemical-free. Farm-to-table freshness every day.',
hours:'6:00 AM – 9:00 PM', delivery:false,
},
{
id:'shop-6', name:'MedPlus Pharmacy', cat:'Pharmacy & Health', emoji:'💊',
rating:4.8, reviews:340, dist:4.1, distLabel:'4.1 km',
open:true, address:'22 Hospital Road, Chengalpattu',
phone:'+91 43210 98765', since:'2017',
tags:['Medicine','Supplements','Baby Care','Personal Care'],
products: ['Vitamins', 'OTC Medicines', 'Baby Products', 'Protein Powder'],
priceRange:'₹10 – ₹5,000',
coverBg:'linear-gradient(135deg,#1a0d2a,#130a1f)',
desc:'Licensed pharmacy open 24x7. Prescription and OTC medicines available.',
hours:'24 Hours', delivery:true,
},
];

/* ═══════════════════════════════════════════════════════════
4. PRODUCT DATA
═══════════════════════════════════════════════════════════ */
const PRODUCTS = [
{ id:'prod-1', name:'Basmati Rice 5kg', shop:'Raj General Store', emoji:'🌾', price:480,
oldPrice:null, rating:4.8, bg:'linear-gradient(135deg,#1a2a1a,#0d1f0d)', stock:'in', off:null },
{ id:'prod-2', name:'iPhone 15 128GB', shop:'TechZone Electronics', emoji:'📱', price:72999,
oldPrice:79999, rating:4.9, bg:'linear-gradient(135deg,#1a1a2a,#0d0d20)', stock:'in', off:9 },
{ id:'prod-3', name:'Premium Sofa 3+1+1', shop:'Bismillah Furniture', emoji:'🛋', price:24999,
oldPrice:32000, rating:4.7, bg:'linear-gradient(135deg,#2a1a0d,#1f120a)', stock:'in', off:22 },
{ id:'prod-4', name:'Split AC 1.5 Ton', shop:'CoolBreeze Appliances', emoji:'❄️', price:36990,
oldPrice:42000, rating:4.6, bg:'linear-gradient(135deg,#0d1f2a,#0a1520)', stock:'in', off:12 },
{ id:'prod-5', name:'Organic Tomatoes 1kg', shop:'Green Basket', emoji:'🍅', price:60,
oldPrice:null, rating:4.5, bg:'linear-gradient(135deg,#1a2a1a,#102010)', stock:'in', off:null },
{ id:'prod-6', name:'Sunflower Oil 5L', shop:'Raj General Store', emoji:'🫙', price:750,
oldPrice:820, rating:4.3, bg:'linear-gradient(135deg,#2a2a0d,#1a1a0a)', stock:'low', off:9 },
{ id:'prod-7', name:'Samsung Galaxy S24', shop:'TechZone Electronics', emoji:'📲', price:64999,
oldPrice:69999, rating:4.8, bg:'linear-gradient(135deg,#1a1a2a,#0d0d20)', stock:'in', off:7 },
{ id:'prod-8', name:'Amul Butter 500g', shop:'Raj General Store', emoji:'🧈', price:280,
oldPrice:null, rating:4.6, bg:'linear-gradient(135deg,#1a2a1a,#0d1f0d)', stock:'in', off:null },
{ id:'prod-9', name:'Washing Machine 7kg',shop:'CoolBreeze Appliances', emoji:'🧺', price:22990,
oldPrice:28000, rating:4.5, bg:'linear-gradient(135deg,#0d1f2a,#0a1520)', stock:'in', off:18 },
{ id:'prod-10',name:'Vitamin C 1000mg', shop:'MedPlus Pharmacy', emoji:'💊', price:299,
oldPrice:350, rating:4.7, bg:'linear-gradient(135deg,#1a0d2a,#130a1f)', stock:'in', off:15 },
{ id:'prod-11',name:'Study Table Oak', shop:'Bismillah Furniture', emoji:'🪑', price:8999,
oldPrice:11000, rating:4.4, bg:'linear-gradient(135deg,#2a1a0d,#1f120a)', stock:'out', off:18 },
{ id:'prod-12',name:'Tata Tea Premium', shop:'Raj General Store', emoji:'🍵', price:210,
oldPrice:null, rating:4.5, bg:'linear-gradient(135deg,#1a1a2a,#0d0d20)', stock:'in', off:null },
];

/* ═══════════════════════════════════════════════════════════
5. MAP DEMO (ready to connect Google Maps API)
═══════════════════════════════════════════════════════════ */
const SHOP_POSITIONS = {
'shop-1': { top:'55%', left:'47%' },
'shop-2': { top:'35%', left:'60%' },
'shop-3': { top:'68%', left:'30%' },

Page 125 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
'shop-4': { top:'42%', left:'38%' },
'shop-5': { top:'25%', left:'55%' },
'shop-6': { top:'72%', left:'62%' },
};

const MAP_COLORS = {
'shop-1':'#10D9A0','shop-2':'#5B63FE','shop-3':'#F5A623',
'shop-4':'#38BDF8','shop-5':'#A78BFA','shop-6':'#FF4D6D',
};

function buildDemoMap() {
const bg = qs('.map-demo-bg');
if (!bg) return;

// Draw pseudo roads


const roads = [
{ type:'h', top:'30%', left:'10%', width:'80%' },
{ type:'h', top:'55%', left:'5%', width:'90%' },
{ type:'h', top:'75%', left:'20%', width:'60%' },
{ type:'v', top:'10%', left:'30%', height:'80%' },
{ type:'v', top:'15%', left:'65%', height:'70%' },
{ type:'v', top:'20%', left:'50%', height:'60%' },
];
[Link](r => {
const el = [Link]('div');
if ([Link] === 'h') {
[Link] = 'map-road-h';
[Link] = `top:${[Link]};left:${[Link]};width:${[Link]}`;
} else {
[Link] = 'map-road-v';
[Link] = `top:${[Link]};left:${[Link]};height:${[Link]}`;
}
[Link](el);
});

// User location
const userDot = [Link]('div');
[Link] = 'user-location-dot';
[Link] = 'top:55%;left:47%;position:absolute;transform:translate(-50%,-50%)';
[Link] = 'Your location';
[Link](userDot);

// Shop markers
[Link](shop => {
const pos = SHOP_POSITIONS[[Link]] || { top:'50%', left:'50%' };
const marker = [Link]('div');
[Link] = 'map-marker';
[Link] = `top:${[Link]};left:${[Link]}`;
[Link] = [Link];
[Link] = `
<div class="marker-label">${[Link]}</div>
<div class="marker-pin" style="background:${MAP_COLORS[[Link]]}">
<span>${[Link]}</span>
</div>
<div class="marker-dot"></div>
`;
[Link]('click', () => selectShopOnMap([Link]));
[Link](marker);
});
}

function selectShopOnMap(shopId) {
// Highlight list card
qsa('.shop-list-card').forEach(c => [Link]('active'));
const card = qs(`[data-shop-id="${shopId}"]`);
if (card) {
[Link]('active');
[Link]({ behavior:'smooth', block:'nearest' });
}
// Highlight marker
qsa('.map-marker').forEach(m => {

Page 126 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const pin = [Link]('.marker-pin');
const color = MAP_COLORS[[Link]];
[Link] = [Link] === shopId
? 'rotate(-45deg) scale(1.2)'
: 'rotate(-45deg)';
});
}

/* ──────────────────────────────────────────
Google Maps API Integration Stub
To activate: replace YOUR_API_KEY below
and call initGoogleMap() instead of buildDemoMap()
────────────────────────────────────────── */
const GOOGLE_MAPS_API_KEY = 'YOUR_API_KEY_HERE'; // <── replace this

function initGoogleMap() {
// Called when Google Maps script loads (callback=initGoogleMap in URL)
const map = new [Link]([Link]('map'), {
center: { lat: 12.6921, lng: 79.9768 }, // Chengalpattu
zoom: 14,
styles: [
{ elementType:'geometry', stylers:[{ color:'#0d1117' }] },
{ elementType:'[Link]', stylers:[{ color:'#8B949E' }] },
{ elementType:'[Link]', stylers:[{ color:'#0D1117' }] },
{ featureType:'road', elementType:'geometry', stylers:[{ color:'#21262D' }] },
{ featureType:'road', elementType:'[Link]', stylers:[{ color:'#30363D' }] },
{ featureType:'water', elementType:'geometry', stylers:[{ color:'#0d1b2a' }] },
],
});

// User marker
new [Link]({
position: { lat:12.6921, lng:79.9768 }, map,
icon: { path: [Link], scale:8, fillColor:'#38BDF8', fillOpacity:1,
strokeColor:'white', strokeWeight:2 },
title: 'Your location'
});

// Shop markers
const shopCoords = {
'shop-1':{ lat:12.6940, lng:79.9780 }, 'shop-2':{ lat:12.6960, lng:79.9810 },
'shop-3':{ lat:12.6900, lng:79.9750 }, 'shop-4':{ lat:12.6930, lng:79.9740 },
'shop-5':{ lat:12.6970, lng:79.9795 }, 'shop-6':{ lat:12.6895, lng:79.9825 },
};
[Link](shop => {
const coords = shopCoords[[Link]];
if (!coords) return;
const marker = new [Link]({
position: coords, map,
label: { text: [Link], fontSize:'18px' },
title: [Link],
});
[Link]('click', () => {
selectShopOnMap([Link]);
openShopModal([Link]);
});
});
}

/* To load Google Maps dynamically: */


function loadGoogleMapsAPI() {
if (GOOGLE_MAPS_API_KEY === 'YOUR_API_KEY_HERE') {
// No key — use demo map
buildDemoMap();
return;
}
// Remove demo bg, show real map div
const demo = qs('.map-demo-bg');
if (demo) { [Link] = 'none'; $('map').[Link] = 'block'; }

const script = [Link]('script');

Page 127 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = `[Link]
{GOOGLE_MAPS_API_KEY}&callback=initGoogleMap`;
[Link] = true;
[Link] = true;
[Link] = initGoogleMap;
[Link](script);
}

/* ═══════════════════════════════════════════════════════════
6. RENDER SHOPS
═══════════════════════════════════════════════════════════ */
function renderShopListPanel() {
const container = $('shop-list-panel');
if (!container) return;
[Link] = [Link](s => `
<div class="shop-list-card" data-shop-id="${[Link]}" onclick="selectShopOnMap('${[Link]}')">
<div class="slc-header">
<span class="slc-icon">${[Link]}</span>
<div>
<div class="slc-name">${[Link]}</div>
<div class="slc-cat">${[Link]}</div>
</div>
</div>
<div class="slc-meta">
<span class="slc-dist">📍 ${[Link]}</span>
<span>⭐ ${[Link]}</span>
<span class="slc-open ${[Link]?'open':'closed'}">${[Link]?'● Open':'● Closed'}</span>
</div>
<div class="slc-actions">
<button class="slc-btn primary" onclick="[Link]();openShopModal('$
{[Link]}')">View Shop</button>
<button class="slc-btn" onclick="[Link]();sendEnquiry('$
{[Link]}')">Enquire</button>
<button class="slc-fav-btn ${[Link]([Link])?'active':''}"
onclick="[Link]();toggleFavShop('${[Link]}',this)">
${[Link]([Link])?'❤️':'🤍'}
</button>
</div>
</div>
`).join('');
}

function renderShopsGrid(shops, containerId) {


const container = $(containerId);
if (!container) return;
[Link] = [Link](s => `
<div class="shop-card" onclick="openShopModal('${[Link]}')">
<div class="shop-card-cover" style="background:${[Link]}">
${[Link]}
<div class="shop-card-cover-badge" style="color:${[Link]?'var(--emerald)':'var(--
rose)'}">
${[Link]?'● Open':'● Closed'}
</div>
</div>
<div class="shop-card-body">
<div class="shop-card-name">${[Link]}</div>
<div class="shop-card-meta">
<div class="shop-card-meta-row">📍 ${[Link]} &nbsp;·&nbsp; ${[Link]}</div>
<div class="shop-card-meta-row">🕐 ${[Link]}</div>
</div>
<div class="shop-card-footer">
<div class="shop-card-rating">⭐ ${[Link]} <span style="color:var(--text2);font-
weight:400">(${[Link]})</span></div>
<div class="shop-card-actions">
<button class="sca-btn sca-primary"
onclick="[Link]();openShopModal('${[Link]}')">View →</button>
<button class="sca-btn sca-heart ${[Link]([Link])?'fav':''}"
onclick="[Link]();toggleFavShop('${[Link]}',this)">
${[Link]([Link])?'❤️':'🤍'}
</button>
</div>

Page 128 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>
</div>
</div>
`).join('');
}

/* ═══════════════════════════════════════════════════════════
7. RENDER PRODUCTS
═══════════════════════════════════════════════════════════ */
function renderProducts(products, containerId) {
const container = $(containerId);
if (!container) return;
[Link] = [Link](p => {
const inWish = [Link]([Link]);
const inCompare = [Link]([Link]);
const stockTag = [Link] === 'in' ? '<span class="prod-stock-tag badge-emerald">● In
Stock</span>'
: [Link] === 'low' ? '<span class="prod-stock-tag"
style="background:var(--amber-dim);color:var(--amber)">⚠ Low Stock</span>'
: '<span class="prod-stock-tag badge-rose">✕ Out of Stock</span>';
const offerTag = [Link] ? `<span class="prod-offer-tag">${[Link]}% OFF</span>` : '';
const oldPriceHtml = [Link] ? `<span class="prod-old-price">₹$
{[Link]('en-IN')}</span>` : '';
return `
<div class="prod-card" data-prod-id="${[Link]}">
<div class="prod-thumb" style="background:${[Link]}">
${[Link]}
${stockTag}
<button class="prod-wish-btn ${inWish?'active':''}"
onclick="toggleWishlist('${[Link]}',this)" title="Wishlist">
${inWish?'❤️':'🤍'}
</button>
${offerTag}
</div>
<div class="prod-body">
<div class="prod-shop">${[Link]}</div>
<div class="prod-name">${[Link]}</div>
<div class="prod-price-row">
<span class="prod-price">₹${[Link]('en-IN')}</span>
${oldPriceHtml}
</div>
<div class="prod-footer">
<span class="prod-rating">⭐ ${[Link]}</span>
<div style="display:flex;gap:5px;align-items:center">
<button class="btn btn-sm btn-ghost" style="font-size:10px;padding:4px 8px"
onclick="toggleCompare('${[Link]}')" title="Compare">⇌</button>
<button class="prod-add-btn ${[Link]==='out'?'':''}"
${[Link]==='out'?'disabled':''} onclick="addToCart('${[Link]}',this)">
${[Link]==='out'?'✕':'+'}
</button>
</div>
</div>
</div>
</div>`;
}).join('');
}

/* ═══════════════════════════════════════════════════════════
8. CART SYSTEM
═══════════════════════════════════════════════════════════ */
function addToCart(prodId, btnEl) {
const prod = [Link](p => [Link] === prodId);
if (!prod || [Link] === 'out') return;

const existing = [Link](c => [Link] === prodId);


if (existing) {
[Link] += 1;
} else {
[Link]({ id:[Link], name:[Link], shop:[Link], emoji:[Link],
price:[Link], qty:1 });
}

Page 129 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
saveState();
updateCartBadges();
addRecentlyViewed(prodId);

if (btnEl) { [Link] = '✓'; [Link]('added');


setTimeout(()=>{ [Link]='+'; [Link]('added'); }, 1800); }
showToast(`${[Link]} added to cart!`, 'success');
}

function renderCart() {
const body = $('cart-body');
if (!body) return;

if (![Link]) {
[Link] = `<div style="text-align:center;padding:48px 20px">
<div style="font-size:52px;margin-bottom:12px">🛒</div>
<div style="font-size:16px;font-weight:700;margin-bottom:6px">Your cart is empty</div>
<div style="font-size:13px;color:var(--text2);margin-bottom:20px">Add products from nearby
shops</div>
<button class="btn btn-primary" onclick="switchPage('home')">Discover Shops</button>
</div>`;
return;
}

[Link] = [Link](item => `


<div class="cart-item" id="ci-${[Link]}">
<div class="cart-img">${[Link]}</div>
<div style="flex:1">
<div class="cart-item-name">${[Link]}</div>
<div class="cart-item-shop">${[Link]}</div>
<div class="cart-item-price">₹${[Link]('en-IN')}</div>
</div>
<div class="qty-ctrl">
<button class="qty-btn" onclick="changeQty('${[Link]}',-1)">−</button>
<span class="qty-val" id="qty-${[Link]}">${[Link]}</span>
<button class="qty-btn" onclick="changeQty('${[Link]}',1)">+</button>
</div>
<button class="rm-btn" onclick="removeCartItem('${[Link]}')">🗑</button>
</div>
`).join('');

updateCartSummary();
}

function changeQty(id, delta) {


const item = [Link](c => [Link] === id);
if (!item) return;
[Link] = [Link](1, [Link] + delta);
const el = $(`qty-${id}`);
if (el) [Link] = [Link];
updateCartSummary();
saveState();
}

function removeCartItem(id) {
const el = $(`ci-${id}`);
if (el) { [Link]='0'; [Link]='translateX(20px)';
[Link]='all .3s'; setTimeout(()=>[Link](),300); }
[Link] = [Link](c => [Link] !== id);
saveState();
updateCartBadges();
updateCartSummary();
showToast('Item removed from cart','error');
if (![Link]) setTimeout(renderCart, 400);
}

function clearCart() {
[Link] = [];
saveState();
updateCartBadges();
renderCart();

Page 130 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
showToast('Cart cleared','info');
}

function updateCartSummary() {
const sub = [Link]((s,c) => s + [Link] * [Link], 0);
const delivery = sub > 0 ? 30 : 0;
const el_sub = $('cart-subtotal'), el_tot = $('cart-total');
if (el_sub) el_sub.textContent = '₹' + [Link]('en-IN');
if (el_tot) el_tot.textContent = '₹' + (sub + delivery).toLocaleString('en-IN');
}

function updateCartBadges() {
const count = [Link]((s,c) => s + [Link], 0);
qsa('.cart-badge').forEach(el => { [Link] = count; [Link] = count ? 'inline' :
'none'; });
}

function applyPromo() {
const code = $('promo-input')?.[Link]().toUpperCase();
if (code === 'SAVE10') showToast('Promo applied! ₹109 off 🎉','success');
else if (code === 'FREE') showToast('Free delivery applied!','success');
else showToast('Invalid promo code','error');
}

function checkout() {
showToast('🎉 Order placed successfully!','success');
[Link] = [];
saveState();
updateCartBadges();
setTimeout(()=> switchPage('orders'), 1200);
}

/* ═══════════════════════════════════════════════════════════
9. WISHLIST
═══════════════════════════════════════════════════════════ */
function toggleWishlist(prodId, btnEl) {
const idx = [Link](prodId);
if (idx > -1) {
[Link](idx,1);
if (btnEl) { [Link]('active'); [Link] = '🤍'; }
showToast('Removed from wishlist','info');
} else {
[Link](prodId);
if (btnEl) { [Link]('active'); [Link] = '❤️'; }
showToast('Added to wishlist ❤️','success');
}
saveState();
renderWishlist();
}

function renderWishlist() {
const container = $('wishlist-container');
if (!container) return;
const wishProds = [Link](p => [Link]([Link]));
if (![Link]) {
[Link] = `<div style="text-align:center;padding:40px;grid-column:1/-1">
<div style="font-size:48px;margin-bottom:12px">💔</div>
<div style="font-size:16px;font-weight:700;margin-bottom:6px">Wishlist is empty</div>
<div style="font-size:13px;color:var(--text2)">Save products you like for later</div>
</div>`;
return;
}
renderProducts(wishProds, 'wishlist-container');
}

/* ═══════════════════════════════════════════════════════════
10. FAVOURITE SHOPS
═══════════════════════════════════════════════════════════ */
function toggleFavShop(shopId, btnEl) {
const idx = [Link](shopId);
if (idx > -1) {

Page 131 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link](idx,1);
if (btnEl) { [Link]('fav'); [Link] = '🤍'; }
showToast('Shop removed from favourites','info');
} else {
[Link](shopId);
if (btnEl) { [Link]('fav'); [Link] = '❤️'; }
showToast('Shop saved to favourites ❤️','success');
}
saveState();
renderFavShops();
}

function renderFavShops() {
const container = $('fav-shops-container');
if (!container) return;
const favs = [Link](s => [Link]([Link]));
if (![Link]) {
[Link] = `<div style="text-align:center;padding:40px;grid-column:1/-1">
<div style="font-size:48px;margin-bottom:12px">🏪</div>
<div style="font-size:15px;font-weight:700;margin-bottom:6px">No favourite shops yet</div>
<div style="font-size:13px;color:var(--text2)">Tap ❤️ on any shop to save it here</div>
</div>`;
return;
}
renderShopsGrid(favs, 'fav-shops-container');
}

function toggleFollowShop(shopId) {
const idx = [Link](shopId);
const btn = $('follow-shop-btn');
if (idx > -1) {
[Link](idx,1);
if (btn) { [Link] = '+ Follow'; [Link]('btn-primary');
[Link]('btn-secondary'); }
showToast('Unfollowed shop','info');
} else {
[Link](shopId);
if (btn) { [Link] = '✓ Following'; [Link]('btn-primary');
[Link]('btn-secondary'); }
showToast('Now following this shop!','success');
}
saveState();
}

/* ═══════════════════════════════════════════════════════════
11. COMPARE
═══════════════════════════════════════════════════════════ */
function toggleCompare(prodId) {
const idx = [Link](prodId);
if (idx > -1) {
[Link](idx,1);
showToast('Removed from compare','info');
} else {
if ([Link] >= 3) { showToast('Max 3 products can be compared','warn');
return; }
[Link](prodId);
const prod = [Link](p => [Link] === prodId);
showToast(`${prod?.name} added to compare`,'info');
}
saveState();
renderCompareBar();
}

function renderCompareBar() {
const bar = $('compare-bar');
if (!bar) return;
if (![Link]) { [Link]('visible'); return; }
[Link]('visible');
const slots = $('compare-slots');
if (!slots) return;
[Link] = [0,1,2].map(i => {

Page 132 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
const prodId = [Link][i];
const prod = prodId ? [Link](p => [Link] === prodId) : null;
return prod
? `<div class="compare-item-slot filled">${[Link]} <span style="font-size:10px;margin-
left:4px">${[Link](0,14)}…</span><div class="rm" onclick="toggleCompare('$
{prodId}')">✕</div></div>`
: `<div class="compare-item-slot">+ Add</div>`;
}).join('');
}

function openCompareModal() {
if ([Link] < 2) { showToast('Add at least 2 products to compare','warn');
return; }
const prods = [Link](p => [Link]([Link]));
// Build compare table
const modal = $('compare-modal');
const body = $('compare-modal-body');
if (!body) return;

const rows = ['name','shop','price','rating','stock'].map(field => {


const label = { name:'Product', shop:'Shop', price:'Price', rating:'Rating', stock:'Stock' }
[field];
const cells = [Link](p => {
if (field === 'price') return `<td style="font-family:var(--mono);font-
weight:700;color:var(--emerald)">₹${[Link]('en-IN')}</td>`;
if (field === 'stock') return `<td><span class="badge ${[Link]==='in'?'badge-
emerald':'badge-rose'}">${[Link]==='in'?'In Stock':'Out'}</span></td>`;
return `<td>${p[field]}</td>`;
}).join('');
return `<tr><td style="color:var(--text2);font-weight:600;font-size:12px">${label}</td>$
{cells}</tr>`;
}).join('');

[Link] = `<table class="data-table">


<thead><tr><th>Attribute</th>${[Link](p=>`<th>${[Link]}
${[Link]}</th>`).join('')}</tr></thead>
<tbody>${rows}</tbody>
</table>`;

[Link]('open');
}

/* ═══════════════════════════════════════════════════════════
12. RECENTLY VIEWED
═══════════════════════════════════════════════════════════ */
function addRecentlyViewed(prodId) {
[Link] = [Link](id => id !== prodId);
[Link](prodId);
if ([Link] > 8) [Link]();
saveState();
renderRecentlyViewed();
}

function renderRecentlyViewed() {
const container = $('recently-viewed');
if (!container) return;
const prods = [Link](id => [Link](p => [Link] === id)).filter(Boolean);
[Link] = [Link](p => `
<div class="h-scroll-item">
<div class="prod-card" style="width:150px" data-prod-id="${[Link]}"
onclick="addRecentlyViewed('${[Link]}')">
<div class="prod-thumb" style="background:${[Link]};height:80px;font-size:32px">${[Link]}
<span class="prod-stock-tag ${[Link]==='in'?'badge-emerald':'badge-rose'}"
style="font-size:9px;padding:2px 6px">${[Link]==='in'?'In Stock':'Out'}</span>
</div>
<div class="prod-body" style="padding:8px">
<div class="prod-name" style="font-size:11px">${[Link]}</div>
<div class="prod-price" style="font-size:13px;margin-top:4px">₹$
{[Link]('en-IN')}</div>
</div>
</div>

Page 133 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
</div>
`).join('');
}

/* ═══════════════════════════════════════════════════════════
13. SHOP DETAIL MODAL
═══════════════════════════════════════════════════════════ */
function openShopModal(shopId) {
const shop = [Link](s => [Link] === shopId);
if (!shop) return;

const isFollow = [Link](shopId);


const isFav = [Link](shopId);
const shopProds = [Link]((_,i) => i % [Link] ===
[Link](s=>[Link]===shopId)).slice(0,3);

$('shop-modal-content').innerHTML = `
<div class="shop-modal-hero">
<div class="shop-modal-info">
<div class="shop-modal-icon-lg">${[Link]}</div>
<div class="shop-modal-name">${[Link]}</div>
<div class="shop-modal-meta">
<div class="shop-modal-meta-row">📍 ${[Link]}</div>
<div class="shop-modal-meta-row">📞 ${[Link]}</div>
<div class="shop-modal-meta-row">⭐ ${[Link]} · ${[Link]} reviews</div>
<div class="shop-modal-meta-row">🕐 ${[Link]}</div>
<div class="shop-modal-meta-row">📦 ${[Link]}</div>
<div class="shop-modal-meta-row" style="color:${[Link]?'var(--emerald)':'var(--
rose)'}">
${[Link]?'● Open Now':'● Closed'}
</div>
</div>
<p style="font-size:12px;color:var(--text2);margin:12px 0;line-height:1.6">$
{[Link]}</p>
<div class="shop-modal-tags">${[Link](t=>`<span
class="shop-tag">${t}</span>`).join('')}</div>
</div>
<div class="shop-modal-actions">
<div class="shop-modal-stat-grid">
<div class="shop-modal-stat"><div class="sms-label">Since</div><div class="sms-val">$
{[Link]}</div></div>
<div class="shop-modal-stat"><div class="sms-label">Products</div><div class="sms-
val">${[Link]*12}+</div></div>
<div class="shop-modal-stat"><div class="sms-label">Delivery</div><div class="sms-
val">${[Link]?'✓ Yes':'✗ No'}</div></div>
<div class="shop-modal-stat"><div class="sms-label">Distance</div><div class="sms-
val">${[Link]}</div></div>
</div>
<button id="follow-shop-btn" class="btn ${isFollow?'btn-primary':'btn-secondary'} btn-
full" onclick="toggleFollowShop('${shopId}')">
${isFollow?'✓ Following':'+ Follow Shop'}
</button>
<button class="btn btn-ghost btn-full" onclick="toggleFavShop('${shopId}', this)">
${isFav?'❤️ Saved':'🤍 Save Shop'}
</button>
<button class="btn btn-ghost btn-full" onclick="sendEnquiry('${shopId}')">
💬 Quick Enquiry
</button>
<button class="btn btn-ghost btn-full" onclick="[Link]('[Link]
📞 Call Shop
</button>
</div>
</div>

<div class="modal-section-title">Available Products Snapshot</div>


<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px">
${[Link](p=>`<span class="chip" style="cursor:default">${p}</span>`).join('')}
</div>

<div class="modal-section-title">Quick Enquiry</div>


<div class="enquiry-form">

Page 134 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<input class="form-input2" placeholder="Your name" id="enq-name" value="$
{currentUser?.name||''}">
<input class="form-input2" placeholder="Phone / WhatsApp" id="enq-phone" value="$
{currentUser?.phone||''}">
</div>
<textarea class="enquiry-form" id="enq-msg"
style="background:var(--navy3);border:1px solid var(--border);border-radius:var(--
r8);padding:10px 14px;font-size:13px;color:var(--text1);font-family:var(--
font);resize:vertical;min-height:80px;outline:none;width:100%;transition:border .2s"
placeholder="Hi, I'm looking for… (product / price enquiry / availability
check)"></textarea>
<button class="btn btn-primary" onclick="submitEnquiry('${shopId}')">Send Enquiry →</button>
</div>
`;

$('shop-modal-overlay').[Link]('open');
[Link] = 'hidden';
}

function closeShopModal() {
$('shop-modal-overlay').[Link]('open');
[Link] = '';
}

function sendEnquiry(shopId) {
openShopModal(shopId);
setTimeout(() => { const enq = $('enq-msg'); if (enq) [Link](); }, 400);
}

function submitEnquiry(shopId) {
const shop = [Link](s => [Link] === shopId);
const name = $('enq-name')?.[Link]();
const msg = $('enq-msg')?.[Link]();
if (!name || !msg) { showToast('Please fill your name and message','warn'); return; }
showToast(`Enquiry sent to ${shop?.name}! They'll contact you soon.`,'success');
closeShopModal();
}

/* ═══════════════════════════════════════════════════════════
14. NOTIFICATIONS
═══════════════════════════════════════════════════════════ */
const NOTIFICATIONS = [
{ id:1, icon:'🛍', title:'Order Delivered!', body:'Your order #ORD-028 from Raj General Store has
been delivered.', time:'2 min ago', unread:true },
{ id:2, icon:'⚡', title:'Flash Sale — TechZone!', body:'iPhone 15 now at ₹72,999. Limited stock,
grab it fast!', time:'1 hr ago', unread:true },
{ id:3, icon:'📦', title:'Back in Stock!', body:'Samsung Galaxy S24 is back at TechZone
Electronics.', time:'3 hr ago', unread:true },
₹ title:'Price Drop Alert', body:'Sunflower Oil 5L dropped by ₹70 at Raj General
{ id:4, icon:'',
Store.', time:'Today', unread:true },
{ id:5, icon:'🏪', title:'New Shop Nearby!', body:'Fresh Greens Organic opened 2.3 km from you.
Check it out!', time:'Yesterday', unread:false },
{ id:6, icon:'⭐', title:'Rate Your Purchase', body:'How was Amul Butter 500g? Share your review.',
time:'2 days ago', unread:false },
{ id:7, icon:'🎉', title:'Exclusive Offer', body:'Use code SAVE10 for 10% off your next order at any
shop.', time:'3 days ago', unread:false },
];

function openNotifDrawer() {
$('notif-drawer').[Link]('open');
$('notif-overlay').[Link]('open');
[Link] = 'hidden';
[Link] = 0;
qsa('.notif-badge').forEach(b => [Link] = 'none');
saveState();
}

function closeNotifDrawer() {
$('notif-drawer').[Link]('open');
$('notif-overlay').[Link]('open');

Page 135 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
[Link] = '';
}

function renderNotifications() {
const body = $('notif-body');
if (!body) return;
[Link] = [Link](n => `
<div class="notif-item ${[Link]?'unread':''}" onclick="markNotifRead(${[Link]})">
<span class="notif-icon">${[Link]}</span>
<div style="flex:1">
<div class="notif-title">${[Link]}</div>
<div class="notif-body">${[Link]}</div>
<div class="notif-time">${[Link]}</div>
</div>
${[Link]?'<div class="notif-unread-dot"></div>':''}
</div>
`).join('');
}

function markNotifRead(id) {
const notif = [Link](n => [Link] === id);
if (notif) { [Link] = false; renderNotifications(); }
}

function markAllRead() {
[Link](n => [Link] = false);
renderNotifications();
showToast('All notifications marked as read','success');
}

/* ═══════════════════════════════════════════════════════════
15. ORDERS
═══════════════════════════════════════════════════════════ */
const ORDERS = [
{ id:'ORD-028', shop:'Raj General Store', shopEmoji:'🏪', items:['🌾 Rice ×1','🫙 Oil ×2','🧂 Salt
×1'], amount:524, status:'delivered', date:'26 Mar 2026 · 10:32 AM', timeline:
['done','done','done','done','done'] },
{ id:'ORD-027', shop:'Fresh Mart', shopEmoji:'🛒', items:['🧈 Butter ×1','🥛 Milk ×2'],
amount:320, status:'processing',date:'25 Mar 2026 · 9:15 AM', timeline:
['done','done','current','',''] },
{ id:'ORD-026', shop:'Green Basket', shopEmoji:'🌿', items:['🍪 Parle-G ×3','🍵 Tea ×1'],
amount:180, status:'pending', date:'24 Mar 2026 · 6:45 PM', timeline:['done','current','','',''] },
{ id:'ORD-025', shop:'TechZone Electronics',shopEmoji:'⚡', items:['📱 Phone Case ×1'],
amount:599, status:'delivered', date:'22 Mar 2026 · 3:00 PM', timeline:
['done','done','done','done','done'] },
];

const TIMELINE_LABELS = ['Placed','Confirmed','Packed','Shipped','Delivered'];


const STATUS_BADGE = { delivered:'badge-emerald', processing:'badge-sky', pending:'badge-amber',
cancelled:'badge-rose' };
const STATUS_TEXT = { delivered:'✓ Delivered', processing:'⟳ Processing', pending:'● Pending',
cancelled:'✕ Cancelled' };

function renderOrders() {
const container = $('orders-list');
if (!container) return;
[Link] = [Link](o => `
<div class="order-card">
<div class="order-card-header">
<div class="order-shop-icon">${[Link]}</div>
<div>
<div style="font-size:13px;font-weight:600">${[Link]}</div>
<div class="order-id">#${[Link]}</div>
<div class="order-meta">${[Link]}</div>
</div>
<div style="margin-left:auto;display:flex;flex-direction:column;align-items:flex-
end;gap:6px">
<span class="badge ${STATUS_BADGE[[Link]]}">${STATUS_TEXT[[Link]]}</span>
<div class="order-amount">₹${[Link]('en-IN')}</div>
</div>
</div>

Page 136 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
<div class="order-items-row">${[Link](i=>`<div
class="order-item-chip">${i}</div>`).join('')}</div>
<div class="order-timeline">
${TIMELINE_LABELS.map((lbl,i) => {
const s = [Link][i];
return `<div class="timeline-step ${s}">
<div class="timeline-dot">${s==='done'?'✓':s==='current'?'⟳':''}</div>
<div class="timeline-label">${lbl}</div>
</div>`;
}).join('')}
</div>
<div class="order-actions">
${[Link]==='delivered' ? `
<button class="btn btn-secondary btn-sm" onclick="showToast('Reorder
placed!','success')">🔄 Reorder</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Review
submitted!','success')">⭐ Rate Order</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Invoice
downloading...','info')">📄 Invoice</button>
` : [Link] === 'processing' ? `
<button class="btn btn-secondary btn-sm" onclick="showToast('Tracking details sent to
your phone','info')">📍 Track</button>
<button class="btn btn-secondary btn-sm" style="color:var(--rose)"
onclick="showToast('Cancellation requested','error')">✕ Cancel</button>
` : `
<button class="btn btn-secondary btn-sm">📞 Contact Shop</button>
`}
</div>
</div>
`).join('');
}

/* ═══════════════════════════════════════════════════════════
16. SEARCH — live search across shops & products
═══════════════════════════════════════════════════════════ */
function initSearch() {
const topSearch = $('topbar-search');
const heroSearch = $('hero-search-input');

[topSearch, heroSearch].forEach(input => {


if (!input) return;
[Link]('input', debounce(function() {
const q = [Link]().toLowerCase();
if (!q) { renderShopsGrid(SHOPS, 'shops-grid-home'); renderProducts([Link](0,8),
'trending-grid'); return; }
const filteredShops = [Link](s =>
[Link]().includes(q) || [Link]().includes(q) || [Link](t =>
[Link]().includes(q))
);
const filteredProds = [Link](p =>
[Link]().includes(q) || [Link]().includes(q)
);
renderShopsGrid(filteredShops, 'shops-grid-home');
renderProducts(filteredProds, 'trending-grid');
if ([Link] || [Link]) switchPage('home');
}, 300));
});
}

function heroSearch() {
const q = $('hero-search-input')?.[Link]().toLowerCase();
if (!q) return;
$('topbar-search').value = q;
$('topbar-search').dispatchEvent(new Event('input'));
}

/* ═══════════════════════════════════════════════════════════
17. FILTERS
═══════════════════════════════════════════════════════════ */
let activeFilters = { category:'All', distance:'All', rating:'All', open:false };

Page 137 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
function applyShopFilter() {
let shops = [...SHOPS];
if ([Link] !== 'All') {
shops = [Link](s => [Link](t =>
[Link]().includes([Link]())));
}
if ([Link] !== 'All') {
const maxDist = parseFloat([Link]);
shops = [Link](s => [Link] <= maxDist);
}
if ([Link] !== 'All') {
const minRating = parseFloat([Link]);
shops = [Link](s => [Link] >= minRating);
}
if ([Link]) {
shops = [Link](s => [Link]);
}
renderShopsGrid(shops, 'nearby-shops-grid');
renderShopListPanel();
}

function setMapFilter(btn, type, value) {


qsa(`[data-filter="${type}"]`).forEach(b => [Link]('active'));
[Link]('active');
activeFilters[type] = value;
applyShopFilter();
}

function toggleOpenFilter(btn) {
[Link] = ![Link];
[Link]('active', [Link]);
applyShopFilter();
}

/* ═══════════════════════════════════════════════════════════
18. PAGE NAVIGATION
═══════════════════════════════════════════════════════════ */
const PAGE_META = {
home: { title:'Discover', sub:'📍 Chengalpattu, TN · 48 shops nearby' },
shops: { title:'Nearby Shops', sub:'Browse and filter shops near you' },
products: { title:'Products', sub:'Trending and recommended items' },
cart: { title:'My Cart', sub:'Review your cart before checkout' },
wishlist: { title:'My Wishlist', sub:'Products you saved for later' },
orders: { title:'My Orders', sub:'Track and manage your orders' },
profile: { title:'My Profile', sub:'Manage your account and preferences' },
};

function switchPage(pageId) {
qsa('.page').forEach(p => [Link]('active'));
qsa('.nav-item').forEach(n => [Link]('active'));
qsa('.mbn-item').forEach(n => [Link]('active'));

const pageEl = $('page-' + pageId);


if (pageEl) [Link]('active');

// Activate sidebar nav


const navEl = qs(`[data-page="${pageId}"]`);
if (navEl) [Link]('active');

// Activate bottom nav


const mbnEl = qs(`.mbn-item[data-page="${pageId}"]`);
if (mbnEl) [Link]('active');

const meta = PAGE_META[pageId] || { title:'', sub:'' };


const tp = $('topbar-title'), ts = $('topbar-sub');
if (tp) [Link] = [Link];
if (ts) [Link] = [Link];

// Page-specific render
if (pageId === 'cart') renderCart();
if (pageId === 'wishlist') renderWishlist();

Page 138 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
if (pageId === 'orders') renderOrders();
if (pageId === 'profile') renderFavShops();

// Scroll to top
[Link]({ top:0, behavior:'smooth' });
}

/* ═══════════════════════════════════════════════════════════
19. SIDEBAR MOBILE TOGGLE
═══════════════════════════════════════════════════════════ */
function toggleSidebar() {
qs('.sidebar').[Link]('mobile-open');
qs('.sidebar-overlay').[Link]('open');
}

function closeSidebar() {
qs('.sidebar').[Link]('mobile-open');
qs('.sidebar-overlay').[Link]('open');
}

/* ═══════════════════════════════════════════════════════════
20. PRICE ALERT
═══════════════════════════════════════════════════════════ */
function setPriceAlert(prodId) {
const prod = [Link](p => [Link] === prodId);
if (!prod) return;
if ([Link](prodId)) {
showToast(`Price alert already set for ${[Link]}`,'info');
} else {
[Link](prodId);
saveState();
showToast(`Price alert set for ${[Link]} 🔔`,'success');
}
}

/* ═══════════════════════════════════════════════════════════
21. TOAST
═══════════════════════════════════════════════════════════ */
function showToast(msg, type='info') {
const container = $('toasts');
if (!container) return;
const colors = { success:'var(--emerald)', error:'var(--rose)', info:'var(--sky)', warn:'var(--
amber)' };
⚠️
const icons = { success:'', error:'', info:'💡', warn:'⚠️' };
const t = [Link]('div');
[Link] = 'toast';
[Link] = `3px solid ${colors[type]||'var(--indigo)'}`;
[Link] = `<span style="font-size:16px">${icons[type]||'ℹ️'}</span><span class="toast-msg">$
{msg}</span>`;
[Link](t);
setTimeout(() => { [Link]('out'); setTimeout(() => [Link](), 350); }, 4000);
}

/* ═══════════════════════════════════════════════════════════
22. UTILITY
═══════════════════════════════════════════════════════════ */
function debounce(fn, ms) {
let timer;
return function(...args) { clearTimeout(timer); timer = setTimeout(() => [Link](this, args), ms);
};
}

function saveProfile() {
const name = $('edit-name')?.[Link]();
const email = $('edit-email')?.[Link]();
if (!name) { showToast('Name cannot be empty','warn'); return; }
[Link] = name;
[Link] = email || [Link];
[Link] ? null : [Link]('rp_user', [Link]({...currentUser}));
[Link]('rp_user', [Link]({...currentUser, loggedIn:true}));
qs('#cu-name').textContent = name;

Page 139 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
qs('#profile-name').textContent = name;
showToast('Profile updated successfully!','success');
}

/* ═══════════════════════════════════════════════════════════
23. INIT
═══════════════════════════════════════════════════════════ */
[Link]('DOMContentLoaded', () => {
if (!initAuth()) return;

// Build map
loadGoogleMapsAPI();

// Render all sections


renderShopListPanel();
renderShopsGrid(SHOPS, 'shops-grid-home');
renderShopsGrid(SHOPS, 'nearby-shops-grid');
renderProducts([Link](0,8), 'trending-grid');
renderProducts([Link](p=>[Link]), 'offers-grid');
renderProducts([Link](0,4), 'new-arrivals-grid');
renderRecentlyViewed();
renderNotifications();
updateCartBadges();
renderCompareBar();

// Search
initSearch();

// Chip filter toggles (generic)


qsa('.chip-filter-group').forEach(group => {
[Link]('.chip').forEach(chip => {
[Link]('click', function() {
[Link]('.chip').forEach(c => [Link]('active'));
[Link]('active');
});
});
});

// Category pills
qsa('.cat-pill').forEach(pill => {
[Link]('click', function() {
qsa('.cat-pill').forEach(p => [Link]('active'));
[Link]('active');
const cat = [Link] || 'All';
[Link] = cat;
applyShopFilter();
});
});

// Hero chips
qsa('.hero-chip').forEach(chip => {
[Link]('click', function() {
qsa('.hero-chip').forEach(c => [Link]('active'));
[Link]('active');
});
});

// Close modals on overlay click


$('shop-modal-overlay')?.addEventListener('click', function(e) {
if ([Link] === this) closeShopModal();
});
$('compare-modal')?.addEventListener('click', function(e) {
if ([Link] === this) [Link]('open');
});

// Close notif drawer overlay


$('notif-overlay')?.addEventListener('click', closeNotifDrawer);

// Sidebar overlay
qs('.sidebar-overlay')?.addEventListener('click', closeSidebar);

Page 140 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran


RetailPro — Full Source Code
// Start on home
switchPage('home');

// Expose globals for inline handlers


[Link] = switchPage;
[Link] = doLogout;
[Link] = toggleSidebar;
[Link] = closeSidebar;
[Link] = openShopModal;
[Link] = closeShopModal;
[Link] = sendEnquiry;
[Link] = submitEnquiry;
[Link] = toggleFavShop;
[Link] = toggleFollowShop;
[Link] = toggleWishlist;
[Link] = addToCart;
[Link] = changeQty;
[Link] = removeCartItem;
[Link] = clearCart;
[Link] = checkout;
[Link] = applyPromo;
[Link] = toggleCompare;
[Link] = openCompareModal;
[Link] = setPriceAlert;
[Link] = addRecentlyViewed;
[Link] = openNotifDrawer;
[Link] = closeNotifDrawer;
[Link] = markAllRead;
[Link] = saveProfile;
[Link] = setMapFilter;
[Link] = toggleOpenFilter;
[Link] = heroSearch;
[Link] = selectShopOnMap;
});

Page 141 | RetailPro — Balasubramaniyam · Mohammed Sitthik · Tharuneeshwaran

You might also like