quite a lot of things
This commit is contained in:
1
internal/web/assets/css/output.css
Normal file
1
internal/web/assets/css/output.css
Normal file
File diff suppressed because one or more lines are too long
1
internal/web/assets/css/output.css.map
Normal file
1
internal/web/assets/css/output.css.map
Normal file
File diff suppressed because one or more lines are too long
249
internal/web/assets/js/dialog.js
Normal file
249
internal/web/assets/js/dialog.js
Normal file
@@ -0,0 +1,249 @@
|
||||
// Dialog component for Billit
|
||||
// Replaces browser confirm/alert dialogs with custom styled modals
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Dialog state
|
||||
let currentResolve = null;
|
||||
let currentElement = null;
|
||||
|
||||
// Create dialog HTML structure
|
||||
function createDialogElement() {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.id = 'dialog';
|
||||
dialog.className = 'dialog-overlay';
|
||||
dialog.innerHTML = `
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-header">
|
||||
<h3 class="dialog-title"></h3>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<p class="dialog-message"></p>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button type="button" class="btn btn-outline dialog-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-danger dialog-confirm">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
// Event listeners
|
||||
dialog.querySelector('.dialog-cancel').addEventListener('click', () => closeDialog(false));
|
||||
dialog.querySelector('.dialog-confirm').addEventListener('click', () => closeDialog(true));
|
||||
dialog.addEventListener('click', (e) => {
|
||||
if (e.target === dialog) closeDialog(false);
|
||||
});
|
||||
|
||||
// Escape key closes dialog
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && dialog.classList.contains('dialog-open')) {
|
||||
closeDialog(false);
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
// Get or create dialog element
|
||||
function getDialog() {
|
||||
return document.getElementById('dialog') || createDialogElement();
|
||||
}
|
||||
|
||||
// Open dialog with options
|
||||
function openDialog(options) {
|
||||
const dialog = getDialog();
|
||||
const title = options.title || 'Confirm';
|
||||
const message = options.message || 'Are you sure?';
|
||||
const confirmText = options.confirmText || 'Confirm';
|
||||
const cancelText = options.cancelText || 'Cancel';
|
||||
const confirmClass = options.confirmClass || 'btn-danger';
|
||||
const html = options.html || null;
|
||||
const wide = options.wide || false;
|
||||
const allowClose = options.allowClose !== false;
|
||||
|
||||
dialog.querySelector('.dialog-title').textContent = title;
|
||||
|
||||
// Support HTML content
|
||||
if (html) {
|
||||
dialog.querySelector('.dialog-body').innerHTML = html;
|
||||
} else {
|
||||
dialog.querySelector('.dialog-body').innerHTML = '<p class="dialog-message">' + escapeHtml(message) + '</p>';
|
||||
}
|
||||
|
||||
dialog.querySelector('.dialog-confirm').textContent = confirmText;
|
||||
dialog.querySelector('.dialog-confirm').className = 'btn ' + confirmClass + ' dialog-confirm';
|
||||
dialog.querySelector('.dialog-cancel').textContent = cancelText;
|
||||
|
||||
// Show/hide cancel button for alert-style dialogs
|
||||
dialog.querySelector('.dialog-cancel').style.display = options.showCancel !== false ? '' : 'none';
|
||||
|
||||
// Wide mode for larger content
|
||||
dialog.querySelector('.dialog-box').style.maxWidth = wide ? '600px' : '400px';
|
||||
|
||||
// Store allowClose setting
|
||||
dialog.dataset.allowClose = allowClose;
|
||||
|
||||
dialog.classList.add('dialog-open');
|
||||
dialog.querySelector('.dialog-confirm').focus();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
currentResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
// Close dialog
|
||||
function closeDialog(result) {
|
||||
const dialog = getDialog();
|
||||
|
||||
// Check if closing is allowed (for disclaimer)
|
||||
if (!result && dialog.dataset.allowClose === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.classList.remove('dialog-open');
|
||||
|
||||
if (currentResolve) {
|
||||
currentResolve(result);
|
||||
currentResolve = null;
|
||||
}
|
||||
|
||||
// If there's a pending HTMX request, trigger it
|
||||
if (result && currentElement) {
|
||||
htmx.trigger(currentElement, 'confirmed');
|
||||
}
|
||||
currentElement = null;
|
||||
}
|
||||
|
||||
// Escape HTML for safe rendering
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.Dialog = {
|
||||
confirm: function(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = { message: options };
|
||||
}
|
||||
return openDialog({ ...options, showCancel: true });
|
||||
},
|
||||
|
||||
alert: function(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = { message: options };
|
||||
}
|
||||
return openDialog({
|
||||
...options,
|
||||
showCancel: false,
|
||||
confirmText: options.confirmText || 'OK',
|
||||
confirmClass: options.confirmClass || 'btn-primary'
|
||||
});
|
||||
},
|
||||
|
||||
// Custom dialog with HTML content
|
||||
custom: function(options) {
|
||||
return openDialog(options);
|
||||
}
|
||||
};
|
||||
|
||||
// HTMX integration: intercept hx-confirm and use custom dialog
|
||||
// Elements can customize the dialog with data attributes:
|
||||
// data-dialog-title="Custom Title"
|
||||
// data-dialog-confirm="Button Text"
|
||||
// data-dialog-class="btn-danger" (or btn-primary, etc.)
|
||||
// If no data-dialog-* attributes are present, uses browser default confirm
|
||||
document.addEventListener('htmx:confirm', function(e) {
|
||||
const element = e.detail.elt;
|
||||
|
||||
// Check if element wants custom dialog (has any data-dialog-* attribute)
|
||||
const hasCustomDialog = element.dataset.dialogTitle ||
|
||||
element.dataset.dialogConfirm ||
|
||||
element.dataset.dialogClass;
|
||||
|
||||
if (!hasCustomDialog) {
|
||||
return; // Let default browser confirm handle it
|
||||
}
|
||||
|
||||
// Prevent default browser confirm
|
||||
e.preventDefault();
|
||||
|
||||
const message = e.detail.question;
|
||||
const title = element.dataset.dialogTitle || 'Confirm';
|
||||
const confirmText = element.dataset.dialogConfirm || 'Confirm';
|
||||
const confirmClass = element.dataset.dialogClass || 'btn-primary';
|
||||
|
||||
// Store element for later
|
||||
currentElement = element;
|
||||
|
||||
Dialog.confirm({
|
||||
title: title,
|
||||
message: message,
|
||||
confirmText: confirmText,
|
||||
confirmClass: confirmClass
|
||||
}).then(function(confirmed) {
|
||||
if (confirmed) {
|
||||
// Issue the request
|
||||
e.detail.issueRequest(true);
|
||||
}
|
||||
currentElement = null;
|
||||
});
|
||||
});
|
||||
|
||||
// Disclaimer dialog - show on first visit
|
||||
function showDisclaimer() {
|
||||
const DISCLAIMER_KEY = 'billit_disclaimer_accepted';
|
||||
|
||||
// Check if already accepted
|
||||
if (localStorage.getItem(DISCLAIMER_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disclaimerHTML = `
|
||||
<div class="disclaimer-content">
|
||||
<p style="font-weight: bold; margin-bottom: 15px;">
|
||||
Please read these terms carefully before using this software. By proceeding, you agree to the conditions below:
|
||||
</p>
|
||||
<ul style="padding-left: 20px; line-height: 1.8; margin: 0;">
|
||||
<li>
|
||||
<strong>1. FREE OF CHARGE & CPA EXEMPTION:</strong> This software is provided strictly <strong>"Free of Charge"</strong> and without any monetary consideration. It therefore does not constitute a "Service" under the Indian Consumer Protection Act, 2019.
|
||||
</li>
|
||||
<li style="margin-top: 10px;">
|
||||
<strong>2. "AS IS" & NO WARRANTY:</strong> The software is provided <strong>"AS IS"</strong>. The developer provides <strong>NO WARRANTY</strong>, express or implied, regarding its performance, accuracy, security, or suitability for any purpose.
|
||||
</li>
|
||||
<li style="margin-top: 10px;">
|
||||
<strong>3. USER ASSUMPTION OF RISK:</strong> The developer is not liable for any financial losses, data corruption, calculation errors, or legal issues resulting from the use or misuse of this application. Users assume all associated risks and agree to indemnify and hold harmless the developer.
|
||||
</li>
|
||||
</ul>
|
||||
<p style="font-size: 0.9em; font-style: italic; color: #666; margin-top: 15px; margin-bottom: 0;">
|
||||
<small>Consult a qualified legal or financial advisor before relying on any data generated by this tool.</small>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Dialog.custom({
|
||||
title: '⚠️ GENERAL USE & NO LIABILITY DISCLAIMER',
|
||||
html: disclaimerHTML,
|
||||
confirmText: 'I Understand & Accept',
|
||||
confirmClass: 'btn-primary',
|
||||
showCancel: false,
|
||||
wide: true,
|
||||
allowClose: false
|
||||
}).then(function(accepted) {
|
||||
if (accepted) {
|
||||
localStorage.setItem(DISCLAIMER_KEY, Date.now().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show disclaimer when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', showDisclaimer);
|
||||
} else {
|
||||
showDisclaimer();
|
||||
}
|
||||
|
||||
})();
|
||||
3521
internal/web/assets/js/htmx.min.js
vendored
Normal file
3521
internal/web/assets/js/htmx.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
116
internal/web/assets/scss/_base.scss
Normal file
116
internal/web/assets/scss/_base.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
// ============================================
|
||||
// BILLIT - Base/Reset Styles
|
||||
// ============================================
|
||||
|
||||
@use 'variables' as *;
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-family-base;
|
||||
font-size: $font-size-base;
|
||||
line-height: $line-height-base;
|
||||
color: $color-gray-900;
|
||||
background-color: $color-white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// Typography
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: $line-height-tight;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 { font-size: $font-size-2xl; }
|
||||
h2 { font-size: $font-size-xl; }
|
||||
h3 { font-size: $font-size-lg; }
|
||||
h4 { font-size: $font-size-md; }
|
||||
h5 { font-size: $font-size-base; }
|
||||
h6 { font-size: $font-size-sm; }
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-accent;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// Lists
|
||||
ul, ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
// Images
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Forms
|
||||
input, select, textarea, button {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
// Focus styles
|
||||
:focus {
|
||||
outline: 2px solid $color-accent;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid $color-accent;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
// Selection
|
||||
::selection {
|
||||
background: $color-primary;
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
// Scrollbar (webkit)
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: $color-gray-100;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: $color-gray-400;
|
||||
|
||||
&:hover {
|
||||
background: $color-gray-500;
|
||||
}
|
||||
}
|
||||
1044
internal/web/assets/scss/_components.scss
Normal file
1044
internal/web/assets/scss/_components.scss
Normal file
File diff suppressed because it is too large
Load Diff
93
internal/web/assets/scss/_print.scss
Normal file
93
internal/web/assets/scss/_print.scss
Normal file
@@ -0,0 +1,93 @@
|
||||
// ============================================
|
||||
// BILLIT - Print Styles
|
||||
// ============================================
|
||||
|
||||
@use 'variables' as *;
|
||||
|
||||
@media print {
|
||||
// Hide non-printable elements
|
||||
.no-print,
|
||||
.header,
|
||||
.btn,
|
||||
button {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
// Reset backgrounds
|
||||
body {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
// Page setup
|
||||
@page {
|
||||
margin: 1cm;
|
||||
size: A4;
|
||||
}
|
||||
|
||||
// Page breaks
|
||||
.page-break {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.avoid-break {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: black !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #000 !important;
|
||||
}
|
||||
|
||||
// Invoice specific
|
||||
.invoice {
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.invoice-header {
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
|
||||
.invoice-table {
|
||||
th {
|
||||
background: #e0e0e0 !important;
|
||||
color: black !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-totals {
|
||||
.row-total {
|
||||
background: #e0e0e0 !important;
|
||||
color: black !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
|
||||
// QR code footer
|
||||
.invoice-qr {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
197
internal/web/assets/scss/_utilities.scss
Normal file
197
internal/web/assets/scss/_utilities.scss
Normal file
@@ -0,0 +1,197 @@
|
||||
// ============================================
|
||||
// BILLIT - Utility Classes
|
||||
// ============================================
|
||||
|
||||
@use 'variables' as *;
|
||||
|
||||
// Display
|
||||
.hidden { display: none !important; }
|
||||
.block { display: block; }
|
||||
.inline { display: inline; }
|
||||
.inline-block { display: inline-block; }
|
||||
.flex { display: flex; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
.grid { display: grid; }
|
||||
|
||||
// Flex utilities
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-1 { flex: 1; }
|
||||
.flex-grow { flex-grow: 1; }
|
||||
.flex-shrink-0 { flex-shrink: 0; }
|
||||
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-center { align-items: center; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.items-stretch { align-items: stretch; }
|
||||
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
|
||||
.gap-1 { gap: $spacing-1; }
|
||||
.gap-2 { gap: $spacing-2; }
|
||||
.gap-3 { gap: $spacing-3; }
|
||||
.gap-4 { gap: $spacing-4; }
|
||||
.gap-6 { gap: $spacing-6; }
|
||||
.gap-8 { gap: $spacing-8; }
|
||||
|
||||
// Text
|
||||
.text-xs { font-size: $font-size-xs; }
|
||||
.text-sm { font-size: $font-size-sm; }
|
||||
.text-base { font-size: $font-size-base; }
|
||||
.text-md { font-size: $font-size-md; }
|
||||
.text-lg { font-size: $font-size-lg; }
|
||||
.text-xl { font-size: $font-size-xl; }
|
||||
.text-2xl { font-size: $font-size-2xl; }
|
||||
.text-3xl { font-size: $font-size-3xl; }
|
||||
|
||||
.font-normal { font-weight: $font-weight-normal; }
|
||||
.font-medium { font-weight: $font-weight-medium; }
|
||||
.font-semibold { font-weight: $font-weight-semibold; }
|
||||
.font-bold { font-weight: $font-weight-bold; }
|
||||
|
||||
.text-left { text-align: left; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.uppercase { text-transform: uppercase; }
|
||||
.lowercase { text-transform: lowercase; }
|
||||
.capitalize { text-transform: capitalize; }
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.whitespace-nowrap { white-space: nowrap; }
|
||||
|
||||
// Colors
|
||||
.text-black { color: $color-black; }
|
||||
.text-white { color: $color-white; }
|
||||
.text-gray { color: $color-gray-600; }
|
||||
.text-gray-dark { color: $color-gray-800; }
|
||||
.text-gray-light { color: $color-gray-500; }
|
||||
.text-primary { color: $color-primary; }
|
||||
.text-accent { color: $color-accent; }
|
||||
.text-success { color: $color-success; }
|
||||
.text-warning { color: $color-warning; }
|
||||
.text-error { color: $color-error; }
|
||||
|
||||
.bg-white { background-color: $color-white; }
|
||||
.bg-gray-50 { background-color: $color-gray-50; }
|
||||
.bg-gray-100 { background-color: $color-gray-100; }
|
||||
.bg-gray-200 { background-color: $color-gray-200; }
|
||||
.bg-black { background-color: $color-black; }
|
||||
.bg-primary { background-color: $color-primary; }
|
||||
|
||||
// Spacing - Margin
|
||||
.m-0 { margin: $spacing-0; }
|
||||
.m-2 { margin: $spacing-2; }
|
||||
.m-4 { margin: $spacing-4; }
|
||||
.m-8 { margin: $spacing-8; }
|
||||
|
||||
.mt-0 { margin-top: $spacing-0; }
|
||||
.mt-2 { margin-top: $spacing-2; }
|
||||
.mt-4 { margin-top: $spacing-4; }
|
||||
.mt-6 { margin-top: $spacing-6; }
|
||||
.mt-8 { margin-top: $spacing-8; }
|
||||
.mt-12 { margin-top: $spacing-12; }
|
||||
|
||||
.mb-0 { margin-bottom: $spacing-0; }
|
||||
.mb-2 { margin-bottom: $spacing-2; }
|
||||
.mb-4 { margin-bottom: $spacing-4; }
|
||||
.mb-6 { margin-bottom: $spacing-6; }
|
||||
.mb-8 { margin-bottom: $spacing-8; }
|
||||
|
||||
.ml-2 { margin-left: $spacing-2; }
|
||||
.ml-4 { margin-left: $spacing-4; }
|
||||
.mr-2 { margin-right: $spacing-2; }
|
||||
.mr-4 { margin-right: $spacing-4; }
|
||||
|
||||
.mx-auto { margin-left: auto; margin-right: auto; }
|
||||
|
||||
// Spacing - Padding
|
||||
.p-0 { padding: $spacing-0; }
|
||||
.p-2 { padding: $spacing-2; }
|
||||
.p-3 { padding: $spacing-3; }
|
||||
.p-4 { padding: $spacing-4; }
|
||||
.p-6 { padding: $spacing-6; }
|
||||
.p-8 { padding: $spacing-8; }
|
||||
|
||||
.px-2 { padding-left: $spacing-2; padding-right: $spacing-2; }
|
||||
.px-4 { padding-left: $spacing-4; padding-right: $spacing-4; }
|
||||
.px-6 { padding-left: $spacing-6; padding-right: $spacing-6; }
|
||||
.px-8 { padding-left: $spacing-8; padding-right: $spacing-8; }
|
||||
|
||||
.py-2 { padding-top: $spacing-2; padding-bottom: $spacing-2; }
|
||||
.py-3 { padding-top: $spacing-3; padding-bottom: $spacing-3; }
|
||||
.py-4 { padding-top: $spacing-4; padding-bottom: $spacing-4; }
|
||||
.py-6 { padding-top: $spacing-6; padding-bottom: $spacing-6; }
|
||||
.py-8 { padding-top: $spacing-8; padding-bottom: $spacing-8; }
|
||||
|
||||
// Width/Height
|
||||
.w-full { width: 100%; }
|
||||
.w-auto { width: auto; }
|
||||
.h-full { height: 100%; }
|
||||
.h-screen { height: 100vh; }
|
||||
.min-h-screen { min-height: 100vh; }
|
||||
|
||||
// Borders
|
||||
.border { border: $border-width solid $border-color; }
|
||||
.border-0 { border: none; }
|
||||
.border-t { border-top: $border-width solid $border-color; }
|
||||
.border-b { border-bottom: $border-width solid $border-color; }
|
||||
.border-l { border-left: $border-width solid $border-color; }
|
||||
.border-r { border-right: $border-width solid $border-color; }
|
||||
.border-dark { border-color: $border-color-dark; }
|
||||
.border-2 { border-width: $border-width-2; }
|
||||
|
||||
// Position
|
||||
.relative { position: relative; }
|
||||
.absolute { position: absolute; }
|
||||
.fixed { position: fixed; }
|
||||
.sticky { position: sticky; }
|
||||
|
||||
.top-0 { top: 0; }
|
||||
.right-0 { right: 0; }
|
||||
.bottom-0 { bottom: 0; }
|
||||
.left-0 { left: 0; }
|
||||
.inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
|
||||
|
||||
// Z-index
|
||||
.z-10 { z-index: 10; }
|
||||
.z-20 { z-index: 20; }
|
||||
.z-50 { z-index: 50; }
|
||||
.z-100 { z-index: 100; }
|
||||
|
||||
// Overflow
|
||||
.overflow-hidden { overflow: hidden; }
|
||||
.overflow-auto { overflow: auto; }
|
||||
.overflow-x-auto { overflow-x: auto; }
|
||||
.overflow-y-auto { overflow-y: auto; }
|
||||
|
||||
// Shadows
|
||||
.shadow { box-shadow: $shadow-sm; }
|
||||
.shadow-md { box-shadow: $shadow-md; }
|
||||
.shadow-lg { box-shadow: $shadow-lg; }
|
||||
.shadow-none { box-shadow: none; }
|
||||
|
||||
// Cursor
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-default { cursor: default; }
|
||||
|
||||
// Opacity
|
||||
.opacity-50 { opacity: 0.5; }
|
||||
.opacity-75 { opacity: 0.75; }
|
||||
|
||||
// Print utilities
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
.print-only { display: block !important; }
|
||||
}
|
||||
|
||||
.print-only { display: none; }
|
||||
116
internal/web/assets/scss/_variables.scss
Normal file
116
internal/web/assets/scss/_variables.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
// ============================================
|
||||
// BILLIT - McMaster-Carr Inspired Design System
|
||||
// Industrial, Dense, Functional, No Roundedness
|
||||
// ============================================
|
||||
|
||||
// Colors - Industrial palette
|
||||
$color-black: #000000;
|
||||
$color-white: #ffffff;
|
||||
$color-gray-50: #fafafa;
|
||||
$color-gray-100: #f5f5f5;
|
||||
$color-gray-200: #eeeeee;
|
||||
$color-gray-300: #e0e0e0;
|
||||
$color-gray-400: #bdbdbd;
|
||||
$color-gray-500: #9e9e9e;
|
||||
$color-gray-600: #757575;
|
||||
$color-gray-700: #616161;
|
||||
$color-gray-800: #424242;
|
||||
$color-gray-900: #212121;
|
||||
|
||||
// Primary - Industrial orange (McMaster signature)
|
||||
$color-primary: #e65100;
|
||||
$color-primary-dark: #bf360c;
|
||||
$color-primary-light: #ff6d00;
|
||||
|
||||
// Accent - Deep blue for links/actions
|
||||
$color-accent: #0d47a1;
|
||||
$color-accent-dark: #002171;
|
||||
$color-accent-light: #1565c0;
|
||||
|
||||
// Status colors
|
||||
$color-success: #2e7d32;
|
||||
$color-warning: #f57c00;
|
||||
$color-error: #c62828;
|
||||
$color-info: #1565c0;
|
||||
|
||||
// Typography
|
||||
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
$font-family-mono: "SF Mono", "Monaco", "Inconsolata", "Fira Mono", "Droid Sans Mono", monospace;
|
||||
|
||||
$font-size-xs: 0.6875rem; // 11px
|
||||
$font-size-sm: 0.75rem; // 12px
|
||||
$font-size-base: 0.8125rem; // 13px
|
||||
$font-size-md: 0.875rem; // 14px
|
||||
$font-size-lg: 1rem; // 16px
|
||||
$font-size-xl: 1.125rem; // 18px
|
||||
$font-size-2xl: 1.25rem; // 20px
|
||||
$font-size-3xl: 1.5rem; // 24px
|
||||
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
|
||||
$line-height-tight: 1.2;
|
||||
$line-height-base: 1.4;
|
||||
$line-height-loose: 1.6;
|
||||
|
||||
// Spacing - Dense, compact
|
||||
$spacing-0: 0;
|
||||
$spacing-1: 0.125rem; // 2px
|
||||
$spacing-2: 0.25rem; // 4px
|
||||
$spacing-3: 0.375rem; // 6px
|
||||
$spacing-4: 0.5rem; // 8px
|
||||
$spacing-5: 0.625rem; // 10px
|
||||
$spacing-6: 0.75rem; // 12px
|
||||
$spacing-8: 1rem; // 16px
|
||||
$spacing-10: 1.25rem; // 20px
|
||||
$spacing-12: 1.5rem; // 24px
|
||||
$spacing-16: 2rem; // 32px
|
||||
$spacing-20: 2.5rem; // 40px
|
||||
|
||||
// Borders - Sharp, no radius
|
||||
$border-width: 1px;
|
||||
$border-width-2: 2px;
|
||||
$border-color: $color-gray-300;
|
||||
$border-color-dark: $color-gray-400;
|
||||
$border-radius: 0; // NO ROUNDEDNESS
|
||||
|
||||
// Shadows - Minimal
|
||||
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
$shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
$shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
// Transitions
|
||||
$transition-fast: 0.1s ease;
|
||||
$transition-base: 0.15s ease;
|
||||
$transition-slow: 0.25s ease;
|
||||
|
||||
// Layout
|
||||
$max-width-sm: 640px;
|
||||
$max-width-md: 768px;
|
||||
$max-width-lg: 1024px;
|
||||
$max-width-xl: 1280px;
|
||||
$max-width-2xl: 1536px;
|
||||
|
||||
// Header
|
||||
$header-height: 40px;
|
||||
$header-bg: $color-black;
|
||||
$header-text: $color-white;
|
||||
|
||||
// Table
|
||||
$table-header-bg: $color-gray-100;
|
||||
$table-border: $color-gray-300;
|
||||
$table-row-hover: $color-gray-50;
|
||||
$table-cell-padding: $spacing-3 $spacing-4;
|
||||
|
||||
// Form inputs
|
||||
$input-height: 28px;
|
||||
$input-padding: $spacing-2 $spacing-4;
|
||||
$input-border: $border-color;
|
||||
$input-focus-border: $color-accent;
|
||||
$input-bg: $color-white;
|
||||
|
||||
// Buttons
|
||||
$btn-height: 28px;
|
||||
$btn-padding: $spacing-2 $spacing-6;
|
||||
10
internal/web/assets/scss/main.scss
Normal file
10
internal/web/assets/scss/main.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
// ============================================
|
||||
// BILLIT - Main SCSS Entry Point
|
||||
// McMaster-Carr Inspired Design System
|
||||
// ============================================
|
||||
|
||||
@use 'variables' as *;
|
||||
@use 'base';
|
||||
@use 'utilities';
|
||||
@use 'components';
|
||||
@use 'print';
|
||||
Reference in New Issue
Block a user