996 lines
27 KiB
Text
996 lines
27 KiB
Text
---
|
|
import SiteLayout from "../layouts/SiteLayout.astro";
|
|
|
|
const rows = Array.from({ length: 20 }, (_, index) => index + 1);
|
|
const gstRate = 0.18;
|
|
const rates = {
|
|
single: 2200,
|
|
double: 2700,
|
|
lunch: 200,
|
|
dinner: 200,
|
|
};
|
|
|
|
const withGst = (amount: number) => amount + amount * gstRate;
|
|
const currency = new Intl.NumberFormat("en-IN", {
|
|
style: "currency",
|
|
currency: "INR",
|
|
maximumFractionDigits: 0,
|
|
});
|
|
---
|
|
|
|
<SiteLayout title="Basic Calculator" description="Guest house and meal cost estimator." brandSubtitle="">
|
|
<section class="calculator-page" aria-labelledby="calculator-title">
|
|
<div class="calculator-heading">
|
|
<div>
|
|
<p class="eyebrow screen-only">Utility</p>
|
|
<h1 id="calculator-title">
|
|
<span class="screen-title">Basic Calculator</span>
|
|
<span class="print-only">Guest House Cost Estimate</span>
|
|
</h1>
|
|
<p class="print-only print-report-note">
|
|
Rates include 18% GST. Accommodation rates are for one night.
|
|
</p>
|
|
<p class="print-only report-meta">Estimate date: <span data-report-date></span></p>
|
|
</div>
|
|
<button class="button button--secondary print-button" type="button" data-print-estimate>
|
|
Print Cost Estimate
|
|
</button>
|
|
</div>
|
|
|
|
<div class="calculator-shell">
|
|
<section class="calculator-panel" aria-labelledby="people-control-title">
|
|
<div class="people-control">
|
|
<div>
|
|
<h2 id="people-control-title">Number of People</h2>
|
|
<p>Use the slider to set up the estimate table.</p>
|
|
</div>
|
|
<output class="people-count" id="people-count-output" for="people-count">N = 5</output>
|
|
</div>
|
|
|
|
<label class="slider-row" for="people-count">
|
|
<span>1</span>
|
|
<input id="people-count" type="range" min="1" max="20" value="5" data-people-count />
|
|
<span>20</span>
|
|
</label>
|
|
|
|
<dl class="rate-grid" aria-label="Rates including GST">
|
|
<div>
|
|
<dt>Single Occupancy</dt>
|
|
<dd>{currency.format(withGst(rates.single))}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Double Occupancy</dt>
|
|
<dd>{currency.format(withGst(rates.double))}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Lunch Per Person</dt>
|
|
<dd>{currency.format(withGst(rates.lunch))}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Dinner Per Person</dt>
|
|
<dd>{currency.format(withGst(rates.dinner))}</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="calculator-panel" aria-labelledby="estimate-table-title">
|
|
<div class="table-head">
|
|
<div>
|
|
<h2 id="estimate-table-title">Estimate</h2>
|
|
<p>Rates include 18% GST. Lunch and dinner are counted twice for double occupancy.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="calculator-table-wrap">
|
|
<table class="calculator-table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Name</th>
|
|
<th scope="col">
|
|
<span class="column-heading">
|
|
Nights
|
|
<label class="night-global-control">
|
|
<span class="sr-only">Set nights for all visible rows</span>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
step="1"
|
|
value="1"
|
|
inputmode="numeric"
|
|
data-nights-all
|
|
aria-label="Set number of nights for all visible rows"
|
|
/>
|
|
</label>
|
|
</span>
|
|
</th>
|
|
<th scope="col">
|
|
<span class="column-heading">
|
|
Single Occupancy
|
|
<button type="button" data-toggle-column="single" aria-label="Toggle single occupancy for all visible rows">
|
|
Toggle all
|
|
</button>
|
|
</span>
|
|
</th>
|
|
<th scope="col">
|
|
<span class="column-heading">
|
|
Double Occupancy
|
|
<button type="button" data-toggle-column="double" aria-label="Toggle double occupancy for all visible rows">
|
|
Toggle all
|
|
</button>
|
|
</span>
|
|
</th>
|
|
<th scope="col">
|
|
<span class="column-heading">
|
|
Lunch
|
|
<button type="button" data-toggle-column="lunch" aria-label="Toggle lunch for all visible rows">
|
|
Toggle all
|
|
</button>
|
|
</span>
|
|
</th>
|
|
<th scope="col">
|
|
<span class="column-heading">
|
|
Dinner
|
|
<button type="button" data-toggle-column="dinner" aria-label="Toggle dinner for all visible rows">
|
|
Toggle all
|
|
</button>
|
|
</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
rows.map((row) => (
|
|
<tr data-estimate-row data-row-index={row}>
|
|
<td>
|
|
<label class="sr-only" for={`person-${row}`}>Person {row} name</label>
|
|
<input
|
|
id={`person-${row}`}
|
|
class="name-input"
|
|
type="text"
|
|
placeholder={`Person ${row}`}
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
data-lpignore="true"
|
|
data-1p-ignore
|
|
/>
|
|
</td>
|
|
<td>
|
|
<label class="sr-only" for={`nights-${row}`}>Number of nights for person {row}</label>
|
|
<input
|
|
id={`nights-${row}`}
|
|
class="night-input"
|
|
type="number"
|
|
min="1"
|
|
step="1"
|
|
value="1"
|
|
inputmode="numeric"
|
|
data-nights
|
|
aria-label={`Number of nights for person ${row}`}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<label class="check-cell">
|
|
<input
|
|
type="checkbox"
|
|
value="single"
|
|
data-cost="single"
|
|
data-accommodation
|
|
aria-label={`Single occupancy for person ${row}`}
|
|
/>
|
|
<span>Single</span>
|
|
</label>
|
|
</td>
|
|
<td>
|
|
<label class="check-cell">
|
|
<input
|
|
type="checkbox"
|
|
value="double"
|
|
data-cost="double"
|
|
data-accommodation
|
|
aria-label={`Double occupancy for person ${row}`}
|
|
/>
|
|
<span>Double</span>
|
|
</label>
|
|
</td>
|
|
<td>
|
|
<label class="check-cell">
|
|
<input type="checkbox" value="lunch" data-cost="lunch" data-meal aria-label={`Lunch for person ${row}`} />
|
|
<span data-meal-label data-base-label="Lunch">Lunch</span>
|
|
</label>
|
|
</td>
|
|
<td>
|
|
<label class="check-cell">
|
|
<input type="checkbox" value="dinner" data-cost="dinner" data-meal aria-label={`Dinner for person ${row}`} />
|
|
<span data-meal-label data-base-label="Dinner">Dinner</span>
|
|
</label>
|
|
</td>
|
|
</tr>
|
|
))
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="total-bar" aria-live="polite">
|
|
<div>
|
|
<span>Total Cost</span>
|
|
<strong data-total-cost>{currency.format(0)}</strong>
|
|
</div>
|
|
<p data-total-note>For 5 people.</p>
|
|
</div>
|
|
<p class="calculator-disclaimer">
|
|
These numbers are indicative and not binding.
|
|
</p>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
const rateMap = {
|
|
single: 2200 * 1.18,
|
|
double: 2700 * 1.18,
|
|
lunch: 200 * 1.18,
|
|
dinner: 200 * 1.18,
|
|
};
|
|
|
|
const currencyFormatter = new Intl.NumberFormat("en-IN", {
|
|
style: "currency",
|
|
currency: "INR",
|
|
maximumFractionDigits: 0,
|
|
});
|
|
|
|
const peopleInput = document.querySelector<HTMLInputElement>("[data-people-count]");
|
|
const peopleOutput = document.querySelector<HTMLOutputElement>("#people-count-output");
|
|
const rows = Array.from(document.querySelectorAll<HTMLTableRowElement>("[data-estimate-row]"));
|
|
const totalCost = document.querySelector<HTMLElement>("[data-total-cost]");
|
|
const totalNote = document.querySelector<HTMLElement>("[data-total-note]");
|
|
const printButton = document.querySelector<HTMLButtonElement>("[data-print-estimate]");
|
|
const columnToggleButtons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-toggle-column]"));
|
|
const globalNightsInput = document.querySelector<HTMLInputElement>("[data-nights-all]");
|
|
const reportDate = document.querySelector<HTMLElement>("[data-report-date]");
|
|
|
|
function getPeopleCount() {
|
|
return Number(peopleInput?.value ?? 1);
|
|
}
|
|
|
|
function syncRows() {
|
|
const peopleCount = getPeopleCount();
|
|
|
|
rows.forEach((row, index) => {
|
|
const visible = index < peopleCount;
|
|
row.hidden = !visible;
|
|
row.querySelectorAll<HTMLInputElement>("input").forEach((input) => {
|
|
input.disabled = !visible;
|
|
});
|
|
});
|
|
|
|
if (peopleOutput) {
|
|
peopleOutput.value = String(peopleCount);
|
|
peopleOutput.textContent = `N = ${peopleCount}`;
|
|
}
|
|
if (totalNote) totalNote.textContent = `For ${peopleCount} ${peopleCount === 1 ? "person" : "people"}.`;
|
|
}
|
|
|
|
function getVisibleRows() {
|
|
return rows.filter((row) => !row.hidden);
|
|
}
|
|
|
|
function normalizeNights(value: string | number | undefined) {
|
|
const nights = Math.floor(Number(value));
|
|
return Number.isFinite(nights) && nights > 0 ? nights : 1;
|
|
}
|
|
|
|
function getNights(row: HTMLTableRowElement) {
|
|
return normalizeNights(row.querySelector<HTMLInputElement>("[data-nights]")?.value);
|
|
}
|
|
|
|
function syncGlobalNights() {
|
|
if (!globalNightsInput) return;
|
|
|
|
const visibleNightValues = getVisibleRows().map(getNights);
|
|
const firstValue = visibleNightValues[0] ?? 1;
|
|
const sameValue = visibleNightValues.every((value) => value === firstValue);
|
|
globalNightsInput.value = sameValue ? String(firstValue) : "";
|
|
}
|
|
|
|
function getColumnInputs(cost: keyof typeof rateMap) {
|
|
return getVisibleRows()
|
|
.map((row) => row.querySelector<HTMLInputElement>(`input[data-cost='${cost}']`))
|
|
.filter((input): input is HTMLInputElement => Boolean(input));
|
|
}
|
|
|
|
function syncColumnToggles() {
|
|
columnToggleButtons.forEach((button) => {
|
|
const cost = button.dataset.toggleColumn as keyof typeof rateMap | undefined;
|
|
if (!cost) return;
|
|
|
|
const inputs = getColumnInputs(cost);
|
|
const allChecked = inputs.length > 0 && inputs.every((input) => input.checked);
|
|
button.setAttribute("aria-pressed", allChecked ? "true" : "false");
|
|
});
|
|
}
|
|
|
|
function getMealMultiplier(row: HTMLTableRowElement) {
|
|
return row.querySelector<HTMLInputElement>("input[data-cost='double']")?.checked ? 2 : 1;
|
|
}
|
|
|
|
function syncPrintValues() {
|
|
rows.forEach((row) => {
|
|
row.querySelectorAll<HTMLLabelElement>(".check-cell").forEach((label) => {
|
|
const input = label.querySelector<HTMLInputElement>("input[type='checkbox']");
|
|
const isDoubledMeal = input?.checked && ["lunch", "dinner"].includes(input.dataset.cost ?? "") && getMealMultiplier(row) === 2;
|
|
label.dataset.printValue = input?.checked ? (isDoubledMeal ? "Yes (2)" : "Yes") : "No";
|
|
});
|
|
});
|
|
}
|
|
|
|
function syncMealLabels() {
|
|
rows.forEach((row) => {
|
|
const mealMultiplier = getMealMultiplier(row);
|
|
const rowIndex = row.dataset.rowIndex ?? "";
|
|
|
|
row.querySelectorAll<HTMLElement>("[data-meal-label]").forEach((label) => {
|
|
const baseLabel = label.dataset.baseLabel ?? label.textContent ?? "";
|
|
label.textContent = mealMultiplier === 2 ? `${baseLabel} x2` : baseLabel;
|
|
});
|
|
|
|
row.querySelectorAll<HTMLInputElement>("input[data-meal]").forEach((input) => {
|
|
const baseLabel = input.dataset.cost === "dinner" ? "Dinner" : "Lunch";
|
|
input.setAttribute(
|
|
"aria-label",
|
|
`${baseLabel}${mealMultiplier === 2 ? " for two people" : ""} for person ${rowIndex}`,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
function syncReportDate() {
|
|
if (!reportDate) return;
|
|
|
|
reportDate.textContent = new Intl.DateTimeFormat("en-IN", {
|
|
dateStyle: "medium",
|
|
}).format(new Date());
|
|
}
|
|
|
|
function updateTotal() {
|
|
let total = 0;
|
|
|
|
rows.forEach((row) => {
|
|
if (row.hidden) return;
|
|
|
|
const mealMultiplier = getMealMultiplier(row);
|
|
const nights = getNights(row);
|
|
|
|
row.querySelectorAll<HTMLInputElement>("input[type='checkbox']:checked").forEach((input) => {
|
|
const cost = input.dataset.cost as keyof typeof rateMap | undefined;
|
|
if (!cost) return;
|
|
|
|
const multiplier = cost === "lunch" || cost === "dinner" ? mealMultiplier : nights;
|
|
total += rateMap[cost] * multiplier;
|
|
});
|
|
});
|
|
|
|
if (totalCost) totalCost.textContent = currencyFormatter.format(total);
|
|
syncColumnToggles();
|
|
syncMealLabels();
|
|
syncPrintValues();
|
|
}
|
|
|
|
peopleInput?.addEventListener("input", () => {
|
|
syncRows();
|
|
syncGlobalNights();
|
|
updateTotal();
|
|
});
|
|
|
|
rows.forEach((row) => {
|
|
row.addEventListener("change", (event) => {
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLInputElement)) return;
|
|
|
|
if (target.matches("[data-accommodation]") && target.checked) {
|
|
row.querySelectorAll<HTMLInputElement>("[data-accommodation]").forEach((input) => {
|
|
if (input !== target) input.checked = false;
|
|
});
|
|
}
|
|
|
|
updateTotal();
|
|
});
|
|
|
|
row.addEventListener("input", (event) => {
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLInputElement) || !target.matches("[data-nights]")) return;
|
|
|
|
syncGlobalNights();
|
|
updateTotal();
|
|
});
|
|
});
|
|
|
|
globalNightsInput?.addEventListener("input", () => {
|
|
const nights = normalizeNights(globalNightsInput.value);
|
|
|
|
getVisibleRows().forEach((row) => {
|
|
const input = row.querySelector<HTMLInputElement>("[data-nights]");
|
|
if (input) input.value = String(nights);
|
|
});
|
|
|
|
syncGlobalNights();
|
|
updateTotal();
|
|
});
|
|
|
|
columnToggleButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const cost = button.dataset.toggleColumn as keyof typeof rateMap | undefined;
|
|
if (!cost) return;
|
|
|
|
const inputs = getColumnInputs(cost);
|
|
const shouldCheck = !inputs.every((input) => input.checked);
|
|
|
|
inputs.forEach((input) => {
|
|
const row = input.closest<HTMLTableRowElement>("[data-estimate-row]");
|
|
|
|
if (shouldCheck && (cost === "single" || cost === "double")) {
|
|
row?.querySelectorAll<HTMLInputElement>("[data-accommodation]").forEach((accommodationInput) => {
|
|
if (accommodationInput !== input) accommodationInput.checked = false;
|
|
});
|
|
}
|
|
|
|
input.checked = shouldCheck;
|
|
});
|
|
|
|
updateTotal();
|
|
});
|
|
});
|
|
|
|
printButton?.addEventListener("click", () => {
|
|
syncPrintValues();
|
|
syncReportDate();
|
|
window.print();
|
|
});
|
|
|
|
syncRows();
|
|
syncGlobalNights();
|
|
syncReportDate();
|
|
updateTotal();
|
|
</script>
|
|
|
|
<style is:global>
|
|
.calculator-page {
|
|
width: min(1180px, calc(100% - 2rem));
|
|
margin: 0 auto;
|
|
padding: 3rem 0 4rem;
|
|
}
|
|
|
|
.calculator-heading {
|
|
display: flex;
|
|
align-items: end;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
margin-bottom: 1.4rem;
|
|
}
|
|
|
|
.calculator-heading h1 {
|
|
font-size: clamp(2.2rem, 5vw, 3.4rem);
|
|
}
|
|
|
|
.print-only {
|
|
display: none;
|
|
}
|
|
|
|
.calculator-shell {
|
|
display: grid;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.calculator-panel {
|
|
border-radius: 8px;
|
|
background: color-mix(in oklch, var(--card) 96%, white);
|
|
box-shadow: var(--shadow-soft);
|
|
padding: 1.1rem;
|
|
}
|
|
|
|
.people-control,
|
|
.table-head {
|
|
display: flex;
|
|
align-items: start;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.people-control h2,
|
|
.table-head h2 {
|
|
font-size: 1.55rem;
|
|
}
|
|
|
|
.people-control p,
|
|
.table-head p,
|
|
.total-bar p {
|
|
color: var(--muted);
|
|
font-size: 0.95rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.people-count {
|
|
min-width: 4.2rem;
|
|
border-radius: 8px;
|
|
background: color-mix(in oklch, var(--secondary) 38%, white);
|
|
color: var(--foreground);
|
|
font-family: var(--font-heading);
|
|
font-size: 2rem;
|
|
line-height: 1;
|
|
padding: 0.62rem 0.75rem;
|
|
text-align: center;
|
|
font-variant-numeric: tabular-nums;
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--secondary) 58%, var(--border));
|
|
}
|
|
|
|
.slider-row {
|
|
display: grid;
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
gap: 0.85rem;
|
|
color: var(--muted);
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.slider-row input {
|
|
width: 100%;
|
|
min-height: 44px;
|
|
accent-color: var(--primary);
|
|
}
|
|
|
|
.rate-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 0.65rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.rate-grid div {
|
|
border-radius: 8px;
|
|
background: color-mix(in oklch, var(--muted-2) 52%, white);
|
|
padding: 0.72rem 0.8rem;
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--border) 70%, transparent);
|
|
}
|
|
|
|
.rate-grid dt {
|
|
color: var(--muted);
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.rate-grid dd {
|
|
margin: 0.25rem 0 0;
|
|
color: var(--foreground);
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.calculator-table-wrap {
|
|
overflow-x: auto;
|
|
border-radius: 8px;
|
|
box-shadow: 0 0 0 1px var(--border);
|
|
}
|
|
|
|
.calculator-table {
|
|
min-width: 860px;
|
|
background: var(--card);
|
|
}
|
|
|
|
.calculator-table th {
|
|
background: color-mix(in oklch, var(--muted-2) 62%, white);
|
|
color: color-mix(in oklch, var(--foreground) 65%, var(--muted));
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.column-heading {
|
|
display: grid;
|
|
gap: 0.36rem;
|
|
align-items: start;
|
|
}
|
|
|
|
.column-heading button {
|
|
width: fit-content;
|
|
min-height: 32px;
|
|
border: 0;
|
|
border-radius: 999px;
|
|
background: var(--card);
|
|
color: color-mix(in oklch, var(--foreground) 70%, var(--muted));
|
|
cursor: pointer;
|
|
font: inherit;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
padding: 0.35rem 0.58rem;
|
|
box-shadow: 0 0 0 1px var(--border);
|
|
}
|
|
|
|
.column-heading button[aria-pressed="true"] {
|
|
background: color-mix(in oklch, var(--primary) 11%, white);
|
|
color: var(--foreground);
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--primary) 24%, var(--border));
|
|
}
|
|
|
|
.night-global-control {
|
|
display: block;
|
|
width: 5.2rem;
|
|
}
|
|
|
|
.night-global-control input,
|
|
.night-input {
|
|
width: 5.2rem;
|
|
min-height: 38px;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
background: var(--card);
|
|
color: var(--foreground);
|
|
font: inherit;
|
|
font-size: 16px;
|
|
font-variant-numeric: tabular-nums;
|
|
padding: 0.35rem 0.45rem;
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--border) 86%, transparent);
|
|
}
|
|
|
|
.night-input {
|
|
min-height: 44px;
|
|
background: color-mix(in oklch, var(--muted-2) 34%, white);
|
|
}
|
|
|
|
.night-global-control input:focus,
|
|
.night-input:focus {
|
|
outline: 2px solid var(--ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.calculator-table td {
|
|
padding: 0.6rem 0.7rem;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.name-input {
|
|
width: 100%;
|
|
min-height: 44px;
|
|
min-width: 13rem;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
background: color-mix(in oklch, var(--muted-2) 34%, white);
|
|
color: var(--foreground);
|
|
font: inherit;
|
|
font-size: 16px;
|
|
padding: 0.52rem 0.65rem;
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--border) 74%, transparent);
|
|
}
|
|
|
|
.name-input:focus {
|
|
outline: 2px solid var(--ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.check-cell {
|
|
display: inline-flex;
|
|
min-height: 44px;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
cursor: pointer;
|
|
color: color-mix(in oklch, var(--foreground) 78%, var(--muted));
|
|
font-size: 0.92rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.check-cell input {
|
|
width: 1.1rem;
|
|
height: 1.1rem;
|
|
margin: 0;
|
|
accent-color: var(--primary);
|
|
}
|
|
|
|
.total-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
margin-top: 1rem;
|
|
border-radius: 8px;
|
|
background:
|
|
linear-gradient(135deg, rgba(49, 91, 75, 0.1), rgba(240, 214, 141, 0.24)),
|
|
var(--card);
|
|
padding: 1rem;
|
|
box-shadow: 0 0 0 1px color-mix(in oklch, var(--primary) 12%, transparent);
|
|
}
|
|
|
|
.total-bar span {
|
|
display: block;
|
|
color: var(--muted);
|
|
font-size: 0.82rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.total-bar strong {
|
|
display: block;
|
|
margin-top: 0.15rem;
|
|
font-family: var(--font-heading);
|
|
font-size: clamp(2rem, 5vw, 3rem);
|
|
font-weight: 400;
|
|
line-height: 1;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.calculator-disclaimer {
|
|
margin-top: 0.8rem;
|
|
color: var(--muted);
|
|
font-size: 0.92rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
clip-path: inset(50%);
|
|
}
|
|
|
|
@media (max-width: 760px) {
|
|
.calculator-heading,
|
|
.people-control,
|
|
.total-bar {
|
|
align-items: stretch;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.print-button {
|
|
width: 100%;
|
|
}
|
|
|
|
.rate-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.rate-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media print {
|
|
@page {
|
|
margin: 16mm 14mm;
|
|
}
|
|
|
|
.site-header,
|
|
.site-footer,
|
|
.print-button,
|
|
.slider-row {
|
|
display: none !important;
|
|
}
|
|
|
|
.screen-only,
|
|
.screen-title {
|
|
display: none !important;
|
|
}
|
|
|
|
.print-only {
|
|
display: block;
|
|
}
|
|
|
|
body {
|
|
background: #fff !important;
|
|
color: #151812;
|
|
font-size: 11pt;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.calculator-page {
|
|
width: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
.calculator-heading {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
border-bottom: 1px solid #20231d;
|
|
padding-bottom: 0.45rem;
|
|
}
|
|
|
|
.calculator-heading h1 {
|
|
color: #151812;
|
|
font-size: 24pt;
|
|
line-height: 1;
|
|
}
|
|
|
|
.print-report-note {
|
|
margin-top: 0.35rem;
|
|
color: #50554c;
|
|
font-size: 9.5pt;
|
|
}
|
|
|
|
.report-meta {
|
|
margin-top: 0.16rem;
|
|
color: #50554c;
|
|
font-size: 9pt;
|
|
}
|
|
|
|
.calculator-panel {
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
padding: 0.35rem 0;
|
|
}
|
|
|
|
.calculator-shell {
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.people-control,
|
|
.table-head {
|
|
margin-bottom: 0.35rem;
|
|
}
|
|
|
|
.people-control h2,
|
|
.table-head h2 {
|
|
color: #151812;
|
|
font-size: 14pt;
|
|
}
|
|
|
|
.people-control p,
|
|
.table-head p,
|
|
.total-bar p {
|
|
color: #50554c;
|
|
font-size: 9pt;
|
|
}
|
|
|
|
.people-count {
|
|
min-width: 0;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
color: #151812;
|
|
font-family: var(--font-body);
|
|
font-size: 12pt;
|
|
font-weight: 700;
|
|
padding: 0;
|
|
}
|
|
|
|
.rate-grid {
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 0;
|
|
margin-top: 0.45rem;
|
|
border: 1px solid #d6d8d1;
|
|
border-right: 0;
|
|
}
|
|
|
|
.rate-grid div {
|
|
border-radius: 0;
|
|
background: #fff;
|
|
box-shadow: none;
|
|
border-right: 1px solid #d6d8d1;
|
|
padding: 0.35rem 0.45rem;
|
|
}
|
|
|
|
.rate-grid dt {
|
|
color: #50554c;
|
|
font-size: 8pt;
|
|
}
|
|
|
|
.rate-grid dd {
|
|
color: #151812;
|
|
font-size: 10.5pt;
|
|
}
|
|
|
|
.calculator-table-wrap {
|
|
overflow: visible;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.calculator-table {
|
|
min-width: 0;
|
|
border: 1px solid #d6d8d1;
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.calculator-table th,
|
|
.calculator-table td {
|
|
border-bottom: 1px solid #d6d8d1;
|
|
padding: 0.26rem 0.34rem;
|
|
}
|
|
|
|
.calculator-table th {
|
|
background: #f4f2ec;
|
|
color: #30342d;
|
|
font-size: 8pt;
|
|
}
|
|
|
|
.column-heading {
|
|
display: block;
|
|
}
|
|
|
|
.column-heading button {
|
|
display: none;
|
|
}
|
|
|
|
.night-global-control {
|
|
display: none;
|
|
}
|
|
|
|
.check-cell span {
|
|
display: none;
|
|
}
|
|
|
|
.check-cell {
|
|
display: flex;
|
|
min-height: 0;
|
|
justify-content: center;
|
|
color: #151812;
|
|
font-size: 9pt;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.check-cell input {
|
|
display: none;
|
|
}
|
|
|
|
.check-cell::after {
|
|
content: attr(data-print-value);
|
|
}
|
|
|
|
.name-input {
|
|
min-height: 0;
|
|
box-shadow: none;
|
|
background: transparent;
|
|
color: #151812;
|
|
font-size: 9pt;
|
|
padding: 0;
|
|
}
|
|
|
|
.night-input {
|
|
width: 100%;
|
|
min-height: 0;
|
|
box-shadow: none;
|
|
background: transparent;
|
|
color: #151812;
|
|
font-size: 9pt;
|
|
padding: 0;
|
|
}
|
|
|
|
.total-bar {
|
|
display: block;
|
|
margin-top: 0.5rem;
|
|
border-radius: 0;
|
|
background: #f8f6ef;
|
|
box-shadow: none;
|
|
border: 1px solid #d6d8d1;
|
|
padding: 0.45rem 0.55rem;
|
|
text-align: right;
|
|
}
|
|
|
|
.calculator-disclaimer {
|
|
margin-top: 0.35rem;
|
|
color: #50554c;
|
|
font-size: 8.5pt;
|
|
text-align: right;
|
|
}
|
|
|
|
.total-bar span {
|
|
color: #50554c;
|
|
font-size: 8pt;
|
|
}
|
|
|
|
.total-bar strong {
|
|
color: #151812;
|
|
font-size: 20pt;
|
|
}
|
|
|
|
tr {
|
|
break-inside: avoid;
|
|
}
|
|
}
|
|
</style>
|
|
</SiteLayout>
|