fix: initial commit

This commit is contained in:
Tim Rijkse
2026-01-14 15:05:12 +01:00
commit dc06a33a72
16 changed files with 2621 additions and 0 deletions

30
.dockerignore Normal file
View File

@@ -0,0 +1,30 @@
# Git
.git
.gitignore
# Editor directories and files
.idea/
.vscode/
*.swp
*.swo
*~
# OS generated files
.DS_Store
Thumbs.db
# Documentation
README.md
*.md
# Local environment
.env
.env.local
.env.*.local
# Logs
*.log
# Temporary files
tmp/
temp/

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
node_modules/
# Editor directories and files
.idea/
.vscode/
*.swp
*.swo
*~
# Logs
*.log
# Local environment
.env
.env.local
.env.*.local
# Temporary files
tmp/
temp/
*.tmp

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy static files
COPY . .
# Set environment
ENV NODE_ENV=production
ENV PORT=3000
# Expose port
EXPOSE 3000
# Start the server
CMD ["npm", "start"]

147
book.html Normal file
View File

@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="The Midnight Library - Between life and death there is a library">
<title>The Midnight Library - BookStore</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="mobile-container">
<site-header>
<top-bar></top-bar>
<horizontal-scroll-nav></horizontal-scroll-nav>
<search-bar></search-bar>
</site-header>
<site-content>
<div class="book-detail">
<!-- Back navigation -->
<a href="index.html" class="back-link">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="20" height="20">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
</svg>
Back to Books
</a>
<!-- Book Cover -->
<div class="book-cover">
<div class="book-cover-placeholder">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" />
</svg>
</div>
</div>
<!-- Book Info -->
<div class="book-info">
<h1 class="book-title">The Midnight Library</h1>
<p class="book-author">by Matt Haig</p>
<div class="book-rating">
<div class="stars">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
</div>
<span class="rating-text">4.5 (2,847 reviews)</span>
</div>
<div class="book-meta">
<div class="meta-item">
<span class="meta-label">Format</span>
<span class="meta-value">Hardcover</span>
</div>
<div class="meta-item">
<span class="meta-label">Pages</span>
<span class="meta-value">304</span>
</div>
<div class="meta-item">
<span class="meta-label">Language</span>
<span class="meta-value">English</span>
</div>
</div>
</div>
<!-- Price & Purchase -->
<div class="purchase-section">
<div class="price-container">
<span class="current-price">$14.99</span>
<span class="original-price">$24.99</span>
<span class="discount-badge">40% OFF</span>
</div>
<div class="purchase-actions">
<button class="btn btn-primary btn-large">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="20" height="20">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
</svg>
Add to Cart
</button>
<button class="btn btn-secondary">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="20" height="20">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
</button>
</div>
</div>
<!-- Description -->
<div class="book-description">
<h2 class="section-heading">About this book</h2>
<p>Between life and death there is a library, and within that library, the shelves go on forever. Every book provides a chance to try another life you could have lived. To see how things would be if you had made other choices... Would you have done anything different, if you had the chance to undo your regrets?</p>
<p>A dazzling novel about all the choices that go into a life well lived, from the internationally bestselling author of Reasons to Stay Alive and How to Stop Time.</p>
</div>
<!-- Reviews -->
<div class="reviews-section">
<h2 class="section-heading">Customer Reviews</h2>
<div class="review-card">
<div class="review-header">
<div class="reviewer-info">
<span class="reviewer-name">Sarah M.</span>
<span class="review-date">December 2025</span>
</div>
<div class="review-stars">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
</div>
</div>
<p class="review-text">This book changed my perspective on life. Beautifully written and deeply thought-provoking. A must-read for anyone going through a difficult time.</p>
</div>
<div class="review-card">
<div class="review-header">
<div class="reviewer-info">
<span class="reviewer-name">James K.</span>
<span class="review-date">November 2025</span>
</div>
<div class="review-stars">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
</div>
</div>
<p class="review-text">Great concept and well-executed. The writing flows effortlessly and keeps you engaged throughout. Highly recommend!</p>
</div>
<button class="btn btn-outline btn-full">View All Reviews</button>
</div>
</div>
</site-content>
<site-footer></site-footer>
</div>
<script type="module" src="js/app.js"></script>
</body>
</html>

623
css/styles.css Normal file
View File

@@ -0,0 +1,623 @@
/* ==========================================================================
CSS Reset & Normalize (Modern Best Practices)
Based on normalize.css v8.0.1 + modern resets
========================================================================== */
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Prevent font size inflation */
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
/* Remove default margin and padding */
* {
margin: 0;
padding: 0;
}
/* Set core root defaults */
html {
font-size: 16px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Set core body defaults */
body {
min-height: 100vh;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.5;
text-rendering: optimizeSpeed;
}
/* Remove list styles on ul, ol elements */
ul,
ol {
list-style: none;
}
/* Anchor elements that don't have a class get default styles */
a:not([class]) {
text-decoration-skip-ink: auto;
}
/* Make images easier to work with */
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
height: auto;
}
/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
font: inherit;
color: inherit;
}
/* Remove default button styles */
button {
background: none;
border: none;
cursor: pointer;
}
/* Make sure textareas without a rows attribute are not tiny */
textarea:not([rows]) {
min-height: 10em;
}
/* Anything that has been anchored to should have extra scroll margin */
:target {
scroll-margin-block: 5ex;
}
/* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
html:focus-within {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Remove the inner border and padding in Firefox */
::-moz-focus-inner {
border-style: none;
padding: 0;
}
/* Restore the focus styles unset by the previous rule */
:-moz-focusring {
outline: 1px dotted ButtonText;
}
/* Remove the additional ':invalid' styles in Firefox */
:-moz-ui-invalid {
box-shadow: none;
}
/* Correct the cursor style of increment and decrement buttons in Safari */
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/* Correct the outline style in Safari */
[type="search"] {
outline-offset: -2px;
}
/* Remove the inner padding in Chrome and Safari on macOS */
::-webkit-search-decoration {
-webkit-appearance: none;
}
/* Correct the inability to style clickable types in iOS and Safari */
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
/* Interactive elements */
summary {
display: list-item;
}
/* Table */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* ==========================================================================
CSS Custom Properties (Design Tokens)
========================================================================== */
:root {
/* Colors */
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-secondary: #64748b;
--color-accent: #f59e0b;
--color-text: #1e293b;
--color-text-light: #64748b;
--color-text-inverse: #ffffff;
--color-background: #ffffff;
--color-background-secondary: #f8fafc;
--color-background-tertiary: #f1f5f9;
--color-border: #e2e8f0;
--color-border-light: #f1f5f9;
--color-success: #22c55e;
--color-error: #ef4444;
--color-warning: #f59e0b;
/* Typography */
--font-family-base: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
--font-family-heading: var(--font-family-base);
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Spacing */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
/* Border Radius */
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.5rem; /* 8px */
--radius-lg: 0.75rem; /* 12px */
--radius-xl: 1rem; /* 16px */
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
--transition-slow: 350ms ease;
/* Layout */
--mobile-max-width: 430px;
--header-height: auto;
--footer-height: auto;
}
/* ==========================================================================
Base Styles
========================================================================== */
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
color: var(--color-text);
background-color: var(--color-background-secondary);
}
/* ==========================================================================
Mobile Container (for desktop viewing)
Centers content in a phone-sized container on larger screens
========================================================================== */
.mobile-container {
width: 100%;
min-height: 100vh;
margin: 0 auto;
background-color: var(--color-background);
position: relative;
}
@media (min-width: 431px) {
body {
display: flex;
justify-content: center;
align-items: flex-start;
padding: var(--spacing-lg) 0;
}
.mobile-container {
max-width: var(--mobile-max-width);
min-height: calc(100vh - var(--spacing-lg) * 2);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
}
/* ==========================================================================
Utility Classes
========================================================================== */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ==========================================================================
Page Components
========================================================================== */
/* Section */
.section {
margin-bottom: var(--spacing-xl);
}
.section-title {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
margin-bottom: var(--spacing-md);
}
.section-heading {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
margin-bottom: var(--spacing-md);
}
/* Book Grid */
.book-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-md);
}
/* ==========================================================================
Book Detail Page
========================================================================== */
.book-detail {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
/* Back Link */
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--font-size-sm);
color: var(--color-text-light);
text-decoration: none;
transition: color var(--transition-fast);
}
.back-link:hover {
color: var(--color-primary);
}
/* Book Cover */
.book-cover {
display: flex;
justify-content: center;
}
.book-cover-placeholder {
width: 180px;
height: 270px;
background-color: var(--color-background-tertiary);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-light);
box-shadow: var(--shadow-md);
}
.book-cover-placeholder svg {
width: 64px;
height: 64px;
opacity: 0.5;
}
.book-cover img {
width: 180px;
height: auto;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
}
/* Book Info */
.book-info {
text-align: center;
}
.book-title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text);
margin-bottom: var(--spacing-xs);
line-height: var(--line-height-tight);
}
.book-author {
font-size: var(--font-size-base);
color: var(--color-text-light);
margin-bottom: var(--spacing-md);
}
.book-rating {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.book-rating .stars {
display: flex;
gap: 2px;
}
.book-rating .stars svg {
width: 18px;
height: 18px;
color: var(--color-accent);
}
.rating-text {
font-size: var(--font-size-sm);
color: var(--color-text-light);
}
.book-meta {
display: flex;
justify-content: center;
gap: var(--spacing-lg);
padding: var(--spacing-md);
background-color: var(--color-background-tertiary);
border-radius: var(--radius-lg);
}
.meta-item {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-xs);
}
.meta-label {
font-size: var(--font-size-xs);
color: var(--color-text-light);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.meta-value {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
}
/* Purchase Section */
.purchase-section {
padding: var(--spacing-md);
background-color: var(--color-background-secondary);
border-radius: var(--radius-lg);
}
.price-container {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.current-price {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text);
}
.original-price {
font-size: var(--font-size-base);
color: var(--color-text-light);
text-decoration: line-through;
}
.discount-badge {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--color-text-inverse);
background-color: var(--color-success);
border-radius: var(--radius-sm);
}
.purchase-actions {
display: flex;
gap: var(--spacing-sm);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
border-radius: var(--radius-lg);
border: none;
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-primary {
flex: 1;
color: var(--color-text-inverse);
background-color: var(--color-primary);
}
.btn-primary:hover {
background-color: var(--color-primary-dark);
}
.btn-secondary {
padding: var(--spacing-sm);
color: var(--color-text);
background-color: var(--color-background);
border: 1px solid var(--color-border);
}
.btn-secondary:hover {
background-color: var(--color-background-tertiary);
}
.btn-outline {
color: var(--color-primary);
background-color: transparent;
border: 1px solid var(--color-primary);
}
.btn-outline:hover {
background-color: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-large {
padding: var(--spacing-md) var(--spacing-lg);
font-size: var(--font-size-base);
}
.btn-full {
width: 100%;
}
/* Book Description */
.book-description p {
font-size: var(--font-size-sm);
color: var(--color-text);
line-height: var(--line-height-relaxed);
margin-bottom: var(--spacing-md);
}
.book-description p:last-child {
margin-bottom: 0;
}
/* Reviews Section */
.reviews-section {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.review-card {
padding: var(--spacing-md);
background-color: var(--color-background-secondary);
border-radius: var(--radius-lg);
}
.review-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-sm);
}
.reviewer-info {
display: flex;
flex-direction: column;
}
.reviewer-name {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
}
.review-date {
font-size: var(--font-size-xs);
color: var(--color-text-light);
}
.review-stars {
display: flex;
gap: 2px;
}
.review-stars svg {
width: 14px;
height: 14px;
color: var(--color-accent);
}
.review-text {
font-size: var(--font-size-sm);
color: var(--color-text);
line-height: var(--line-height-normal);
}

127
index.html Normal file
View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="BookStore - Discover and buy your next favorite book">
<title>BookStore - Home</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="mobile-container">
<site-header>
<top-bar></top-bar>
<horizontal-scroll-nav></horizontal-scroll-nav>
<search-bar></search-bar>
</site-header>
<site-content>
<section class="section">
<h2 class="section-title">Featured Books</h2>
<div class="book-grid">
<book-card
title="The Midnight Library"
author="Matt Haig"
price="$14.99"
rating="4.5"
href="book.html"
></book-card>
<book-card
title="Atomic Habits"
author="James Clear"
price="$16.99"
rating="5"
href="book.html"
></book-card>
<book-card
title="The Psychology of Money"
author="Morgan Housel"
price="$12.99"
rating="4"
href="book.html"
></book-card>
<book-card
title="Project Hail Mary"
author="Andy Weir"
price="$18.99"
rating="4.5"
href="book.html"
></book-card>
</div>
</section>
<section class="section">
<h2 class="section-title">New Releases</h2>
<div class="book-grid">
<book-card
title="Tomorrow, and Tomorrow"
author="Gabrielle Zevin"
price="$15.99"
rating="4"
href="book.html"
></book-card>
<book-card
title="The House in the Pines"
author="Ana Reyes"
price="$13.99"
rating="3.5"
href="book.html"
></book-card>
<book-card
title="Demon Copperhead"
author="Barbara Kingsolver"
price="$19.99"
rating="5"
href="book.html"
></book-card>
<book-card
title="The Light We Carry"
author="Michelle Obama"
price="$17.99"
rating="4.5"
href="book.html"
></book-card>
</div>
</section>
<section class="section">
<h2 class="section-title">Best Sellers</h2>
<div class="book-grid">
<book-card
title="Where the Crawdads Sing"
author="Delia Owens"
price="$11.99"
rating="4.5"
href="book.html"
></book-card>
<book-card
title="The Silent Patient"
author="Alex Michaelides"
price="$14.99"
rating="4"
href="book.html"
></book-card>
<book-card
title="Educated"
author="Tara Westover"
price="$13.99"
rating="4.5"
href="book.html"
></book-card>
<book-card
title="Becoming"
author="Michelle Obama"
price="$16.99"
rating="5"
href="book.html"
></book-card>
</div>
</section>
</site-content>
<site-footer></site-footer>
</div>
<script type="module" src="js/app.js"></script>
</body>
</html>

18
js/app.js Normal file
View File

@@ -0,0 +1,18 @@
/**
* Main Application Entry Point
* Imports and registers all web components
*/
// Import all components
import './components/site-header.js';
import './components/top-bar.js';
import './components/horizontal-scroll-nav.js';
import './components/search-bar.js';
import './components/site-content.js';
import './components/site-footer.js';
import './components/book-card.js';
// App initialization (if needed)
document.addEventListener('DOMContentLoaded', () => {
console.log('BookStore app initialized');
});

211
js/components/book-card.js Normal file
View File

@@ -0,0 +1,211 @@
/**
* Book Card Component
* Reusable card displaying book thumbnail, title, author, and price
*/
class BookCard extends HTMLElement {
static get observedAttributes() {
return ['title', 'author', 'price', 'image', 'href', 'rating'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
if (this.shadowRoot) {
this.render();
}
}
get title() {
return this.getAttribute('title') || 'Book Title';
}
get author() {
return this.getAttribute('author') || 'Author Name';
}
get price() {
return this.getAttribute('price') || '$0.00';
}
get image() {
return this.getAttribute('image') || '';
}
get href() {
return this.getAttribute('href') || 'book.html';
}
get rating() {
return parseFloat(this.getAttribute('rating')) || 0;
}
renderStars(rating) {
const fullStars = Math.floor(rating);
const hasHalf = rating % 1 >= 0.5;
const emptyStars = 5 - fullStars - (hasHalf ? 1 : 0);
let stars = '';
// Full stars
for (let i = 0; i < fullStars; i++) {
stars += `<svg class="star star-full" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>`;
}
// Half star
if (hasHalf) {
stars += `<svg class="star star-half" viewBox="0 0 24 24">
<defs>
<linearGradient id="halfGrad">
<stop offset="50%" stop-color="currentColor"/>
<stop offset="50%" stop-color="transparent"/>
</linearGradient>
</defs>
<path fill="url(#halfGrad)" stroke="currentColor" stroke-width="1" d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>`;
}
// Empty stars
for (let i = 0; i < emptyStars; i++) {
stars += `<svg class="star star-empty" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>`;
}
return stars;
}
render() {
// Generate placeholder image if none provided
const imageHtml = this.image
? `<img src="${this.image}" alt="${this.title}" class="book-image">`
: `<div class="book-image placeholder">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" />
</svg>
</div>`;
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.card {
display: block;
text-decoration: none;
color: inherit;
background-color: var(--color-background, #ffffff);
border-radius: var(--radius-lg, 0.75rem);
overflow: hidden;
transition: transform var(--transition-fast, 150ms ease),
box-shadow var(--transition-fast, 150ms ease);
}
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1));
}
.card:active {
transform: translateY(0);
}
.image-container {
position: relative;
aspect-ratio: 3 / 4;
background-color: var(--color-background-tertiary, #f1f5f9);
overflow: hidden;
}
.book-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.book-image.placeholder {
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-light, #64748b);
}
.book-image.placeholder svg {
width: 48px;
height: 48px;
opacity: 0.5;
}
.content {
padding: var(--spacing-sm, 0.5rem);
}
.title {
font-size: var(--font-size-sm, 0.875rem);
font-weight: var(--font-weight-semibold, 600);
color: var(--color-text, #1e293b);
margin-bottom: var(--spacing-xs, 0.25rem);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: var(--line-height-tight, 1.25);
}
.author {
font-size: var(--font-size-xs, 0.75rem);
color: var(--color-text-light, #64748b);
margin-bottom: var(--spacing-xs, 0.25rem);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rating {
display: flex;
align-items: center;
gap: 2px;
margin-bottom: var(--spacing-xs, 0.25rem);
}
.star {
width: 12px;
height: 12px;
color: var(--color-accent, #f59e0b);
}
.star-empty {
color: var(--color-border, #e2e8f0);
}
.price {
font-size: var(--font-size-base, 1rem);
font-weight: var(--font-weight-bold, 700);
color: var(--color-primary, #2563eb);
}
</style>
<a href="${this.href}" class="card">
<div class="image-container">
${imageHtml}
</div>
<div class="content">
<h3 class="title">${this.title}</h3>
<p class="author">${this.author}</p>
${this.rating > 0 ? `<div class="rating">${this.renderStars(this.rating)}</div>` : ''}
<p class="price">${this.price}</p>
</div>
</a>
`;
}
}
customElements.define('book-card', BookCard);

View File

@@ -0,0 +1,113 @@
/**
* Horizontal Scroll Nav Component
* Pill-style category buttons in a horizontal scroller
*/
class HorizontalScrollNav extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.categories = [
{ id: 'all', label: 'All', active: true },
{ id: 'fiction', label: 'Fiction', active: false },
{ id: 'non-fiction', label: 'Non-Fiction', active: false },
{ id: 'mystery', label: 'Mystery', active: false },
{ id: 'romance', label: 'Romance', active: false },
{ id: 'sci-fi', label: 'Sci-Fi', active: false },
{ id: 'biography', label: 'Biography', active: false },
{ id: 'history', label: 'History', active: false },
];
}
connectedCallback() {
this.render();
this.addEventListeners();
}
addEventListeners() {
const buttons = this.shadowRoot.querySelectorAll('.nav-pill');
buttons.forEach(button => {
button.addEventListener('click', (e) => {
this.setActiveCategory(e.target.dataset.id);
});
});
}
setActiveCategory(id) {
this.categories = this.categories.map(cat => ({
...cat,
active: cat.id === id
}));
this.render();
this.addEventListeners();
// Dispatch custom event for parent components
this.dispatchEvent(new CustomEvent('category-change', {
detail: { categoryId: id },
bubbles: true,
composed: true
}));
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.nav-container {
display: flex;
gap: var(--spacing-sm, 0.5rem);
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
padding: var(--spacing-xs, 0.25rem) 0;
}
.nav-container::-webkit-scrollbar {
display: none;
}
.nav-pill {
flex-shrink: 0;
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
font-size: var(--font-size-sm, 0.875rem);
font-weight: var(--font-weight-medium, 500);
color: var(--color-text-light, #64748b);
background-color: var(--color-background-tertiary, #f1f5f9);
border: none;
border-radius: var(--radius-full, 9999px);
cursor: pointer;
transition: all var(--transition-fast, 150ms ease);
white-space: nowrap;
}
.nav-pill:hover {
background-color: var(--color-border, #e2e8f0);
}
.nav-pill.active {
color: var(--color-text-inverse, #ffffff);
background-color: var(--color-primary, #2563eb);
}
.nav-pill.active:hover {
background-color: var(--color-primary-dark, #1d4ed8);
}
</style>
<nav class="nav-container" role="navigation" aria-label="Book categories">
${this.categories.map(cat => `
<button
class="nav-pill ${cat.active ? 'active' : ''}"
data-id="${cat.id}"
${cat.active ? 'aria-current="true"' : ''}
>
${cat.label}
</button>
`).join('')}
</nav>
`;
}
}
customElements.define('horizontal-scroll-nav', HorizontalScrollNav);

View File

@@ -0,0 +1,98 @@
/**
* Search Bar Component
* Search input with icon
*/
class SearchBar extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.addEventListeners();
}
addEventListeners() {
const input = this.shadowRoot.querySelector('.search-input');
const form = this.shadowRoot.querySelector('.search-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
this.dispatchEvent(new CustomEvent('search', {
detail: { query: input.value },
bubbles: true,
composed: true
}));
});
input.addEventListener('input', (e) => {
this.dispatchEvent(new CustomEvent('search-input', {
detail: { query: e.target.value },
bubbles: true,
composed: true
}));
});
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.search-form {
display: flex;
align-items: center;
position: relative;
}
.search-icon {
position: absolute;
left: var(--spacing-md, 1rem);
width: 20px;
height: 20px;
color: var(--color-text-light, #64748b);
pointer-events: none;
}
.search-input {
width: 100%;
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
padding-left: calc(var(--spacing-md, 1rem) + 20px + var(--spacing-sm, 0.5rem));
font-size: var(--font-size-sm, 0.875rem);
color: var(--color-text, #1e293b);
background-color: var(--color-background-tertiary, #f1f5f9);
border: 1px solid transparent;
border-radius: var(--radius-lg, 0.75rem);
outline: none;
transition: all var(--transition-fast, 150ms ease);
}
.search-input::placeholder {
color: var(--color-text-light, #64748b);
}
.search-input:focus {
background-color: var(--color-background, #ffffff);
border-color: var(--color-primary, #2563eb);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
</style>
<form class="search-form" role="search">
<svg class="search-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
<input
type="search"
class="search-input"
placeholder="Search books, authors..."
aria-label="Search books and authors"
>
</form>
`;
}
}
customElements.define('search-bar', SearchBar);

View File

@@ -0,0 +1,35 @@
/**
* Site Content Component
* Main content area wrapper with slot for page content
*/
class SiteContent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
flex: 1;
min-height: 0;
}
.content {
padding: var(--spacing-md, 1rem);
}
</style>
<main class="content">
<slot></slot>
</main>
`;
}
}
customElements.define('site-content', SiteContent);

View File

@@ -0,0 +1,111 @@
/**
* Site Footer Component
* Footer with navigation links, social icons, and copyright
*/
class SiteFooter extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
background-color: var(--color-background-secondary, #f8fafc);
border-top: 1px solid var(--color-border, #e2e8f0);
}
.footer {
padding: var(--spacing-lg, 1.5rem) var(--spacing-md, 1rem);
}
.footer-nav {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-md, 1rem);
margin-bottom: var(--spacing-lg, 1.5rem);
}
.footer-link {
font-size: var(--font-size-sm, 0.875rem);
color: var(--color-text-light, #64748b);
text-decoration: none;
transition: color var(--transition-fast, 150ms ease);
}
.footer-link:hover {
color: var(--color-primary, #2563eb);
}
.social-icons {
display: flex;
gap: var(--spacing-md, 1rem);
margin-bottom: var(--spacing-lg, 1.5rem);
}
.social-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
color: var(--color-text-light, #64748b);
background-color: var(--color-background, #ffffff);
border-radius: var(--radius-full, 9999px);
transition: all var(--transition-fast, 150ms ease);
}
.social-icon:hover {
color: var(--color-primary, #2563eb);
background-color: var(--color-background-tertiary, #f1f5f9);
}
.social-icon svg {
width: 20px;
height: 20px;
}
.copyright {
font-size: var(--font-size-xs, 0.75rem);
color: var(--color-text-light, #64748b);
text-align: center;
}
</style>
<footer class="footer">
<nav class="footer-nav">
<a href="#" class="footer-link">About Us</a>
<a href="#" class="footer-link">Contact</a>
<a href="#" class="footer-link">FAQ</a>
<a href="#" class="footer-link">Privacy Policy</a>
<a href="#" class="footer-link">Terms of Service</a>
</nav>
<div class="social-icons">
<a href="#" class="social-icon" aria-label="Facebook">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
</a>
<a href="#" class="social-icon" aria-label="Twitter">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
<a href="#" class="social-icon" aria-label="Instagram">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/>
</svg>
</a>
</div>
<p class="copyright">&copy; 2026 BookStore. All rights reserved.</p>
</footer>
`;
}
}
customElements.define('site-footer', SiteFooter);

View File

@@ -0,0 +1,41 @@
/**
* Site Header Component
* Sticky header container that holds top-bar, navigation, and search
*/
class SiteHeader extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
position: sticky;
top: 0;
z-index: 100;
background-color: var(--color-background, #ffffff);
}
.header {
display: flex;
flex-direction: column;
gap: var(--spacing-sm, 0.5rem);
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
border-bottom: 1px solid var(--color-border, #e2e8f0);
}
</style>
<header class="header">
<slot></slot>
</header>
`;
}
}
customElements.define('site-header', SiteHeader);

110
js/components/top-bar.js Normal file
View File

@@ -0,0 +1,110 @@
/**
* Top Bar Component
* Contains logo, menu icon, and cart icon
*/
class TopBar extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
}
.logo {
font-size: var(--font-size-xl, 1.25rem);
font-weight: var(--font-weight-bold, 700);
color: var(--color-text, #1e293b);
text-decoration: none;
}
.actions {
display: flex;
align-items: center;
gap: var(--spacing-sm, 0.5rem);
}
.icon-button {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: var(--radius-md, 0.5rem);
background: transparent;
border: none;
cursor: pointer;
color: var(--color-text, #1e293b);
transition: background-color var(--transition-fast, 150ms ease);
}
.icon-button:hover {
background-color: var(--color-background-tertiary, #f1f5f9);
}
.icon-button:active {
background-color: var(--color-border, #e2e8f0);
}
.icon-button svg {
width: 24px;
height: 24px;
}
.cart-badge {
position: relative;
}
.badge {
position: absolute;
top: 4px;
right: 4px;
min-width: 18px;
height: 18px;
padding: 0 5px;
font-size: var(--font-size-xs, 0.75rem);
font-weight: var(--font-weight-semibold, 600);
color: var(--color-text-inverse, #ffffff);
background-color: var(--color-primary, #2563eb);
border-radius: var(--radius-full, 9999px);
display: flex;
align-items: center;
justify-content: center;
}
</style>
<div class="top-bar">
<a href="index.html" class="logo">BookStore</a>
<div class="actions">
<button class="icon-button" aria-label="Menu">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
<button class="icon-button cart-badge" aria-label="Shopping cart">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
</svg>
<span class="badge">2</span>
</button>
</div>
</div>
`;
}
}
customElements.define('top-bar', TopBar);

891
package-lock.json generated Normal file
View File

@@ -0,0 +1,891 @@
{
"name": "milinda-pitch",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "milinda-pitch",
"version": "1.0.0",
"dependencies": {
"serve": "^14.2.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@zeit/schemas": {
"version": "2.36.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
"integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg=="
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-align": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
"dependencies": {
"string-width": "^4.1.0"
}
},
"node_modules/ansi-align/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-align/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/ansi-align/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-align/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/arch": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
"integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/boxen": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
"integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
"dependencies": {
"ansi-align": "^3.0.1",
"camelcase": "^7.0.0",
"chalk": "^5.0.1",
"cli-boxes": "^3.0.0",
"string-width": "^5.1.2",
"type-fest": "^2.13.0",
"widest-line": "^4.0.1",
"wrap-ansi": "^8.0.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/camelcase": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
"integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chalk": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
"integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/chalk-template/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/chalk-template/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
"integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/clipboardy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
"integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
"dependencies": {
"arch": "^2.2.0",
"execa": "^5.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.1",
"onetime": "^5.1.2",
"signal-exit": "^3.0.3",
"strip-final-newline": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-port-reachable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
"integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"dependencies": {
"mime-db": "~1.33.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types/node_modules/mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-is-inside": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
"integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
"integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"engines": {
"node": ">=6"
}
},
"node_modules/range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/registry-auth-token": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
"integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
"dependencies": {
"rc": "^1.1.6",
"safe-buffer": "^5.0.1"
}
},
"node_modules/registry-url": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
"integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
"dependencies": {
"rc": "^1.0.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/serve": {
"version": "14.2.5",
"resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz",
"integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==",
"dependencies": {
"@zeit/schemas": "2.36.0",
"ajv": "8.12.0",
"arg": "5.0.2",
"boxen": "7.0.0",
"chalk": "5.0.1",
"chalk-template": "0.4.0",
"clipboardy": "3.0.0",
"compression": "1.8.1",
"is-port-reachable": "4.0.0",
"serve-handler": "6.1.6",
"update-check": "1.5.4"
},
"bin": {
"serve": "build/main.js"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/serve-handler": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
"dependencies": {
"bytes": "3.0.0",
"content-disposition": "0.5.2",
"mime-types": "2.1.18",
"minimatch": "3.1.2",
"path-is-inside": "1.0.2",
"path-to-regexp": "3.3.0",
"range-parser": "1.2.0"
}
},
"node_modules/serve-handler/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"engines": {
"node": ">=6"
}
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/update-check": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
"integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
"dependencies": {
"registry-auth-token": "3.3.2",
"registry-url": "3.1.0"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
"integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
"dependencies": {
"string-width": "^5.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
}
}
}

15
package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "milinda-pitch",
"version": "1.0.0",
"description": "BookStore - Discover and buy your next favorite book",
"scripts": {
"start": "serve -s . -l ${PORT:-3000}",
"serve": "serve -s . -l 3000"
},
"dependencies": {
"serve": "^14.2.4"
},
"engines": {
"node": ">=18.0.0"
}
}