From b5b280d6a7a05ff6eab94ef517fb9d8c3a98c524 Mon Sep 17 00:00:00 2001 From: LUKZConnectz <163946633+LUKZConnectz@users.noreply.github.com> Date: Sun, 7 Jun 2026 07:23:55 +0700 Subject: [PATCH] Build rider profit dashboard --- README.md | 22 ++- index.html | 292 +++++++++++++++++++++++++++----- script.js | 484 +++++++++++++++++++++++++++++++++++++++++++++-------- style.css | 319 +++++++++-------------------------- 4 files changed, 760 insertions(+), 357 deletions(-) diff --git a/README.md b/README.md index 42f2503..4582e8d 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# pxomdev.github.io \ No newline at end of file +# Rider Profit Dashboard + +เว็บสำหรับไรเดอร์บันทึกค่ารอบ ระยะทาง ค่าน้ำมัน และกำไรสุทธิแบบ responsive ด้วย Tailwind CSS + +## Features + +- บันทึกค่ารอบและระยะทางของแต่ละงาน +- คำนวณลิตรที่ใช้จากอัตราสิ้นเปลืองรถ เช่น 40 กม./ลิตร +- คำนวณค่าน้ำมันและกำไรสุทธิอัตโนมัติ +- สรุปรายวัน รายสัปดาห์ รายเดือน หรือทั้งหมด +- Dark Mode และบันทึกข้อมูลใน localStorage +- ดึงราคาน้ำมันจาก API พร้อม fallback/cache หาก API หรือ CORS ไม่พร้อมใช้งาน +- Export เป็น Excel (`.xls`) และ PDF ผ่านคำสั่งพิมพ์ของเบราว์เซอร์ + +## Run locally + +```bash +python3 -m http.server 4173 +``` + +จากนั้นเปิด `http://127.0.0.1:4173/index.html` diff --git a/index.html b/index.html index 1ebfe64..8571525 100644 --- a/index.html +++ b/index.html @@ -1,44 +1,248 @@ - - - - - - - Login | เข้าสู่ระบบ - - - - -
-

เข้าสู่ระบบ

- Status Image -
-
-
- -
-
- -
- -
-
- - - - - - + + + + + + + Rider Profit Dashboard | บันทึกค่ารอบ + + + + + + + + +
+
+
+
+ +
+
+
+
+

+ Grab Rider Fuel & Profit Tracker +

+

+ บันทึกค่ารอบ ค่าน้ำมัน และกำไรสุทธิ +

+

+ จดรายได้แต่ละงาน ใส่กิโลเมตร ระบบจะคำนวณลิตรที่ใช้ ค่าน้ำมัน และสรุปผลรายวัน รายสัปดาห์ รายเดือนให้อัตโนมัติ +

+
+
+ + +
+
+
+ +
+
+

รายได้ค่ารอบ

+

฿0.00

+

รวมจากงานที่บันทึก

+
+
+

ค่าน้ำมันรวม

+

฿0.00

+

0.00 ลิตร

+
+
+

กำไรสุทธิ

+

฿0.00

+

ค่ารอบ - ค่าน้ำมัน

+
+
+

ระยะทางทั้งหมด

+

0.00 กม.

+

0 งาน

+
+
+ +
+
+
+
+
+

เพิ่มงานส่งของ

+

บันทึกค่ารอบและระยะทางของแต่ละงาน

+
+ Auto save +
+ +
+ + + + + + +
+ +
+
+
+

ลิตรที่ใช้

+

0.00 L

+
+
+

ค่าน้ำมัน

+

฿0.00

+
+
+

กำไร

+

฿0.00

+
+
+
+ + +
+ +
+

ราคาน้ำมันวันนี้

+

กำลังโหลดราคาน้ำมัน...

+
+
+
+ +
+
+
+
+

สรุปผลงาน

+

เลือกช่วงเวลาเพื่อดูรายวัน รายสัปดาห์ หรือรายเดือน

+
+
+ + +
+
+ +
+
+

จำนวนงาน

+

0

+
+
+

ค่ารอบ

+

฿0.00

+
+
+

ค่าน้ำมัน

+

฿0.00

+
+
+

กำไรสุทธิ

+

฿0.00

+
+
+
+ +
+
+
+

รายการงานทั้งหมด

+

ข้อมูลเก็บในเครื่องของคุณด้วย localStorage

+
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + + + +
วันที่ค่ารอบกม.ลิตรค่าน้ำมันกำไรหมายเหตุจัดการ
+
+ +
+
+
+
+
+ + + + diff --git a/script.js b/script.js index dc7ee71..b60428d 100644 --- a/script.js +++ b/script.js @@ -1,74 +1,410 @@ -const statusImage = document.getElementById("statusImage"); -const usernameInput = document.getElementById("username"); -const passwordInput = document.getElementById("password"); -const loginForm = document.getElementById("loginForm"); - -const successModal = document.getElementById("successModal"); -const closeModal = document.getElementById("closeModal"); - -let toggleImage = true; -let isSubmitted = false; - -function updateImage() { - if (usernameInput.value || passwordInput.value) { - statusImage.src = toggleImage ? "./left.jpg" : "./right.jpg"; - toggleImage = !toggleImage; - } else if (isSubmitted) { - statusImage.src = "./wait.jpg"; - } -} - -function resetImage() { - if (!usernameInput.value && !passwordInput.value) { - statusImage.src = "./wait.jpg"; - } -} - -usernameInput.addEventListener("input", function () { - isSubmitted = false; - updateImage(); -}); - -passwordInput.addEventListener("input", function () { - isSubmitted = false; - updateImage(); -}); - -usernameInput.addEventListener("blur", resetImage); -passwordInput.addEventListener("blur", resetImage); - -loginForm.addEventListener("submit", function (event) { - event.preventDefault(); - isSubmitted = true; - - if (!usernameInput.value && !passwordInput.value) { - statusImage.src = "./wait.jpg"; - return; - } - - if (!usernameInput.value) { - statusImage.src = "./wait.jpg"; - return; - } - - if (!passwordInput.value) { - statusImage.src = "./wait.jpg"; - return; - } - - successModal.style.display = "flex"; -}); - -closeModal.addEventListener("click", function () { - successModal.style.display = "none"; - isSubmitted = false; - updateImage(); -}); - -window.addEventListener("click", function (event) { - if (event.target === successModal) { - successModal.style.display = "none"; - isSubmitted = false; - updateImage(); - } -}); +const STORAGE_KEY = "rider-profit-jobs-v1"; +const SETTINGS_KEY = "rider-profit-settings-v1"; +const FUEL_CACHE_KEY = "rider-profit-fuel-v1"; + +const fallbackFuels = [ + { name: "Gasohol 95", price: 31.85 }, + { name: "Gasohol 91", price: 31.48 }, + { name: "E20", price: 29.64 }, + { name: "Diesel B7", price: 31.94 }, +]; + +const elements = { + jobForm: document.getElementById("jobForm"), + jobDate: document.getElementById("jobDate"), + fare: document.getElementById("fare"), + distance: document.getElementById("distance"), + efficiency: document.getElementById("efficiency"), + fuelPrice: document.getElementById("fuelPrice"), + note: document.getElementById("note"), + previewLiters: document.getElementById("previewLiters"), + previewFuelCost: document.getElementById("previewFuelCost"), + previewProfit: document.getElementById("previewProfit"), + fuelCards: document.getElementById("fuelCards"), + fuelStatus: document.getElementById("fuelStatus"), + refreshFuelBtn: document.getElementById("refreshFuelBtn"), + jobsBody: document.getElementById("jobsBody"), + emptyState: document.getElementById("emptyState"), + totalFare: document.getElementById("totalFare"), + totalFuelCost: document.getElementById("totalFuelCost"), + totalLiters: document.getElementById("totalLiters"), + totalProfit: document.getElementById("totalProfit"), + totalDistance: document.getElementById("totalDistance"), + totalJobs: document.getElementById("totalJobs"), + summaryRange: document.getElementById("summaryRange"), + summaryDate: document.getElementById("summaryDate"), + rangeJobs: document.getElementById("rangeJobs"), + rangeFare: document.getElementById("rangeFare"), + rangeFuel: document.getElementById("rangeFuel"), + rangeProfit: document.getElementById("rangeProfit"), + exportExcelBtn: document.getElementById("exportExcelBtn"), + exportPdfBtn: document.getElementById("exportPdfBtn"), + clearBtn: document.getElementById("clearBtn"), + themeToggle: document.getElementById("themeToggle"), + themeIcon: document.getElementById("themeIcon"), +}; + +let jobs = loadJobs(); +let settings = loadSettings(); + +function today() { + return new Date().toISOString().slice(0, 10); +} + +function loadJobs() { + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; + } catch { + return []; + } +} + +function saveJobs() { + localStorage.setItem(STORAGE_KEY, JSON.stringify(jobs)); +} + +function loadSettings() { + try { + return JSON.parse(localStorage.getItem(SETTINGS_KEY)) || {}; + } catch { + return {}; + } +} + +function saveSettings() { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); +} + +function baht(value) { + return new Intl.NumberFormat("th-TH", { + style: "currency", + currency: "THB", + minimumFractionDigits: 2, + }).format(Number(value) || 0); +} + +function decimal(value, digits = 2) { + return new Intl.NumberFormat("th-TH", { + minimumFractionDigits: digits, + maximumFractionDigits: digits, + }).format(Number(value) || 0); +} + +function getCalculation({ fare, distance, efficiency, fuelPrice }) { + const liters = efficiency > 0 ? distance / efficiency : 0; + const fuelCost = liters * fuelPrice; + const profit = fare - fuelCost; + return { liters, fuelCost, profit }; +} + +function hydrateForm() { + elements.jobDate.value = today(); + elements.summaryDate.value = today(); + elements.efficiency.value = settings.efficiency || 40; + elements.fuelPrice.value = settings.fuelPrice || fallbackFuels[0].price; + applyTheme(settings.theme || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")); + updatePreview(); +} + +function updatePreview() { + const fare = Number(elements.fare.value) || 0; + const distance = Number(elements.distance.value) || 0; + const efficiency = Number(elements.efficiency.value) || 0; + const fuelPrice = Number(elements.fuelPrice.value) || 0; + const { liters, fuelCost, profit } = getCalculation({ fare, distance, efficiency, fuelPrice }); + + elements.previewLiters.textContent = `${decimal(liters)} L`; + elements.previewFuelCost.textContent = baht(fuelCost); + elements.previewProfit.textContent = baht(profit); +} + +function render() { + renderJobs(); + renderTotals(); + renderRangeSummary(); +} + +function renderJobs() { + elements.jobsBody.innerHTML = ""; + elements.emptyState.classList.toggle("hidden", jobs.length > 0); + + [...jobs] + .sort((a, b) => `${b.date}${b.createdAt}`.localeCompare(`${a.date}${a.createdAt}`)) + .forEach((job) => { + const row = document.createElement("tr"); + row.className = "hover:bg-slate-50 dark:hover:bg-slate-800/50"; + row.innerHTML = ` + ${job.date} + ${baht(job.fare)} + ${decimal(job.distance)} กม. + ${decimal(job.liters)} L + ${baht(job.fuelCost)} + ${baht(job.profit)} + ${escapeHtml(job.note || "-")} + + + + `; + elements.jobsBody.appendChild(row); + }); +} + +function summarize(list) { + return list.reduce( + (sum, job) => ({ + jobs: sum.jobs + 1, + fare: sum.fare + job.fare, + distance: sum.distance + job.distance, + liters: sum.liters + job.liters, + fuel: sum.fuel + job.fuelCost, + profit: sum.profit + job.profit, + }), + { jobs: 0, fare: 0, distance: 0, liters: 0, fuel: 0, profit: 0 } + ); +} + +function renderTotals() { + const sum = summarize(jobs); + elements.totalFare.textContent = baht(sum.fare); + elements.totalFuelCost.textContent = baht(sum.fuel); + elements.totalLiters.textContent = `${decimal(sum.liters)} ลิตร`; + elements.totalProfit.textContent = baht(sum.profit); + elements.totalDistance.textContent = `${decimal(sum.distance)} กม.`; + elements.totalJobs.textContent = `${sum.jobs} งาน`; +} + +function renderRangeSummary() { + const filtered = jobs.filter((job) => inSelectedRange(job.date)); + const sum = summarize(filtered); + elements.rangeJobs.textContent = sum.jobs; + elements.rangeFare.textContent = baht(sum.fare); + elements.rangeFuel.textContent = baht(sum.fuel); + elements.rangeProfit.textContent = baht(sum.profit); +} + +function inSelectedRange(dateString) { + const range = elements.summaryRange.value; + if (range === "all") return true; + + const base = new Date(`${elements.summaryDate.value || today()}T00:00:00`); + const target = new Date(`${dateString}T00:00:00`); + + if (range === "day") return dateString === (elements.summaryDate.value || today()); + + if (range === "week") { + const day = base.getDay() || 7; + const start = new Date(base); + start.setDate(base.getDate() - day + 1); + const end = new Date(start); + end.setDate(start.getDate() + 6); + return target >= start && target <= end; + } + + if (range === "month") { + return target.getFullYear() === base.getFullYear() && target.getMonth() === base.getMonth(); + } + + return true; +} + +function renderFuelCards(fuels, source = "API") { + elements.fuelCards.innerHTML = ""; + fuels.forEach((fuel) => { + const card = document.createElement("button"); + card.type = "button"; + card.className = "fuel-card text-left transition hover:-translate-y-0.5 hover:border-emerald-300"; + card.innerHTML = ` +

${escapeHtml(fuel.name)}

+

${decimal(fuel.price)} ฿/L

+

แตะเพื่อใช้ราคานี้

+ `; + card.addEventListener("click", () => { + elements.fuelPrice.value = fuel.price; + settings.fuelPrice = Number(fuel.price); + saveSettings(); + updatePreview(); + }); + elements.fuelCards.appendChild(card); + }); + + const updated = new Date().toLocaleString("th-TH", { dateStyle: "medium", timeStyle: "short" }); + elements.fuelStatus.textContent = `อัปเดตล่าสุด ${updated} • แหล่งข้อมูล: ${source}`; +} + +async function fetchFuelPrices() { + elements.fuelStatus.textContent = "กำลังโหลดราคาน้ำมันแบบ Real-time..."; + const cached = readFuelCache(); + + try { + const response = await fetch("https://api.allorigins.win/raw?url=" + encodeURIComponent("https://energy.go.th/index"), { + cache: "no-store", + }); + if (!response.ok) throw new Error("Fuel API unavailable"); + const html = await response.text(); + const fuels = parseFuelPrices(html); + if (fuels.length < 2) throw new Error("Cannot parse fuel prices"); + localStorage.setItem(FUEL_CACHE_KEY, JSON.stringify({ fuels, savedAt: Date.now() })); + renderFuelCards(fuels, "energy.go.th"); + if (!settings.fuelPrice) { + elements.fuelPrice.value = fuels[0].price; + settings.fuelPrice = fuels[0].price; + saveSettings(); + updatePreview(); + } + } catch (error) { + const fuels = cached?.fuels?.length ? cached.fuels : fallbackFuels; + renderFuelCards(fuels, cached ? "cache" : "fallback"); + elements.fuelStatus.textContent += " • ใช้ข้อมูลสำรอง เพราะ API อาจติด CORS หรือไม่พร้อมใช้งาน"; + } +} + +function parseFuelPrices(html) { + const text = html.replace(/<[^>]+>/g, " ").replace(/ /g, " "); + const fuelNames = ["Gasohol 95", "Gasohol 91", "E20", "E85", "Diesel", "Premium Diesel"]; + const fuels = []; + + fuelNames.forEach((name) => { + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const regex = new RegExp(`${escaped}[^0-9]{0,80}(\\d{2,3}\\.\\d{2})`, "i"); + const match = text.match(regex); + if (match) fuels.push({ name, price: Number(match[1]) }); + }); + + return fuels.slice(0, 6); +} + +function readFuelCache() { + try { + return JSON.parse(localStorage.getItem(FUEL_CACHE_KEY)); + } catch { + return null; + } +} + +function escapeHtml(value) { + return String(value).replace(/[&<>'"]/g, (char) => ({ + "&": "&", + "<": "<", + ">": ">", + "'": "'", + '"': """, + }[char])); +} + +function exportExcel() { + const rows = jobs.map((job) => ` + + ${job.date} + ${job.fare} + ${job.distance} + ${job.efficiency} + ${job.fuelPrice} + ${job.liters.toFixed(2)} + ${job.fuelCost.toFixed(2)} + ${job.profit.toFixed(2)} + ${escapeHtml(job.note || "")} + `).join(""); + + const table = ` + + + ${rows} +
วันที่ค่ารอบกม.กม./ลิตรบาท/ลิตรลิตรค่าน้ำมันกำไรหมายเหตุ
`; + const blob = new Blob(["\ufeff", table], { type: "application/vnd.ms-excel;charset=utf-8" }); + downloadBlob(blob, `rider-profit-${today()}.xls`); +} + +function downloadBlob(blob, filename) { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); +} + +function applyTheme(theme) { + const isDark = theme === "dark"; + document.documentElement.classList.toggle("dark", isDark); + elements.themeIcon.textContent = isDark ? "☀️" : "🌙"; + settings.theme = theme; + saveSettings(); +} + +elements.jobForm.addEventListener("submit", (event) => { + event.preventDefault(); + const fare = Number(elements.fare.value) || 0; + const distance = Number(elements.distance.value) || 0; + const efficiency = Number(elements.efficiency.value) || 40; + const fuelPrice = Number(elements.fuelPrice.value) || 0; + const { liters, fuelCost, profit } = getCalculation({ fare, distance, efficiency, fuelPrice }); + + jobs.push({ + id: crypto.randomUUID(), + date: elements.jobDate.value || today(), + fare, + distance, + efficiency, + fuelPrice, + liters, + fuelCost, + profit, + note: elements.note.value.trim(), + createdAt: new Date().toISOString(), + }); + + settings.efficiency = efficiency; + settings.fuelPrice = fuelPrice; + saveSettings(); + saveJobs(); + elements.jobForm.reset(); + elements.jobDate.value = today(); + elements.efficiency.value = settings.efficiency; + elements.fuelPrice.value = settings.fuelPrice; + updatePreview(); + render(); +}); + +[elements.fare, elements.distance, elements.efficiency, elements.fuelPrice].forEach((input) => { + input.addEventListener("input", updatePreview); +}); + +elements.efficiency.addEventListener("change", () => { + settings.efficiency = Number(elements.efficiency.value) || 40; + saveSettings(); +}); + +elements.fuelPrice.addEventListener("change", () => { + settings.fuelPrice = Number(elements.fuelPrice.value) || fallbackFuels[0].price; + saveSettings(); +}); + +elements.jobsBody.addEventListener("click", (event) => { + const id = event.target.dataset.delete; + if (!id) return; + jobs = jobs.filter((job) => job.id !== id); + saveJobs(); + render(); +}); + +[elements.summaryRange, elements.summaryDate].forEach((input) => { + input.addEventListener("change", renderRangeSummary); +}); + +elements.refreshFuelBtn.addEventListener("click", fetchFuelPrices); +elements.exportExcelBtn.addEventListener("click", exportExcel); +elements.exportPdfBtn.addEventListener("click", () => window.print()); +elements.clearBtn.addEventListener("click", () => { + if (!jobs.length || !confirm("ต้องการล้างข้อมูลงานทั้งหมดใช่ไหม?")) return; + jobs = []; + saveJobs(); + render(); +}); + +elements.themeToggle.addEventListener("click", () => { + applyTheme(document.documentElement.classList.contains("dark") ? "light" : "dark"); +}); + +hydrateForm(); +render(); +fetchFuelPrices(); diff --git a/style.css b/style.css index cc7351d..3dfa1d5 100644 --- a/style.css +++ b/style.css @@ -1,238 +1,81 @@ -@import url("https://fonts.googleapis.com/css2?family=Prompt:wght@300;400;500;600&family=Poppins:wght@300;400;600&display=swap"); - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "Prompt", "Poppins", sans-serif; - background-color: #f5f5f5; - min-height: 100vh; - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - color: #333; -} - -.container { - background: #ffffff; - padding: 40px; - border-radius: 16px; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08); - text-align: center; - width: 380px; - transition: all 0.3s ease; - animation: fadeIn 0.6s ease-out; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(-10px); } - to { opacity: 1; transform: translateY(0); } -} - -h2 { - color: #333; - margin-bottom: 25px; - font-weight: 500; - letter-spacing: 0.5px; - position: relative; - display: inline-block; -} - -h2::after { - content: ""; - position: absolute; - bottom: -8px; - left: 50%; - transform: translateX(-50%); - width: 40px; - height: 2px; - background: #333; - border-radius: 2px; -} - -#statusImage { - width: 280px; - height: 280px; - margin: 20px auto; - border-radius: 12px; - object-fit: cover; - transition: all 0.4s ease; -} - - -#loginForm { - margin-top: 25px; -} - -.input-group { - margin-bottom: 15px; - text-align: left; -} - -input { - width: 100%; - padding: 15px 20px; - border: 1px solid #e0e0e0; - border-radius: 8px; - font-size: 15px; - outline: none; - transition: all 0.3s; - background: #ffffff; - font-family: "Prompt", sans-serif; - color: #333; -} - -input::placeholder { - color: #aaa; -} - -input:focus { - border-color: #555; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05); -} - -button { - width: 100%; - padding: 15px; - background-color: #333; - color: white; - font-size: 16px; - font-weight: 500; - border: none; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); - letter-spacing: 0.5px; - margin-top: 10px; - font-family: "Prompt", sans-serif; -} - -button:hover { - background-color: #222; - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); - transform: translateY(-2px); -} - -button:active { - transform: translateY(1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.3); - justify-content: center; - align-items: center; - backdrop-filter: blur(3px); - animation: fadeIn 0.3s ease-out; -} - -.modal-content { - background: white; - padding: 35px; - border-radius: 16px; - text-align: center; - position: relative; - max-width: 400px; - width: 90%; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); - animation: scaleIn 0.4s ease-out; -} - -@keyframes scaleIn { - from { opacity: 0; transform: scale(0.95); } - to { opacity: 1; transform: scale(1); } -} - -.modal h2 { - color: #333; - margin-bottom: 25px; - font-size: 18px; -} - -.modal img { - width: 280px; - height: 280px; - border-radius: 12px; - object-fit: cover; - margin: 10px auto 15px; - transition: transform 0.3s ease; -} - -.close { - position: absolute; - top: 15px; - right: 20px; - font-size: 24px; - cursor: pointer; - color: #333; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.05); - border-radius: 50%; - transition: all 0.3s; -} - -.close:hover { - color: white; - background: #333; -} - -/* Error message styling */ -.error-message { - background-color: rgba(0, 0, 0, 0.05); - color: #e53935; - padding: 12px 15px; - border-radius: 8px; - margin-bottom: 15px; - font-size: 14px; - border-left: 3px solid #e53935; - text-align: left; - animation: fadeIn 0.3s ease-in-out; - display: none; -} - -/* Responsive styles */ -@media screen and (max-width: 480px) { - .container { - width: 100%; - padding: 30px 20px; - } - - #statusImage { - width: 240px; - height: 240px; - } - - .modal-content { - width: 90%; - padding: 25px 20px; - } - - .modal img { - width: 220px; - height: 220px; - } - - h2 { - font-size: 20px; - } - - .modal h2 { - font-size: 18px; - } -} \ No newline at end of file +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: "Prompt", "Inter", sans-serif; +} + +.field { + width: 100%; + border-radius: 1rem; + border: 1px solid rgb(226 232 240); + background: rgba(255, 255, 255, 0.92); + padding: 0.875rem 1rem; + color: rgb(15 23 42); + outline: none; + transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease; +} + +.field:focus { + border-color: rgb(16 185 129); + box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.14); +} + +.dark .field { + border-color: rgb(51 65 85); + background: rgba(15, 23, 42, 0.86); + color: rgb(248 250 252); +} + +.dark .field:focus { + border-color: rgb(52 211 153); + box-shadow: 0 0 0 4px rgba(52, 211, 153, 0.13); +} + +.fuel-card { + border-radius: 1.5rem; + border: 1px solid rgb(226 232 240); + background: rgb(248 250 252); + padding: 1rem; +} + +.dark .fuel-card { + border-color: rgb(51 65 85); + background: rgba(30, 41, 59, 0.68); +} + +@media print { + body { + background: white !important; + color: black !important; + } + + header, + form, + #refreshFuelBtn, + #themeToggle, + #exportExcelBtn, + #exportPdfBtn, + #clearBtn, + .fuel-card button, + td:last-child, + th:last-child { + display: none !important; + } + + main { + max-width: 100% !important; + padding: 0 !important; + } + + section, + article { + box-shadow: none !important; + border: 1px solid #ddd !important; + } +}