This document specifies the comprehensive software requirements for PetMart India, an enterprise-grade e-commerce platform specializing in pet foods, toys, and accessories. The platform serves the Indian market with localized features, compliance with local regulations, and integration with domestic payment and logistics providers.
| Audience | Relevant Sections | Purpose |
|---|---|---|
| Development Team | All sections | Implementation reference |
| Project Managers | 1-5, 10 | Planning and tracking |
| QA Team | 3-5, 9 | Test case development |
| Business Stakeholders | 1-4, 10 | Business alignment |
| System Administrators | 6-8 | Deployment and maintenance |
| UI/UX Designers | 3-4, 8 | Interface design |
| Component | Specification | Justification |
|---|---|---|
| Client - Web | Chrome 90+, Firefox 88+, Safari 14+, Edge 90+ | Modern browser features required |
| Client - Mobile | iOS 13+, Android 9+ | 95% market coverage |
| Server OS | Ubuntu 22.04 LTS / Amazon Linux 2 | Stability and support |
| Runtime | Node.js 18 LTS | Long-term support |
| Database | PostgreSQL 14+, MongoDB 5+ | Performance and features |
| Cache | Redis 6+ | Performance and persistence |
| Container | Docker 20+, Kubernetes 1.24+ | Orchestration and scaling |
| Constraint Type | Description | Impact |
|---|---|---|
| Regulatory | GST compliance, PCI DSS, Indian IT Act | High - Legal requirement |
| Technical | Mobile-first design, PWA support | High - User experience |
| Business | Support COD, UPI payments | High - Market requirement |
| Cultural | Multi-language, regional preferences | Medium - User adoption |
| Performance | < 3 sec page load, 99.9% uptime | High - User retention |
| ID | Requirement | Priority | Acceptance Criteria | Error Handling |
|---|---|---|---|---|
| FR-UM-001 | System SHALL allow user registration via email/phone | H | - Valid email format - 10-digit mobile for India - Unique identifier |
- Invalid format: "Please enter valid email/phone" - Duplicate: "Account already exists" |
| FR-UM-002 | System SHALL implement OTP verification | H | - 6-digit OTP - Valid for 10 minutes - Max 3 attempts |
- Invalid OTP: "Incorrect OTP" - Expired: "OTP expired, request new" |
| FR-UM-003 | System SHALL support social login | M | - Google OAuth 2.0 - Facebook Login - Auto-link existing accounts |
- Auth failure: "Login failed, try again" - Network error: Retry mechanism |
| FR-UM-004 | System SHALL implement JWT authentication | H | - Access token: 15 min - Refresh token: 7 days - Secure httpOnly cookies |
- Token expired: Auto-refresh - Invalid token: Force re-login |
| FR-UM-005 | System SHALL support password reset | H | - Email/SMS verification - Secure token generation - 24-hour validity |
- Invalid email: "Email not found" - Token expired: "Link expired" |
| FR-UM-006 | System SHALL maintain pet profiles | H | - Multiple pets per user - Pet type, breed, age - Dietary restrictions |
- Required fields validation - Age must be positive number |
// User Registration API with validation and error handling
async function registerUser(req, res) {
try {
const { email, phone, password, name } = req.body;
// Input validation
const errors = validateRegistration(req.body);
if (errors.length > 0) {
return res.status(400).json({
success: false,
errors,
message: "Validation failed"
});
}
// Check existing user
const existingUser = await User.findOne({
$or: [{ email }, { phone }]
});
if (existingUser) {
return res.status(409).json({
success: false,
error: "USER_EXISTS",
message: "An account with this email or phone already exists"
});
}
// Create user with transaction
const user = await db.transaction(async (trx) => {
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await User.create({
email,
phone,
password: hashedPassword,
name,
status: 'PENDING_VERIFICATION'
}, { transaction: trx });
// Generate OTP
const otp = generateOTP();
await OTPModel.create({
userId: newUser.id,
otp: otp,
expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes
}, { transaction: trx });
// Send OTP (async, don't wait)
sendOTP(email || phone, otp).catch(console.error);
return newUser;
});
res.status(201).json({
success: true,
message: "Registration successful. Please verify OTP.",
userId: user.id
});
} catch (error) {
logger.error('Registration error:', error);
res.status(500).json({
success: false,
error: "INTERNAL_ERROR",
message: "Registration failed. Please try again."
});
}
}
| ID | Requirement | Priority | Details | Edge Cases |
|---|---|---|---|---|
| FR-PC-001 | System SHALL support hierarchical categorization | H | - 3-level hierarchy max - Category → Subcategory → Type - Multiple category assignment |
- Circular references prevented - Orphaned products handled |
| FR-PC-002 | System SHALL manage product variants | H | - Size, Color, Flavor, Pack Size - Independent pricing - Variant-specific inventory |
- Max 50 variants per product - Variant combination validation |
| FR-PC-003 | System SHALL store comprehensive product data | H | - Nutritional information - Ingredients with allergens - Feeding guidelines - Safety warnings |
- Required fields for pet food - HTML sanitization for descriptions |
| FR-PC-004 | System SHALL handle media assets | H | - Primary image required - Up to 10 images - Video support (YouTube/Vimeo) - 360° view support |
- Image size < 5MB - Supported formats: JPG, PNG, WebP - Auto-resize and optimize |
| FR-PC-005 | System SHALL track inventory real-time | H | - Stock levels per variant - Low stock alerts (< 10) - Reserved stock for cart items - Batch tracking for expiry |
- Negative inventory prevention - Concurrent update handling |
// MongoDB Product Schema with detailed structure
const productSchema = {
_id: ObjectId,
sku: { type: String, unique: true, required: true },
name: {
en: String,
hi: String,
regional: Map
},
slug: { type: String, unique: true },
description: {
short: String,
detailed: String,
features: [String],
specifications: Map
},
category: {
primary: { id: ObjectId, path: String },
secondary: [{ id: ObjectId, path: String }]
},
brand: {
id: ObjectId,
name: String,
logo: String
},
petType: {
type: String,
enum: ['dog', 'cat', 'bird', 'fish', 'rabbit', 'hamster', 'other'],
breed: [String], // Specific breeds
ageGroup: ['puppy', 'adult', 'senior'],
size: ['small', 'medium', 'large', 'giant']
},
variants: [{
variantId: String,
type: String, // size, color, flavor
value: String,
sku: String,
price: {
mrp: Number,
selling: Number,
currency: { type: String, default: 'INR' }
},
inventory: {
available: Number,
reserved: Number,
threshold: Number
},
attributes: Map
}],
pricing: {
basePrice: Number,
mrp: Number,
discount: {
type: { type: String, enum: ['percentage', 'fixed'] },
value: Number,
validUntil: Date
},
tax: {
hsn: String,
gstRate: Number,
inclusive: Boolean
},
bulk: [{
minQty: Number,
discount: Number
}]
},
nutrition: {
ingredients: [String],
allergens: [String],
analysis: {
protein: String,
fat: String,
fiber: String,
moisture: String
},
feedingGuide: [{
weightRange: String,
amount: String
}]
},
media: {
primary: String,
images: [{
url: String,
alt: String,
order: Number
}],
videos: [{
type: String, // youtube, vimeo, hosted
url: String,
thumbnail: String
}]
},
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String],
canonicalUrl: String
},
status: {
type: String,
enum: ['draft', 'pending', 'active', 'suspended', 'discontinued'],
default: 'draft'
},
vendor: {
id: ObjectId,
name: String,
fulfillmentTime: Number // in days
},
shipping: {
weight: Number, // in grams
dimensions: {
length: Number,
width: Number,
height: Number
},
freeShipping: Boolean,
shippingClass: String
},
ratings: {
average: { type: Number, default: 0 },
count: { type: Number, default: 0 },
distribution: {
5: { type: Number, default: 0 },
4: { type: Number, default: 0 },
3: { type: Number, default: 0 },
2: { type: Number, default: 0 },
1: { type: Number, default: 0 }
}
},
flags: {
featured: Boolean,
newArrival: Boolean,
bestSeller: Boolean,
prescription: Boolean,
imported: Boolean,
organic: Boolean
},
relatedProducts: [ObjectId],
crossSells: [ObjectId],
timestamps: {
created: Date,
updated: Date,
lastStockUpdate: Date
}
};
| ID | Requirement | Priority | Implementation Details | Error Scenarios |
|---|---|---|---|---|
| FR-SC-001 | Cart persistence across sessions | H | - 30-day cookie for guests - Database storage for users - Merge guest cart on login |
- Session expired - Conflicting items on merge |
| FR-SC-002 | Real-time stock validation | H | - Check on add - Validate on load - Check before checkout |
- Stock depleted - Reserved by other users |
| FR-SC-003 | Dynamic pricing updates | H | - Price changes notification - Discount expiry handling - Tax calculation per item |
- Price increased - Discount expired |
| FR-SC-004 | Cart abandonment recovery | M | - Email after 2 hours - Push notification after 24h - Discount incentive after 48h |
- Email bounce - User unsubscribed |
class CartService {
async addToCart(userId, sessionId, productId, variantId, quantity) {
return await this.db.transaction(async (trx) => {
// 1. Validate product and variant
const product = await this.productService.getProduct(productId, variantId);
if (!product || product.status !== 'active') {
throw new ValidationError('Product not available');
}
// 2. Check stock availability
const stockAvailable = await this.inventoryService.checkStock(
variantId,
quantity,
trx
);
if (!stockAvailable.available) {
throw new StockError({
message: 'Insufficient stock',
availableQty: stockAvailable.quantity,
requested: quantity
});
}
// 3. Reserve stock temporarily (15 minutes)
await this.inventoryService.reserveStock(
variantId,
quantity,
userId || sessionId,
15 * 60 * 1000, // 15 minutes
trx
);
// 4. Get or create cart
let cart = await this.getCart(userId, sessionId, trx);
if (!cart) {
cart = await this.createCart(userId, sessionId, trx);
}
// 5. Check for existing item
const existingItem = cart.items.find(
item => item.variantId === variantId
);
if (existingItem) {
// Update quantity with validation
const newQty = existingItem.quantity + quantity;
if (newQty > product.maxOrderQty) {
throw new ValidationError(
`Maximum ${product.maxOrderQty} items allowed`
);
}
existingItem.quantity = newQty;
} else {
// Add new item
cart.items.push({
productId,
variantId,
quantity,
price: product.price,
discount: product.discount || 0,
tax: this.calculateTax(product),
addedAt: new Date()
});
}
// 6. Recalculate totals
cart = this.calculateCartTotals(cart);
// 7. Apply business rules
cart = await this.applyBusinessRules(cart, trx);
// 8. Save cart
await this.saveCart(cart, trx);
// 9. Track event
await this.analytics.track('cart.item.added', {
userId,
sessionId,
productId,
variantId,
quantity,
cartValue: cart.total
});
return cart;
});
}
calculateCartTotals(cart) {
let subtotal = 0;
let totalDiscount = 0;
let totalTax = 0;
cart.items.forEach(item => {
const itemTotal = item.price * item.quantity;
const itemDiscount = item.discount * item.quantity;
const itemTax = this.calculateItemTax(item);
subtotal += itemTotal;
totalDiscount += itemDiscount;
totalTax += itemTax;
item.total = itemTotal - itemDiscount + itemTax;
});
cart.subtotal = subtotal;
cart.discount = totalDiscount;
cart.tax = totalTax;
cart.shipping = this.calculateShipping(cart);
cart.total = subtotal - totalDiscount + totalTax + cart.shipping;
// Apply coupon if exists
if (cart.coupon) {
const couponDiscount = this.applyCoupon(cart.coupon, cart);
cart.couponDiscount = couponDiscount;
cart.total -= couponDiscount;
}
return cart;
}
async applyBusinessRules(cart, trx) {
const rules = await this.getRules(trx);
// Minimum order value
if (cart.total < rules.minOrderValue) {
cart.warnings.push({
type: 'MIN_ORDER',
message: `Minimum order value is ₹${rules.minOrderValue}`,
value: rules.minOrderValue - cart.total
});
}
// Free shipping threshold
if (cart.total >= rules.freeShippingThreshold) {
cart.shipping = 0;
cart.messages.push({
type: 'FREE_SHIPPING',
message: 'You qualify for free shipping!'
});
}
// Bulk discount
const totalItems = cart.items.reduce((sum, item) => sum + item.quantity, 0);
if (totalItems >= rules.bulkDiscountThreshold) {
cart.bulkDiscount = cart.subtotal * rules.bulkDiscountPercent / 100;
cart.total -= cart.bulkDiscount;
}
return cart;
}
}
| ID | Requirement | Edge Cases | Handling Strategy |
|---|---|---|---|
| FR-PM-001 | Multiple payment gateway support | - Primary gateway down - Gateway specific errors |
- Automatic failover to backup - Gateway-specific error mapping |
| FR-PM-002 | Payment retry mechanism | - Network timeout - User abandonment - Double payment |
- Idempotency keys - 3 retry attempts - Duplicate detection |
| FR-PM-003 | Partial payment support | - Wallet + Card combo - Insufficient wallet balance |
- Split payment flow - Balance calculation |
| FR-PM-004 | Refund processing | - Partial refunds - Failed refund to source - Refund to different account |
- Refund queue system - Alternative refund methods - Manual intervention flow |
| FR-PM-005 | Payment reconciliation | - Gateway webhook delays - Mismatched amounts - Missing transactions |
- Daily reconciliation job - Webhook retry mechanism - Manual reconciliation dashboard |
| Element | Description |
|---|---|
| Use Case ID | UC-001 |
| Use Case Name | Complete Product Purchase |
| Actor | Customer |
| Preconditions | - Products available in catalog - Payment gateway operational |
| Trigger | Customer wants to buy pet products |
| Basic Flow | 1. Customer browses/searches products 2. Views product details 3. Adds to cart 4. Proceeds to checkout 5. Enters delivery details 6. Makes payment 7. Receives confirmation |
| Alternative Flows | - Guest checkout without registration - Apply coupon during checkout - Change address during checkout |
| Exception Flows | - Product out of stock - Payment failure - Address not serviceable |
| Postconditions | - Order created in system - Inventory updated - Confirmation sent to customer |
| Component | Metric | Target | Measurement Method | Degradation Plan |
|---|---|---|---|---|
| API Response | P50 Latency | < 200ms | APM Monitoring | Cache warming |
| API Response | P95 Latency | < 500ms | APM Monitoring | Rate limiting |
| API Response | P99 Latency | < 1000ms | APM Monitoring | Circuit breaker |
| Page Load | First Contentful Paint | < 1.5s | Lighthouse | Progressive rendering |
| Page Load | Time to Interactive | < 3s | Lighthouse | Code splitting |
| Database | Query Time P95 | < 100ms | DB Monitoring | Query optimization |
| Search | Result Time | < 500ms | Elasticsearch | Caching layer |
| Checkout | Complete Time | < 30s | End-to-end | Async processing |
| Security Aspect | Requirement | Implementation | Monitoring |
|---|---|---|---|
| Authentication | Strong password policy | - Min 8 chars - Complexity requirements - Password history |
Failed login attempts |
| Authorization | Role-based access | - JWT with permissions - Resource-level checks |
Unauthorized access attempts |
| Data Protection | Encryption | - AES-256 for PII - TLS 1.3 for transit - Tokenization for cards |
Encryption status |
| Input Validation | Sanitization | - XSS prevention - SQL injection prevention - File upload validation |
Malicious input attempts |
| Session Management | Secure sessions | - HttpOnly cookies - CSRF tokens - Session timeout |
Session hijacking attempts |
| API Security | Rate limiting | - 100 req/min per IP - 1000 req/min per user - Exponential backoff |
API abuse patterns |
| Dimension | Current | Target (1 Year) | Target (3 Years) | Strategy |
|---|---|---|---|---|
| Users | 10K MAU | 100K MAU | 1M MAU | Auto-scaling |
| Orders | 100/day | 1000/day | 10000/day | Queue-based processing |
| Products | 1000 | 10000 | 100000 | NoSQL for catalog |
| Data Storage | 100GB | 1TB | 10TB | Sharding strategy |
| API Calls | 1M/day | 10M/day | 100M/day | API Gateway + CDN |
-- Core User Tables
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255),
first_name VARCHAR(100),
last_name VARCHAR(100),
avatar_url VARCHAR(500),
email_verified BOOLEAN DEFAULT FALSE,
phone_verified BOOLEAN DEFAULT FALSE,
status ENUM('active', 'suspended', 'deleted') DEFAULT 'active',
last_login TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_phone (phone),
INDEX idx_status (status)
);
-- Product Management Tables
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sku VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description_short TEXT,
description_long TEXT,
category_id UUID REFERENCES categories(id),
brand_id UUID REFERENCES brands(id),
vendor_id UUID REFERENCES vendors(id),
base_price DECIMAL(10,2) NOT NULL,
mrp DECIMAL(10,2),
hsn_code VARCHAR(20),
gst_rate DECIMAL(5,2) DEFAULT 18,
weight_grams INTEGER,
status ENUM('draft', 'active', 'suspended', 'discontinued') DEFAULT 'draft',
meta_title VARCHAR(255),
meta_description TEXT,
keywords TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_sku (sku),
INDEX idx_slug (slug),
INDEX idx_category (category_id),
INDEX idx_brand (brand_id),
INDEX idx_vendor (vendor_id),
INDEX idx_status (status),
FULLTEXT INDEX idx_search (name, description_short, keywords)
);
-- Order Management
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_number VARCHAR(20) UNIQUE NOT NULL,
user_id UUID REFERENCES users(id),
shipping_address_id UUID REFERENCES addresses(id),
billing_address_id UUID REFERENCES addresses(id),
subtotal DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) DEFAULT 0,
tax_amount DECIMAL(10,2) NOT NULL,
shipping_amount DECIMAL(10,2) DEFAULT 0,
total_amount DECIMAL(10,2) NOT NULL,
coupon_id UUID REFERENCES coupons(id),
payment_method ENUM('card', 'upi', 'netbanking', 'wallet', 'cod'),
payment_status ENUM('pending', 'processing', 'completed', 'failed', 'refunded'),
order_status ENUM('placed', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled'),
notes TEXT,
tracking_number VARCHAR(100),
expected_delivery DATE,
delivered_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order_number (order_number),
INDEX idx_user (user_id),
INDEX idx_status (order_status, payment_status),
INDEX idx_created (created_at)
);
-- Inventory Tracking
CREATE TABLE inventory (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_variant_id UUID REFERENCES product_variants(id),
warehouse_id UUID REFERENCES warehouses(id),
quantity_available INTEGER NOT NULL DEFAULT 0,
quantity_reserved INTEGER NOT NULL DEFAULT 0,
quantity_sold INTEGER NOT NULL DEFAULT 0,
reorder_level INTEGER DEFAULT 10,
reorder_quantity INTEGER DEFAULT 50,
last_restock_date DATE,
last_sale_date DATE,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_variant_warehouse (product_variant_id, warehouse_id),
INDEX idx_low_stock (quantity_available, reorder_level),
CONSTRAINT chk_quantities CHECK (
quantity_available >= 0 AND
quantity_reserved >= 0 AND
quantity_reserved <= quantity_available
)
);
# OpenAPI 3.0 Specification Sample
openapi: 3.0.0
info:
title: PetMart India API
version: 1.0.0
paths:
/api/products:
get:
summary: List products
parameters:
- name: category
in: query
schema:
type: string
- name: sort
in: query
schema:
type: string
enum: [price_asc, price_desc, popularity, rating]
- name: page
in: query
schema:
type: integer
default: 1
responses:
'200':
description: Product list
content:
application/json:
schema:
type: object
properties:
products:
type: array
items:
$ref: '#/components/schemas/Product'
pagination:
$ref: '#/components/schemas/Pagination'
/api/cart:
post:
summary: Add item to cart
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- productId
- variantId
- quantity
properties:
productId:
type: string
format: uuid
variantId:
type: string
format: uuid
quantity:
type: integer
minimum: 1
responses:
'200':
description: Cart updated
'400':
description: Invalid request
'409':
description: Insufficient stock
| Interface | Purpose | Specification |
|---|---|---|
| Barcode Scanner | Inventory management | - USB/Bluetooth connection - Support EAN/UPC codes - SDK integration |
| Label Printer | Shipping labels | - Network/USB printer - 4x6 inch labels - ZPL/EPL support |
| POS Terminal | Offline stores | - API integration - Real-time sync - Offline capability |
// Razorpay Integration Interface
interface PaymentGateway {
// Create payment order
createOrder(params: {
amount: number;
currency: string;
receipt: string;
notes?: Record<string, any>;
}): Promise<{
id: string;
amount: number;
currency: string;
status: string;
}>;
// Verify payment signature
verifySignature(params: {
orderId: string;
paymentId: string;
signature: string;
}): boolean;
// Process refund
refund(params: {
paymentId: string;
amount?: number;
reason?: string;
}): Promise<{
id: string;
status: string;
amount: number;
}>;
// Webhook handler
handleWebhook(
body: any,
signature: string
): Promise<WebhookEvent>;
}
// Usage Example
class RazorpayService implements PaymentGateway {
private razorpay: Razorpay;
constructor(config: RazorpayConfig) {
this.razorpay = new Razorpay({
key_id: config.keyId,
key_secret: config.keySecret
});
}
async createOrder(params: CreateOrderParams) {
try {
const order = await this.razorpay.orders.create({
amount: params.amount * 100, // Convert to paise
currency: params.currency,
receipt: params.receipt,
notes: params.notes
});
return {
id: order.id,
amount: order.amount / 100,
currency: order.currency,
status: order.status
};
} catch (error) {
throw new PaymentGatewayError(
'Failed to create payment order',
error
);
}
}
}
| Module | Edge Case | Solution | Implementation |
|---|---|---|---|
| Cart | Product price changed after adding | - Notify user of price change - Show old vs new price - Allow acceptance |
Price versioning with timestamps |
| Cart | Item out of stock during checkout | - Real-time stock check - Reserve stock for 15 min - Suggest alternatives |
Distributed locking with Redis |
| Payment | Double payment attempt | - Idempotency keys - Payment status check - Prevent duplicate |
Unique request ID tracking |
| Order | Partial shipment available | - Split order option - Wait for full stock - Cancel unavailable items |
Order splitting logic |
| Delivery | Address not serviceable | - Check during cart - Suggest nearest serviceable - Store pickup option |
Pincode serviceability API |
| Returns | Product damaged in return shipping | - Photo evidence requirement - Insurance coverage - Escalation process |
Return workflow with stages |
{
"success": false,
"error": {
"code": "PRODUCT_OUT_OF_STOCK",
"message": "The requested product is currently out of stock",
"details": {
"productId": "123e4567-e89b-12d3-a456-426614174000",
"availableQuantity": 0,
"expectedRestockDate": "2025-04-10"
},
"suggestions": [
{
"action": "NOTIFY_WHEN_AVAILABLE",
"message": "Get notified when back in stock"
},
{
"action": "VIEW_ALTERNATIVES",
"message": "View similar products",
"link": "/products/similar/123e4567"
}
]
},
"timestamp": "2025-04-20T10:30:00Z",
"requestId": "req_abc123"
}
| Requirement ID | Requirement Description | Design Component | Implementation | Test Case ID |
|---|---|---|---|---|
| User Management | ||||
| FR-UM-001 | User registration via email/phone | User Service, Auth Module | `POST /api/auth/register` | TC-UM-001 |
| FR-UM-002 | OTP verification | Notification Service | `POST /api/auth/verify-otp` | TC-UM-002 |
| FR-UM-003 | Social login | OAuth Integration | OAuth 2.0 flow | TC-UM-003 |
| FR-UM-004 | JWT authentication | API Gateway | JWT middleware | TC-UM-004 |
| Product Catalog | ||||
| FR-PC-001 | Product categorization | Product Service | MongoDB schema | TC-PC-001 |
| FR-PC-002 | Product variants | Product Service | Variant model | TC-PC-002 |
| FR-PC-003 | Product details | Product Service | Product API | TC-PC-003 |
| Shopping Cart | ||||
| FR-SC-001 | Cart persistence | Cart Service | Redis + PostgreSQL | TC-SC-001 |
| FR-SC-002 | Stock validation | Inventory Service | Real-time check | TC-SC-002 |
| FR-SC-003 | Price calculation | Cart Service | Calculation engine | TC-SC-003 |
| Payment | ||||
| FR-PM-001 | Payment gateway integration | Payment Service | Razorpay SDK | TC-PM-001 |
| FR-PM-002 | UPI payments | Payment Service | UPI flow | TC-PM-002 |
| FR-PM-003 | Card payments | Payment Service | PCI compliance | TC-PM-003 |
| Order Management | ||||
| FR-OM-001 | Order ID generation | Order Service | UUID + sequence | TC-OM-001 |
| FR-OM-002 | Order status tracking | Order Service | State machine | TC-OM-002 |
| FR-OM-003 | Order cancellation | Order Service | Cancellation flow | TC-OM-003 |
| Module | Unit Tests | Integration Tests | E2E Tests | Coverage |
|---|---|---|---|---|
| User Management | 45 | 12 | 5 | 92% |
| Product Catalog | 38 | 10 | 4 | 88% |
| Shopping Cart | 32 | 8 | 3 | 85% |
| Payment | 28 | 15 | 6 | 90% |
| Order Management | 35 | 11 | 5 | 87% |
| Total | 178 | 56 | 23 | 88.4% |
| Term | Definition | Context |
|---|---|---|
| SKU | Stock Keeping Unit | Unique identifier for each product variant |
| HSN | Harmonized System of Nomenclature | Tax classification code for GST |
| GSTIN | Goods and Services Tax Identification Number | Tax registration number |
| COD | Cash on Delivery | Payment method where customer pays upon delivery |
| OTP | One-Time Password | Temporary code for verification |
| JWT | JSON Web Token | Authentication token format |
| PII | Personally Identifiable Information | Sensitive user data requiring protection |
| RTO | Return to Origin | When delivery fails and item returns to seller |
| NDR | Non-Delivery Report | Report when delivery attempt fails |
| API | Application Programming Interface | Interface for software communication |
| CDN | Content Delivery Network | Distributed content serving |
| WAF | Web Application Firewall | Security layer for web apps |
| Regulation | Requirement | Status | Notes |
|---|---|---|---|
| GST Compliance | |||
| HSN Code | All products must have HSN codes | ✅ Required | Automated validation |
| Tax Invoice | Generate GST-compliant invoices | ✅ Required | Template ready |
| GSTIN Validation | Verify vendor GSTIN | ✅ Required | API integration |
| Data Protection | |||
| User Consent | Explicit consent for data collection | ✅ Required | Consent management |
| Data Deletion | Right to erasure implementation | ✅ Required | Soft delete with purge |
| Data Portability | Export user data on request | ⚠️ Planned | Q2 2025 |
| Consumer Protection | |||
| Return Policy | Clear 7-day return policy | ✅ Required | Policy page |
| Product Authenticity | Genuine product guarantee | ✅ Required | Vendor verification |
| Pricing Transparency | Show all charges upfront | ✅ Required | Checkout breakdown |
# Kubernetes Deployment Configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: petmart-groovy
namespace: production
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: petmart-groovy
template:
metadata:
labels:
app: petmart-groovy
spec:
containers:
- name: api
image: petmart/api:v1.0.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: petmart-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
// Successful Product List Response
{
"success": true,
"data": {
"products": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"sku": "DOG-FOOD-001",
"name": "Premium Adult Dog Food",
"slug": "premium-adult-dog-food",
"description": "Complete nutrition for adult dogs",
"price": {
"mrp": 2499,
"selling": 1999,
"currency": "INR",
"discount": {
"amount": 500,
"percentage": 20
}
},
"images": {
"primary": "https://cdn.petmart.in/products/dog-food-001-main.jpg",
"thumbnails": [
"https://cdn.petmart.in/products/dog-food-001-thumb-1.jpg"
]
},
"availability": {
"inStock": true,
"quantity": 150
},
"rating": {
"average": 4.5,
"count": 234
},
"category": {
"id": "cat-001",
"name": "Dog Food",
"path": "pets/dogs/food"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 156,
"pages": 8
},
"filters": {
"applied": {
"category": "dog-food",
"priceRange": "1000-3000"
},
"available": {
"brands": ["Pedigree", "Royal Canin", "Drools"],
"priceRanges": ["0-1000", "1000-3000", "3000+"],
"ratings": ["4+", "3+", "2+"]
}
}
},
"meta": {
"timestamp": "2025-04-20T10:30:00Z",
"version": "1.0.0",
"requestId": "req_123456"
}
}
| Role | Name | Signature | Date | Comments |
|---|---|---|---|---|
| Project Sponsor | ||||
| Project Manager | ||||
| Technical Architect | ||||
| QA Lead | ||||
| Security Officer | ||||
| Business Analyst |
| Version | Date | Author | Changes | Approved By |
|---|---|---|---|---|
| 1.0 | 2025-04-10 | Initial Team | Initial draft | - |
| 2.0 | 2025-04-10 | Enhanced Team | - Added detailed |
This Technical Design Document provides comprehensive technical specifications for implementing the PetMart India e-commerce platform. It serves as the primary reference for developers, architects, and DevOps engineers.
The document covers:
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend | |||
| Web Framework | Next.js | 14.x | Server-side rendering, SEO |
| UI Framework | React.js | 18.x | Component-based UI |
| Styling | Tailwind CSS | 3.x | Utility-first CSS |
| State Management | Redux Toolkit | 2.x | Global state management |
| Form Handling | React Hook Form | 7.x | Form validation |
| HTTP Client | Axios | 1.x | API communication |
| Mobile | |||
| Framework | React Native | 0.72.x | Cross-platform mobile |
| Navigation | React Navigation | 6.x | Mobile navigation |
| State | Redux Toolkit | 2.x | State management |
| Backend | |||
| Runtime | Node.js | 20.x LTS | JavaScript runtime |
| Framework | Express.js | 4.x | Web framework |
| Validation | Joi | 17.x | Schema validation |
| ORM | Prisma | 5.x | Database ORM |
| Authentication | Passport.js | 0.6.x | Auth strategies |
| Database | |||
| Primary DB | PostgreSQL | 15.x | Relational data |
| Document DB | MongoDB | 6.x | Product catalog |
| Cache | Redis | 7.x | Session & cache |
| Search | Elasticsearch | 8.x | Full-text search |
| DevOps | |||
| Container | Docker | 24.x | Containerization |
| Orchestration | Kubernetes | 1.28.x | Container orchestration |
| CI/CD | GitHub Actions | - | Automation |
| Monitoring | Prometheus + Grafana | Latest | Metrics & monitoring |
# Package.json structure for backend
{
"name": "petmart-backend",
"version": "1.0.0",
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"dependencies": {
"express": "^4.18.2",
"@prisma/client": "^5.7.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"joi": "^17.11.0",
"winston": "^3.11.0",
"bull": "^4.11.5",
"ioredis": "^5.3.2",
"multer": "^1.4.5",
"aws-sdk": "^2.1515.0",
"razorpay": "^2.9.2",
"nodemailer": "^6.9.7",
"twilio": "^4.19.3"
},
"devDependencies": {
"@types/node": "^20.10.5",
"typescript": "^5.3.3",
"ts-node": "^10.9.2",
"jest": "^29.7.0",
"supertest": "^6.3.3",
"eslint": "^8.56.0",
"prettier": "^3.1.1"
}
}
// Product Catalog Collection
const ProductSchema = {
_id: ObjectId,
sku: String,
name: String,
slug: String,
description: {
short: String,
long: String,
features: [String],
specifications: Object
},
category: {
primary: String,
secondary: [String],
tags: [String]
},
pricing: {
basePrice: Number,
mrp: Number,
currency: String,
taxRate: Number,
discounts: [{
type: String,
value: Number,
validFrom: Date,
validUntil: Date
}]
},
variants: [{
sku: String,
type: String, // size, color, flavor
value: String,
priceModifier: Number,
stock: Number,
images: [String]
}],
images: [{
url: String,
alt: String,
isPrimary: Boolean,
order: Number
}],
inventory: {
inStock: Boolean,
quantity: Number,
warehouse: [{
location: String,
quantity: Number
}]
},
seo: {
title: String,
description: String,
keywords: [String],
canonicalUrl: String
},
ratings: {
average: Number,
count: Number,
distribution: {
5: Number,
4: Number,
3: Number,
2: Number,
1: Number
}
},
vendor: {
id: String,
name: String,
rating: Number
},
createdAt: Date,
updatedAt: Date,
publishedAt: Date
};
// User Activity Collection
const UserActivitySchema = {
_id: ObjectId,
userId: String,
sessionId: String,
events: [{
type: String, // view, click, add_to_cart, purchase
timestamp: Date,
data: {
productId: String,
category: String,
searchQuery: String,
filters: Object,
referrer: String
}
}],
device: {
type: String,
os: String,
browser: String,
ip: String
},
location: {
country: String,
state: String,
city: String
},
createdAt: Date
};
// Session Store
const SessionSchema = {
key: `session:${sessionId}`,
value: {
userId: String,
role: String,
permissions: Array,
createdAt: Timestamp,
expiresAt: Timestamp
},
ttl: 3600 // 1 hour
};
// Shopping Cart Cache
const CartCacheSchema = {
key: `cart:${userId}`,
value: {
items: [{
productId: String,
variantId: String,
quantity: Number,
price: Number,
addedAt: Timestamp
}],
subtotal: Number,
updatedAt: Timestamp
},
ttl: 86400 // 24 hours
};
// Product View Cache
const ProductCacheSchema = {
key: `product:${productId}`,
value: JSON.stringify(productData),
ttl: 300 // 5 minutes
};
// Rate Limiting
const RateLimitSchema = {
key: `rate:${userId}:${endpoint}`,
value: requestCount,
ttl: 60 // 1 minute window
};
// POST /api/v1/auth/register
interface RegisterRequest {
email: string;
phone: string;
password: string;
firstName: string;
lastName: string;
}
interface RegisterResponse {
success: boolean;
message: string;
data: {
userId: string;
email: string;
requiresVerification: boolean;
};
}
// POST /api/v1/auth/login
interface LoginRequest {
username: string; // email or phone
password: string;
}
interface LoginResponse {
success: boolean;
data: {
accessToken: string;
refreshToken: string;
expiresIn: number;
user: {
id: string;
email: string;
firstName: string;
lastName: string;
role: string;
};
};
}
// POST /api/v1/auth/verify-otp
interface VerifyOTPRequest {
phone: string;
otp: string;
}
interface VerifyOTPResponse {
success: boolean;
message: string;
data: {
verified: boolean;
accessToken?: string;
};
}
// GET /api/v1/products
interface GetProductsRequest {
page?: number;
limit?: number;
category?: string;
brand?: string[];
minPrice?: number;
maxPrice?: number;
petType?: string;
ageGroup?: string;
sortBy?: 'price' | 'rating' | 'newest';
sortOrder?: 'asc' | 'desc';
}
interface GetProductsResponse {
success: boolean;
data: {
products: Product[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
filters: {
categories: Category[];
brands: Brand[];
priceRange: {
min: number;
max: number;
};
};
};
}
// GET /api/v1/products/:id
interface GetProductResponse {
success: boolean;
data: {
product: {
id: string;
sku: string;
name: string;
description: string;
price: number;
mrp: number;
discount: number;
images: Image[];
variants: Variant[];
specifications: Record<string, any>;
nutritionalInfo?: NutritionalInfo;
inStock: boolean;
rating: {
average: number;
count: number;
};
vendor: {
id: string;
name: string;
rating: number;
};
};
recommendations: Product[];
};
}
// POST /api/v1/products/search
interface SearchProductsRequest {
query: string;
filters?: {
category?: string;
brand?: string[];
priceRange?: {
min: number;
max: number;
};
};
limit?: number;
}
// POST /api/v1/orders
interface CreateOrderRequest {
items: Array<{
productId: string;
variantId?: string;
quantity: number;
}>;
addressId: string;
paymentMethod: 'COD' | 'ONLINE';
couponCode?: string;
notes?: string;
}
interface CreateOrderResponse {
success: boolean;
data: {
orderId: string;
orderNumber: string;
amount: {
subtotal: number;
tax: number;
shipping: number;
discount: number;
total: number;
};
paymentDetails?: {
paymentOrderId: string;
amount: number;
currency: string;
};
estimatedDelivery: string;
};
}
// GET /api/v1/orders/:id
interface GetOrderResponse {
success: boolean;
data: {
order: {
id: string;
orderNumber: string;
status: OrderStatus;
items: OrderItem[];
timeline: Array<{
status: string;
timestamp: string;
description: string;
}>;
shipping: {
address: Address;
trackingId?: string;
carrier?: string;
};
payment: {
method: string;
status: string;
transactionId?: string;
};
amounts: OrderAmounts;
createdAt: string;
deliveredAt?: string;
};
};
}
// Standardized Error Response
interface ErrorResponse {
success: false;
error: {
code: string;
message: string;
details?: any;
timestamp: string;
path: string;
requestId: string;
};
}
// Error Codes
enum ErrorCodes {
// Authentication Errors (1xxx)
AUTH_INVALID_CREDENTIALS = 'AUTH_1001',
AUTH_TOKEN_EXPIRED = 'AUTH_1002',
AUTH_UNAUTHORIZED = 'AUTH_1003',
// Validation Errors (2xxx)
VALIDATION_ERROR = 'VAL_2001',
INVALID_INPUT = 'VAL_2002',
// Business Logic Errors (3xxx)
PRODUCT_OUT_OF_STOCK = 'BIZ_3001',
INVALID_COUPON = 'BIZ_3002',
ORDER_CANNOT_CANCEL = 'BIZ_3003',
// System Errors (5xxx)
INTERNAL_ERROR = 'SYS_5001',
DATABASE_ERROR = 'SYS_5002',
EXTERNAL_SERVICE_ERROR = 'SYS_5003'
}
// Express Middleware Configuration
class APIServer {
setupMiddleware() {
// Security Headers
this.app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
}));
// CORS Configuration
this.app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
}));
// Rate Limiting
this.app.use('/api/', rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
success: false,
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests',
},
});
},
}));
// Request Parsing
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Request ID Generation
this.app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || uuidv4();
res.setHeader('X-Request-ID', req.id);
next();
});
// Request Logging
this.app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim()),
},
}));
// API Versioning
this.app.use('/api/v1', v1Routes);
// Error Handler
this.app.use(errorHandler);
}
}
// auth.service.ts
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
export class AuthService {
private readonly ACCESS_TOKEN_EXPIRY = '15m';
private readonly REFRESH_TOKEN_EXPIRY = '7d';
async registerUser(data: RegisterDTO): Promise<User> {
// Validate if user exists
const existingUser = await this.userRepository.findByEmail(data.email);
if (existingUser) {
throw new ConflictError('User already exists');
}
// Hash password
const hashedPassword = await bcrypt.hash(data.password, 10);
// Create user
const user = await this.userRepository.create({
...data,
password: hashedPassword,
id: uuidv4(),
isVerified: false,
});
// Send verification email
await this.emailService.sendVerificationEmail(user);
// Send welcome SMS
if (data.phone) {
await this.smsService.sendWelcomeSMS(data.phone);
}
return user;
}
async authenticateUser(credentials: LoginDTO): Promise<AuthResult> {
// Find user
const user = await this.userRepository.findByUsername(credentials.username);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
}
// Verify password
const isValidPassword = await bcrypt.compare(
credentials.password,
user.password
);
if (!isValidPassword) {
throw new UnauthorizedError('Invalid credentials');
}
// Check if account is verified
if (!user.isVerified && process.env.REQUIRE_VERIFICATION === 'true') {
throw new ForbiddenError('Please verify your account');
}
// Generate tokens
const tokens = await this.generateTokens(user);
// Update last login
await this.userRepository.updateLastLogin(user.id);
// Cache session
await this.cacheService.set(
`session:${user.id}`,
JSON.stringify({
userId: user.id,
role: user.role,
loginAt: new Date(),
}),
3600 // 1 hour
);
return {
user: this.sanitizeUser(user),
tokens,
};
}
private generateTokens(user: User): TokenPair {
const payload = {
userId: user.id,
email: user.email,
role: user.role,
};
const accessToken = jwt.sign(
payload,
process.env.JWT_SECRET!,
{ expiresIn: this.ACCESS_TOKEN_EXPIRY }
);
const refreshToken = jwt.sign(
payload,
process.env.REFRESH_SECRET!,
{ expiresIn: this.REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
}
// elasticsearch.service.ts
import { Client } from '@elastic/elasticsearch';
export class ElasticsearchService {
private client: Client;
private readonly INDEX_NAME = 'products';
constructor() {
this.client = new Client({
node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200',
auth: {
username: process.env.ELASTIC_USERNAME!,
password: process.env.ELASTIC_PASSWORD!,
},
});
}
async searchProducts(query: string, filters?: SearchFilters): Promise<SearchResult> {
const searchQuery = {
index: this.INDEX_NAME,
body: {
query: {
bool: {
must: [
{
multi_match: {
query,
fields: ['name^3', 'description^2', 'category', 'brand', 'tags'],
type: 'best_fields',
fuzziness: 'AUTO',
},
},
],
filter: this.buildFilters(filters),
},
},
aggs: {
categories: {
terms: { field: 'category.keyword' },
},
brands: {
terms: { field: 'brand.keyword' },
},
price_range: {
stats: { field: 'price' },
},
},
highlight: {
fields: {
name: {},
description: {},
},
},
sort: this.buildSort(filters?.sortBy),
from: (filters?.page - 1) * filters?.limit || 0,
size: filters?.limit || 20,
},
};
const response = await this.client.search(searchQuery);
return this.formatSearchResponse(response);
}
private buildFilters(filters?: SearchFilters): any[] {
const esFilters = [];
if (filters?.category) {
esFilters.push({
term: { 'category.keyword': filters.category },
});
}
if (filters?.brand?.length) {
esFilters.push({
terms: { 'brand.keyword': filters.brand },
});
}
if (filters?.minPrice || filters?.maxPrice) {
esFilters.push({
range: {
price: {
gte: filters.minPrice || 0,
lte: filters.maxPrice || 999999,
},
},
});
}
if (filters?.inStock) {
esFilters.push({
term: { inStock: true },
});
}
if (filters?.petType) {
esFilters.push({
term: { 'petType.keyword': filters.petType },
});
}
return esFilters;
}
async indexProduct(product: Product): Promise<void> {
await this.client.index({
index: this.INDEX_NAME,
id: product.id,
body: {
...product,
suggest: {
input: [product.name, product.brand, product.category],
weight: product.popularity || 1,
},
},
});
}
}
// order.service.ts
export class OrderService {
async createOrder(userId: string, orderData: CreateOrderDTO): Promise<Order> {
const session = await this.startTransaction();
try {
// Validate inventory
for (const item of orderData.items) {
const available = await this.inventoryService.checkAvailability(
item.productId,
item.quantity
);
if (!available) {
throw new BusinessError(`Product ${item.productId} is out of stock`);
}
}
// Calculate pricing
const pricing = await this.calculateOrderPricing(orderData);
// Create order
const order = await this.orderRepository.create({
userId,
orderNumber: this.generateOrderNumber(),
items: orderData.items,
addressId: orderData.addressId,
...pricing,
status: OrderStatus.PENDING,
}, session);
// Reserve inventory
for (const item of orderData.items) {
await this.inventoryService.reserveStock(
item.productId,
item.quantity,
session
);
}
// Process payment
if (orderData.paymentMethod === 'ONLINE') {
const paymentOrder = await this.paymentService.createPaymentOrder({
orderId: order.id,
amount: pricing.total,
currency: 'INR',
});
order.paymentOrderId = paymentOrder.id;
await this.orderRepository.update(order.id, order, session);
}
await this.commitTransaction(session);
// Send notifications
this.eventEmitter.emit('order.created', order);
return order;
} catch (error) {
await this.rollbackTransaction(session);
throw error;
}
}
private async calculateOrderPricing(orderData: CreateOrderDTO): Promise<OrderPricing> {
let subtotal = 0;
const items = [];
for (const item of orderData.items) {
const product = await this.productService.findById(item.productId);
const itemTotal = product.price * item.quantity;
subtotal += itemTotal;
items.push({
...item,
price: product.price,
total: itemTotal,
tax: itemTotal * (product.gstRate / 100),
});
}
const taxAmount = items.reduce((sum, item) => sum + item.tax, 0);
const shippingCharge = await this.calculateShipping(orderData.addressId, subtotal);
const discount = orderData.couponCode
? await this.couponService.calculateDiscount(orderData.couponCode, subtotal)
: 0;
return {
subtotal,
taxAmount,
shippingCharge,
discount,
total: subtotal + taxAmount + shippingCharge - discount,
items,
};
}
}
// payment.service.ts
import Razorpay from 'razorpay';
import crypto from 'crypto';
export class PaymentService {
private razorpay: Razorpay;
constructor() {
this.razorpay = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID!,
key_secret: process.env.RAZORPAY_KEY_SECRET!,
});
}
async createPaymentOrder(data: CreatePaymentOrderDTO): Promise<PaymentOrder> {
const options = {
amount: Math.round(data.amount * 100), // Convert to paise
currency: data.currency || 'INR',
receipt: data.orderId,
payment_capture: 1,
notes: {
orderId: data.orderId,
userId: data.userId,
},
};
const razorpayOrder = await this.razorpay.orders.create(options);
// Save to database
const transaction = await this.transactionRepository.create({
orderId: data.orderId,
paymentOrderId: razorpayOrder.id,
amount: data.amount,
currency: data.currency,
status: TransactionStatus.PENDING,
gatewayResponse: razorpayOrder,
});
return {
id: razorpayOrder.id,
amount: data.amount,
currency: data.currency,
orderId: data.orderId,
};
}
async verifyPayment(data: VerifyPaymentDTO): Promise<boolean> {
const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = data;
// Verify signature
const generatedSignature = crypto
.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET!)
.update(`${razorpay_order_id}|${razorpay_payment_id}`)
.digest('hex');
if (generatedSignature !== razorpay_signature) {
throw new Error('Invalid payment signature');
}
// Fetch payment details from Razorpay
const payment = await this.razorpay.payments.fetch(razorpay_payment_id);
// Update transaction
await this.transactionRepository.update({
paymentOrderId: razorpay_order_id,
paymentId: razorpay_payment_id,
status: payment.status === 'captured'
? TransactionStatus.SUCCESS
: TransactionStatus.FAILED,
gatewayResponse: payment,
});
return payment.status === 'captured';
}
async processRefund(orderId: string, amount?: number): Promise<Refund> {
const transaction = await this.transactionRepository.findByOrderId(orderId);
if (!transaction || transaction.status !== TransactionStatus.SUCCESS) {
throw new Error('No successful transaction found');
}
const refundAmount = amount || transaction.amount;
const refund = await this.razorpay.payments.refund(transaction.paymentId, {
amount: Math.round(refundAmount * 100),
notes: {
orderId,
reason: 'Customer requested refund',
},
});
// Save refund record
await this.refundRepository.create({
transactionId: transaction.id,
refundId: refund.id,
amount: refundAmount,
status: RefundStatus.PENDING,
gatewayResponse: refund,
});
return refund;
}
}
// security.middleware.ts
export class SecurityMiddleware {
// JWT Authentication
async authenticateToken(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
error: 'Authentication required',
});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
req.user = decoded;
// Check if token is blacklisted
const isBlacklisted = await this.redis.get(`blacklist:${token}`);
if (isBlacklisted) {
throw new Error('Token revoked');
}
next();
} catch (error) {
return res.status(403).json({
success: false,
error: 'Invalid or expired token',
});
}
}
// Role-Based Access Control
authorize(roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({
success: false,
error: 'Authentication required',
});
}
if (!roles.includes(req.user.role) ) {
return res.status(403).json({
success: false,
error: 'Insufficient permissions',
});
}
next();
};
}
// Input Sanitization
sanitizeInput(req: Request, res: Response, next: NextFunction) {
// Sanitize query parameters
req.query = this.sanitizeObject(req.query);
// Sanitize body
if (req.body) {
req.body = this.sanitizeObject(req.body);
}
// Sanitize params
req.params = this.sanitizeObject(req.params);
next();
}
private sanitizeObject(obj: any): any {
if (typeof obj === 'string') {
return validator.escape(obj);
}
if (Array.isArray(obj)) {
return obj.map(item => this.sanitizeObject(item));
}
if (obj && typeof obj === 'object') {
const sanitized: any = {};
for (const key in obj) {
sanitized[key] = this.sanitizeObject(obj[key]);
}
return sanitized;
}
return obj;
}
// CSRF Protection
csrfProtection(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const token = req.headers['x-csrf-token'];
const sessionToken = req.session?.csrfToken;
if (!token || token !== sessionToken) {
return res.status(403).json({
success: false,
error: 'Invalid CSRF token',
});
}
}
next();
}
}
// encryption.service.ts
export class EncryptionService {
private algorithm = 'aes-256-gcm';
private key: Buffer;
constructor() {
this.key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex');
}
encrypt(text: string): EncryptedData {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
};
}
decrypt(data: EncryptedData): string {
const decipher = crypto.createDecipheriv(
this.algorithm,
this.key,
Buffer.from(data.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(data.authTag, 'hex'));
let decrypted = decipher.update(data.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
# docker-compose.yml
version: '3.8'
services:
# Frontend Services
web-app:
build:
context: ./frontend
dockerfile: Dockerfile
environment:
- NEXT_PUBLIC_API_URL=${API_URL}
- NEXT_PUBLIC_RAZORPAY_KEY=${RAZORPAY_KEY}
ports:
- "3000:3000"
depends_on:
- api-gateway
admin-panel:
build:
context: ./admin
dockerfile: Dockerfile
ports:
- "3001:3001"
depends_on:
- api-gateway
# Backend Services
api-gateway:
build:
context: ./services/gateway
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- PORT=4000
ports:
- "4000:4000"
depends_on:
- auth-service
- user-service
- product-service
- order-service
auth-service:
build:
context: ./services/auth
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
- REDIS_URL=${REDIS_URL}
depends_on:
- postgres
- redis
user-service:
build:
context: ./services/user
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
depends_on:
- postgres
- redis
product-service:
build:
context: ./services/product
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- MONGODB_URL=${MONGODB_URL}
- ELASTICSEARCH_URL=${ELASTICSEARCH_URL}
- REDIS_URL=${REDIS_URL}
depends_on:
- mongodb
- elasticsearch
- redis
order-service:
build:
context: ./services/order
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
depends_on:
- postgres
- redis
payment-service:
build:
context: ./services/payment
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- RAZORPAY_KEY_ID=${RAZORPAY_KEY_ID}
- RAZORPAY_KEY_SECRET=${RAZORPAY_KEY_SECRET}
depends_on:
- postgres
notification-service:
build:
context: ./services/notification
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- TWILIO_SID=${TWILIO_SID}
- TWILIO_TOKEN=${TWILIO_TOKEN}
- SENDGRID_API_KEY=${SENDGRID_API_KEY}
depends_on:
- postgres
- redis
# Databases
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=petmart
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
mongodb:
image: mongo:6
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
volumes:
- mongo_data:/data/db
ports:
- "27017:27017"
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
ports:
- "6379:6379"
elasticsearch:
image: elasticsearch:8.11.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
volumes:
- elastic_data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
# Message Queue
rabbitmq:
image: rabbitmq:3-management-alpine
environment:
- RABBITMQ_DEFAULT_USER=${RABBITMQ_USER}
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
# Monitoring
prometheus:
image: prom/prometheus:latest
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3003:3000"
depends_on:
- prometheus
volumes:
postgres_data:
mongo_data:
redis_data:
elastic_data:
rabbitmq_data:
prometheus_data:
grafana_data:
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: petmart-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: petmart-api
template:
metadata:
labels:
app: petmart-api
spec:
containers:
- name: api
image: petmart/api:latest
ports:
- containerPort: 4000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: petmart-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 4000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: petmart-api-service
namespace: production
spec:
selector:
app: petmart-api
ports:
- port: 80
targetPort: 4000
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: petmart-api-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: petmart-api
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
// webhook.controller.ts
export class WebhookController {
// Razorpay Webhook
@Post('/webhooks/razorpay')
async handleRazorpayWebhook(req: Request, res: Response) {
const signature = req.headers['x-razorpay-signature'];
// Verify webhook signature
const isValid = this.verifyRazorpaySignature(
req.body,
signature,
process.env.RAZORPAY_WEBHOOK_SECRET!
);
if (!isValid) {
return res.status(400).json({ error: 'Invalid signature' });
}
const event = req.body.event;
const payload = req.body.payload;
switch (event) {
case 'payment.captured':
await this.handlePaymentCaptured(payload);
break;
case 'payment.failed':
await this.handlePaymentFailed(payload);
break;
case 'refund.processed':
await this.handleRefundProcessed(payload);
break;
default:
console.log(`Unhandled event: ${event}`);
}
res.status(200).json({ received: true });
}
// Shiprocket Webhook
@Post('/webhooks/shiprocket')
async handleShiprocketWebhook(req: Request, res: Response) {
const { event_name, data } = req.body;
switch (event_name) {
case 'shipment.pickup':
await this.updateOrderStatus(data.order_id, 'PICKED_UP');
break;
case 'shipment.delivered':
await this.updateOrderStatus(data.order_id, 'DELIVERED');
break;
case 'shipment.cancelled':
await this.handleShipmentCancelled(data);
break;
}
res.status(200).json({ received: true });
}
}
// cache.service.ts
export class CacheService {
private redis: Redis;
private defaultTTL = 300; // 5 minutes
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT!),
password: process.env.REDIS_PASSWORD,
db: 0,
});
}
// Multi-layer caching
async get<T>(key: string): Promise<T | null> {
// L1 Cache - Memory
const memoryCache = this.memoryCache.get(key);
if (memoryCache) {
return memoryCache;
}
// L2 Cache - Redis
const redisCache = await this.redis.get(key);
if (redisCache) {
const data = JSON.parse(redisCache);
this.memoryCache.set(key, data, 60); // 1 minute in memory
return data;
}
return null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
// Set in Redis
await this.redis.setex(key, ttl || this.defaultTTL, serialized);
// Set in memory cache
this.memoryCache.set(key, value, Math.min(ttl || this.defaultTTL, 60));
}
// Cache invalidation patterns
async invalidatePattern(pattern: string): Promise<void> {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
// Clear from memory cache
this.memoryCache.keys().forEach(key => {
if (key.match(pattern)) {
this.memoryCache.del(key);
}
});
}
}
// Usage in Product Service
export class ProductService {
async getProductById(id: string): Promise<Product> {
const cacheKey = `product:${id}`;
// Check cache
const cached = await this.cache.get<Product>(cacheKey);
if (cached) {
return cached;
}
// Fetch from database
const product = await this.productRepository.findById(id);
// Cache the result
await this.cache.set(cacheKey, product, 300);
return product;
}
}
-- Indexes for PostgreSQL
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_price ON products(base_price);
CREATE INDEX idx_cart_items_user_product ON cart_items(user_id, product_id);
-- Materialized View for Analytics
CREATE MATERIALIZED VIEW product_analytics AS
SELECT
p.id,
p.name,
p.category_id,
COUNT(DISTINCT oi.order_id) as order_count,
SUM(oi.quantity) as total_sold,
AVG(r.rating) as avg_rating,
COUNT(r.id) as review_count
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id
LEFT JOIN reviews r ON p.id = r.product_id
GROUP BY p.id, p.name, p.category_id;
-- Refresh materialized view
CREATE OR REPLACE FUNCTION refresh_product_analytics()
RETURNS void AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY product_analytics;
END;
$$ LANGUAGE plpgsql;
// Optimized query with pagination
export class ProductRepository {
async findProducts(filters: FilterDTO, pagination: PaginationDTO) {
const query = this.prisma.product.findMany({
where: this.buildWhereClause(filters),
include: {
category: true,
brand: true,
images: {
where: { isPrimary: true },
take: 1,
},
_count: {
select: {
reviews: true,
orderItems: true,
},
},
},
orderBy: this.buildOrderBy(filters.sortBy),
skip: (pagination.page - 1) * pagination.limit,
take: pagination.limit,
});
// Use cursor-based pagination for large datasets
if (pagination.cursor) {
return this.prisma.product.findMany({
where: {
...this.buildWhereClause(filters),
id: { gt: pagination.cursor },
},
take: pagination.limit,
orderBy: { id: 'asc' },
});
}
return query;
}
// Batch loading to avoid N+1 queries
async getProductsWithDetails(productIds: string[]) {
const products = await this.prisma.product.findMany({
where: { id: { in: productIds } },
});
const images = await this.prisma.productImage.findMany({
where: { productId: { in: productIds } },
});
const reviews = await this.prisma.review.groupBy({
by: ['productId'],
where: { productId: { in: productIds } },
_avg: { rating: true },
_count: true,
});
// Combine results
return products.map(product => ({
...product,
images: images.filter(img => img.productId === product.id),
rating: reviews.find(r => r.productId === product.id)?._avg.rating || 0,
reviewCount: reviews.find(r => r.productId === product.id)?._count || 0,
}));
}
}
# OpenAPI 3.0 Specification
openapi: 3.0.0
info:
title: PetMart India API
version: 1.0.0
description: E-commerce API for pet products
servers:
- url: https://api.petmartindia.com/v1
description: Production server
- url: https://staging-api.petmartindia.com/v1
description: Staging server
paths:
/products:
get:
summary: Get list of products
parameters:
- in: query
name: page
schema:
type: integer
default: 1
- in: query
name: limit
schema:
type: integer
default: 20
responses:
200:
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ProductListResponse'
components:
schemas:
Product:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
price:
type: number
description:
type: string
# .env.production
NODE_ENV=production
PORT=4000
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/petmart
MONGODB_URL=mongodb://user:password@localhost:27017/petmart
REDIS_URL=redis://localhost:6379
# Authentication
JWT_SECRET=your-secret-key
JWT_EXPIRY=15m
REFRESH_SECRET=your-refresh-secret
REFRESH_EXPIRY=7d
# Payment Gateway
RAZORPAY_KEY_ID=rzp_live_xxxxx
RAZORPAY_KEY_SECRET=xxxxx
RAZORPAY_WEBHOOK_SECRET=xxxxx
# Email Service
SENDGRID_API_KEY=SG.x.x
EMAIL_FROM=noreply@petmartindia.com
# SMS Service
TWILIO_ACCOUNT_SID=ACxxxxx
TWILIO_AUTH_TOKEN=xxxxx
TWILIO_PHONE_NUMBER=+1234567890
# Storage
AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx
AWS_S3_BUCKET=petmart-assets
AWS_REGION=ap-south-1
# Monitoring
SENTRY_DSN=https://xxxxx@sentry.io/xxxxx
NEW_RELIC_LICENSE_KEY=xxxxx
## Code Style Guide
### TypeScript/JavaScript
- Use TypeScript for all backend code
- Follow ESLint configuration
- Use Prettier for formatting
- Implement interfaces for all DTOs
### Git Workflow
- Feature branches: feature/feature-name
- Bug fixes: bugfix/issue-number
- Hotfixes: hotfix/description
- Commit format: type(scope): description
### Testing Requirements
- Unit test coverage: minimum 80%
- Integration tests for all API endpoints
- E2E tests for critical user flows
- Performance tests for high-traffic endpoints
### Documentation
- JSDoc comments for all public methods
- README.md for each service
- API documentation using OpenAPI/Swagger
- Architecture Decision Records (ADRs)
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2025-04-10 | Development Team | Initial version |
| Goal | Description | Success Metric |
|---|---|---|
| Findability | Users find products within 3 clicks | Click-through rate > 70% |
| Conversion | Streamlined checkout process | Cart abandonment < 30% |
| Engagement | Interactive pet profiles | Profile completion > 60% |
| Trust | Clear product information | Return rate < 5% |
| Accessibility | WCAG compliance | Accessibility score > 95% |
<!-- Primary Navigation -->
<nav class="primary-nav">
<div class="nav-container">
<div class="logo">
<img src="logo.svg" alt="PetMart India" />
</div>
<div class="search-bar">
<input type="search" placeholder="Search for products..." />
<button class="search-btn">🔍</button>
</div>
<div class="nav-actions">
<button class="nav-item">
<span class="icon">❤️</span>
<span class="label">Wishlist</span>
</button>
<button class="nav-item">
<span class="icon">👤</span>
<span class="label">Account</span>
</button>
<button class="nav-item cart">
<span class="icon">🛒</span>
<span class="badge">3</span>
<span class="label">Cart</span>
</button>
</div>
</div>
<div class="category-nav">
<button class="category-item">🐕 Dogs</button>
<button class="category-item">🐈 Cats</button>
<button class="category-item">🐦 Birds</button>
<button class="category-item">🐠 Fish</button>
<button class="category-item">🐹 Small Pets</button>
<button class="category-item deals">💰 Deals</button>
<button class="category-item">📦 Subscribe & Save</button>
</div>
</nav>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
/* Wireframe Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.wireframe {
font-family: 'Segoe UI', Arial, sans-serif;
max-width: 1440px;
margin: 0 auto;
border: 2px solid #333;
}
/* Header */
.header {
border-bottom: 2px solid #333;
padding: 20px;
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.logo {
width: 150px;
height: 50px;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #999;
}
.search-section {
flex: 1;
max-width: 600px;
margin: 0 40px;
}
.search-bar {
display: flex;
border: 2px solid #333;
border-radius: 25px;
overflow: hidden;
}
.search-input {
flex: 1;
padding: 12px 20px;
border: none;
background: #f9f9f9;
}
.search-btn {
padding: 12px 24px;
background: #333;
color: white;
border: none;
cursor: pointer;
}
.header-actions {
display: flex;
gap: 20px;
}
.header-action {
width: 40px;
height: 40px;
border: 2px solid #333;
border-radius: 50%;
background: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* Navigation */
.nav-categories {
display: flex;
gap: 20px;
padding: 15px 0;
border-top: 1px solid #ddd;
}
.nav-item {
padding: 8px 16px;
border: 1px solid #999;
border-radius: 20px;
background: white;
cursor: pointer;
}
/* Hero Section */
.hero {
height: 400px;
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
border-bottom: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.hero-content {
text-align: center;
}
.hero-title {
font-size: 48px;
margin-bottom: 20px;
color: #333;
}
.hero-subtitle {
font-size: 20px;
color: #666;
margin-bottom: 30px;
}
.cta-button {
padding: 15px 40px;
font-size: 18px;
background: #ff6b6b;
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
}
/* Category Grid */
.category-section {
padding: 40px 20px;
border-bottom: 2px solid #333;
}
.section-title {
font-size: 32px;
text-align: center;
margin-bottom: 30px;
color: #333;
}
.category-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.category-card {
border: 2px solid #333;
border-radius: 10px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: transform 0.3s;
}
.category-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.category-icon {
width: 80px;
height: 80px;
margin: 0 auto 15px;
background: #f0f0f0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
}
/* Product Grid */
.product-section {
padding: 40px 20px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.product-card {
border: 2px solid #333;
border-radius: 10px;
overflow: hidden;
}
.product-image {
height: 200px;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #333;
}
.product-info {
padding: 15px;
}
.product-name {
font-size: 16px;
margin-bottom: 8px;
color: #333;
}
.product-price {
font-size: 20px;
font-weight: bold;
color: #ff6b6b;
margin-bottom: 10px;
}
.add-to-cart {
width: 100%;
padding: 10px;
background: #333;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* Footer */
.footer {
background: #333;
color: white;
padding: 40px 20px;
margin-top: 40px;
}
.footer-content {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 40px;
margin-bottom: 30px;
}
.footer-section h4 {
margin-bottom: 15px;
}
.footer-links {
list-style: none;
}
.footer-links li {
margin-bottom: 10px;
opacity: 0.8;
}
</style>
</head>
<body>
<div class="wireframe">
<!-- Header -->
<header class="header">
<div class="header-top">
<div class="logo">LOGO</div>
<div class="search-section">
<div class="search-bar">
<input type="text" class="search-input" placeholder="Search for pet products...">
<button class="search-btn">Search</button>
</div>
</div>
<div class="header-actions">
<div class="header-action">❤️</div>
<div class="header-action">👤</div>
<div class="header-action">🛒</div>
</div>
</div>
<nav class="nav-categories">
<button class="nav-item">🐕 Dogs</button>
<button class="nav-item">🐈 Cats</button>
<button class="nav-item">🐦 Birds</button>
<button class="nav-item">🐠 Fish</button>
<button class="nav-item">💰 Deals</button>
<button class="nav-item">📦 Subscribe</button>
</nav>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="hero-content">
<h1 class="hero-title">Everything Your Pet Needs</h1>
<p class="hero-subtitle">Premium pet products delivered to your doorstep</p>
<button class="cta-button">Shop Now</button>
</div>
</section>
<!-- Category Section -->
<section class="category-section">
<h2 class="section-title">Shop by Pet</h2>
<div class="category-grid">
<div class="category-card">
<div class="category-icon">🐕</div>
<h3>Dogs</h3>
</div>
<div class="category-card">
<div class="category-icon">🐈</div>
<h3>Cats</h3>
</div>
<div class="category-card">
<div class="category-icon">🐦</div>
<h3>Birds</h3>
</div>
<div class="category-card">
<div class="category-icon">🐠</div>
<h3>Fish</h3>
</div>
</div>
</section>
<!-- Product Section -->
<section class="product-section">
<h2 class="section-title">Featured Products</h2>
<div class="product-grid">
<div class="product-card">
<div class="product-image">Image</div>
<div class="product-info">
<h3 class="product-name">Premium Dog Food</h3>
<p class="product-price">₹1,299</p>
<button class="add-to-cart">Add to Cart</button>
</div>
</div>
<div class="product-card">
<div class="product-image">Image</div>
<div class="product-info">
<h3 class="product-name">Cat Toy Set</h3>
<p class="product-price">₹499</p>
<button class="add-to-cart">Add to Cart</button>
</div>
</div>
<div class="product-card">
<div class="product-image">Image</div>
<div class="product-info">
<h3 class="product-name">Bird Cage</h3>
<p class="product-price">₹2,999</p>
<button class="add-to-cart">Add to Cart</button>
</div>
</div>
<div class="product-card">
<div class="product-image">Image</div>
<div class="product-info">
<h3 class="product-name">Fish Food</h3>
<p class="product-price">₹299</p>
<button class="add-to-cart">Add to Cart</button>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="footer-section">
<h4>About</h4>
<ul class="footer-links">
<li>About Us</li>
<li>Careers</li>
<li>Press</li>
</ul>
</div>
<div class="footer-section">
<h4>Help</h4>
<ul class="footer-links">
<li>Contact Us</li>
<li>FAQ</li>
<li>Shipping</li>
</ul>
</div>
<div class="footer-section">
<h4>Policies</h4>
<ul class="footer-links">
<li>Privacy Policy</li>
<li>Terms of Use</li>
<li>Returns</li>
</ul>
</div>
<div class="footer-section">
<h4>Connect</h4>
<ul class="footer-links">
<li>Facebook</li>
<li>Instagram</li>
<li>Twitter</li>
</ul>
</div>
</div>
</footer>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.product-detail {
max-width: 1440px;
margin: 0 auto;
padding: 20px;
}
.breadcrumb {
padding: 10px 0;
color: #666;
font-size: 14px;
}
.product-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
margin-top: 20px;
}
.product-gallery {
display: grid;
gap: 20px;
}
.main-image {
width: 100%;
height: 500px;
background: #f5f5f5;
border: 2px solid #333;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.thumbnail-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.thumbnail {
height: 100px;
background: #f5f5f5;
border: 2px solid #333;
border-radius: 5px;
cursor: pointer;
}
.product-details {
padding: 20px;
}
.product-title {
font-size: 32px;
margin-bottom: 10px;
color: #333;
}
.product-rating {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.stars {
color: #ffa500;
}
.price-section {
margin: 20px 0;
padding: 20px;
background: #f9f9f9;
border-radius: 10px;
}
.current-price {
font-size: 36px;
color: #ff6b6b;
font-weight: bold;
}
.original-price {
font-size: 20px;
color: #999;
text-decoration: line-through;
margin-left: 10px;
}
.discount-badge {
display: inline-block;
padding: 5px 10px;
background: #4caf50;
color: white;
border-radius: 5px;
margin-left: 10px;
}
.variant-section {
margin: 30px 0;
}
.variant-label {
font-weight: bold;
margin-bottom: 10px;
display: block;
}
.variant-options {
display: flex;
gap: 10px;
}
.variant-option {
padding: 10px 20px;
border: 2px solid #333;
border-radius: 5px;
cursor: pointer;
background: white;
}
.variant-option.selected {
background: #333;
color: white;
}
.quantity-section {
margin: 30px 0;
display: flex;
align-items: center;
gap: 20px;
}
.quantity-controls {
display: flex;
align-items: center;
border: 2px solid #333;
border-radius: 5px;
}
.qty-btn {
padding: 10px 15px;
background: white;
border: none;
cursor: pointer;
font-size: 20px;
}
.qty-input {
width: 60px;
padding: 10px;
border: none;
text-align: center;
font-size: 18px;
}
.action-buttons {
display: flex;
gap: 20px;
margin-top: 30px;
}
.btn-primary {
flex: 1;
padding: 15px;
background: #ff6b6b;
color: white;
border: none;
border-radius: 5px;
font-size: 18px;
cursor: pointer;
}
.btn-secondary {
padding: 15px 30px;
background: white;
border: 2px solid #333;
border-radius: 5px;
font-size: 18px;
cursor: pointer;
}
.product-info-tabs {
margin-top: 60px;
border-top: 2px solid #333;
}
.tab-list {
display: flex;
gap: 30px;
padding: 20px 0;
border-bottom: 1px solid #ddd;
}
.tab {
padding: 10px;
cursor: pointer;
font-weight: bold;
}
.tab.active {
color: #ff6b6b;
border-bottom: 3px solid #ff6b6b;
}
.tab-content {
padding: 30px 0;
}
</style>
</head>
<body>
<div class="product-detail">
<div class="breadcrumb">
Home > Dogs > Food > Premium Dog Food
</div>
<div class="product-container">
<div class="product-gallery">
<div class="main-image">Main Product Image</div>
<div class="thumbnail-list">
<div class="thumbnail">1</div>
<div class="thumbnail">2</div>
<div class="thumbnail">3</div>
<div class="thumbnail">4</div>
</div>
</div>
<div class="product-details">
<h1 class="product-title">Royal Canin Premium Dog Food</h1>
<div class="product-rating">
<span class="stars">⭐⭐⭐⭐⭐</span>
<span>4.5 (234 reviews)</span>
</div>
<div class="price-section">
<span class="current-price">₹1,299</span>
<span class="original-price">₹1,599</span>
<span class="discount-badge">20% OFF</span>
</div>
<div class="variant-section">
<label class="variant-label">Size:</label>
<div class="variant-options">
<button class="variant-option">1 kg</button>
<button class="variant-option selected">3 kg</button>
<button class="variant-option">5 kg</button>
<button class="variant-option">10 kg</button>
</div>
</div>
<div class="variant-section">
<label class="variant-label">Flavor:</label>
<div class="variant-options">
<button class="variant-option selected">Chicken</button>
<button class="variant-option">Lamb</button>
<button class="variant-option">Fish</button>
</div>
</div>
<div class="quantity-section">
<label class="variant-label">Quantity:</label>
<div class="quantity-controls">
<button class="qty-btn">-</button>
<input type="text" class="qty-input" value="1">
<button class="qty-btn">+</button>
</div>
</div>
<div class="action-buttons">
<button class="btn-primary">Add to Cart</button>
<button class="btn-secondary">❤️</button>
</div>
<div class="product-highlights">
<h3>Product Highlights</h3>
<ul>
<li>✓ High-quality protein for muscle development</li>
<li>✓ Essential vitamins and minerals</li>
<li>✓ Suitable for adult dogs</li>
<li>✓ Improves coat health</li>
</ul>
</div>
</div>
</div>
<div class="product-info-tabs">
<div class="tab-list">
<div class="tab active">Description</div>
<div class="tab">Ingredients</div>
<div class="tab">Nutritional Info</div>
<div class="tab">Reviews</div>
</div>
<div class="tab-content">
Tab content goes here...
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.color-system {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
}
.color-section {
margin-bottom: 40px;
}
.section-title {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.color-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
.color-card {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.color-sample {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.color-info {
padding: 15px;
background: white;
}
.color-name {
font-weight: 600;
margin-bottom: 5px;
}
.color-hex {
color: #666;
font-size: 14px;
}
/* Brand Colors */
.primary { background: #FF6B6B; }
.secondary { background: #4ECDC4; }
.accent { background: #FFE66D; }
.dark { background: #2D3436; }
.light { background: #F7F7F7; }
/* Semantic Colors */
.success { background: #4CAF50; }
.warning { background: #FFA726; }
.error { background: #EF5350; }
.info { background: #42A5F5; }
/* Neutral Colors */
.gray-900 { background: #212121; }
.gray-700 { background: #616161; }
.gray-500 { background: #9E9E9E; }
.gray-300 { background: #E0E0E0; }
.gray-100 { background: #F5F5F5; }
</style>
</head>
<body>
<div class="color-system">
<div class="color-section">
<h2 class="section-title">Primary Brand Colors</h2>
<div class="color-grid">
<div class="color-card">
<div class="color-sample primary">Primary</div>
<div class="color-info">
<div class="color-name">Coral Red</div>
<div class="color-hex">#FF6B6B</div>
</div>
</div>
<div class="color-card">
<div class="color-sample secondary">Secondary</div>
<div class="color-info">
<div class="color-name">Turquoise</div>
<div class="color-hex">#4ECDC4</div>
</div>
</div>
<div class="color-card">
<div class="color-sample accent" style="color: #333;">Accent</div>
<div class="color-info">
<div class="color-name">Sunshine</div>
<div class="color-hex">#FFE66D</div>
</div>
</div>
<div class="color-card">
<div class="color-sample dark">Dark</div>
<div class="color-info">
<div class="color-name">Charcoal</div>
<div class="color-hex">#2D3436</div>
</div>
</div>
</div>
</div>
<div class="color-section">
<h2 class="section-title">Semantic Colors</h2>
<div class="color-grid">
<div class="color-card">
<div class="color-sample success">Success</div>
<div class="color-info">
<div class="color-name">Green</div>
<div class="color-hex">#4CAF50</div>
</div>
</div>
<div class="color-card">
<div class="color-sample warning">Warning</div>
<div class="color-info">
<div class="color-name">Orange</div>
<div class="color-hex">#FFA726</div>
</div>
</div>
<div class="color-card">
<div class="color-sample error">Error</div>
<div class="color-info">
<div class="color-name">Red</div>
<div class="color-hex">#EF5350</div>
</div>
</div>
<div class="color-card">
<div class="color-sample info">Info</div>
<div class="color-info">
<div class="color-name">Blue</div>
<div class="color-hex">#42A5F5</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');
.typography-system {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
}
.type-section {
margin-bottom: 60px;
}
.type-scale {
margin-bottom: 40px;
}
.type-sample {
margin-bottom: 24px;
padding: 20px;
border-left: 4px solid #FF6B6B;
background: #f9f9f9;
}
.type-label {
font-size: 12px;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
}
/* Type Scale */
.display-1 {
font-family: 'Poppins', sans-serif;
font-size: 56px;
font-weight: 700;
line-height: 1.2;
}
.display-2 {
font-family: 'Poppins', sans-serif;
font-size: 48px;
font-weight: 600;
line-height: 1.2;
}
.h1 {
font-family: 'Poppins', sans-serif;
font-size: 40px;
font-weight: 600;
line-height: 1.3;
}
.h2 {
font-family: 'Poppins', sans-serif;
font-size: 32px;
font-weight: 600;
line-height: 1.3;
}
.h3 {
font-family: 'Poppins', sans-serif;
font-size: 24px;
font-weight: 500;
line-height: 1.4;
}
.h4 {
font-family: 'Poppins', sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1.4;
}
.body-large {
font-family: 'Inter', sans-serif;
font-size: 18px;
font-weight: 400;
line-height: 1.6;
}
.body-regular {
font-family: 'Inter', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 1.6;
}
.body-small {
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.5;
}
.caption {
font-family: 'Inter', sans-serif;
font-size: 12px;
font-weight: 400;
line-height: 1.4;
}
.button-text {
font-family: 'Inter', sans-serif;
font-size: 16px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
</style>
</head>
<body>
<div class="typography-system">
<h1 style="margin-bottom: 40px;">Typography System</h1>
<div class="type-section">
<h2>Type Scale</h2>
<div class="type-sample">
<div class="type-label">Display 1 - 56px/700</div>
<div class="display-1">Welcome to PetMart</div>
</div>
<div class="type-sample">
<div class="type-label">Display 2 - 48px/600</div>
<div class="display-2">Everything for Your Pet</div>
</div>
<div class="type-sample">
<div class="type-label">Heading 1 - 40px/600</div>
<div class="h1">Premium Pet Products</div>
</div>
<div class="type-sample">
<div class="type-label">Heading 2 - 32px/600</div>
<div class="h2">Shop by Category</div>
</div>
<div class="type-sample">
<div class="type-label">Heading 3 - 24px/500</div>
<div class="h3">Featured Products</div>
</div>
<div class="type-sample">
<div class="type-label">Heading 4 - 20px/500</div>
<div class="h4">Product Details</div>
</div>
<div class="type-sample">
<div class="type-label">Body Large - 18px/400</div>
<div class="body-large">We provide high-quality pet products for dogs, cats, birds, and more.</div>
</div>
<div class="type-sample">
<div class="type-label">Body Regular - 16px/400</div>
<div class="body-regular">Browse our extensive collection of pet foods, toys, and accessories.</div>
</div>
<div class="type-sample">
<div class="type-label">Body Small - 14px/400</div>
<div class="body-small">Free shipping on orders above ₹500. Same-day delivery available.</div>
</div>
<div class="type-sample">
<div class="type-label">Caption - 12px/400</div>
<div class="caption">*Terms and conditions apply</div>
</div>
<div class="type-sample">
<div class="type-label">Button - 16px/600</div>
<div class="button-text">Add to Cart</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.component-library {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
font-family: 'Inter', sans-serif;
}
.component-section {
margin-bottom: 60px;
}
.component-title {
font-size: 28px;
margin-bottom: 30px;
color: #333;
}
.component-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
/* Button Styles */
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-primary {
background: #FF6B6B;
color: white;
}
.btn-primary:hover {
background: #FF5252;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
}
.btn-secondary {
background: #4ECDC4;
color: white;
}
.btn-secondary:hover {
background: #3DB5AC;
transform: translateY(-2px);
}
.btn-outline {
background: transparent;
border: 2px solid #FF6B6B;
color: #FF6B6B;
}
.btn-outline:hover {
background: #FF6B6B;
color: white;
}
.btn-ghost {
background: transparent;
color: #333;
}
.btn-ghost:hover {
background: #f5f5f5;
}
.btn-icon {
width: 48px;
height: 48px;
padding: 0;
border-radius: 50%;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
}
.btn-small {
padding: 8px 16px;
font-size: 14px;
}
.btn-disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Form Elements */
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.form-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #E0E0E0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-input:focus {
outline: none;
border-color: #FF6B6B;
}
.form-select {
width: 100%;
padding: 12px 16px;
border: 2px solid #E0E0E0;
border-radius: 8px;
font-size: 16px;
background: white;
cursor: pointer;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.card-image {
width: 100%;
height: 200px;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}
.card-body {
padding: 20px;
}
.card-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
.card-text {
color: #666;
line-height: 1.6;
}
/* Badges */
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.badge-primary {
background: #FF6B6B;
color: white;
}
.badge-success {
background: #4CAF50;
color: white;
}
.badge-warning {
background: #FFA726;
color: white;
}
.badge-info {
background: #42A5F5;
color: white;
}
/* Alerts */
.alert {
padding: 16px 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
}
.alert-success {
background: #E8F5E9;
color: #2E7D32;
border-left: 4px solid #4CAF50;
}
.alert-warning {
background: #FFF3E0;
color: #E65100;
border-left: 4px solid #FFA726;
}
.alert-error {
background: #FFEBEE;
color: #C62828;
border-left: 4px solid #EF5350;
}
.alert-info {
background: #E3F2FD;
color: #1565C0;
border-left: 4px solid #42A5F5;
}
</style>
</head>
<body>
<div class="component-library">
<h1 class="component-title">Component Library</h1>
<!-- Buttons -->
<div class="component-section">
<h2>Buttons</h2>
<div class="component-grid">
<button class="btn btn-primary">Primary Button</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-outline">Outline</button>
<button class="btn btn-ghost">Ghost</button>
</div>
<h3>Button Sizes</h3>
<div class="component-grid">
<button class="btn btn-primary btn-small">Small</button>
<button class="btn btn-primary">Regular</button>
<button class="btn btn-primary btn-large">Large</button>
</div>
<h3>Icon Buttons</h3>
<div class="component-grid">
<button class="btn btn-primary btn-icon">❤️</button>
<button class="btn btn-outline btn-icon">🛒</button>
<button class="btn btn-ghost btn-icon">⚙️</button>
</div>
</div>
<!-- Form Elements -->
<div class="component-section">
<h2>Form Elements</h2>
<div class="form-group">
<label class="form-label">Email Address</label>
<input type="email" class="form-input" placeholder="Enter your email">
</div>
<div class="form-group">
<label class="form-label">Pet Type</label>
<select class="form-select">
<option>Select pet type</option>
<option>Dog</option>
<option>Cat</option>
<option>Bird</option>
</select>
</div>
</div>
<!-- Cards -->
<div class="component-section">
<h2>Product Card</h2>
<div style="max-width: 300px;">
<div class="card">
<div class="card-image">Product Image</div>
<div class="card-body">
<h3 class="card-title">Premium Dog Food</h3>
<p class="card-text">High-quality nutrition for your pet</p>
<div style="margin: 16px 0;">
<span class="badge badge-success">In Stock</span>
<span class="badge badge-warning" style="margin-left: 8px;">Sale</span>
</div>
<button class="btn btn-primary" style="width: 100%;">Add to Cart</button>
</div>
</div>
</div>
</div>
<!-- Alerts -->
<div class="component-section">
<h2>Alerts</h2>
<div class="alert alert-success">
✓ Your order has been placed successfully!
</div>
<div class="alert alert-warning">
⚠️ This product is running low in stock
</div>
<div class="alert alert-error">
✕ Payment failed. Please try again.
</div>
<div class="alert alert-info">
ℹ️ Free shipping on orders above ₹500
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
/* Responsive Grid System */
.container {
width: 100%;
max-width: 1440px;
margin: 0 auto;
padding: 0 16px;
}
@media (min-width: 768px) {
.container {
padding: 0 24px;
}
}
@media (min-width: 1024px) {
.container {
padding: 0 32px;
}
}
.grid {
display: grid;
gap: 20px;
}
/* Mobile: 1 column */
.grid-cols-1 {
grid-template-columns: 1fr;
}
/* Tablet: 2 columns */
@media (min-width: 768px) {
.grid-cols-md-2 {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop: 3 columns */
@media (min-width: 1024px) {
.grid-cols-lg-3 {
grid-template-columns: repeat(3, 1fr);
}
}
/* Large Desktop: 4 columns */
@media (min-width: 1440px) {
.grid-cols-xl-4{
grid-template-columns: repeat(4, 1fr);
}
}
/* Responsive Navigation */
.nav-mobile {
display: block;
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #e0e0e0;
z-index: 1000;
}
.nav-mobile-items {
display: flex;
justify-content: space-around;
padding: 8px 0;
}
.nav-mobile-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
font-size: 12px;
color: #666;
}
.nav-mobile-icon {
font-size: 24px;
margin-bottom: 4px;
}
@media (min-width: 768px) {
.nav-mobile {
display: none;
}
}
/* Responsive Typography */
.text-responsive {
font-size: 14px;
line-height: 1.5;
}
@media (min-width: 768px) {
.text-responsive {
font-size: 16px;
line-height: 1.6;
}
}
@media (min-width: 1024px) {
.text-responsive {
font-size: 18px;
line-height: 1.7;
}
}
/* Responsive Spacing */
.spacing-responsive {
padding: 16px;
}
@media (min-width: 768px) {
.spacing-responsive {
padding: 24px;
}
}
@media (min-width: 1024px) {
.spacing-responsive {
padding: 32px;
}
}
/* Responsive Images */
.image-responsive {
width: 100%;
height: auto;
display: block;
}
.aspect-ratio-16-9 {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
overflow: hidden;
}
.aspect-ratio-16-9 img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Mobile-specific styles */
@media (max-width: 767px) {
.hide-mobile {
display: none !important;
}
.stack-mobile {
flex-direction: column !important;
}
.full-width-mobile {
width: 100% !important;
}
}
/* Tablet-specific styles */
@media (min-width: 768px) and (max-width: 1023px) {
.hide-tablet {
display: none !important;
}
}
/* Desktop-specific styles */
@media (min-width: 1024px) {
.hide-desktop {
display: none !important;
}
}
</style>
</head>
<body>
<div class="container">
<h2>Responsive Grid Example</h2>
<div class="grid grid-cols-1 grid-cols-md-2 grid-cols-lg-3 grid-cols-xl-4">
<div style="background: #f5f5f5; padding: 20px; border-radius: 8px;">
<h3>Grid Item 1</h3>
<p class="text-responsive">Responsive text that scales with screen size</p>
</div>
<div style="background: #f5f5f5; padding: 20px; border-radius: 8px;">
<h3>Grid Item 2</h3>
<p class="text-responsive">Adapts to different breakpoints</p>
</div>
<div style="background: #f5f5f5; padding: 20px; border-radius: 8px;">
<h3>Grid Item 3</h3>
<p class="text-responsive">Mobile-first approach</p>
</div>
<div style="background: #f5f5f5; padding: 20px; border-radius: 8px;">
<h3>Grid Item 4</h3>
<p class="text-responsive">Flexible layout system</p>
</div>
</div>
<!-- Mobile Navigation (visible only on mobile) -->
<nav class="nav-mobile">
<div class="nav-mobile-items">
<div class="nav-mobile-item">
<span class="nav-mobile-icon">🏠</span>
<span>Home</span>
</div>
<div class="nav-mobile-item">
<span class="nav-mobile-icon">🔍</span>
<span>Search</span>
</div>
<div class="nav-mobile-item">
<span class="nav-mobile-icon">📦</span>
<span>Orders</span>
</div>
<div class="nav-mobile-item">
<span class="nav-mobile-icon">🛒</span>
<span>Cart</span>
</div>
<div class="nav-mobile-item">
<span class="nav-mobile-icon">👤</span>
<span>Profile</span>
</div>
</div>
</nav>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
/* Micro-interactions */
@keyframes heartBeat {
0% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.3); }
70% { transform: scale(1); }
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeInUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.interactions {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
}
/* Like Button */
.like-button {
background: none;
border: none;
font-size: 32px;
cursor: pointer;
transition: transform 0.2s;
}
.like-button:hover {
transform: scale(1.1);
}
.like-button.liked {
animation: heartBeat 1s;
color: #FF6B6B;
}
/* Add to Cart Animation */
.add-to-cart-btn {
position: relative;
padding: 12px 24px;
background: #FF6B6B;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
overflow: hidden;
}
.add-to-cart-btn::after {
content: '✓ Added';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
transition: opacity 0.3s;
}
.add-to-cart-btn.added::after {
opacity: 1;
}
.add-to-cart-btn.added .btn-text {
opacity: 0;
}
/* Loading States */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-text {
height: 20px;
margin-bottom: 10px;
border-radius: 4px;
}
.skeleton-image {
height: 200px;
border-radius: 8px;
margin-bottom: 10px;
}
/* Hover Effects */
.hover-lift {
transition: transform 0.3s, box-shadow 0.3s;
}
.hover-lift:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.hover-grow {
transition: transform 0.3s;
}
.hover-grow:hover {
transform: scale(1.05);
}
/* Toast Notifications */
.toast {
position: fixed;
bottom: 20px;
right: 20px;
padding: 16px 24px;
background: #333;
color: white;
border-radius: 8px;
animation: slideIn 0.3s ease-out;
z-index: 1000;
}
.toast.success {
background: #4CAF50;
}
.toast.error {
background: #EF5350;
}
/* Progress Indicators */
.progress-bar {
width: 100%;
height: 4px;
background: #e0e0e0;
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #FF6B6B;
border-radius: 2px;
transition: width 0.3s ease;
}
/* Tab Transitions */
.tabs {
display: flex;
gap: 20px;
border-bottom: 2px solid #e0e0e0;
margin-bottom: 20px;
}
.tab {
padding: 12px 20px;
background: none;
border: none;
cursor: pointer;
position: relative;
transition: color 0.3s;
}
.tab::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: #FF6B6B;
transition: width 0.3s;
}
.tab.active::after {
width: 100%;
}
/* Form Validation Feedback */
.form-field {
position: relative;
margin-bottom: 20px;
}
.form-field.error .form-input {
border-color: #EF5350;
animation: shake 0.5s;
}
.form-field.success .form-input {
border-color: #4CAF50;
}
.validation-message {
font-size: 12px;
margin-top: 4px;
animation: fadeInUp 0.3s;
}
.validation-message.error {
color: #EF5350;
}
.validation-message.success {
color: #4CAF50;
}
</style>
</head>
<body>
<div class="interactions">
<h2>Micro-interactions Demo</h2>
<!-- Like Button -->
<div style="margin: 40px 0;">
<h3>Like Animation</h3>
<button class="like-button" onclick="this.classList.toggle('liked')">❤️</button>
</div>
<!-- Add to Cart -->
<div style="margin: 40px 0;">
<h3>Add to Cart Animation</h3>
<button class="add-to-cart-btn" onclick="addToCart(this)">
<span class="btn-text">Add to Cart</span>
</button>
</div>
<!-- Loading Skeleton -->
<div style="margin: 40px 0;">
<h3>Loading States</h3>
<div style="max-width: 300px;">
<div class="skeleton skeleton-image"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text" style="width: 80%;"></div>
</div>
</div>
<!-- Hover Effects -->
<div style="margin: 40px 0;">
<h3>Hover Effects</h3>
<div style="display: flex; gap: 20px;">
<div class="hover-lift" style="padding: 20px; background: #f5f5f5; border-radius: 8px;">
Lift on Hover
</div>
<div class="hover-grow" style="padding: 20px; background: #f5f5f5; border-radius: 8px;">
Grow on Hover
</div>
</div>
</div>
<!-- Progress Bar -->
<div style="margin: 40px 0;">
<h3>Progress Indicator</h3>
<div class="progress-bar">
<div class="progress-fill" style="width: 60%;"></div>
</div>
</div>
<!-- Tabs -->
<div style="margin: 40px 0;">
<h3>Tab Navigation</h3>
<div class="tabs">
<button class="tab active">Description</button>
<button class="tab">Reviews</button>
<button class="tab">Shipping</button>
</div>
</div>
<!-- Form Validation -->
<div style="margin: 40px 0;">
<h3>Form Validation</h3>
<div class="form-field error">
<input type="email" class="form-input" placeholder="Enter email" style="width: 300px; padding: 10px; border: 2px solid #e0e0e0; border-radius: 4px;">
<div class="validation-message error">Please enter a valid email address</div>
</div>
</div>
</div>
<script>
function addToCart(button) {
button.classList.add('added');
setTimeout(() => {
button.classList.remove('added');
}, 2000);
}
// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
/* Accessibility Styles */
.accessibility-demo {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
}
/* Focus Styles */
*:focus {
outline: 3px solid #4A90E2;
outline-offset: 2px;
}
/* Skip to Content */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
/* High Contrast Mode */
@media (prefers-contrast: high) {
.btn {
border: 2px solid currentColor;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Color Contrast Examples */
.good-contrast {
background: #FFFFFF;
color: #2C3E50; /* Contrast ratio: 12.63:1 */
}
.poor-contrast {
background: #FFFFFF;
color: #CCCCCC; /* Contrast ratio: 1.6:1 - FAILS */
}
/* Screen Reader Only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Accessible Forms */
.accessible-form {
max-width: 500px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-weight: 600;
margin-bottom: 5px;
}
.form-label .required {
color: #EF5350;
margin-left: 4px;
}
.form-input {
width: 100%;
padding: 10px;
border: 2px solid #333;
border-radius: 4px;
font-size: 16px;
}
.form-help {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.error-message {
color: #D32F2F;
font-size: 14px;
margin-top: 5px;
display: flex;
align-items: center;
gap: 5px;
}
/* Accessible Buttons */
.btn-accessible {
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
border: 2px solid transparent;
border-radius: 4px;
cursor: pointer;
min-height: 44px; /* Touch target size */
min-width: 44px;
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
body {
background: #1a1a1a;
color: #ffffff;
}
.form-input {
background: #2a2a2a;
color: #ffffff;
border-color: #4a4a4a;
}
}
</style>
</head>
<body>
<div class="accessibility-demo">
<!-- Skip to main content link -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Semantic HTML with ARIA -->
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
<ul>
<li><a href="#" aria-current="page">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
</header>
<main id="main-content" role="main">
<h1>Accessibility Guidelines</h1>
<!-- Color Contrast Examples -->
<section aria-labelledby="contrast-heading">
<h2 id="contrast-heading">Color Contrast</h2>
<div class="good-contrast" style="padding: 20px; margin: 10px 0;">
✓ Good contrast (12.63:1) - Easily readable
</div>
<div class="poor-contrast" style="padding: 20px; margin: 10px 0;">
✗ Poor contrast (1.6:1) - Hard to read
</div>
</section>
<!-- Accessible Form -->
<section aria-labelledby="form-heading">
<h2 id="form-heading">Accessible Form Example</h2>
<form class="accessible-form" aria-label="Contact form">
<div class="form-group">
<label for="name" class="form-label">
Full Name <span class="required" aria-label="required">*</span>
</label>
<input
type="text"
id="name"
name="name"
class="form-input"
aria-required="true"
aria-describedby="name-help"
>
<span id="name-help" class="form-help">Enter your first and last name</span>
</div>
<div class="form-group">
<label for="email" class="form-label">
Email Address <span class="required" aria-label="required">*</span>
</label>
<input
type="email"
id="email"
name="email"
class="form-input"
aria-required="true"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error" class="error-message" role="alert">
<span aria-hidden="true">⚠️</span> Please enter a valid email address
</span>
</div>
<div class="form-group">
<label for="pet-type" class="form-label">Pet Type</label>
<select id="pet-type" name="pet-type" class="form-input">
<option value="">Select a pet type</option>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="bird">Bird</option>
<option value="other">Other</option>
</select>
</div>
<button type="submit" class="btn-accessible" style="background: #4CAF50; color: white;">
Submit Form
</button>
</form>
</section>
<!-- Image with Alt Text -->
<section aria-labelledby="images-heading">
<h2 id="images-heading">Images with Alt Text</h2>
<figure>
<img src="placeholder.jpg" alt="Happy golden retriever playing with a red ball in a sunny park" style="max-width: 400px; height: 200px; background: #f0f0f0;">
<figcaption>Descriptive alt text helps screen reader users understand image content</figcaption>
</figure>
</section>
<!-- Keyboard Navigation Demo -->
<section aria-labelledby="keyboard-heading">
<h2 id="keyboard-heading">Keyboard Navigation</h2>
<p>All interactive elements should be keyboard accessible. Try tabbing through these buttons:</p>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn-accessible" style="background: #FF6B6B; color: white;">Button 1</button>
<button class="btn-accessible" style="background: #4ECDC4; color: white;">Button 2</button>
<button class="btn-accessible" style="background: #FFE66D; color: #333;">Button 3</button>
</div>
</section>
<!-- Screen Reader Announcements -->
<div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
<span id="sr-announcement"></span>
</div>
</main>
<footer role="contentinfo">
<p>© 2025 PetMart India. All rights reserved.</p>
</footer>
</div>
</body>
</html>
| Component | Specifications | Assets Required |
|---|---|---|
| Logo | SVG format, min 40px height | Logo variations, favicon |
| Colors | Hex codes, RGB values | Color palette document |
| Typography | Font files, sizes, weights | Web fonts, fallbacks |
| Icons | SVG sprite, 24x24 base | Icon library |
| Images | WebP format, multiple sizes | Image optimization guide |
| Spacing | 8px grid system | Spacing scale |
| Animations | Duration, easing curves | Animation library |
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2025-04-10 | Design Team | Initial version |