Adds dark mode support and responsive design

Introduces system and manual dark mode toggles across multiple pages
Implements consistent theming with CSS variables
Enhances user experience with responsive layout adjustments
This commit is contained in:
Janus C. H. Knudsen 2025-12-30 00:36:01 +01:00
parent da5d9df274
commit 1718d4d5a1
4 changed files with 2977 additions and 0 deletions

View file

@ -0,0 +1,719 @@
<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leverandører - Backend</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ==========================================
FONT FACE (Poppins)
========================================== */
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* ==========================================
CSS VARIABLES (Design System)
========================================== */
:root {
--color-surface: #fff;
--color-background: #f5f5f5;
--color-background-hover: #f0f0f0;
--color-background-alt: #fafafa;
--color-border: #e0e0e0;
--color-text: #333;
--color-text-secondary: #666;
--color-teal: #00897b;
--color-blue: #1976d2;
--color-red: #e53935;
--color-amber: #f59e0b;
--color-purple: #8b5cf6;
--color-green: #43a047;
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
/* Dark mode - følger system preference ELLER manuel toggle */
@media (prefers-color-scheme: dark) {
:root:not(.light-mode) {
--color-surface: #1e1e1e;
--color-background: #121212;
--color-background-hover: #2a2a2a;
--color-background-alt: #1a1a1a;
--color-border: #333;
--color-text: #e0e0e0;
--color-text-secondary: #999;
--color-teal: #26a69a;
--color-blue: #42a5f5;
--color-red: #ef5350;
--color-amber: #ffb74d;
--color-purple: #a78bfa;
--color-green: #66bb6a;
}
}
/* Manuel dark mode override */
:root.dark-mode {
--color-surface: #1e1e1e;
--color-background: #121212;
--color-background-hover: #2a2a2a;
--color-background-alt: #1a1a1a;
--color-border: #333;
--color-text: #e0e0e0;
--color-text-secondary: #999;
--color-teal: #26a69a;
--color-blue: #42a5f5;
--color-red: #ef5350;
--color-amber: #ffb74d;
--color-purple: #a78bfa;
--color-green: #66bb6a;
}
/* ==========================================
RESET & BASE
========================================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-size: 14px;
color: var(--color-text);
background: var(--color-background);
line-height: 1.5;
}
a {
color: var(--color-teal);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* ==========================================
TOPBAR
========================================== */
swp-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 100;
}
swp-topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
swp-page-title {
font-size: 16px;
font-weight: 600;
}
swp-topbar-right {
display: flex;
align-items: center;
gap: 12px;
}
/* ==========================================
LAYOUT
========================================== */
swp-page-container {
display: block;
max-width: 1400px;
margin: 0 auto;
padding: 24px;
}
/* ==========================================
BUTTONS
========================================== */
swp-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
font-family: var(--font-family);
border-radius: 6px;
cursor: pointer;
transition: all 150ms ease;
border: none;
}
swp-btn svg {
width: 16px;
height: 16px;
fill: currentColor;
}
swp-btn.primary {
background: var(--color-teal);
color: white;
}
swp-btn.primary:hover {
background: #00796b;
}
swp-btn.secondary {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
swp-btn.secondary:hover {
background: var(--color-background-hover);
}
swp-btn.icon-only {
padding: 10px;
}
swp-btn.icon-only svg {
width: 18px;
height: 18px;
}
/* ==========================================
FILTER BAR
========================================== */
swp-filter-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 20px;
background: var(--color-surface);
border-radius: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
swp-search-input {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
}
swp-search-input img {
width: 16px;
height: 16px;
opacity: 0.5;
}
@media (prefers-color-scheme: dark) {
:root:not(.light-mode) swp-search-input img {
filter: invert(1);
}
}
:root.dark-mode swp-search-input img {
filter: invert(1);
}
swp-search-input input {
border: none;
outline: none;
font-size: 13px;
font-family: var(--font-family);
width: 400px;
background: transparent;
color: var(--color-text);
}
swp-filter-spacer {
flex: 1;
}
/* ==========================================
STATS BAR
========================================== */
swp-stats-bar {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 20px;
}
swp-stat-card {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 20px;
background: var(--color-surface);
border-radius: 8px;
border: 1px solid var(--color-border);
}
swp-stat-card swp-stat-value {
font-size: 22px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--color-text);
}
swp-stat-card swp-stat-label {
font-size: 12px;
color: var(--color-text-secondary);
}
swp-stat-card.highlight swp-stat-value {
color: var(--color-teal);
}
swp-stat-card.warning swp-stat-value {
color: var(--color-amber);
}
/* ==========================================
TABLE
========================================== */
swp-table {
display: block;
background: var(--color-surface);
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--color-border);
}
swp-table-header,
swp-table-row {
display: grid;
grid-template-columns: minmax(200px, 1fr) 150px 80px 140px 80px 40px;
align-items: center;
}
swp-table-header {
background: var(--color-background-alt);
border-bottom: 1px solid var(--color-border);
padding: 12px 20px;
}
swp-th {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
}
swp-th.right {
text-align: right;
}
swp-th.center {
text-align: center;
}
swp-table-body {
display: block;
}
swp-table-row {
padding: 14px 20px;
border-bottom: 1px solid var(--color-border);
transition: background 150ms ease;
cursor: pointer;
}
swp-table-row:last-child {
border-bottom: none;
}
swp-table-row:hover {
background: var(--color-background-hover);
}
swp-td {
font-size: 14px;
color: var(--color-text);
}
swp-td.right {
text-align: right;
}
swp-td.center {
text-align: center;
}
swp-td.mono {
font-family: var(--font-mono);
font-size: 13px;
}
swp-td.muted {
color: var(--color-text-secondary);
}
/* ==========================================
SUPPLIER CELL
========================================== */
swp-supplier-cell {
display: flex;
flex-direction: column;
gap: 2px;
}
swp-supplier-name {
font-weight: 500;
color: var(--color-text);
}
swp-supplier-city {
font-size: 12px;
color: var(--color-text-secondary);
}
/* ==========================================
STATUS BADGE
========================================== */
swp-status-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 11px;
font-weight: 500;
border-radius: 12px;
}
swp-status-badge.active {
background: color-mix(in srgb, var(--color-green) 15%, white);
color: var(--color-green);
}
swp-status-badge.inactive {
background: color-mix(in srgb, var(--color-text-secondary) 15%, white);
color: var(--color-text-secondary);
}
/* ==========================================
ROW ARROW
========================================== */
swp-row-arrow {
display: flex;
align-items: center;
justify-content: flex-end;
}
swp-row-arrow svg {
width: 18px;
height: 18px;
fill: var(--color-text-secondary);
transition: transform 150ms ease;
}
swp-table-row:hover swp-row-arrow svg {
transform: translateX(4px);
fill: var(--color-teal);
}
/* ==========================================
TABLE FOOTER
========================================== */
swp-table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: var(--color-background-alt);
border-top: 1px solid var(--color-border);
font-size: 13px;
color: var(--color-text-secondary);
}
/* ==========================================
RESPONSIVE
========================================== */
@media (max-width: 1200px) {
swp-stats-bar {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 900px) {
swp-table-header,
swp-table-row {
grid-template-columns: 1fr 80px 80px 40px;
}
swp-th:nth-child(2),
swp-td:nth-child(2),
swp-th:nth-child(3),
swp-td:nth-child(3),
swp-th:nth-child(4),
swp-td:nth-child(4) {
display: none;
}
}
</style>
</head>
<body>
<!-- Topbar -->
<swp-topbar>
<swp-topbar-left>
<swp-page-title>Leverandører</swp-page-title>
</swp-topbar-left>
<swp-topbar-right>
<swp-btn class="secondary icon-only" id="themeToggle" title="Skift tema">
<svg id="sunIcon" viewBox="0 0 24 24"><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/></svg>
<svg id="moonIcon" viewBox="0 0 24 24" style="display:none"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/></svg>
</swp-btn>
<swp-btn class="primary">
<svg viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
Ny leverandør
</swp-btn>
</swp-topbar-right>
</swp-topbar>
<swp-page-container>
<!-- Filter Bar -->
<swp-filter-bar>
<swp-search-input>
<img src="icons/search.svg" alt="Søg" />
<input type="search" id="searchInput" placeholder="Søg leverandør, kontaktperson..." />
</swp-search-input>
<swp-filter-spacer></swp-filter-spacer>
</swp-filter-bar>
<!-- Stats Bar -->
<swp-stats-bar>
<swp-stat-card class="highlight">
<swp-stat-value>12</swp-stat-value>
<swp-stat-label>Leverandører i alt</swp-stat-label>
</swp-stat-card>
<swp-stat-card>
<swp-stat-value>10</swp-stat-value>
<swp-stat-label>Aktive</swp-stat-label>
</swp-stat-card>
<swp-stat-card>
<swp-stat-value>45.230 kr</swp-stat-value>
<swp-stat-label>Indkøb denne måned</swp-stat-label>
</swp-stat-card>
<swp-stat-card class="warning">
<swp-stat-value>3</swp-stat-value>
<swp-stat-label>Afventende ordrer</swp-stat-label>
</swp-stat-card>
</swp-stats-bar>
<!-- Table -->
<swp-table>
<swp-table-header>
<swp-th>Leverandør</swp-th>
<swp-th>Kontakt</swp-th>
<swp-th class="center">Produkter</swp-th>
<swp-th>Sidste ordre</swp-th>
<swp-th>Status</swp-th>
<swp-th></swp-th>
</swp-table-header>
<swp-table-body>
<swp-table-row data-id="1" onclick="location.href='poc-leverandoer.html'">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Beauty Group Denmark</swp-supplier-name>
<swp-supplier-city>København</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Lars Hansen</swp-td>
<swp-td class="center mono">24</swp-td>
<swp-td class="muted">15. december 2024</swp-td>
<swp-td><swp-status-badge class="active">Aktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="2">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Salon Supplies ApS</swp-supplier-name>
<swp-supplier-city>Aarhus</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Mette Nielsen</swp-td>
<swp-td class="center mono">18</swp-td>
<swp-td class="muted">22. december 2024</swp-td>
<swp-td><swp-status-badge class="active">Aktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="3">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Pro Hair Distribution</swp-supplier-name>
<swp-supplier-city>Odense</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Anders Sørensen</swp-td>
<swp-td class="center mono">32</swp-td>
<swp-td class="muted">10. december 2024</swp-td>
<swp-td><swp-status-badge class="active">Aktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="4">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Nordic Beauty Import</swp-supplier-name>
<swp-supplier-city>Aalborg</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Pia Kristensen</swp-td>
<swp-td class="center mono">15</swp-td>
<swp-td class="muted">28. november 2024</swp-td>
<swp-td><swp-status-badge class="active">Aktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="5">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Color World A/S</swp-supplier-name>
<swp-supplier-city>Vejle</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Thomas Berg</swp-td>
<swp-td class="center mono">8</swp-td>
<swp-td class="muted">5. december 2024</swp-td>
<swp-td><swp-status-badge class="inactive">Inaktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="6">
<swp-td>
<swp-supplier-cell>
<swp-supplier-name>Tools & More</swp-supplier-name>
<swp-supplier-city>Roskilde</swp-supplier-city>
</swp-supplier-cell>
</swp-td>
<swp-td>Karen Olsen</swp-td>
<swp-td class="center mono">12</swp-td>
<swp-td class="muted">18. december 2024</swp-td>
<swp-td><swp-status-badge class="active">Aktiv</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
</swp-table-body>
<swp-table-footer>
<span>Viser 6 af 12 leverandører</span>
<span>Side 1 af 2</span>
</swp-table-footer>
</swp-table>
</swp-page-container>
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0"></script>
<script>
// Supplier data for search
const suppliers = [
{ id: 1, name: 'Beauty Group Denmark', contact: 'Lars Hansen', city: 'København' },
{ id: 2, name: 'Salon Supplies ApS', contact: 'Mette Nielsen', city: 'Aarhus' },
{ id: 3, name: 'Pro Hair Distribution', contact: 'Anders Sørensen', city: 'Odense' },
{ id: 4, name: 'Nordic Beauty Import', contact: 'Pia Kristensen', city: 'Aalborg' },
{ id: 5, name: 'Color World A/S', contact: 'Thomas Berg', city: 'Vejle' },
{ id: 6, name: 'Tools & More', contact: 'Karen Olsen', city: 'Roskilde' }
];
// Initialize Fuse.js
const fuse = new Fuse(suppliers, {
keys: ['name', 'contact', 'city'],
threshold: 0.3,
ignoreLocation: true
});
const searchInput = document.getElementById('searchInput');
const tableBody = document.querySelector('swp-table-body');
const allRows = Array.from(tableBody.querySelectorAll('swp-table-row'));
// Search handler
searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
if (query === '') {
// Show all rows
allRows.forEach(row => row.style.display = '');
} else {
// Search with Fuse.js
const results = fuse.search(query);
const matchedIds = results.map(r => r.item.id);
allRows.forEach(row => {
const rowId = parseInt(row.dataset.id);
row.style.display = matchedIds.includes(rowId) ? '' : 'none';
});
}
});
// Theme toggle
const themeToggle = document.getElementById('themeToggle');
const sunIcon = document.getElementById('sunIcon');
const moonIcon = document.getElementById('moonIcon');
const root = document.documentElement;
function updateIcons() {
const isDark = root.classList.contains('dark-mode') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches && !root.classList.contains('light-mode'));
sunIcon.style.display = isDark ? 'none' : 'block';
moonIcon.style.display = isDark ? 'block' : 'none';
}
updateIcons();
themeToggle.addEventListener('click', () => {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
// System is dark - toggle to light or back to system
root.classList.toggle('light-mode');
root.classList.remove('dark-mode');
} else {
// System is light - toggle to dark or back to system
root.classList.toggle('dark-mode');
root.classList.remove('light-mode');
}
updateIcons();
});
</script>
</body>
</html>