/* Perrunos — the whole interface.
   Colours all come from theme.css; nothing here hard-codes a hue except the
   fixed booking colours, which have to keep meaning the same thing when the
   theme changes. */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  min-height: 100vh;
}

h1,
h2,
h3,
h4 {
  color: var(--text-strong);
  margin: 0 0 0.6rem;
  line-height: 1.25;
}

h1 {
  font-size: 1.5rem;
}
h2 {
  font-size: 1.2rem;
}
h3 {
  font-size: 1.02rem;
}

a {
  color: var(--primary);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

hr {
  border: 0;
  border-top: 1px solid var(--card-stroke);
  margin: 1rem 0;
}

/* ----------------------------- Layout ----------------------------- */

.app {
  display: grid;
  grid-template-columns: 216px 1fr;
  min-height: 100vh;
}

.sidebar {
  background: var(--card);
  border-right: 1px solid var(--card-stroke);
  padding: 0.9rem 0.7rem 2rem;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-weight: 700;
  color: var(--text-strong);
  font-size: 1.1rem;
  padding: 0.2rem 0.5rem 0.9rem;
  letter-spacing: 0.01em;
}
.brand small {
  font-weight: 400;
  font-size: 0.7rem;
  color: var(--text-faint);
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.nav-group {
  margin-top: 0.85rem;
  padding: 0 0.55rem 0.25rem;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-faint);
}

.nav a {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.42rem 0.55rem;
  border-radius: 7px;
  color: var(--text);
  font-size: 0.9rem;
  text-decoration: none;
  white-space: nowrap;
}
.nav a:hover {
  background: var(--widget-hover);
  text-decoration: none;
}
.nav a.on {
  background: var(--primary);
  color: var(--on-primary);
  font-weight: 600;
}
.nav a .ico {
  width: 1.15rem;
  text-align: center;
  flex: none;
}
.nav a .count {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.nav a.on .count {
  color: var(--on-primary);
  opacity: 0.85;
}

.main {
  padding: 1.1rem 1.4rem 4rem;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.topbar h1 {
  margin: 0;
  margin-right: auto;
}

.who {
  font-size: 0.82rem;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* ----------------------------- Cards & grids ----------------------------- */

.card {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 11px;
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
}
.card > :last-child {
  margin-bottom: 0;
}

.card-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.7rem;
  flex-wrap: wrap;
}
.card-head h2,
.card-head h3 {
  margin: 0;
  margin-right: auto;
}
/* A heading row that carries a photograph keeps it its own size. */
.card-head .thumb {
  flex: none;
}

.section-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-faint);
  margin: 0 0 0.45rem;
  font-weight: 600;
}

.cols {
  display: grid;
  gap: 1rem;
  align-items: start;
}
.cols-2 {
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.cols-3 {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.split {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(300px, 1fr);
  gap: 1rem;
  align-items: start;
}

/* Stat tiles */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 0.7rem;
  margin-bottom: 1rem;
}
.stat {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 10px;
  padding: 0.65rem 0.8rem;
}
.stat .k {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-faint);
  margin-bottom: 0.2rem;
}
.stat .v {
  font-size: 1.32rem;
  font-weight: 650;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.stat .v.pos {
  color: var(--success);
}
.stat .v.neg {
  color: var(--error);
}
.stat .sub {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.15rem;
}

/* ----------------------------- Controls ----------------------------- */

button,
.btn {
  font: inherit;
  font-size: 0.87rem;
  border-radius: 8px;
  border: 1px solid var(--widget-stroke);
  background: var(--widget);
  color: var(--text);
  padding: 0.42rem 0.78rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
}
button:hover,
.btn:hover {
  background: var(--widget-hover);
  text-decoration: none;
}
button:active,
.btn:active {
  background: var(--widget-active);
}
button:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  font-weight: 600;
}
.btn-primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}
.btn-primary:active {
  background: var(--primary-active);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.btn-ghost:hover {
  background: var(--widget-hover);
  color: var(--text);
}

.btn-danger {
  color: var(--error);
  border-color: color-mix(in srgb, var(--error) 45%, transparent);
  background: transparent;
}
.btn-danger:hover {
  background: color-mix(in srgb, var(--error) 14%, transparent);
}

.btn-sm {
  font-size: 0.78rem;
  padding: 0.24rem 0.5rem;
}

input,
select,
textarea {
  font: inherit;
  font-size: 0.88rem;
  color: var(--text);
  background: var(--field);
  border: 1px solid var(--widget-stroke);
  border-radius: 8px;
  padding: 0.42rem 0.55rem;
  width: 100%;
  min-width: 0;
}
input:focus,
select:focus,
textarea:focus {
  outline: 2px solid var(--primary);
  outline-offset: -1px;
}
input[type="checkbox"],
input[type="radio"] {
  width: auto;
  accent-color: var(--primary);
}
textarea {
  resize: vertical;
  min-height: 4.4rem;
  line-height: 1.45;
}
select {
  cursor: pointer;
}

label {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-bottom: 0.18rem;
}
.check {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.86rem;
  color: var(--text);
  margin: 0;
  cursor: pointer;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.6rem 0.7rem;
  align-items: end;
}
.form-grid .wide {
  grid-column: 1 / -1;
}
.field {
  min-width: 0;
}

.row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}
.row-tight {
  gap: 0.3rem;
}
.spacer {
  margin-left: auto;
}

/* Segmented control: the egui app's radio strips. */
.seg {
  display: inline-flex;
  border: 1px solid var(--widget-stroke);
  border-radius: 8px;
  overflow: hidden;
  background: var(--widget);
}
.seg a,
.seg button {
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-muted);
  padding: 0.34rem 0.66rem;
  font-size: 0.82rem;
}
.seg a:hover,
.seg button:hover {
  background: var(--widget-hover);
  color: var(--text);
  text-decoration: none;
}
.seg a.on,
.seg button.on {
  background: var(--primary);
  color: var(--on-primary);
  font-weight: 600;
}

/* ----------------------------- Chips ----------------------------- */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-items: center;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  padding: 0.16rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--primary) 16%, transparent);
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--primary) 32%, transparent);
  white-space: nowrap;
}
.chip a,
.chip button {
  all: unset;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.9em;
  line-height: 1;
}
.chip a:hover,
.chip button:hover {
  color: var(--error);
}
.chip.facet {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent) 36%, transparent);
}
.chip.tag {
  background: var(--widget);
  border-color: var(--widget-stroke);
  color: var(--text-muted);
}
/* A tag on a record is a link to everything else wearing it. It has to look
   like it does something, or nobody finds out that it does. */
a.chip {
  text-decoration: none;
  cursor: pointer;
}
a.chip:hover {
  border-color: var(--primary);
  color: var(--text-strong);
}

/* The filters actually in force, on their own line under the controls, each
   with an ✕. A filter you cannot see is one you forget you set, and then the
   list looks like it is missing people. */
.active-filters {
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid color-mix(in srgb, var(--card-stroke) 55%, transparent);
  /* One chip tall, always — the row is rendered whether or not anything is in
     it, so turning a filter on does not push the table down the page. Sized to
     the chip rather than to the text, because the chip is the taller of the
     two and it is what decides the height when there is one. */
  min-height: 1.45rem;
}

/* The tags already in use, offered under a tag box. */
.tag-picker {
  margin-top: 0.4rem;
  align-items: center;
}
/* Beside the box rather than below it — the client form puts them on one line. */
.row > .tag-picker {
  margin-top: 0;
}
.tag-picker .chip {
  cursor: pointer;
  font-family: inherit;
}
/* A chip that is itself a button — the "+ Nervioso" ones that put a tag on a
   record. They submit the form, so this works with scripting off. */
button.chip {
  cursor: pointer;
  font-family: inherit;
  font-size: 0.75rem;
}
button.chip:hover {
  border-color: var(--primary);
  color: var(--text-strong);
}
/* Already on this record — click to take it off again. */
.tag-picker .chip.on {
  background: color-mix(in srgb, var(--primary) 20%, transparent);
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  color: var(--text-strong);
}

.badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.12rem 0.42rem;
  border-radius: 5px;
  background: var(--widget);
  color: var(--text-muted);
  border: 1px solid var(--widget-stroke);
  white-space: nowrap;
}
.badge.ok {
  color: var(--success);
  border-color: color-mix(in srgb, var(--success) 40%, transparent);
  background: color-mix(in srgb, var(--success) 12%, transparent);
}
.badge.warn {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.badge.bad {
  color: var(--error);
  border-color: color-mix(in srgb, var(--error) 40%, transparent);
  background: color-mix(in srgb, var(--error) 12%, transparent);
}
.badge.info {
  color: var(--info);
  border-color: color-mix(in srgb, var(--info) 40%, transparent);
  background: color-mix(in srgb, var(--info) 12%, transparent);
}
/* A groom the shop owes. The gold is fixed rather than taken from the palette,
   for the same reason the calendar's is: a full loyalty card has to go on
   looking like one after the theme is changed. */
.badge.free {
  color: #e0a200;
  border-color: color-mix(in srgb, #e0a200 45%, transparent);
  background: color-mix(in srgb, #e0a200 14%, transparent);
}
/* A name that opens a WhatsApp chat — on the booking card, the client page and
   the receipt. It inherits the surrounding colour (a heading stays a heading)
   and only underlines on hover, so it reads as the name it is with the 💬 in
   front doing the signalling. Neutral in print. */
.wa-name {
  color: inherit;
  text-decoration: none;
}
.wa-name:hover {
  text-decoration: underline;
}
@media print {
  .wa-name {
    color: inherit;
    text-decoration: none;
  }
}

/* A booking held for someone not registered yet. Fixed yellow, like the
   calendar block it labels, so the colour means the same thing everywhere. The
   text is a darker shade of the same hue so it reads on a pale tint. */
.badge.unreg {
  color: #9a7b00;
  border-color: color-mix(in srgb, #f2c230 55%, transparent);
  background: color-mix(in srgb, #f2c230 18%, transparent);
}

/* A field's value where there is no box to type it in — the locked half of a
   page that has an edit mode. Sized and spaced like the input it replaces, so
   turning editing on does not make the card jump. */
.ro {
  margin: 0;
  padding: 0.4rem 0;
  min-height: 1.2rem;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* A pet row on the add-client form. Ruled off from the one above so three
   blank rows read as three animals rather than nine loose boxes. */
.pet-row + .pet-row {
  border-top: 1px solid var(--widget-stroke);
  margin-top: 0.6rem;
  padding-top: 0.6rem;
}

/* ----------------------------- Loyalty card ----------------------------- */

/* One mark per groom towards the free one. Read across a counter with a dog on
   it, so the filled ones have to be obvious at a glance and from an angle. */
.pips {
  display: flex;
  gap: 0.32rem;
  align-items: center;
}
.pips .pip {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  border: 2px solid var(--widget-stroke);
  background: transparent;
}
.pips .pip.on {
  background: #e0a200;
  border-color: #e0a200;
}

/* ----------------------------- Tables ----------------------------- */

.table-wrap {
  overflow-x: auto;
  margin: 0 -0.25rem;
  padding: 0 0.25rem;
}

/* The lists are what the shop reads all day — clients, pets, sales — often at
   arm's length across a counter. Bigger than the rest of the interface on
   purpose. */
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}
th {
  text-align: left;
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-faint);
  font-weight: 600;
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--card-stroke);
  white-space: nowrap;
}
th a {
  color: inherit;
}
th a:hover {
  color: var(--text);
}
td {
  padding: 0.52rem 0.5rem;
  border-bottom: 1px solid color-mix(in srgb, var(--card-stroke) 60%, transparent);
  vertical-align: middle;
}
/* The secondary lines in a row — a phone number, a species, a date — were set
   against the page's size rather than the table's, so they stayed small while
   everything around them grew. */
table .small {
  font-size: 0.88rem;
}
table .chip {
  font-size: 0.8rem;
}
tbody tr:hover td {
  background: color-mix(in srgb, var(--widget-hover) 45%, transparent);
}
td.num,
th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
td.actions {
  text-align: right;
  white-space: nowrap;
}
/* A column that takes only the width it needs, so the slack in the table goes
   to the columns with real text in them. Without this the pets column swallowed
   it and pushed the tags out into the middle of the row. */
th.tight,
td.tight {
  width: 1%;
  white-space: nowrap;
}
/* Chips in a tight column stay on one line. The column sizes itself to its
   content, and a wrapping flex row's smallest size is one chip — which stacked
   them into a vertical pile one chip wide. */
td.tight .chips {
  flex-wrap: nowrap;
}
/* In a column that takes the slack there is room to wrap properly, so a client
   with a lot of tags gets a second line rather than a column the width of the
   table. */
td .chips {
  row-gap: 0.25rem;
}

/* The form's default submit button: what Enter in a text box activates.
   Hidden with CSS, not the `hidden` attribute — an unrendered button is not a
   candidate for the default, and then the first *visible* submit button gets
   the keypress along with its name and value. */
.default-submit {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
}
tr.dim td {
  opacity: 0.55;
}
/* A row that opens its own record when clicked. The links inside it still win,
   so they keep the pointer they would have had anyway. */
tr[data-row-href],
tr[data-row-dialog] {
  cursor: pointer;
}
/* A phone number that goes to WhatsApp rather than to the row's own record. */
a.wa {
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
a.wa:hover {
  color: var(--primary);
  text-decoration: underline;
}

.muted {
  color: var(--text-muted);
}
.faint {
  color: var(--text-faint);
}
.small {
  font-size: 0.8rem;
}
.strong {
  color: var(--text-strong);
  font-weight: 600;
}
.pos {
  color: var(--success);
}
.neg {
  color: var(--error);
}
.nowrap {
  white-space: nowrap;
}
.right {
  text-align: right;
}
.num {
  font-variant-numeric: tabular-nums;
}

.empty {
  color: var(--text-faint);
  font-size: 0.88rem;
  padding: 1.1rem 0.2rem;
  text-align: center;
}

/* ----------------------------- Photos ----------------------------- */

.thumb {
  border-radius: 9px;
  object-fit: cover;
  background: var(--widget);
  border: 1px solid var(--card-stroke);
  display: block;
  aspect-ratio: 1;
}
.thumb-sm {
  width: 36px;
  height: 36px;
  border-radius: 7px;
}
.thumb-md {
  width: 64px;
  height: 64px;
}
.thumb-lg {
  width: 160px;
  height: 160px;
}

.no-photo {
  display: grid;
  place-items: center;
  color: var(--text-faint);
  font-size: 1.1rem;
  background: var(--widget);
  border: 1px dashed var(--widget-stroke);
}

.gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.gallery figure {
  margin: 0;
  position: relative;
}
.gallery .thumb {
  /* The stored square is 900px, so this stays sharp on a retina screen
     (360 x 2 = 720). Going past 450 would start to soften unless the stored
     size goes up with it. */
  width: 360px;
  height: 360px;
}
.gallery .rm {
  position: absolute;
  top: 5px;
  right: 5px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-radius: 6px;
  padding: 0.05rem 0.3rem;
  font-size: 0.72rem;
  border: 1px solid var(--card-stroke);
  cursor: pointer;
  color: var(--text-muted);
}
.gallery .rm:hover {
  color: var(--error);
}
.gallery figure .primary-mark {
  position: absolute;
  bottom: 5px;
  left: 5px;
  font-size: 0.68rem;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-radius: 5px;
  padding: 0.05rem 0.3rem;
  color: var(--text-muted);
}

/* ----------------------------- Point of sale ----------------------------- */

.pos-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(320px, 1fr);
  gap: 1rem;
  align-items: start;
}

.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(146px, 1fr));
  gap: 0.55rem;
  max-height: 62vh;
  overflow-y: auto;
  padding-right: 0.2rem;
}

.pick {
  border: 1px solid var(--card-stroke);
  background: var(--card);
  border-radius: 10px;
  padding: 0.5rem;
  cursor: pointer;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
  color: var(--text);
  align-items: stretch;
}
.pick:hover {
  border-color: var(--primary);
  background: var(--widget-hover);
}
.pick .thumb {
  width: 100%;
  height: auto;
}
.pick .nm {
  font-weight: 600;
  font-size: 0.83rem;
  line-height: 1.25;
  color: var(--text-strong);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.pick .pr {
  font-size: 0.82rem;
  color: var(--primary);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.pick .st {
  font-size: 0.72rem;
  color: var(--text-faint);
}
.pick.out {
  opacity: 0.45;
}

.cart-line {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: 0.45rem;
  align-items: center;
  padding: 0.35rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--card-stroke) 55%, transparent);
}
.cart-line .qty {
  width: 4.2rem;
  text-align: center;
}
/* The grooming price, typed in at the till. Sized for a four-figure amount so
   the number never scrolls inside its own box mid-sale. */
.cart-line .price {
  width: 5.6rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Still empty, and the sale cannot go through until it isn't. Marked in the
   same colour the app uses for anything else that is waiting on you. */
.cart-line .price.wanted {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.warn-text {
  color: var(--accent);
  margin-top: 0.4rem;
}
.total-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--text-strong);
  padding-top: 0.6rem;
  font-variant-numeric: tabular-nums;
}

/* Search results dropdown (client / item lookups). */
.lookup {
  position: relative;
}
.lookup-results {
  position: absolute;
  z-index: 40;
  left: 0;
  right: 0;
  top: calc(100% + 2px);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 9px;
  box-shadow: 0 10px 26px rgb(0 0 0 / 0.22);
  max-height: 17rem;
  overflow-y: auto;
}
.lookup-results:empty {
  display: none;
}
/* One suggestion: what kind of thing it is, then what it is called. */
a.suggest {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--card-stroke) 45%, transparent);
}
a.suggest:last-child {
  border-bottom: 0;
}
a.suggest:hover {
  background: var(--widget-hover);
  text-decoration: none;
}
a.suggest > .faint:first-child {
  min-width: 4.5rem;
  flex: none;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.66rem;
}
.lookup-results button {
  display: flex;
  width: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  text-align: left;
  padding: 0.4rem 0.6rem;
  gap: 0.45rem;
  align-items: center;
}
.lookup-results button:hover {
  background: var(--widget-hover);
}

/* ----------------------------- Calendar ----------------------------- */

.cal-month {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--card-stroke);
  border: 1px solid var(--card-stroke);
  border-radius: 10px;
  overflow: hidden;
}
.cal-month .dow {
  background: var(--card);
  padding: 0.35rem 0.5rem;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-faint);
  font-weight: 600;
  text-align: center;
}
.cal-day {
  background: var(--card);
  min-height: 92px;
  padding: 0.3rem 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: inherit;
}
.cal-day:hover {
  background: var(--widget-hover);
  text-decoration: none;
}
.cal-day.other {
  opacity: 0.4;
}
.cal-day.today .dnum {
  background: var(--primary);
  color: var(--on-primary);
  border-radius: 999px;
  padding: 0 0.38rem;
}
.cal-day.sel {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}
.cal-day .dnum {
  font-size: 0.76rem;
  color: var(--text-muted);
  font-weight: 600;
  align-self: flex-start;
}
.cal-ev {
  font-size: 0.7rem;
  border-radius: 4px;
  padding: 0.06rem 0.28rem;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.cal-more {
  font-size: 0.66rem;
  color: var(--text-faint);
}

/* Week grid */
.week {
  position: relative;
  display: grid;
  grid-template-columns: 54px repeat(7, minmax(0, 1fr));
  border: 1px solid var(--card-stroke);
  border-radius: 10px;
  overflow: hidden;
  background: var(--card);
  user-select: none;
}
.week-head {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--card);
  border-bottom: 1px solid var(--card-stroke);
  padding: 0.3rem 0.4rem;
  text-align: center;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.week-head.today {
  color: var(--primary);
  font-weight: 700;
}
.week-gutter {
  border-right: 1px solid var(--card-stroke);
  position: relative;
}
.week-gutter .hr {
  position: absolute;
  right: 0.3rem;
  font-size: 0.68rem;
  color: var(--text-faint);
  transform: translateY(-50%);
  font-variant-numeric: tabular-nums;
}
.week-col {
  position: relative;
  border-right: 1px solid color-mix(in srgb, var(--card-stroke) 70%, transparent);
}
.week-col:last-child {
  border-right: 0;
}
.week-col.today {
  background: color-mix(in srgb, var(--primary) 5%, transparent);
}
.week-hourline {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid color-mix(in srgb, var(--card-stroke) 65%, transparent);
}
.week-halfline {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px dotted color-mix(in srgb, var(--card-stroke) 45%, transparent);
}
.week-block {
  position: absolute;
  border-radius: 6px;
  color: #fff;
  font-size: 0.7rem;
  padding: 0.12rem 0.3rem;
  overflow: hidden;
  cursor: grab;
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.22);
  line-height: 1.2;
}
.week-block .bt {
  font-variant-numeric: tabular-nums;
  opacity: 0.9;
  font-size: 0.66rem;
}
.week-block .bn {
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.week-block.dragging {
  cursor: grabbing;
  opacity: 0.85;
  z-index: 10;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.35);
}
.week-block .grip {
  position: absolute;
  left: 0;
  right: 0;
  height: 7px;
  cursor: ns-resize;
}
.week-block .grip.top {
  top: 0;
}
.week-block .grip.bot {
  bottom: 0;
}
.week-now {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 2px solid var(--error);
  z-index: 5;
  pointer-events: none;
}

/* ----------------------------- Dialogs & toasts ----------------------------- */

dialog {
  border: 1px solid var(--card-stroke);
  background: var(--card);
  color: var(--text);
  border-radius: 12px;
  padding: 1.1rem 1.2rem;
  max-width: min(640px, 94vw);
  width: 100%;
  box-shadow: 0 18px 50px rgb(0 0 0 / 0.35);
}
dialog::backdrop {
  background: rgb(0 0 0 / 0.45);
}
dialog h2 {
  margin-top: 0;
}
.dialog-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 1rem;
}

#toasts {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 100;
  pointer-events: none;
}
.toast {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-left: 3px solid var(--primary);
  border-radius: 9px;
  padding: 0.55rem 0.85rem;
  font-size: 0.86rem;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.28);
  animation: toast-in 0.16s ease-out;
  max-width: 22rem;
}
.toast.err {
  border-left-color: var(--error);
  color: var(--error);
}
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

.banner {
  background: color-mix(in srgb, var(--error) 12%, var(--card));
  border: 1px solid color-mix(in srgb, var(--error) 45%, transparent);
  color: var(--error);
  border-radius: 9px;
  padding: 0.6rem 0.85rem;
  margin-bottom: 1rem;
  font-size: 0.87rem;
}
.banner.warn {
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
}

/* Over, and never paid for. The whole block goes red — the fill comes from the
   server so it survives whatever colour the booking was given — and the text
   stays white, which it already was. Loud on purpose: this is work the shop did
   and has not been paid for. */
.week-block.overdue {
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);
}
.cal-ev.overdue {
  font-weight: 600;
}

/* A booking with no registered client: filled yellow (set inline, from
   UNREGISTERED_YELLOW) with dark text, since white on yellow cannot be read.
   The fill is the same in month and week, so both override the white default. */
.week-block.unreg,
.cal-ev.unreg {
  color: #3d2f00;
}
.week-block.unreg .bt {
  opacity: 0.75;
}

/* The animals a booking is for, with their faces on. A groomer knows the dog
   before the name, so the picture is the point rather than decoration. */
.who-pets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin: 0.2rem 0 0.7rem;
}
.who-pet {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  text-decoration: none;
  color: var(--text);
  font-weight: 600;
  font-size: 0.9rem;
  padding: 0.2rem 0.5rem 0.2rem 0.2rem;
  border: 1px solid var(--card-stroke);
  border-radius: 999px;
  background: var(--widget);
}
.who-pet:hover {
  border-color: var(--primary);
  text-decoration: none;
}
.who-pet .thumb {
  border-radius: 999px;
}

/* One row per service on the payment dialog. Scrolls, because a groomer's
   catalog is long and the dialog is not. */
/* What has been added so far. Empty to begin with, and it says so rather than
   being a box with nothing in it. */
.pay-lines {
  max-height: 12rem;
  overflow-y: auto;
  border: 1px solid var(--card-stroke);
  border-radius: 8px;
  padding: 0.35rem 0.55rem;
  margin-top: 0.6rem;
  min-height: 2.6rem;
}
.pay-lines:empty::before {
  content: attr(data-empty);
  color: var(--text-faint);
  font-size: 0.82rem;
  display: block;
  padding: 0.35rem 0;
}
/* Choose a service, price it, add it — one row, in the order you do them.
   These inherit .row, which wraps, and the price box had no width of its own:
   it took the whole line and pushed the button onto a third. Three controls
   that read as one action should not be a column. */
.pay-add {
  margin-top: 0.6rem;
  flex-wrap: nowrap;
}
.pay-add select {
  flex: 1 1 auto;
  /* Without this a select refuses to shrink below its longest option, which is
     what would push the other two off the end again on a narrow dialog. */
  min-width: 0;
}
.pay-add .price {
  flex: 0 0 6.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.pay-add .btn {
  flex: none;
  white-space: nowrap;
}
.pay-total {
  font-size: 1.1rem;
  margin-bottom: 0.7rem;
}
.pay-line .num {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}
.pay-line {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  padding: 0.2rem 0;
  font-size: 0.86rem;
  color: var(--text);
  cursor: pointer;
}
/* The name takes whatever room is left, so every price box starts at the same
   place down the column. Left to size itself, each row put its box wherever
   that row's name happened to end — a column of amounts you cannot read down.
   A long name is cut rather than allowed to push the price out of line. */
.pay-line > span:first-child {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pay-line .price {
  width: 6rem;
  text-align: right;
  flex: none;
  font-variant-numeric: tabular-nums;
}

/* ----------------------------- Charts ----------------------------- */

.chart {
  width: 100%;
  height: auto;
  overflow: visible;
}
.chart .grid {
  stroke: var(--card-stroke);
  stroke-width: 1;
}
.chart .axis {
  fill: var(--text-faint);
  font-size: 10px;
}
.chart .bar {
  fill: var(--primary);
}
.chart .bar.neg {
  fill: var(--error);
}
.chart .line {
  fill: none;
  stroke: var(--primary);
  stroke-width: 2;
}
.chart .line.cost {
  stroke: var(--error);
}
.chart .area {
  fill: color-mix(in srgb, var(--primary) 16%, transparent);
}

.meter {
  height: 7px;
  border-radius: 4px;
  background: var(--widget);
  overflow: hidden;
}
.meter > i {
  display: block;
  height: 100%;
  background: var(--primary);
}

/* ----------------------------- Login ----------------------------- */

.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}
.auth-card {
  width: min(400px, 100%);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 13px;
  padding: 1.6rem;
}
.auth-card h1 {
  margin-bottom: 0.2rem;
}
.auth-card .sub {
  color: var(--text-muted);
  font-size: 0.87rem;
  margin-bottom: 1.2rem;
}
.auth-card label {
  margin-top: 0.7rem;
}
.auth-card button {
  width: 100%;
  justify-content: center;
  margin-top: 1.1rem;
  padding: 0.55rem;
}

/* ----------------------------- htmx niceties ----------------------------- */

.htmx-indicator {
  opacity: 0;
  transition: opacity 0.15s;
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}
.htmx-swapping {
  opacity: 0.55;
}

/* Except the calendar refreshing itself. Fading is feedback for something you
   asked for; nobody asked for this one, and a screen that dips once a minute in
   the corner of the room reads as a fault rather than as an answer. */
#bookings-view.htmx-swapping {
  opacity: 1;
}

/* ----------------------------- Splitting a payment ----------------------------- */

.pay-split {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
  margin-top: 0.45rem;
}
.split-part {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  max-width: 7.5rem;
}
.split-part .price {
  width: 100%;
}
/* Holds its line whether or not there is anything to say, so typing into the
   boxes doesn't shuffle the buttons underneath up and down. */
.split-left {
  align-self: center;
  min-height: 1.2em;
}

/* ----------------------------- Narrow screens ----------------------------- */

@media (max-width: 900px) {
  .app {
    grid-template-columns: 1fr;
  }
  .sidebar {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--card-stroke);
  }
  .nav {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .nav-group {
    width: 100%;
    margin-top: 0.4rem;
  }
  .split,
  .pos-grid {
    grid-template-columns: 1fr;
  }
  .main {
    padding: 0.9rem 0.8rem 3rem;
  }
  .week {
    grid-template-columns: 42px repeat(7, minmax(0, 1fr));
    font-size: 0.9em;
  }
  /* On a phone the three of them on one line leaves a service dropdown too
     narrow to read the service in. The row is the right shape where there is
     room for it, and not worth squeezing where there isn't. */
  .pay-add {
    flex-wrap: wrap;
  }
  .pay-add select {
    flex: 1 1 100%;
  }
}

/* ----------------------------- Receipts ----------------------------- */
/*
   The same document the PDF draws. Fixed colours rather than theme variables:
   a receipt is the shop's own stationery and should not change because
   somebody picked a different theme for the till.
*/
:root {
  --rc-plum: #744961;
  --rc-rose: #bb6389;
  --rc-pale: #fef7fa;
  --rc-stone: #f5e5ec;
  --rc-rule: #e8bcce;
  --rc-ink: #212121;
  --rc-muted: #6b6b6b;
}
.receipt-page {
  background: var(--bg);
  padding: 1rem;
}
.receipt-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 210mm;
  margin: 0 auto 1rem;
}
.receipt {
  max-width: 210mm;
  margin: 0 auto;
  padding: 15mm;
  background: #fff;
  color: var(--rc-ink);
  border-radius: 4px;
  box-shadow: 0 1px 12px rgb(0 0 0 / 12%);
  font-size: 0.82rem;
}
.rc-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2rem;
}
.rc-shop {
  display: flex;
  align-items: center;
  gap: 4mm;
}
.rc-logo {
  height: 12mm;
  width: auto;
  flex: none;
}
.rc-name {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--rc-rose);
  line-height: 1.1;
}
.rc-sub {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.rc-title {
  text-align: right;
}
.rc-title h1 {
  margin: 0;
  font-size: 2.1rem;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--rc-plum);
  line-height: 1;
}
.rc-strapline {
  color: var(--rc-rose);
  font-size: 0.76rem;
  margin-top: 0.2rem;
}
.rc-contact {
  margin: 7mm 0 0;
  padding-bottom: 3mm;
  border-bottom: 1px solid var(--rc-rule);
  font-size: 0.76rem;
}
/* The paired blocks: who it is for, and what it comes to. */
.rc-boxes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5mm;
  margin-top: 7mm;
}
.rc-boxes-close {
  margin-top: 6mm;
}
.rc-box {
  padding: 4mm;
}
.rc-box-pale {
  background: var(--rc-pale);
}
.rc-box-stone {
  background: var(--rc-stone);
}
.rc-section {
  color: var(--rc-rose);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.rc-section-loose {
  margin-top: 6mm;
}
.rc-who {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0.4rem 0 0.25rem;
}
.rc-box-pale div:not(.rc-section):not(.rc-who) {
  color: var(--rc-muted);
}
.rc-meta {
  margin: 0.5rem 0 0;
}
.rc-meta > div {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.14rem 0;
}
.rc-meta dt {
  color: var(--rc-muted);
}
.rc-meta dd {
  margin: 0;
  font-weight: 700;
}
.rc-lines {
  width: 100%;
  margin-top: 2mm;
  border-collapse: collapse;
}
.rc-lines th {
  background: var(--rc-rose);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-align: left;
  padding: 0.45rem 0.7rem;
}
.rc-lines th.num,
.rc-lines td.num {
  text-align: right;
}
.rc-lines td {
  padding: 0.52rem 0.7rem;
}
/* Banded rather than one tint: a sale of a dozen things is read across, and
   the eye needs the line it started on. */
.rc-lines tbody tr:nth-child(even) {
  background: var(--rc-pale);
}
.rc-totals .rc-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.22rem 0;
  color: var(--rc-muted);
}
.rc-totals .rc-row span:last-child {
  color: var(--rc-ink);
  font-weight: 700;
}
/* The total on its own band, bled to the edges of its block. */
.rc-total {
  background: var(--rc-rose);
  margin: 0.4rem -4mm;
  padding: 0.4rem 4mm !important;
  font-size: 1.05rem;
  font-weight: 700;
  text-transform: uppercase;
}
.rc-total span,
.rc-total span:last-child {
  color: #fff !important;
}
.rc-notes {
  margin: 0.45rem 0 0;
  white-space: pre-wrap;
}
/* The one editable thing on the document. Off the paper entirely. */
.rc-edit {
  margin-top: 0.5rem;
}
.rc-edit summary {
  cursor: pointer;
  color: var(--rc-rose);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.rc-edit textarea {
  width: 100%;
  margin: 0.4rem 0;
  font: inherit;
}
.rc-pay {
  margin: 0.35rem 0 0;
}
.rc-banner {
  margin-top: 7mm;
  padding: 2.6mm;
  background: var(--rc-plum);
  color: #fff;
  font-weight: 700;
  text-align: center;
}
.rc-foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 16mm;
  font-size: 0.7rem;
  color: var(--rc-muted);
}
.rc-foot span:nth-child(2) {
  text-align: center;
}
.rc-foot span:last-child {
  text-align: right;
}

@media print {
  @page {
    size: A4;
    margin: 0;
  }
  .receipt-page {
    background: #fff;
    padding: 0;
  }
  .receipt {
    max-width: none;
    margin: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .no-print {
    display: none !important;
  }
  /* Printers drop background colours by default, and this design is mostly
     background colour. Without these the table header, the total band and the
     thank-you banner all come out blank. */
  .rc-lines th,
  .rc-lines tbody tr:nth-child(even),
  .rc-box-pale,
  .rc-box-stone,
  .rc-total,
  .rc-banner {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

@media print {
  .sidebar,
  .topbar button,
  .btn,
  #toasts {
    display: none !important;
  }
  .app {
    grid-template-columns: 1fr;
  }
  .card {
    break-inside: avoid;
  }
}
