Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 94 additions & 31 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#f5f7ff">

<!-- SEO Meta Tags -->
<title>Rebill - Invoice Generator</title>
<title>Rebill - Free Invoice Generator | Create PDF Invoices Online</title>
<meta name="description"
content="Create professional invoices for free. Open-source, privacy-first invoice generator with PDF export, auto-save, and no sign-up required.">
<meta name="keywords" content="invoice generator, free invoice, bill maker, PDF invoice, open source invoicing">
Expand All @@ -16,43 +17,107 @@
<!-- Open Graph / Social -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://rebill.mrjl.dev/">
<meta property="og:title" content="Rebill - Invoice Generator">
<meta property="og:title" content="Rebill - Free Invoice Generator | Create PDF Invoices Online">
<meta property="og:description"
content="Create professional invoices for free. Open-source, privacy-first, no sign-up required.">
<meta property="og:site_name" content="Rebill">
<meta property="og:image" content="https://rebill.mrjl.dev/src/assets/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Rebill - Free Invoice Generator with PDF Export">

<!-- Twitter -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Rebill - Invoice Generator">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Rebill - Free Invoice Generator">
<meta name="twitter:description"
content="Create professional invoices for free. Open-source, privacy-first, no sign-up required.">
<meta name="twitter:image" content="https://rebill.mrjl.dev/src/assets/og-image.png">
<meta name="twitter:image:alt" content="Rebill - Free Invoice Generator with PDF Export">

<!-- Structured Data: SoftwareApplication Schema -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Rebill",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"description": "Free, open-source invoice generator. Create professional invoices with PDF export, auto-save, and complete privacy - no sign-up required.",
"url": "https://rebill.mrjl.dev/",
"author": {
"@type": "Person",
"name": "Mahesh Rijal"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5",
"ratingCount": "1"
},
"featureList": [
"PDF Invoice Export",
"Auto-save drafts",
"JSON import/export",
"Multiple currency support",
"Tax and discount calculations",
"100% client-side processing",
"No sign-up required"
]
}
</script>

<!-- Structured Data: WebApplication Schema -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Rebill Invoice Generator",
"url": "https://rebill.mrjl.dev/",
"browserRequirements": "Requires JavaScript. Works on all modern browsers.",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Any"
}
</script>

<!-- Favicon -->
<link rel="icon" type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🧾%3C/text%3E%3C/svg%3E">

<!-- Performance: Preconnect & Preload -->
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Styles & Scripts -->
<link rel="stylesheet" href="src/css/main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/vfs_fonts.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/vfs_fonts.min.js"></script>
<script type="module" src="src/js/main.js"></script>
</head>

<body>
<div class="container">
<a href="#billForm" class="skip-link">Skip to main content</a>
<header class="visually-hidden">
<h1>Rebill - Free Online Invoice Generator</h1>
</header>
<main class="container">
<div class="form-section">
<div class="header-row">
<h2>Rebill - Invoice Generator <a href="https://github.com/maheshrijal/rebill" target="_blank"
rel="noopener" class="open-source-badge">Open Source</a></h2>
<button type="button" id="historyToggle" class="ghost">📋 History</button>
<button type="button" id="historyToggle" class="ghost" aria-label="Toggle invoice history panel"><span aria-hidden="true">📋</span> History</button>
</div>
<div id="historyPanel" class="history-panel" style="display: none;">
<div class="history-header">
<h3>Invoice History</h3>
<button type="button" id="clearHistoryBtn" class="ghost small">Clear All</button>
</div>
<div id="historyList" class="history-list">
<p class="history-empty">No invoices saved yet. Generate a bill to save it here.</p>
<p class="history-empty">No invoices saved yet. Generate a bill to save it here…</p>
</div>
</div>
<form id="billForm" autocomplete="off">
Expand All @@ -61,20 +126,20 @@ <h3>Seller</h3>
<div class="form-grid">
<div class="form-group span-2">
<label for="sellerName">Seller Name</label>
<input type="text" id="sellerName" value="" placeholder="Your Business Name" required>
<input type="text" id="sellerName" value="" placeholder="Your Business Name…" required autocomplete="organization">
</div>
<div class="form-group span-2">
<label for="sellerAddress">Seller Address</label>
<textarea id="sellerAddress" rows="3" placeholder="123 Business Street&#10;City, State ZIP"
required></textarea>
<textarea id="sellerAddress" rows="3" placeholder="123 Business Street&#10;City, State ZIP…"
required autocomplete="street-address"></textarea>
</div>
<div class="form-group">
<label for="sellerEmail">Seller Email</label>
<input type="email" id="sellerEmail" value="" placeholder="you@example.com">
<input type="email" id="sellerEmail" value="" placeholder="you@example.com" autocomplete="email" spellcheck="false">
</div>
<div class="form-group">
<label for="sellerPhone">Seller Phone</label>
<input type="text" id="sellerPhone" value="" placeholder="+1 234 567 8900">
<input type="tel" id="sellerPhone" value="" placeholder="+1 234 567 8900" autocomplete="tel">
</div>
</div>
</div>
Expand All @@ -84,20 +149,20 @@ <h3>Bill To</h3>
<div class="form-grid">
<div class="form-group span-2">
<label for="billToName">Customer Name</label>
<input type="text" id="billToName" value="" placeholder="Customer or Company Name" required>
<input type="text" id="billToName" value="" placeholder="Customer or Company Name…" required autocomplete="off">
</div>
<div class="form-group span-2">
<label for="billToAddress">Customer Address</label>
<textarea id="billToAddress" rows="3" placeholder="456 Customer Ave&#10;City, State ZIP"
required></textarea>
<textarea id="billToAddress" rows="3" placeholder="456 Customer Ave&#10;City, State ZIP…"
required autocomplete="off"></textarea>
</div>
<div class="form-group">
<label for="billToEmail">Customer Email</label>
<input type="email" id="billToEmail" value="" placeholder="customer@example.com">
<input type="email" id="billToEmail" value="" placeholder="customer@example.com" autocomplete="off" spellcheck="false">
</div>
<div class="form-group">
<label for="billToPhone">Customer Phone</label>
<input type="text" id="billToPhone" value="" placeholder="+1 987 654 3210">
<input type="tel" id="billToPhone" value="" placeholder="+1 987 654 3210" autocomplete="off">
</div>
</div>
</div>
Expand All @@ -120,9 +185,9 @@ <h3>Invoice Details</h3>
<div class="form-group">
<label for="invoiceNumber">Invoice Number</label>
<div class="number-input-wrapper">
<button type="button" class="number-btn decrement" id="decrementInvoice">−</button>
<input type="text" id="invoiceNumber" value="18" required>
<button type="button" class="number-btn increment" id="incrementInvoice">+</button>
<button type="button" class="number-btn decrement" id="decrementInvoice" aria-label="Decrease invoice number">−</button>
<input type="text" id="invoiceNumber" value="18" required aria-label="Invoice number">
<button type="button" class="number-btn increment" id="incrementInvoice" aria-label="Increase invoice number">+</button>
</div>
</div>
<div class="form-group">
Expand All @@ -149,7 +214,7 @@ <h3>Line Items</h3>
<span></span>
</div>
<div id="itemsContainer"></div>
<button type="button" id="addItemBtn" class="ghost">Add Line Item</button>
<button type="button" id="addItemBtn" class="ghost" aria-label="Add new line item">+ Add Line Item</button>
</div>

<div class="form-block">
Expand Down Expand Up @@ -195,25 +260,23 @@ <h3>Import / Export</h3>
<button type="button" id="exportJsonBtn" class="ghost">Export JSON</button>
<button type="button" id="downloadJsonBtn" class="secondary">Download JSON</button>
<button type="button" id="importJsonBtn" class="secondary">Import JSON</button>
<label for="importFile" class="file-input-label">📎 Choose File</label>
<label for="importFile" class="file-input-label"><span aria-hidden="true">📎</span> Choose File</label>
<input type="file" id="importFile" accept="application/json" class="visually-hidden">
</div>
</div>

<div class="button-row">
<button type="button" class="primary" onclick="generateBill()">Generate Bill</button>
<button type="button" class="secondary" onclick="downloadPDF()" id="downloadBtn"
style="display:none;">Download PDF</button>
<button type="button" class="secondary" onclick="shareInvoice()" id="shareBtn"
style="display:none;">📤 Share</button>
<button type="button" class="primary" id="generateBillBtn">Generate Bill</button>
<button type="button" class="secondary" id="downloadBtn" style="display:none;">Download PDF</button>
<button type="button" class="secondary" id="shareBtn" style="display:none;" aria-label="Share invoice"><span aria-hidden="true">📤</span> Share</button>
<button type="button" class="ghost" id="resetDraftBtn">Reset Draft</button>
</div>
</form>
</div>

<div class="invoice-placeholder" id="invoicePlaceholder">
<div class="placeholder-content">
<div class="placeholder-icon">📄</div>
<div class="placeholder-icon" aria-hidden="true">📄</div>
<h3>Invoice Preview</h3>
<p>Fill in the form on the left and click <strong>Generate Bill</strong> to see your invoice here.</p>
</div>
Expand Down Expand Up @@ -278,7 +341,7 @@ <h3 id="displayNotesHeading"></h3>
<p id="displayInstructions"></p>
</div>
</div>
</div>
</main>
</body>

</html>
8 changes: 8 additions & 0 deletions robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Robots.txt for Rebill
# https://rebill.mrjl.dev

User-agent: *
Allow: /

# Sitemap location
Sitemap: https://rebill.mrjl.dev/sitemap.xml
9 changes: 9 additions & 0 deletions sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://rebill.mrjl.dev/</loc>
<lastmod>2026-01-24</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
12 changes: 12 additions & 0 deletions src/css/base/reset.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,16 @@ html {
body {
background: var(--background);
min-height: 100vh;
position: relative;
}

/* Subtle noise texture overlay for depth */
body::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
opacity: 0.035;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
z-index: 9999;
}
17 changes: 15 additions & 2 deletions src/css/base/typography.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=Plus+Jakarta+Sans:wght@500;600;700;800&display=swap');

body {
font-family: 'Inter', 'Manrope', system-ui, -apple-system, sans-serif;
font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.6;
color: var(--text-primary);
Expand All @@ -15,5 +15,18 @@ h3,
h4,
h5,
h6 {
font-family: 'Plus Jakarta Sans', 'DM Sans', system-ui, sans-serif;
color: var(--text-primary);
text-wrap: balance;
}

/* Tabular numbers for invoice amounts */
.invoice-table td,
.totals-preview span,
.item-total,
#displayBalanceDue,
#displaySubtotal,
#displayTaxAmount,
#displayDiscount {
font-variant-numeric: tabular-nums;
}
24 changes: 20 additions & 4 deletions src/css/components/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ button {
align-items: center;
justify-content: center;
gap: var(--space-2);
transition: all var(--transition-base);
outline: none;
transition:
background-color var(--transition-base),
border-color var(--transition-base),
box-shadow var(--transition-base),
transform var(--transition-base),
color var(--transition-base);
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}

button:focus-visible {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.4);
outline: 2px solid var(--primary-500);
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.25);
}

.button-row {
Expand Down Expand Up @@ -52,7 +60,15 @@ button:focus-visible {
color: var(--text-secondary);
border: 1.5px solid var(--border-light);
border-radius: var(--radius-md);
transition: all var(--transition-base);
transition:
background-color var(--transition-base),
border-color var(--transition-base),
color var(--transition-base);
}

.file-input-label:focus-within {
outline: 2px solid var(--primary-500);
outline-offset: 2px;
}

.file-input-label:hover {
Expand Down
Loading