Salon — Tip & Commission Split
A trustworthy, editorial tip and commission calculator for a boutique salon. Enter a service total, set gratuity by percent or flat amount, and slide a commission rate to see the stylist commission, gratuity, salon share and take-home resolve instantly in a tidy ledger with a stacked-bar visualization. A pooled-tip mode splits a shared gratuity across the team by worked hours or evenly, with add and remove stylist rows and live per-person payouts. Vanilla HTML, CSS and JS — responsive and keyboard accessible.
MCP
代码
:root {
--gold: #b08d57;
--gold-d: #8c6d3f;
--gold-soft: #efe2cf;
--rose: #c9a78f;
--rose-soft: #f3e6dc;
--ink: #1c1814;
--ink-2: #3d362f;
--muted: #8a7d70;
--cream: #f7f1e8;
--bg: #faf6ef;
--white: #ffffff;
--line: rgba(28, 24, 20, 0.1);
--line-2: rgba(28, 24, 20, 0.18);
--ok: #5f8a6b;
--warn: #c08a3e;
--danger: #b3503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--shadow-sm: 0 1px 2px rgba(28, 24, 20, 0.05), 0 4px 14px rgba(28, 24, 20, 0.05);
--shadow-md: 0 18px 48px -18px rgba(28, 24, 20, 0.32);
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: var(--sans);
line-height: 1.5;
color: var(--ink);
background: var(--bg);
background-image: radial-gradient(900px 480px at 92% -8%, rgba(176, 141, 87, 0.1), transparent 70%),
radial-gradient(720px 420px at -6% 4%, rgba(201, 167, 143, 0.12), transparent 65%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3 {
font-family: var(--serif);
font-weight: 600;
line-height: 1.08;
margin: 0;
color: var(--ink);
}
button {
font-family: inherit;
cursor: pointer;
color: inherit;
}
input {
font-family: inherit;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.shell {
max-width: 1060px;
margin: 0 auto;
padding: clamp(20px, 4vw, 52px) clamp(16px, 4vw, 40px) 72px;
}
/* ───────── Masthead ───────── */
.masthead {
margin-bottom: 30px;
}
.brand {
display: flex;
align-items: center;
gap: 16px;
}
.brand__mark {
display: grid;
place-items: center;
width: 52px;
height: 52px;
flex: none;
border-radius: 50%;
font-family: var(--serif);
font-weight: 700;
font-size: 19px;
letter-spacing: 0.5px;
color: var(--white);
background: linear-gradient(150deg, var(--gold) 0%, var(--gold-d) 100%);
box-shadow: 0 10px 22px -10px rgba(140, 109, 63, 0.7);
}
.eyebrow {
margin: 0 0 2px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--gold-d);
}
.masthead h1 {
font-size: clamp(28px, 5vw, 40px);
font-weight: 600;
}
.masthead__note {
margin: 16px 0 0;
max-width: 46ch;
color: var(--ink-2);
font-size: 15px;
}
/* ───────── Layout ───────── */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 22px;
align-items: start;
}
.panel {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 26px;
box-shadow: var(--shadow-md);
}
.panel--split {
margin-top: 22px;
}
.panel__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
margin-bottom: 22px;
}
.panel__head h2 {
font-size: 24px;
}
.panel__sub {
margin: 4px 0 0;
font-size: 13px;
color: var(--muted);
}
.chip {
flex: none;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
padding: 6px 12px;
border-radius: 999px;
color: var(--gold-d);
background: var(--gold-soft);
}
.chip--ghost {
background: transparent;
border: 1px solid var(--line-2);
color: var(--ink-2);
}
/* ───────── Fields ───────── */
.field {
margin-bottom: 22px;
}
.field:last-child {
margin-bottom: 0;
}
.field__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.field label {
display: block;
margin-bottom: 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.field__row label {
margin-bottom: 0;
}
.money {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 0 14px;
background: var(--cream);
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
}
.money:focus-within {
border-color: var(--gold);
background: var(--white);
box-shadow: 0 0 0 4px rgba(176, 141, 87, 0.16);
}
.money__sym {
font-family: var(--serif);
font-size: 22px;
font-weight: 600;
color: var(--gold-d);
}
.money input {
flex: 1;
width: 100%;
border: 0;
background: transparent;
outline: none;
padding: 14px 0;
font-size: 22px;
font-weight: 600;
color: var(--ink);
}
.hint {
margin: 9px 0 0;
font-size: 12.5px;
color: var(--muted);
}
/* ───────── Segmented control ───────── */
.seg {
display: inline-flex;
padding: 3px;
gap: 2px;
border-radius: 999px;
background: var(--cream);
border: 1px solid var(--line);
}
.seg__btn {
border: 0;
background: transparent;
font-size: 12.5px;
font-weight: 600;
color: var(--muted);
padding: 7px 14px;
border-radius: 999px;
transition: color 0.18s, background 0.18s, box-shadow 0.18s;
}
.seg__btn.is-active {
color: var(--ink);
background: var(--white);
box-shadow: var(--shadow-sm);
}
/* ───────── Tip presets ───────── */
.tip-presets {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 12px;
}
.preset {
border: 1px solid var(--line-2);
background: var(--white);
border-radius: var(--r-sm);
padding: 11px 0;
font-weight: 600;
font-size: 14px;
color: var(--ink-2);
transition: all 0.16s;
}
.preset:hover {
border-color: var(--gold);
color: var(--gold-d);
}
.preset.is-active {
border-color: var(--gold);
background: var(--gold-soft);
color: var(--gold-d);
}
/* ───────── Slider ───────── */
.rate-out {
font-family: var(--serif);
font-size: 22px;
font-weight: 700;
color: var(--gold-d);
}
.slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
border-radius: 999px;
background: linear-gradient(90deg, var(--gold) var(--fill, 45%), var(--gold-soft) var(--fill, 45%));
outline: none;
margin: 4px 0 2px;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--white);
border: 2px solid var(--gold);
box-shadow: 0 4px 12px -3px rgba(140, 109, 63, 0.6);
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--white);
border: 2px solid var(--gold);
box-shadow: 0 4px 12px -3px rgba(140, 109, 63, 0.6);
cursor: pointer;
}
.slider:focus-visible {
box-shadow: 0 0 0 4px rgba(176, 141, 87, 0.2);
}
/* ───────── Ledger panel ───────── */
.panel--ledger {
background: linear-gradient(180deg, #fffdf9 0%, var(--white) 38%);
}
.bigfig {
display: flex;
flex-direction: column;
gap: 2px;
padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px solid var(--line);
}
.bigfig__label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.bigfig__value {
font-family: var(--serif);
font-size: clamp(40px, 8vw, 52px);
font-weight: 700;
letter-spacing: -0.5px;
color: var(--ink);
}
.bar {
display: flex;
height: 14px;
border-radius: 999px;
overflow: hidden;
background: var(--cream);
box-shadow: inset 0 0 0 1px var(--line);
}
.bar__seg {
height: 100%;
transition: width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.bar__seg--comm {
background: linear-gradient(90deg, var(--gold), var(--gold-d));
}
.bar__seg--tip {
background: var(--rose);
}
.bar__seg--salon {
background: #c8bca9;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 12px 0 22px;
}
.legend__item {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12.5px;
color: var(--ink-2);
}
.dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex: none;
}
.dot--comm {
background: var(--gold);
}
.dot--tip {
background: var(--rose);
}
.dot--salon {
background: #c8bca9;
}
.ledger {
margin: 0 0 22px;
}
.ledger__row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--line);
}
.ledger__row dt {
color: var(--ink-2);
font-size: 14.5px;
}
.ledger__row dd {
margin: 0;
font-weight: 600;
font-variant-numeric: tabular-nums;
font-size: 15px;
}
.ledger__row--muted dt,
.ledger__row--muted dd {
color: var(--muted);
}
.ledger__row--total {
border-bottom: 0;
margin-top: 4px;
padding-top: 16px;
}
.ledger__row--total dt {
font-family: var(--serif);
font-size: 19px;
color: var(--ink);
}
.ledger__row--total dd {
font-family: var(--serif);
font-size: 22px;
font-weight: 700;
color: var(--gold-d);
}
/* ───────── Buttons ───────── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 14px 20px;
border: 1px solid transparent;
border-radius: var(--r-md);
font-size: 14.5px;
font-weight: 600;
letter-spacing: 0.01em;
transition: transform 0.12s, box-shadow 0.18s, background 0.18s;
}
.btn:active {
transform: translateY(1px);
}
.btn--primary {
color: var(--white);
background: linear-gradient(150deg, var(--gold) 0%, var(--gold-d) 100%);
box-shadow: 0 14px 26px -12px rgba(140, 109, 63, 0.75);
}
.btn--primary:hover {
box-shadow: 0 18px 30px -12px rgba(140, 109, 63, 0.85);
}
.btn--ghost {
background: var(--cream);
border-color: var(--line-2);
color: var(--ink-2);
}
.btn--ghost:hover {
border-color: var(--gold);
color: var(--gold-d);
}
/* ───────── Split / stylists ───────── */
.field--pool {
max-width: 280px;
margin-bottom: 22px;
}
.stylists {
list-style: none;
margin: 0 0 16px;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.stylist {
display: grid;
grid-template-columns: 38px 1fr 118px 96px 34px;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--cream);
animation: rowIn 0.28s ease;
}
@keyframes rowIn {
from {
opacity: 0;
transform: translateY(-6px);
}
}
.stylist__avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background: linear-gradient(150deg, var(--rose-soft), var(--rose));
display: grid;
place-items: center;
font-family: var(--serif);
font-weight: 700;
font-size: 15px;
color: var(--white);
}
.stylist__name {
border: 1px solid transparent;
background: transparent;
border-radius: var(--r-sm);
padding: 8px 10px;
font-size: 15px;
font-weight: 500;
color: var(--ink);
outline: none;
min-width: 0;
transition: border-color 0.18s, background 0.18s;
}
.stylist__name:focus {
border-color: var(--gold);
background: var(--white);
}
.stylist__hours {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 0 10px;
background: var(--white);
transition: border-color 0.18s, box-shadow 0.18s;
}
.stylist__hours:focus-within {
border-color: var(--gold);
box-shadow: 0 0 0 3px rgba(176, 141, 87, 0.14);
}
.stylist__hourval {
width: 100%;
border: 0;
background: transparent;
outline: none;
padding: 8px 0;
font-size: 14px;
font-weight: 600;
color: var(--ink);
}
.stylist__unit {
font-size: 12px;
color: var(--muted);
}
.stylist.is-even .stylist__hours {
opacity: 0.4;
pointer-events: none;
}
.stylist__amt {
text-align: right;
font-weight: 700;
font-size: 16px;
font-variant-numeric: tabular-nums;
color: var(--gold-d);
}
.stylist__rm {
width: 30px;
height: 30px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--white);
color: var(--muted);
font-size: 13px;
line-height: 1;
display: grid;
place-items: center;
transition: all 0.16s;
}
.stylist__rm:hover {
border-color: var(--danger);
color: var(--danger);
background: #fbeeeb;
}
/* ───────── Toast ───────── */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--ink);
color: var(--cream);
padding: 12px 20px;
border-radius: 999px;
font-size: 13.5px;
font-weight: 500;
box-shadow: 0 18px 40px -14px rgba(28, 24, 20, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 50;
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ───────── Responsive ───────── */
@media (max-width: 820px) {
.grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.panel {
padding: 20px;
}
.panel__head {
flex-direction: column;
}
.tip-presets {
grid-template-columns: repeat(2, 1fr);
}
.stylist {
grid-template-columns: 36px 1fr 30px;
grid-template-areas:
"av name rm"
"av hours amt";
row-gap: 8px;
}
.stylist__avatar {
grid-area: av;
}
.stylist__name {
grid-area: name;
}
.stylist__hours {
grid-area: hours;
}
.stylist__amt {
grid-area: amt;
align-self: center;
}
.stylist__rm {
grid-area: rm;
justify-self: end;
}
.field--pool {
max-width: 100%;
}
}(function () {
"use strict";
var fmt = function (n) {
if (!isFinite(n)) n = 0;
return "$" + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
var num = function (el) {
var v = parseFloat(el.value);
return isFinite(v) && v >= 0 ? v : 0;
};
var initials = function (name) {
var p = (name || "").trim().split(/\s+/).filter(Boolean);
if (!p.length) return "•";
return (p[0][0] + (p[1] ? p[1][0] : "")).toUpperCase();
};
/* ───────── Toast ───────── */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
/* ════════════ Calculator ════════════ */
var serviceTotal = document.getElementById("serviceTotal");
var tipValue = document.getElementById("tipValue");
var tipSym = document.getElementById("tipSym");
var commissionRate = document.getElementById("commissionRate");
var rateOut = document.getElementById("rateOut");
var presets = document.getElementById("tipPresets");
var tipMode = "pct"; // "pct" | "amt"
function activeTip() {
// returns absolute tip dollars based on mode
var service = num(serviceTotal);
if (tipMode === "pct") {
return service * (num(tipValue) / 100);
}
return num(tipValue);
}
function recalc() {
var service = num(serviceTotal);
var rate = num(commissionRate) / 100;
var tip = activeTip();
var commission = service * rate;
var salon = service - commission;
var take = commission + tip;
document.getElementById("rowService").textContent = fmt(service);
document.getElementById("rowComm").textContent = fmt(commission);
document.getElementById("rowTip").textContent = fmt(tip);
document.getElementById("rowSalon").textContent = fmt(salon);
document.getElementById("rowTake").textContent = fmt(take);
document.getElementById("takeHome").textContent = fmt(take);
// stacked bar — proportion of grand total (service + tip)
var grand = service + tip;
var pc = grand > 0 ? (commission / grand) * 100 : 0;
var pt = grand > 0 ? (tip / grand) * 100 : 0;
var ps = grand > 0 ? (salon / grand) * 100 : 0;
document.getElementById("barComm").style.width = pc + "%";
document.getElementById("barTip").style.width = pt + "%";
document.getElementById("barSalon").style.width = ps + "%";
}
function syncRate() {
var v = num(commissionRate);
rateOut.textContent = v + "%";
commissionRate.style.setProperty("--fill", v + "%");
}
// tip mode toggle
document.querySelectorAll("[data-tipmode]").forEach(function (btn) {
btn.addEventListener("click", function () {
tipMode = btn.getAttribute("data-tipmode");
document.querySelectorAll("[data-tipmode]").forEach(function (b) {
var on = b === btn;
b.classList.toggle("is-active", on);
b.setAttribute("aria-pressed", on ? "true" : "false");
});
if (tipMode === "pct") {
tipSym.textContent = "%";
presets.style.display = "";
if (num(tipValue) > 100) tipValue.value = "20";
} else {
tipSym.textContent = "$";
presets.style.display = "none";
// convert current % into a dollar value for convenience
tipValue.value = activeTipFromPctSnapshot().toFixed(0);
clearPresets();
}
recalc();
});
});
var lastPct = 20;
function activeTipFromPctSnapshot() {
return num(serviceTotal) * (lastPct / 100);
}
function clearPresets() {
presets.querySelectorAll(".preset").forEach(function (p) {
p.classList.remove("is-active");
});
}
// tip presets
presets.querySelectorAll(".preset").forEach(function (p) {
p.addEventListener("click", function () {
clearPresets();
p.classList.add("is-active");
var pct = Math.round(parseFloat(p.getAttribute("data-tip")) * 100);
lastPct = pct;
tipMode = "pct";
document.querySelectorAll("[data-tipmode]").forEach(function (b) {
var on = b.getAttribute("data-tipmode") === "pct";
b.classList.toggle("is-active", on);
b.setAttribute("aria-pressed", on ? "true" : "false");
});
tipSym.textContent = "%";
presets.style.display = "";
tipValue.value = String(pct);
recalc();
});
});
serviceTotal.addEventListener("input", recalc);
tipValue.addEventListener("input", function () {
if (tipMode === "pct") lastPct = num(tipValue);
clearPresets();
recalc();
});
commissionRate.addEventListener("input", function () {
syncRate();
recalc();
});
document.getElementById("settleBtn").addEventListener("click", function () {
toast("Ticket settled — " + document.getElementById("takeHome").textContent + " to Aria Vance.");
});
syncRate();
recalc();
/* ════════════ Split mode ════════════ */
var list = document.getElementById("stylistList");
var tpl = document.getElementById("stylistTpl");
var poolTip = document.getElementById("poolTip");
var splitMode = "hours"; // "hours" | "even"
var seed = [
{ name: "Aria Vance", hours: 6 },
{ name: "Léa Moreau", hours: 4.5 },
{ name: "Noor Haddad", hours: 3 }
];
function rows() {
return Array.prototype.slice.call(list.querySelectorAll(".stylist"));
}
function recalcSplit() {
var pool = num(poolTip);
var r = rows();
var n = r.length;
if (!n) return;
var totalHours = 0;
r.forEach(function (row) {
totalHours += num(row.querySelector(".stylist__hourval"));
});
r.forEach(function (row) {
var amtEl = row.querySelector(".stylist__amt");
var share;
if (splitMode === "even") {
share = pool / n;
} else {
var h = num(row.querySelector(".stylist__hourval"));
share = totalHours > 0 ? pool * (h / totalHours) : 0;
}
amtEl.textContent = fmt(share);
});
}
function makeRow(data) {
var node = tpl.content.firstElementChild.cloneNode(true);
var nameI = node.querySelector(".stylist__name");
var hourI = node.querySelector(".stylist__hourval");
var avatar = node.querySelector(".stylist__avatar");
nameI.value = data.name || "";
hourI.value = data.hours != null ? String(data.hours) : "";
avatar.textContent = initials(data.name);
nameI.addEventListener("input", function () {
avatar.textContent = initials(nameI.value);
});
hourI.addEventListener("input", recalcSplit);
node.querySelector(".stylist__rm").addEventListener("click", function () {
if (rows().length <= 1) {
toast("Keep at least one stylist on the split.");
return;
}
node.remove();
recalcSplit();
});
node.classList.toggle("is-even", splitMode === "even");
list.appendChild(node);
return node;
}
seed.forEach(makeRow);
recalcSplit();
poolTip.addEventListener("input", recalcSplit);
document.querySelectorAll("[data-splitmode]").forEach(function (btn) {
btn.addEventListener("click", function () {
splitMode = btn.getAttribute("data-splitmode");
document.querySelectorAll("[data-splitmode]").forEach(function (b) {
var on = b === btn;
b.classList.toggle("is-active", on);
b.setAttribute("aria-pressed", on ? "true" : "false");
});
rows().forEach(function (row) {
row.classList.toggle("is-even", splitMode === "even");
});
recalcSplit();
});
});
document.getElementById("addStylist").addEventListener("click", function () {
var n = rows().length + 1;
var row = makeRow({ name: "", hours: 3 });
row.querySelector(".stylist__name").placeholder = "Stylist " + n;
row.querySelector(".stylist__name").focus();
recalcSplit();
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tip & Commission Split · Maison Lumière Salon</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="shell">
<header class="masthead">
<div class="brand">
<span class="brand__mark" aria-hidden="true">ML</span>
<div class="brand__text">
<p class="eyebrow">Maison Lumière Salon</p>
<h1>Tip & Commission Split</h1>
</div>
</div>
<p class="masthead__note">
Reconcile a ticket in seconds — commission, gratuity and salon share, settled fairly.
</p>
</header>
<div class="grid">
<!-- ───────────────────────── Calculator ───────────────────────── -->
<section class="panel" aria-labelledby="calc-title">
<div class="panel__head">
<h2 id="calc-title">Service ticket</h2>
<span class="chip" id="ticketRef">Ticket #LM-4821</span>
</div>
<div class="field">
<label for="serviceTotal">Service total</label>
<div class="money">
<span class="money__sym">$</span>
<input
id="serviceTotal"
type="number"
inputmode="decimal"
min="0"
step="1"
value="180"
aria-describedby="serviceHint"
/>
</div>
<p class="hint" id="serviceHint">Balayage & gloss with Aria Vance.</p>
</div>
<div class="field">
<div class="field__row">
<label for="tipValue">Gratuity</label>
<div class="seg" role="group" aria-label="Tip entry mode">
<button type="button" class="seg__btn is-active" data-tipmode="pct" aria-pressed="true">
Percent
</button>
<button type="button" class="seg__btn" data-tipmode="amt" aria-pressed="false">
Amount
</button>
</div>
</div>
<div class="tip-presets" id="tipPresets" aria-label="Quick tip percentages">
<button type="button" class="preset" data-tip="0.18">18%</button>
<button type="button" class="preset is-active" data-tip="0.2">20%</button>
<button type="button" class="preset" data-tip="0.22">22%</button>
<button type="button" class="preset" data-tip="0.25">25%</button>
</div>
<div class="money" id="tipInputWrap">
<span class="money__sym" id="tipSym">%</span>
<input id="tipValue" type="number" inputmode="decimal" min="0" step="1" value="20" />
</div>
</div>
<div class="field">
<div class="field__row">
<label for="commissionRate">Commission rate</label>
<output class="rate-out" id="rateOut">45%</output>
</div>
<input
id="commissionRate"
class="slider"
type="range"
min="0"
max="100"
step="1"
value="45"
aria-describedby="rateHint"
/>
<p class="hint" id="rateHint">
Stylist keeps this share of the service; gratuity is paid out in full.
</p>
</div>
</section>
<!-- ───────────────────────── Breakdown ───────────────────────── -->
<section class="panel panel--ledger" aria-labelledby="ledger-title" aria-live="polite">
<div class="panel__head">
<h2 id="ledger-title">Take-home breakdown</h2>
<span class="chip chip--ghost" id="stylistTag">Aria Vance</span>
</div>
<div class="bigfig">
<span class="bigfig__label">Stylist take-home</span>
<span class="bigfig__value" id="takeHome">$153.00</span>
</div>
<div class="bar" role="img" aria-label="Distribution of the ticket">
<span class="bar__seg bar__seg--comm" id="barComm" style="width: 45%"></span>
<span class="bar__seg bar__seg--tip" id="barTip" style="width: 20%"></span>
<span class="bar__seg bar__seg--salon" id="barSalon" style="width: 35%"></span>
</div>
<div class="legend">
<span class="legend__item"><i class="dot dot--comm"></i>Commission</span>
<span class="legend__item"><i class="dot dot--tip"></i>Gratuity</span>
<span class="legend__item"><i class="dot dot--salon"></i>Salon</span>
</div>
<dl class="ledger">
<div class="ledger__row">
<dt>Service total</dt>
<dd id="rowService">$180.00</dd>
</div>
<div class="ledger__row">
<dt>Stylist commission</dt>
<dd id="rowComm">$81.00</dd>
</div>
<div class="ledger__row">
<dt>Gratuity</dt>
<dd id="rowTip">$36.00</dd>
</div>
<div class="ledger__row ledger__row--muted">
<dt>Salon share</dt>
<dd id="rowSalon">$99.00</dd>
</div>
<div class="ledger__row ledger__row--total">
<dt>Stylist take-home</dt>
<dd id="rowTake">$117.00</dd>
</div>
</dl>
<button type="button" class="btn btn--primary" id="settleBtn">Settle ticket</button>
</section>
</div>
<!-- ───────────────────────── Split mode ───────────────────────── -->
<section class="panel panel--split" aria-labelledby="split-title">
<div class="panel__head">
<div>
<h2 id="split-title">Share a pooled tip</h2>
<p class="panel__sub">Divide today’s gratuity across the team.</p>
</div>
<div class="seg" role="group" aria-label="Split method">
<button type="button" class="seg__btn is-active" data-splitmode="hours" aria-pressed="true">
By hours
</button>
<button type="button" class="seg__btn" data-splitmode="even" aria-pressed="false">
Evenly
</button>
</div>
</div>
<div class="field field--pool">
<label for="poolTip">Pooled gratuity</label>
<div class="money">
<span class="money__sym">$</span>
<input id="poolTip" type="number" inputmode="decimal" min="0" step="1" value="240" />
</div>
</div>
<ul class="stylists" id="stylistList"></ul>
<button type="button" class="btn btn--ghost" id="addStylist">+ Add stylist</button>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<template id="stylistTpl">
<li class="stylist">
<div class="stylist__avatar" aria-hidden="true"></div>
<input class="stylist__name" type="text" aria-label="Stylist name" />
<div class="stylist__hours">
<input class="stylist__hourval" type="number" inputmode="decimal" min="0" step="0.5" aria-label="Hours worked" />
<span class="stylist__unit">hrs</span>
</div>
<span class="stylist__amt">$0.00</span>
<button type="button" class="stylist__rm" aria-label="Remove stylist">✕</button>
</li>
</template>
<script src="script.js"></script>
</body>
</html>Tip & Commission Split
A calm, numeric reconciliation tool for Maison Lumière Salon, built to feel like the kind of screen a front-desk manager actually trusts at close. Enter the service total, choose gratuity as a percentage — with quick 18 / 20 / 22 / 25 presets — or switch to a flat amount, then slide the commission rate. Every change flows straight into a take-home figure set in Cormorant Garamond, a stacked gold-and-rose bar that shows how the ticket divides, and a hairline ledger tallying commission, gratuity, salon share and the stylist’s final take.
The second card handles a pooled gratuity. Divide today’s tip across the team by worked hours or split it evenly, adding or removing stylist rows as the floor changes; each person’s payout recalculates live, avatars derive their initials from the names you type, and the hours fields gracefully dim when an even split makes them moot.
Throughout, gold hairlines, generous whitespace and tabular figures carry the maison aesthetic while keeping the math legible. Controls are keyboard-friendly with aria-pressed toggles and a polite live region, contrast meets WCAG AA, and the layout reflows down to 360px. No frameworks, no build step — three files and it works.