/* Grillliste — the Dienstplan layout.
 *
 * Mobile first, in the literal sense: everything below is written for a
 * 360-px-wide phone and widened by the two min-width blocks at the end. The
 * QR code is the front door, so the phone is the design and the desktop is
 * the variant.
 *
 * Dark is the default and light is a switch, not a preference query: the
 * button at the top right sets data-theme on <html> and the choice is
 * remembered. prefers-color-scheme is honoured for anyone who has not chosen.
 *
 * Fonts are served from this host. A German site that pulls its fonts from
 * Google hands every visitor's address to Google on every page, and this one
 * has no reason to call anybody at all.
 */

@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-var-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-mono-var-latin.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

/* Dark, the default. */
:root {
  --surface: #0f141a;
  --ink: #e7edf3;
  --ink-2: #9aa8b6;
  --hair: #26313d;
  --band: #151c24;
  --green: #3fbf6f;
  --green-soft: #123a24;
  --on-green: #08150d;
  --red: #f2777a;
  --red-soft: #3a1c1e;
  --input: #141b23;
  --input-line: #324050;
  --bubble: #e7edf3;
  --bubble-ink: #0f141a;
  --sans: "Archivo", system-ui, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  color-scheme: dark;
}

/* Light, whether preferred or chosen. Only the tokens change, and the chosen
 * one is written last so an explicit switch beats the system preference in
 * both directions. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --surface: #ffffff;
    --ink: #0f172a;
    --ink-2: #475569;
    --hair: #e2e8f0;
    --band: #f1f5f9;
    --green: #166534;
    --green-soft: #dcfce7;
    --on-green: #ffffff;
    --red: #b91c1c;
    --red-soft: #fee2e2;
    --input: #ffffff;
    --input-line: #cbd5e1;
    --bubble: #0f172a;
    --bubble-ink: #ffffff;
    color-scheme: light;
  }
}

:root[data-theme="light"] {
  --surface: #ffffff;
  --ink: #0f172a;
  --ink-2: #475569;
  --hair: #e2e8f0;
  --band: #f1f5f9;
  --green: #166534;
  --green-soft: #dcfce7;
  --on-green: #ffffff;
  --red: #b91c1c;
  --red-soft: #fee2e2;
  --input: #ffffff;
  --input-line: #cbd5e1;
  --bubble: #0f172a;
  --bubble-ink: #ffffff;
  color-scheme: light;
}

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

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

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.45;
}

.page {
  max-width: 760px;
  margin: 0 auto;
  padding: 16px 14px 8px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.page-wide {
  max-width: 1080px;
}

a {
  color: var(--green);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 2px;
}

/* Keyboard users get to the form without walking the header. */
.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  position: static;
  display: inline-block;
  margin-bottom: 8px;
}

/* ---- header --------------------------------------------------------- */

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding-bottom: 14px;
}

.brand {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
}

.theme {
  font: inherit;
  font-size: 16px;
  width: 44px;
  height: 44px;
  border-radius: 6px;
  border: 1px solid var(--input-line);
  background: var(--input);
  color: var(--ink);
  cursor: pointer;
}

.hdr {
  border-bottom: 1px solid var(--hair);
  padding-bottom: 16px;
}

.hdr-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
}

.kicker {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-2);
  letter-spacing: 0.04em;
}

h1 {
  font-size: 26px;
  font-weight: 700;
  margin: 6px 0 0;
  letter-spacing: -0.02em;
  line-height: 1.1;
  text-wrap: balance;
}

h2 {
  font-size: 19px;
  font-weight: 700;
  margin: 26px 0 10px;
  letter-spacing: -0.01em;
}

.sub {
  margin: 12px 0 0;
  font-size: 15px;
  color: var(--ink-2);
  max-width: 60ch;
}

.banner {
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: 6px;
  background: var(--green-soft);
  border: 1px solid var(--green);
  color: var(--ink);
  font-size: 15px;
}

/* ---- forms ---------------------------------------------------------- */

.entry,
.stack {
  display: grid;
  gap: 20px;
  padding-top: 20px;
}

.fields {
  display: grid;
  gap: 14px;
}

.field {
  display: grid;
  gap: 6px;
}

.field > span,
.field > label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.field em {
  font-style: normal;
  font-weight: 400;
  opacity: 0.75;
}

/* Required, said in the one way a form is allowed to say it without a
   sentence. aria-hidden in the markup: the input's own `required` is what a
   screen reader announces, and two announcements of one fact is noise. */
.req {
  color: var(--green);
  font-weight: 700;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
select,
textarea {
  font: inherit;
  font-size: 16px;
  width: 100%;
  background: var(--input);
  color: var(--ink);
  border: 1px solid var(--input-line);
  border-radius: 6px;
  padding: 12px;
  min-height: 46px;
}

textarea {
  min-height: 88px;
  resize: vertical;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--green);
}

.check {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  min-height: 44px;
}

.check input {
  width: 20px;
  height: 20px;
  accent-color: var(--green);
}

/* Hidden from people, visible to a bot filling every field it finds. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.error {
  color: var(--red);
  font-size: 14px;
  margin: 0;
}

.error-summary {
  background: var(--red-soft);
  border: 1px solid var(--red);
  border-radius: 6px;
  padding: 12px 14px;
  margin: 0 0 4px;
}

.error-summary ul {
  margin: 0;
  padding-left: 20px;
}

.hint {
  color: var(--ink-2);
  font-size: 14px;
  margin: 0;
}

/* ---- the item table ------------------------------------------------- */

/* ONE GRID TEMPLATE FOR ALL FOUR ITEM KINDS — priced, free listed, free
 * counted, and any of them with a note. The kinds differ in what they put in
 * the cells, never in where the cells are, so every row lines up with every
 * other row.
 *
 * It used to be two templates and they disagreed. A counted free item is not
 * `.item-free` (that meant "free and uncounted"), so on wide screens it took
 * the template with no `free` area while `.free` still asked for one — a named
 * area that does not exist auto-places instead of erroring, which is why the
 * badge and the stepper landed on separate lines with nothing aligned.
 *
 * --qty-col is shared by the header row and the item rows so the two cannot
 * drift, and it is wide enough for the stepper: at 140px the buttons overflowed
 * their cell and squeezed the price column. */
.table {
  --qty-col: 152px;
  border: 1px solid var(--hair);
  border-radius: 8px;
  overflow: hidden;
}

.thead {
  display: none;
}

.item {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  grid-template-areas:
    "icon name  price"
    "icon qty   qty";
  column-gap: 12px;
  /* No row gap: the stepper supplies its own, so a row that has no stepper
     collapses to one line instead of carrying an empty second one. */
  row-gap: 0;
  align-items: center;
  padding: 10px 12px;
  border-top: 1px solid var(--hair);
}

.items > .item:first-child {
  border-top: 0;
}

.items > .item:nth-child(even) {
  background: var(--band);
}

.icon {
  grid-area: icon;
  font-size: 20px;
  text-align: center;
  width: 28px;
}

.name {
  grid-area: name;
  font-size: 16px;
  font-weight: 500;
}

.name small {
  display: block;
  font-size: 12px;
  color: var(--ink-2);
  font-weight: 400;
}

/* The price and the "Frei" badge are the same cell: one column, one edge to
   read down. Right-aligned and never wrapped — a shortened price is a wrong
   price. */
.price {
  grid-area: price;
  justify-self: end;
  text-align: right;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 14px;
}

.qty {
  grid-area: qty;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-top: 8px;
}

/* A free listed item has no stepper at all; without this its empty cell would
   still push the row taller than its neighbours. */
.qty:empty {
  padding-top: 0;
}

/* 44 px on a phone, because that is a thumb. The wide layout sizes them to
   the row instead — see the min-width block. */
.qty button {
  background: var(--input);
  border: 1px solid var(--input-line);
  border-radius: 6px;
  color: var(--ink);
  width: 44px;
  height: 44px;
  font: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  flex: 0 0 auto;
}

.qty button:hover {
  border-color: var(--green);
  color: var(--green);
}

/* The quantity is a real number input, kept visible: the two buttons write to
 * it, so there is one value on the page and one thing to read out. The native
 * spinners come off because the buttons are the 44-px version of them. */
.qty input[type="number"] {
  width: 3.6em;
  min-height: 44px;
  padding: 12px 4px;
  text-align: center;
  font-family: var(--mono);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  appearance: textfield;
  -moz-appearance: textfield;
}

.qty input[type="number"]::-webkit-outer-spin-button,
.qty input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.free {
  grid-area: price;
  justify-self: end;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--green);
  background: var(--green-soft);
  padding: 4px 8px;
  border-radius: 4px;
}

/* ---- the sticky total ----------------------------------------------- */

.sticky {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  border-top: 2px solid var(--ink);
  padding: 14px 0 8px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}

.total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  font-size: 14px;
  color: var(--ink-2);
}

.total strong {
  font-family: var(--mono);
  font-size: 26px;
  color: var(--ink);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  align-items: center;
}

.actions-single {
  grid-template-columns: 1fr;
}

.submit,
.button {
  font: inherit;
  font-weight: 600;
  font-size: 16px;
  background: var(--green);
  color: var(--on-green);
  border: 0;
  border-radius: 6px;
  padding: 13px 22px;
  min-height: 46px;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.button-quiet {
  background: var(--input);
  color: var(--ink);
  border: 1px solid var(--input-line);
}

.button-danger {
  background: var(--red-soft);
  color: var(--red);
  border: 1px solid var(--red);
}

.paypal {
  font-weight: 500;
  color: var(--green);
  background: var(--green-soft);
  padding: 12px 14px;
  border-radius: 6px;
  text-decoration: none;
  font-size: 14px;
  min-height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.paypal[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}

/* ---- lists and tables in the organiser area ------------------------- */

.rows {
  display: grid;
  gap: 10px;
}

.row {
  border: 1px solid var(--hair);
  border-radius: 8px;
  padding: 12px 14px;
  display: grid;
  gap: 6px;
}

.row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.row-title {
  font-weight: 600;
  font-size: 16px;
}

.row-meta {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-2);
}

.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 4px;
}

.row-actions .button,
.row-actions .submit {
  font-size: 14px;
  padding: 9px 14px;
  min-height: 44px;
}

/* A destructive action opens instead of firing: <details> is the confirmation,
 * and it costs no script. The marker goes because the summary is styled as a
 * button and a triangle inside one reads as damage. */
.row-actions details {
  display: inline-block;
}

.row-actions summary {
  list-style: none;
  cursor: pointer;
}

.row-actions summary::-webkit-details-marker {
  display: none;
}

.row-actions details[open] {
  display: block;
  width: 100%;
  border: 1px solid var(--red);
  border-radius: 8px;
  padding: 10px 12px;
}

.row-actions details[open] form {
  display: grid;
  gap: 10px;
  padding-top: 10px;
}

.tag {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 4px;
  background: var(--band);
  color: var(--ink-2);
  border: 1px solid var(--hair);
}

.tag-open {
  color: var(--green);
  background: var(--green-soft);
  border-color: var(--green);
}

.grid-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}

.grid-table th,
.grid-table td {
  text-align: left;
  padding: 10px 8px;
  border-bottom: 1px solid var(--hair);
  vertical-align: top;
}

.grid-table th {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--band);
}

.grid-table .num {
  font-family: var(--mono);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.scroll-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* ---- the mascot ----------------------------------------------------- */

/* The sprite's own geometry and timeline are generated: see
 * wwwroot/mascot/grillmeister.css. What is here is the chrome around it. */

/* The box the mascot occupies is the generated stylesheet's, not this one's
 * (DECISIONS G-22): it knows the art's own resolution and picks integer
 * multiples of it. This used to scale it by 0.8 on phones, which was a
 * workaround for a fixed 136-px box and would now land the sprite on a
 * fractional pixel grid. */
/* The bubble hangs outside both of these on purpose, so neither may clip
 * it. `visible` is the default; it is written down because it is now load
 * bearing, and a later `overflow: hidden` here would silently cut the
 * bubble in half instead of breaking something obvious. */
.hdr-mascot {
  flex: 0 0 auto;
  overflow: visible;
}

.mascot {
  overflow: visible;
}

/* The four layers are one picture to a screen reader; this is the element
 * that says so, and it has to fill the box the layers are positioned in. */
.mascot-art {
  position: absolute;
  inset: 0;
}

/* THE BUBBLE NEVER CHANGES THE LAYOUT AND IS NEVER CLIPPED.
 *
 * It was `left: 40px; top: -6px; white-space: nowrap`: a desktop offset
 * and a promise never to wrap. A long word ran off the right of a 360 px
 * phone, the document grew sideways, and the operator got a scrollbar.
 *
 * Absolute, so it takes no space in the flow and the header does not move
 * when he speaks. `left` and `top` are written by mascot.js from the two
 * bounding boxes and the viewport width, clamped so it can never leave the
 * screen; the zeroes here are only what it starts at. The size is the
 * stylesheet's half: as wide as the words up to 12 rem, then it wraps, and
 * a single unbroken 40-character word breaks anywhere rather than pushing
 * the box wider than that. */
.bubble {
  position: absolute;
  left: 0;
  top: 0;
  width: max-content;
  max-width: 12rem;
  overflow-wrap: anywhere;
  padding: 7px 11px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  background: var(--bubble);
  color: var(--bubble-ink);
  animation: bubble-pop 0.18s ease-out;
  z-index: 1;
}

/* The tail follows the bubble. Which side it points from is whichever
 * side mascot.js found room on, so it is written on the element rather
 * than assumed here. */
.bubble::after {
  content: "";
  position: absolute;
  border: 8px solid transparent;
}

.bubble[data-side="above-right"] {
  transform-origin: left bottom;
}

.bubble[data-side="above-right"]::after {
  left: 12px;
  bottom: -8px;
  border-bottom: 0;
  border-top-color: var(--bubble);
}

.bubble[data-side="above-left"] {
  transform-origin: right bottom;
}

.bubble[data-side="above-left"]::after {
  right: 12px;
  bottom: -8px;
  border-bottom: 0;
  border-top-color: var(--bubble);
}

.bubble[data-side="right"] {
  transform-origin: left center;
}

.bubble[data-side="right"]::after {
  left: -8px;
  top: 50%;
  margin-top: -8px;
  border-left: 0;
  border-right-color: var(--bubble);
}

.bubble[data-side="left"] {
  transform-origin: right center;
}

.bubble[data-side="left"]::after {
  right: -8px;
  top: 50%;
  margin-top: -8px;
  border-right: 0;
  border-left-color: var(--bubble);
}

@keyframes bubble-pop {
  from {
    transform: scale(0.6);
    opacity: 0;
  }

  to {
    transform: scale(1);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bubble {
    animation: none;
  }
}

.mascot .pause {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid var(--input-line);
  background: var(--input);
  color: var(--ink);
  font: 600 11px/1 var(--mono);
  cursor: pointer;
  opacity: 0.55;
  z-index: 1;
}

.mascot .pause:hover,
.mascot .pause:focus-visible {
  opacity: 1;
}

/* ---- the tag editor (mascot words) ---------------------------------- */

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 0;
}

.tags li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--band);
  border: 1px solid var(--hair);
  border-radius: 6px;
  padding: 6px 6px 6px 12px;
  font-family: var(--mono);
  font-size: 14px;
}

.tags button {
  font: inherit;
  font-size: 16px;
  line-height: 1;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}

.tags button:hover {
  color: var(--red);
  background: var(--red-soft);
}

.tag-add {
  display: flex;
  gap: 10px;
  align-items: stretch;
}

.tag-add input {
  flex: 1 1 auto;
}

ul.tags {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ---- the QR page ---------------------------------------------------- */

.qr {
  display: grid;
  gap: 14px;
  justify-items: center;
  text-align: center;
  padding: 18px 0;
}

.qr img {
  width: 100%;
  max-width: 320px;
  height: auto;
  background: #ffffff;
  padding: 12px;
  border-radius: 8px;
}

.qr-link {
  font-family: var(--mono);
  font-size: 13px;
  word-break: break-all;
}

/* ---- footer --------------------------------------------------------- */

.foot {
  margin-top: auto;
  padding: 26px 0 10px;
  color: var(--ink-2);
  font-size: 12px;
  font-family: var(--mono);
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}

/* THE TWO LINKS SHARE A ROW AT EVERY WIDTH.
 *
 * The footer itself still wraps — at 390 px the company line goes above
 * and that is what should happen. What must not happen is the break
 * landing BETWEEN Impressum and Datenschutz, which is what the operator
 * saw, so they are a group that does not wrap and neither of them breaks
 * inside itself. Together they are about 162 px in this 12 px monospace,
 * which fits the 328 px of content a 360 px phone leaves. */
.foot-links {
  display: flex;
  flex-wrap: nowrap;
  gap: 18px;
}

.foot-links a {
  white-space: nowrap;
}

.foot a {
  color: inherit;
}

/* ---- the nav strip in the organiser area ---------------------------- */

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 0 0;
  border-bottom: 1px solid var(--hair);
  padding-bottom: 12px;
}

.nav a {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  padding: 10px 12px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  border-radius: 6px;
  border: 1px solid transparent;
}

.nav a:hover {
  border-color: var(--input-line);
}

.nav a[aria-current="page"] {
  background: var(--green-soft);
  color: var(--green);
}

.nav .who {
  margin-left: auto;
  align-self: center;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-2);
}

.notice {
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: 6px;
  background: var(--green-soft);
  border: 1px solid var(--green);
  font-size: 15px;
}

/* ---- wider than a phone --------------------------------------------- */

@media (min-width: 640px) {
  .page {
    padding: 26px 20px 10px;
  }

  h1 {
    font-size: 30px;
  }

  .fields {
    grid-template-columns: 1fr 1fr;
  }

  .field-wide {
    grid-column: 1 / -1;
  }

  .thead {
    display: grid;
    grid-template-columns: 28px 1fr auto var(--qty-col);
    gap: 12px;
    padding: 8px 12px;
    background: var(--band);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-2);
  }

  .item {
    grid-template-columns: 28px 1fr auto var(--qty-col);
    grid-template-areas: "icon name price qty";
  }

  /* One row, so the stepper stops carrying the phone layout's top spacing. */
  .qty {
    padding-top: 0;
  }

  /* Sized to the row rather than to a thumb: there is a pointer here, and a
     44-px button is what overflowed the column in the first place. */
  .qty button {
    width: 36px;
    height: 36px;
    font-size: 18px;
  }

  .qty input[type="number"] {
    width: 3.4em;
    min-height: 36px;
    padding: 8px 4px;
  }

  .sticky {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
  }

  .actions {
    display: flex;
    gap: 12px;
  }
}

@media print {
  .topbar,
  .nav,
  .foot,
  .row-actions,
  .no-print {
    display: none !important;
  }

  body {
    background: #ffffff;
    color: #000000;
  }

  .qr img {
    max-width: 60mm;
  }
}
