/* Typefaces are served from this site, never from Google at runtime: a board
   must look right on a Fire Stick with the router unplugged.
   Anton for signage headings, Archivo for everything read at a distance,
   Barlow Condensed for the small uppercase labels above prices. */
@font-face {
  font-family: 'Anton';
  src: url('/fonts/anton.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Archivo';
  src: url('/fonts/archivo-var.woff2') format('woff2-variations'),
       url('/fonts/archivo-var.woff2') format('woff2');
  font-weight: 400 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Barlow Condensed';
  src: url('/fonts/barlow-condensed.woff2') format('woff2');
  font-weight: 600 700; font-style: normal; font-display: swap;
}

/* NO FAKE BOLD, EVER.
 *
 * Item names ask for weight 600. Archivo has a real 600 and gets it. Anton -
 * the face most boards set their names in - only exists at 400, and a browser
 * asked for a weight a face does not have fakes it by smearing the outline of
 * every letter thicker. On a face already that heavy and that narrow, the
 * smear fills in the insides of the letters: the h and the p of "Chips" close
 * up and the whole list reads as out of focus from across a shop.
 *
 * The prices never had this, because they ask for no weight and so get the
 * real face - which is exactly what George saw on Maggie's board: blurry
 * names, crisp prices, from the same typeface.
 *
 * So the board never synthesises a weight or a slant. A face that has 600
 * still gets 600; a face that does not is drawn as it was designed. */
.board, .board * { font-synthesis: none; }

/* The rest of the faces a shop can set its board in. A browser fetches a face
   only when something is set in it, so declaring them all costs nothing until
   one is chosen - and the service worker has them all, so choosing one with the
   router unplugged still works. */
@font-face {
  font-family: 'Oswald';
  src: url('/fonts/oswald.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bebas Neue';
  src: url('/fonts/bebas-neue.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Fjalla One';
  src: url('/fonts/fjalla-one.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alfa Slab One';
  src: url('/fonts/alfa-slab.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Playfair Display';
  src: url('/fonts/playfair.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Lobster';
  src: url('/fonts/lobster.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Pacifico';
  src: url('/fonts/pacifico.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Staatliches';
  src: url('/fonts/staatliches.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Righteous';
  src: url('/fonts/righteous.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Caveat';
  src: url('/fonts/caveat.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Permanent Marker';
  src: url('/fonts/permanent-marker.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bungee';
  src: url('/fonts/bungee.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}

/* Menu board styling, shared by the TV player and the dashboard preview.
   All sizing is derived from viewport width, so one stylesheet fits a 24" screen
   over the counter and a 65" board on the back wall with no changes. */

:root {
  --density: 1;
  --overscan: 0px;
  --display: 'Anton', 'Haettenschweiler', 'Arial Narrow', sans-serif;
  --body: 'Archivo', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --label: 'Barlow Condensed', 'Archivo', system-ui, sans-serif;
}

html.board-root {
  /* Every rem below scales with the screen. This is the single sizing knob. */
  font-size: calc(0.82vw * var(--density, 1));
  background: var(--paper, #f4f8ff);
}

.board {
  --radius: 0.9rem;
  box-sizing: border-box;
  width: 100%;
  /* Fixed to the screen, never taller. Everything is scaled to fit inside this
     box by fitBoard() - a menu board that scrolls is a menu board nobody reads. */
  height: 100vh;
  max-height: 100vh;
  padding: var(--overscan);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 1.75rem;
  line-height: 1.22;
  /* Figures line up in columns and never jump as a price changes. */
  font-variant-numeric: tabular-nums lining;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

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

/* ------------------------------------------------------------ chrome */

.board-header {
  display: flex; align-items: center; gap: 1.2rem;
  padding: 0.9rem 1.6rem;
  background: linear-gradient(100deg, var(--accent-dark), var(--accent));
  color: #fff;
  flex: 0 0 auto;
}
/* Hidden wins over scrolling: a page with no header on a shop whose band
   scrolls was showing an empty band, and the picture was fitted under it. */
.board-header.hidden, .board-header.hidden.scrolls { display: none; }
/* A band that scrolls: same lap as the strip along the bottom, so a long shop
   name or a notice up top goes past rather than being cut off. */
.board-header.scrolls { display: block; overflow: hidden; white-space: nowrap; }
.board-header.scrolls .board-head-inner {
  display: inline-flex; align-items: center; gap: 1.2rem;
  padding-left: 100%; animation: ticker var(--head-secs, 40s) linear infinite;
}
.board-head-inner { display: flex; align-items: center; gap: 1.2rem; min-width: 0; }
@media (prefers-reduced-motion: reduce) {
  .board-header.scrolls .board-head-inner { animation: none; padding-left: 0; }
}
.board.flush .board-header { padding: 0.55rem 1.05rem; }
.board-logo { height: 3.4rem; width: auto; object-fit: contain; }
.board-shop {
  font-family: var(--display); font-size: 3rem; line-height: 1;
  letter-spacing: 0.01em; text-transform: uppercase;
}
.board-tagline { font-size: 1.5rem; opacity: 0.88; margin-left: auto; }

/* Who drew the board. Small, in a corner, out of the way of a price: there to
   be noticed once and then ignored, on a screen nobody stands close to. */
.board { position: relative; }
.board-mark {
  position: absolute; right: 1.1%; bottom: 1.1%; z-index: 4;
  font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: max(0.7rem, 1.5vh); font-weight: 600; letter-spacing: .02em;
  color: rgba(255, 255, 255, .8); text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
  padding: .25em .6em; border-radius: 999px; background: rgba(0, 0, 0, .3);
  pointer-events: none; white-space: nowrap;
}

/* The line under an item's name, on a board that has asked for it. Smaller
   and quieter than the name: it is there to answer "what comes with it",
   not to be read from the back of the queue. */
.rows tbody tr.desc th {
  font-family: var(--desc-face, inherit);
  font-size: calc(var(--rowpx) * var(--desc-scale, 0.38));
  color: var(--desc-ink, inherit);
  font-weight: 400; opacity: 0.72; padding-top: 0;
  text-transform: none; white-space: normal; line-height: 1.15;
}

.board-body { flex: 1 1 auto; padding: 1.2rem; min-height: 0; overflow: hidden; }

.board-ticker {
  flex: 0 0 auto;
  background: var(--accent-dark); color: #fff;
  font-size: 1.45rem; padding: 0.45rem 0; line-height: 1.3; font-weight: 600;
  overflow: hidden; white-space: nowrap;
}
/* How long one lap of the message takes. A shop sets it; a long notice
   needs longer or nobody finishes reading it before it leaves. */
.board-ticker span { display: inline-block; padding-left: 100%;
  animation: ticker var(--ticker-secs, 40s) linear infinite; }
@keyframes ticker { to { transform: translateX(-100%); } }
/* Held still. Allergy advice and card-payments-welcome are read once and then
   ignored, and a line that slides past forever is the thing a shop notices
   about its own board every time it walks past. A long message still wants
   scrolling: a still one only shows what fits across the band. */
.board-ticker.still span {
  animation: none; padding-left: 0; display: block; width: 100%; text-align: center;
}
@media (prefers-reduced-motion: reduce) { .board-ticker span { animation: none; padding-left: 1.6rem; } }

/* -------------------------------------------------------------- grid */

.page-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 12), minmax(0, 1fr));
  gap: 1rem;
  /* Rows take the height their content needs; fitBoard() then scales the whole
     board up until it fills the screen. Stretching rows to equal heights would
     give a one-line note as much wall as the entire kebab price table. */
  align-content: start;
  align-items: stretch;
  grid-auto-rows: min-content;
  height: 100%;
}

.panel {
  grid-column: span var(--span, 12);
  background: #fff;
  border-radius: var(--radius);
  padding: 1.05rem 1.25rem 1.1rem;
  box-shadow: 0 0.1rem 0.5rem rgba(10, 30, 60, 0.07);
  min-width: 0;
  display: flex; flex-direction: column;
}
.panel.accent { background: linear-gradient(160deg, var(--accent), var(--accent-dark)); color: #fff; }
.panel.accent .panel-title, .panel.accent .cat-sub { color: #fff; }
.panel-spacer { background: none; box-shadow: none; }

.panel-title {
  font-family: var(--display);
  font-size: 3.1rem; line-height: 0.94; margin: 0;
  text-transform: uppercase; letter-spacing: 0.005em;
  color: var(--accent-dark);
}
.cat-head {
  margin-bottom: 0.65rem; padding-bottom: 0.35rem;
  border-bottom: 0.18rem solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.panel.accent .cat-head { border-bottom-color: rgba(255, 255, 255, 0.32); }
.cat-sub { font-size: 1.3rem; opacity: 0.72; margin-top: 0.12rem; }
.cat-note {
  margin-top: auto; padding-top: 0.55rem;
  font-size: 1.15rem; opacity: 0.72; font-style: italic; line-height: 1.25;
}
.panel.accent .cat-note { opacity: 0.9; }

/* ------------------------------------------------------------ tables */

.table-wrap { width: 100%; }
table.price-matrix { width: 100%; border-collapse: collapse; font-size: 1.85rem; }
table.price-matrix th, table.price-matrix td { padding: 0.34rem 0.5rem; }
table.price-matrix thead th {
  font-size: 1.3rem; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--accent); font-weight: 700; text-align: center;
}
/* Meat groups get a tinted band so the eye reads "Doner / Chicken / Mixed"
   before it starts hunting for a number. */
table.price-matrix .group-row th {
  font-size: 1.55rem; letter-spacing: 0.05em; padding: 0.2rem 0.5rem 0.22rem;
  color: var(--accent-dark);
}
table.price-matrix .group-row th:not(:empty) {
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border-bottom: 0.16rem solid var(--accent);
  border-radius: 0.35rem 0.35rem 0 0;
}
.panel.accent table.price-matrix .group-row th:not(:empty) {
  background: rgba(255, 255, 255, 0.16); color: #fff; border-bottom-color: #ffd400;
}
table.price-matrix tbody th {
  text-align: left; font-weight: 700; text-transform: uppercase;
  font-size: 1.62rem; letter-spacing: 0.005em; white-space: nowrap;
  padding-right: 1rem;
}
table.price-matrix tbody td {
  text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap;
  font-weight: 650;
}
table.price-matrix tbody tr:nth-child(even) { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.panel.accent table.price-matrix tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.08); }
.panel.accent table.price-matrix thead th { color: #ffe58a; }
table.price-matrix.compact { font-size: 1.7rem; }
td.single-price { text-align: right !important; padding-right: 0.6rem; }
.dash { opacity: 0.35; }

/* -------------------------------------------------------------- lists */

.item-list { list-style: none; margin: 0; padding: 0; }
.item {
  display: flex; align-items: baseline; gap: 0.5rem;
  padding: 0.26rem 0; font-size: 1.78rem;
}
.item-main { display: flex; flex-direction: column; min-width: 0; }
.item-name { font-weight: 700; text-transform: uppercase; letter-spacing: 0.004em; }
.item-desc { font-size: 1.18rem; opacity: 0.68; text-transform: none; font-weight: 400; line-height: 1.2; }
/* A visible leader line is what lets someone track a long name across to its
   price from six metres away. */
.dots {
  flex: 1 1 auto; min-width: 1.5rem;
  border-bottom: 0.16rem dotted currentColor; opacity: 0.42;
  transform: translateY(-0.3rem);
}
.item-price { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }

.badge {
  display: inline-block; margin-left: 0.4rem; padding: 0.05rem 0.4rem;
  background: var(--accent); color: #fff; border-radius: 0.3rem;
  font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.04em; vertical-align: middle;
}
.panel.accent .badge { background: #fff; color: var(--accent-dark); }

.tags { display: inline-flex; gap: 0.2rem; margin-left: 0.35rem; vertical-align: middle; }
.tag {
  display: inline-grid; place-items: center; min-width: 1.25rem; height: 1.25rem;
  padding: 0 0.25rem; border-radius: 0.3rem; font-size: 0.85rem; font-weight: 700;
  background: #e8eef8; color: var(--accent-dark);
}
.tag-veg { background: #1e8e3e; color: #fff; }
.tag-spicy { background: #d93025; color: #fff; }
.tag-fish { background: #0b79d0; color: #fff; }
.tag-gf { background: #a15c00; color: #fff; }
.tag-new { background: #ffd400; color: #3a2c00; }

.is-soldout { opacity: 0.45; }
.is-soldout .item-price, .is-soldout td { text-decoration: line-through; }
.is-hidden { display: none !important; }
.soldout-flag {
  font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: #d93025; font-weight: 700;
}

/* ------------------------------------------------------- price blocks */

.price-row { display: flex; flex-wrap: wrap; gap: 0.5rem; flex: 1 1 auto; align-items: stretch; }
.price-block {
  flex: 1 1 4.5rem; min-width: 0;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  background: rgba(255, 255, 255, 0.14); border-radius: 0.6rem; padding: 0.5rem 0.25rem;
}
.panel:not(.accent) .price-block { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.pb-label { font-size: 1.18rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.92; text-align: center; font-weight: 600; }
.pb-price { font-family: var(--display); font-size: 2.5rem; line-height: 1.05; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* -------------------------------------------------------------- tiles */

.tile-row { display: flex; gap: 0.8rem; align-items: flex-start; }
.tile { flex: 1 1 0; text-align: center; min-width: 0; }
.tile-media {
  /* A wide, shallow crop: tall boxes squeeze the price off a busy board, and a
     landscape frame is what food photos are actually shot in. */
  aspect-ratio: 16 / 10; max-height: 9rem; width: 100%;
  border-radius: 0.6rem; overflow: hidden;
  background: rgba(255, 255, 255, 0.18); margin-bottom: 0.4rem;
}
.tile-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile-name { font-size: 1.42rem; font-weight: 700; text-transform: uppercase; line-height: 1.08; }
.tile-price { font-family: var(--display); font-size: 2.3rem; line-height: 1.05; margin-top: 0.1rem; }

.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem 0.7rem; align-items: center; }
.chip { font-size: 1.6rem; text-transform: uppercase; font-weight: 700; }
.chip + .chip::before { content: '|'; opacity: 0.4; margin-right: 0.7rem; }
.chip b { margin-left: 0.3rem; }
.chip-price { margin-top: 0.45rem; font-family: var(--display); font-size: 2.6rem; }

/* --------------------------------------------------------------- cards */

.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.8rem; }
.card { background: rgba(255, 255, 255, 0.1); border-radius: 0.6rem; overflow: hidden; }
.card-media { aspect-ratio: 16 / 10; background: rgba(0, 0, 0, 0.08); }
.card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-body { padding: 0.5rem 0.6rem; }
.card-body h3 { margin: 0 0 0.15rem; font-size: 1.4rem; text-transform: uppercase; }
.card-body p { margin: 0 0 0.3rem; font-size: 1.05rem; opacity: 0.7; }
.card-price { font-family: var(--display); font-size: 1.9rem; }

/* ---------------------------------------------------------------- hero */

.panel-hero { padding: 0; overflow: hidden; position: relative; background: var(--accent-dark); color: #fff; }
.hero-media { position: absolute; inset: 0; background-size: cover; background-position: center top; }
.hero-media.no-image {
  background:
    radial-gradient(circle at 78% 18%, color-mix(in srgb, #fff 16%, transparent), transparent 55%),
    linear-gradient(155deg, var(--accent), var(--accent-dark) 75%);
}
/* Without a photo behind it the darkening wash is not needed and only muddies
   the colour. */
.hero-media.no-image::after { background: none; }
.hero-media::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6, 26, 56, 0.05) 0%, rgba(6, 26, 56, 0.35) 45%, rgba(6, 26, 56, 0.93) 100%);
}
.hero-body { position: relative; padding: 1.2rem; margin-top: auto; z-index: 1; }
.hero-badge { font-size: 1.3rem; letter-spacing: 0.2em; color: #ffd400; }
.hero-title {
  font-family: var(--display); font-size: 4.6rem; line-height: 0.92;
  margin: 0.2rem 0; text-transform: uppercase;
  text-shadow: 0 0.12rem 0.5rem rgba(0, 0, 0, 0.45);
}
.hero-sub { font-size: 1.6rem; opacity: 0.92; }
.hero-note { font-size: 1.15rem; opacity: 0.8; margin-top: 0.2rem; }
.hero-price { margin-top: 0.6rem; }
.starburst {
  display: inline-grid; place-items: center;
  font-family: var(--display); font-size: 3.4rem; color: var(--accent-dark);
  background: #fff; padding: 0.6rem 1.2rem; border-radius: 999px;
  box-shadow: 0 0.2rem 0.8rem rgba(0, 0, 0, 0.3);
}
.panel-hero.banner { flex-direction: row; align-items: center; min-height: 8rem; }
.panel-hero.banner .hero-body { margin: 0; display: flex; align-items: center; gap: 1.2rem; width: 100%; }
.panel-hero.banner .hero-title { font-size: 3.4rem; }
.panel-hero.banner .hero-price { margin: 0 0 0 auto; }

.panel-note {
  background: var(--accent-dark); color: #fff;
  align-items: center; justify-content: center; text-align: center;
  flex-direction: row; gap: 0.9rem; flex-wrap: wrap;
  padding: 0.7rem 1.2rem;
  /* Sit at the top of its row rather than stretching to match a taller panel
     beside it. A one-line flash has no business being as tall as a price list. */
  align-self: start;
}
.note-title { font-family: var(--display); font-size: 2.4rem; text-transform: uppercase; line-height: 1; }
.note-body {
  font-size: 2rem; font-weight: 800; color: var(--accent-dark);
  background: #ffd400; padding: 0.12rem 0.7rem; border-radius: 0.4rem; line-height: 1.15;
}

.panel-image { padding: 0; overflow: hidden; }
.panel-image img { width: 100%; height: 100%; display: block; }
.image-caption { position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.6); color: #fff; padding: 0.4rem 0.8rem; font-size: 1.2rem; }

.placeholder {
  width: 100%; height: 100%; display: grid; place-items: center;
  color: rgba(255, 255, 255, 0.5); font-size: 1.4rem;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,0.06) 0 10px, transparent 10px 20px);
}

.text-body p { margin: 0 0 0.4rem; font-size: 1.4rem; }

/* --------------------------------------------------- portrait screens */

.board.portrait .page-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.board.portrait .panel { grid-column: span min(var(--span, 6), 6); }

/* ====================================================================
   FLUSH BOARDS — the printed chip-shop look
   Edge-to-edge blocks, blue banners with the sizes printed inside them,
   no floating cards. Turned on per screen with chrome: "flush".
   ==================================================================== */

.board.flush { --radius: 0; }
.board.flush .board-body { padding: 0; }

.page-columns {
  display: grid;
  grid-template-columns: var(--tracks, 1fr 1fr 1fr);
  height: 100%;
  gap: 0;
}
.board-col {
  display: flex; flex-direction: column; min-width: 0;
  border-right: 0.22rem dotted color-mix(in srgb, var(--accent) 45%, #fff);
}
.board-col:last-child { border-right: 0; }

.board.flush .panel {
  border-radius: 0; box-shadow: none; background: #fff;
  padding: 0.75rem 1.05rem 0.85rem; min-height: 0;
}
.board.flush .panel.grows { flex: 1 1 auto; }
.board.flush .panel.accent { background: var(--accent); }

/* ---- the banner: name and sizes on one line ---- */
.board.flush .panel-band { padding: 0; background: var(--accent); color: #fff; }
.band { display: flex; align-items: center; gap: 1rem; padding: 0.55rem 1.05rem 0.6rem; }
.band.center { flex-direction: column; gap: 0.1rem; text-align: center; }
.band-head { min-width: 0; }
.band-title {
  font-family: var(--display); font-size: 3.4rem; line-height: 0.9;
  margin: 0; text-transform: uppercase; letter-spacing: 0.005em; color: #fff;
}
.band-sub { margin: 0.1rem 0 0; font-size: 1.15rem; line-height: 1.15; opacity: 0.95; }
.band-sizes { display: flex; gap: 1.4rem; margin-left: auto; align-items: flex-end; }
.band.center + .band-sizes, .band-sizes.below { margin: 0; justify-content: center; }
.band-sizes.below {
  background: #fff; color: var(--ink); padding: 0.35rem 0.2rem 0.45rem;
  display: flex; gap: 0;
}
.band-sizes.below .band-size { flex: 1 1 0; min-width: 0; padding: 0 0.25rem; }
.band-sizes.below .band-size-label { font-size: 0.98rem; letter-spacing: 0.02em; }
.band-sizes.below .band-size-price { font-size: 1.8rem; }
.band-sizes.below .band-size + .band-size { border-left: 0.14rem solid color-mix(in srgb, var(--accent) 30%, transparent); }
.band-sizes.below .band-size-label { color: var(--accent-dark); }
.band-size { text-align: center; line-height: 1; }
.band-size-label {
  font-size: 1.18rem; text-transform: uppercase; letter-spacing: 0.05em;
  font-weight: 700; opacity: 0.95; margin-bottom: 0.12rem;
}
.band-size-price {
  font-family: var(--display); font-size: 2.5rem; font-variant-numeric: tabular-nums;
}
.board.flush .panel-band + .panel { border-top: 0; }

/* ---- lists on a flush board: no leaders, prices hard right ---- */
.board.flush .item-list .dots { display: none; }
.board.flush .item { font-size: 1.85rem; padding: 0.14rem 0; }
.board.flush .item-name { font-weight: 600; letter-spacing: 0; }
.board.flush .item-price { font-weight: 600; }
.board.flush table.price-matrix { font-size: 1.85rem; }
.board.flush table.price-matrix tbody th { font-weight: 600; text-transform: uppercase; }
.board.flush table.price-matrix tbody tr:nth-child(even) { background: none; }
.board.flush table.price-matrix tbody td { text-align: right; font-weight: 600; }
.board.flush table.price-matrix thead th { color: var(--accent-dark); font-size: 1.25rem; }
.board.flush .cat-head { border-bottom: 0; margin-bottom: 0.3rem; }
.board.flush .panel-title {
  font-family: var(--display); font-size: 2.9rem; line-height: 0.95;
  color: var(--accent-dark);
}
.board.flush .panel.accent .panel-title { color: #bcd8ff; }
.board.flush .cat-sub { font-size: 1.2rem; }

/* ---- the round flash, e.g. COD BITES 3 FOR £4.50 ---- */
.roundel {
  float: right; margin: 0 0 0.5rem 0.8rem;
  width: 7.6rem; height: 7.6rem; border-radius: 50%;
  background: var(--accent); color: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; line-height: 1.05; padding: 0.3rem;
}
.roundel span { font-size: 1.15rem; font-weight: 700; text-transform: uppercase; }
.roundel b { font-family: var(--display); font-size: 2.2rem; }

/* ---- the value pill under the sauces ---- */
.value-pill {
  background: var(--accent); color: #fff; border-radius: 999px;
  padding: 0.5rem 1.4rem; margin-top: 0.6rem; align-self: stretch;
}
.pill-line { display: flex; align-items: baseline; gap: 0.55rem; font-size: 1.5rem; }
.pill-label { text-transform: uppercase; font-weight: 700; }
.pill-line b { font-family: var(--display); font-size: 2rem; }
.pill-line i { font-style: normal; font-size: 1.2rem; opacity: 0.9; }

/* ---- the white price tag on a banner, e.g. £4.00 EACH ---- */
.price-tag {
  margin-left: auto; background: #fff; color: var(--accent-dark);
  border-radius: 999px; padding: 0.25rem 1.1rem; text-align: center; line-height: 1;
}
.price-tag b { font-family: var(--display); font-size: 2.3rem; display: block; }
.price-tag span { font-size: 1rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.board.flush .cat-head { display: flex; align-items: center; gap: 0.8rem; }

.cat-art { display: block; width: 100%; margin-top: 0.5rem; object-fit: contain; }
/* A panel marked to grow puts its slack at the very end, so the picture stays
   with the words it illustrates and the pill stays with the picture. */
.board.flush .panel.grows { justify-content: flex-start; }

/* ---- kids meals ---- */
.board.flush .panel .kids { display: flex; flex-direction: column; height: 100%; position: relative; }
.kids-title {
  font-family: var(--display); font-size: 3.2rem; line-height: 0.92;
  text-transform: uppercase; margin: 0; text-align: center; color: #fff;
}
.kids-sub { text-align: center; font-size: 1.15rem; margin: 0.25rem 0 0.6rem; line-height: 1.2; }
.kids-body { display: flex; gap: 0.5rem; align-items: flex-start; }
.kids-list {
  list-style: none; margin: 0; padding: 0; font-size: 1.6rem; line-height: 1.32;
  text-transform: uppercase; white-space: nowrap;
}
.kids-art { position: absolute; right: 0; bottom: 0; width: 32%; }
.starburst-badge {
  margin-left: auto; flex: 0 0 auto;
  width: 6.6rem; height: 6.6rem; background: #fff; color: var(--accent-dark);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 1;
  clip-path: polygon(50% 0%, 60% 9%, 72% 3%, 77% 16%, 90% 15%, 89% 28%, 100% 34%,
    93% 45%, 100% 57%, 88% 63%, 92% 76%, 79% 78%, 77% 91%, 65% 87%, 55% 97%,
    44% 88%, 33% 96%, 26% 84%, 13% 87%, 13% 74%, 1% 69%, 8% 58%, 0% 47%,
    11% 40%, 6% 27%, 19% 25%, 20% 12%, 33% 15%, 40% 4%);
}
.sb-only, .sb-each { font-size: 0.95rem; text-transform: uppercase; font-weight: 700; letter-spacing: 0.06em; }
.sb-price { font-family: var(--display); font-size: 2.2rem; margin: 0.05rem 0; }

/* ---- flat "|" separated list, e.g. pies ---- */
.board.flush .chip-row { gap: 0.3rem 0.6rem; }
.board.flush .chip { font-size: 1.6rem; font-weight: 600; }
.board.flush .chip-price { display: none; }

.board.flush .panel-image { padding: 0; background: #fff; }
.board.flush .panel-image img { width: 100%; height: auto; display: block; }

/* A list filling a column should use the column. Letting the table take the
   full height spreads its rows into the space, which is how the printed board
   reads - airy, not a block of text with a gap under it. */
.board.flush .panel.grows .table-wrap { flex: 1 1 auto; display: flex; }
.board.flush .panel.grows table.price-matrix { height: 100%; }
.board.flush .panel.grows .item-list { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-around; }
.board.flush .kids-list { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-around; }
.board.flush .panel.grows .kids-body { flex: 1 1 auto; }

/* The printed boards this style copies carry their allergy wording as a line of
   text in the banner, not as little badges beside each item. */
.board.flush .tags { display: none; }

/* ---- tiles and swatches where no photograph exists -------------------
   A solid colour block with the name set large reads from across a shop.
   A weak drawing does not, and a grey placeholder looks unfinished. */
.tile.no-art {
  background: var(--swatch, var(--accent));
  color: #fff; border-radius: 0.6rem;
  padding: 0.7rem 0.6rem 0.75rem;
  display: flex; flex-direction: column; justify-content: center;
  min-height: 5.5rem;
}
.tile.no-art .tile-name { font-size: 1.55rem; line-height: 1.08; }
.tile.no-art .tile-price {
  font-family: var(--display); font-size: 2.5rem; line-height: 1.02; margin-top: 0.25rem;
}
.panel.accent .tile.no-art { box-shadow: inset 0 0 0 0.14rem rgba(255, 255, 255, 0.25); }

.swatch-row { display: flex; flex-wrap: wrap; gap: 0.45rem 1.1rem; align-items: center; }
.swatch { display: inline-flex; align-items: center; gap: 0.45rem; }
.swatch-dot {
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--swatch); box-shadow: inset 0 -0.25rem 0.4rem rgba(0, 0, 0, 0.22);
  flex: 0 0 auto;
}
.swatch-name { font-size: 1.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.01em; }

/* ====================================================================
   FINISH — depth, grading and typographic hierarchy.
   A menu board is lit, glossy and read from six metres. Flat fills and
   hairline borders read as a web form; grading, a lit top edge and real
   weight contrast read as signage.
   ==================================================================== */

.board {
  background:
    radial-gradient(120% 90% at 12% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
    var(--paper);
}

/* ---- panels sit in light, not in outline ---- */
.board:not(.flush) .panel {
  background: linear-gradient(178deg, #fff, color-mix(in srgb, var(--accent) 4%, #fff));
  box-shadow:
    inset 0 0.09rem 0 rgba(255, 255, 255, 0.9),
    0 0.08rem 0.2rem rgba(8, 26, 56, 0.06),
    0 0.7rem 1.6rem -0.6rem rgba(8, 26, 56, 0.22);
}
.board .panel.accent,
.board.flush .panel.accent {
  background: linear-gradient(155deg,
    color-mix(in srgb, var(--accent) 92%, #fff),
    var(--accent) 45%,
    color-mix(in srgb, var(--accent-dark) 78%, var(--accent)));
  box-shadow:
    inset 0 0.09rem 0 rgba(255, 255, 255, 0.28),
    0 0.7rem 1.6rem -0.7rem rgba(4, 18, 44, 0.5);
}

/* ---- banners get the same light ---- */
.board.flush .panel-band {
  background: linear-gradient(168deg,
    color-mix(in srgb, var(--accent) 88%, #fff), var(--accent) 55%,
    color-mix(in srgb, var(--accent-dark) 55%, var(--accent)));
  box-shadow: inset 0 0.1rem 0 rgba(255, 255, 255, 0.3), inset 0 -0.18rem 0 rgba(3, 16, 40, 0.22);
}

/* ---- display type: tighter, heavier, with optical spacing ---- */
.panel-title, .band-title, .kids-title, .hero-title, .board-shop {
  letter-spacing: 0.008em;
  -webkit-font-smoothing: antialiased;
}
.band-title, .kids-title { text-shadow: 0 0.08rem 0.28rem rgba(3, 14, 36, 0.32); }

/* Small uppercase labels are a different voice from the headings. */
.band-size-label, .pb-label, .roundel span, .sb-only, .sb-each,
table.price-matrix thead th, .price-tag span {
  font-family: var(--label);
  font-weight: 600;
  letter-spacing: 0.1em;
}

/* ---- prices lead ---- */
.item-price, .band-size-price, .pb-price, .tile-price, .chip-price,
table.price-matrix tbody td, .card-price {
  font-variant-numeric: tabular-nums lining;
}
.item-price { font-weight: 800; letter-spacing: -0.012em; }
table.price-matrix tbody td { font-weight: 700; letter-spacing: -0.012em; }
.band-size-price, .pb-price, .tile-price { letter-spacing: -0.005em; }

/* ---- section headings get a coloured rule, not a grey line ---- */
.cat-head {
  border-bottom: 0.16rem solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.board:not(.flush) .panel-title::after {
  content: ''; display: block; width: 2.6rem; height: 0.28rem; margin-top: 0.3rem;
  background: var(--hi); border-radius: 0.14rem;
}
.panel.accent .panel-title::after { background: #fff; }

/* ---- the round flash and starburst read as objects, not shapes ---- */
.roundel {
  background: radial-gradient(120% 120% at 30% 22%,
    color-mix(in srgb, var(--accent) 78%, #fff), var(--accent) 55%, var(--accent-dark));
  box-shadow: 0 0.5rem 1.1rem -0.3rem rgba(4, 18, 44, 0.5), inset 0 0.12rem 0 rgba(255,255,255,.3);
}
.roundel b, .sb-price { letter-spacing: -0.01em; }
.starburst-badge { filter: drop-shadow(0 0.35rem 0.7rem rgba(3, 16, 40, 0.45)); }
.starburst, .price-tag {
  box-shadow: 0 0.45rem 1rem -0.25rem rgba(4, 18, 44, 0.45), inset 0 -0.14rem 0 rgba(10, 30, 60, 0.1);
}
.value-pill {
  background: linear-gradient(170deg, color-mix(in srgb, var(--accent) 85%, #fff), var(--accent-dark));
  box-shadow: inset 0 0.1rem 0 rgba(255,255,255,.26), 0 0.5rem 1rem -0.4rem rgba(4,18,44,.45);
}

/* ---- colour blocks where a photo will go ---- */
.tile.no-art {
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--swatch) 88%, #fff), var(--swatch) 50%,
    color-mix(in srgb, var(--swatch) 72%, #000));
  box-shadow: inset 0 0.1rem 0 rgba(255,255,255,.24), 0 0.4rem 0.9rem -0.4rem rgba(4,18,44,.5);
}
.tile.no-art .tile-price { text-shadow: 0 0.08rem 0.22rem rgba(0,0,0,.3); }
.swatch-dot {
  background: radial-gradient(120% 120% at 32% 26%,
    color-mix(in srgb, var(--swatch) 65%, #fff), var(--swatch) 60%,
    color-mix(in srgb, var(--swatch) 70%, #000));
  box-shadow: 0 0.15rem 0.35rem rgba(4,18,44,.35);
}

/* ---- header and ticker ---- */
.board-header {
  background: linear-gradient(100deg, var(--accent-dark), var(--accent) 140%);
  box-shadow: inset 0 -0.14rem 0 rgba(3, 16, 40, 0.25);
}
.board-ticker {
  background: linear-gradient(180deg, var(--accent-dark), color-mix(in srgb, var(--accent-dark) 82%, #000));
  box-shadow: inset 0 0.1rem 0 rgba(255,255,255,.12);
}

/* ---- feature panels ---- */
.hero-badge { font-family: var(--label); letter-spacing: 0.24em; font-weight: 600; }
.hero-title { letter-spacing: 0.004em; }
.starburst { background: linear-gradient(175deg, #fff, #e9eef7); }

/* A panel told to fill its column should distribute its content through that
   space, not stack it at the top with a void underneath. */
.board.flush .panel.grows { display: flex; flex-direction: column; }
.board.flush .panel.grows > .swatch-row { margin-bottom: 0.6rem; }
.board.flush .panel.grows > .value-pill { margin-top: auto; margin-bottom: 0; }
.board.flush .kids-body { align-items: stretch; }
.board.flush .kids-list { justify-content: space-evenly; }
.board.flush .starburst-badge { align-self: center; }

/* The note strip is a dark flash and must win over the generic panel gradient,
   which otherwise leaves white text on a white ground. */
.board .panel.panel-note {
  background: linear-gradient(168deg,
    color-mix(in srgb, var(--accent) 60%, var(--accent-dark)), var(--accent-dark));
  color: #fff;
  box-shadow: inset 0 0.1rem 0 rgba(255, 255, 255, 0.18),
    0 0.6rem 1.3rem -0.6rem rgba(4, 18, 44, 0.55);
}
.board .panel.panel-note .note-title { color: #fff; }

/* ====================================================================
   BRANDED BACKGROUND
   A shop's own artwork behind the menu. These are usually a frame - name,
   border, illustration - around an empty middle, so the image goes full
   bleed and the menu is inset into the space it leaves.
   ==================================================================== */

/* The frame is the room the artwork is allowed: the whole body, which already
   sits below the banner and above the scrolling message. */
.stage-frame { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.page-stage {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  box-sizing: border-box;
  display: flex; flex-direction: column;
}
/* The picture itself, so it can be cropped and nudged without the browser's
   background shorthand fighting us over what "cover" means. */
.stage-art {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
}
/* Everything except the picture sits above it. Each of these positions itself
   (the inset box and the free-placement layer are both absolute), so this only
   settles the stacking - setting position here would undo theirs. */
.page-stage > *:not(.stage-art) { z-index: 1; }
/* The clear area the menu sits in, as a share of the picture on both axes. */
.stage-inset { position: absolute; display: flex; flex-direction: column; }
.stage-inset > .page-grid,
.stage-inset > .page-columns { flex: 1 1 auto; height: auto; }
.stage-inset > .page-zones { flex: 1 1 auto; }
.page-stage > .page-free { position: absolute; inset: 0; }

/* The body stops being a padded container once artwork owns the whole frame. */
.board-body:has(> .stage-frame) { padding: 0; position: relative; }
/* The dark behind a picture is for the bars either side of one that does not
   fill the screen. A board with no picture has nothing to letterbox, so it
   keeps its own paper colour - otherwise dark ink lands on a dark board. */
.board:has(.stage-art) { background: #0a1424; }

/* A banner strip across the top, for the shops that want one above the board. */
.board-banner {
  flex: 0 0 auto;
  padding: 0.5rem 1.4rem;
  background: linear-gradient(100deg, var(--accent-dark), var(--accent));
  color: #fff;
  font-family: var(--display); font-size: 2.1rem; line-height: 1.1;
  letter-spacing: 0.02em; text-transform: uppercase; text-align: center;
}

/* On artwork, white cards would cover the design. Panels become glass: the
   background reads through, the type stays legible. */
.board:has(.stage-art) .panel:not(.bare) {
  background: rgba(6, 18, 38, 0.42);
  backdrop-filter: blur(2px);
  color: #f2f6ff;
  box-shadow: inset 0 0.06rem 0 rgba(255, 255, 255, 0.12);
  border-radius: 0.7rem;
}
/* Artwork that already draws its own boxes gets nothing over the top: a glass
   card on the shop's own cream panel is the grey smear nobody asked for. */
.board:has(.stage-art) .panel.bare {
  background: none; backdrop-filter: none; box-shadow: none;
}
.board:has(.stage-art) .panel.accent:not(.bare) {
  background: linear-gradient(155deg, rgba(21, 96, 214, 0.82), rgba(7, 42, 94, 0.86));
}
.board:has(.stage-art) .panel:not(.bare) .panel-title,
.board:has(.stage-art) .panel:not(.bare) .band-title,
.board:has(.stage-art) .panel:not(.bare) .kids-title { color: #f6f1e2; }
.board:has(.stage-art) .cat-head { border-bottom-color: rgba(255, 255, 255, 0.28); }
.board:has(.stage-art) table.price-matrix thead th { color: #f0c473; }
.board:has(.stage-art) table.price-matrix tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.05); }
.board:has(.stage-art) .dots { opacity: 0.32; }
.board:has(.stage-art) .cat-note,
.board:has(.stage-art) .cat-sub { color: rgba(242, 246, 255, 0.75); }
.board:has(.stage-art) .panel-title::after { background: #d8a94b; }
.board:has(.stage-art) .board-col { border-right-color: rgba(255, 255, 255, 0.22); }

/* Browsers without :has() still get a readable board, just without the glass. */
@supports not (selector(:has(*))) {
  .stage-frame { position: static; }
}

/* ---- artwork that draws its own boxes -------------------------------
   The design supplies the panel and the heading; the menu supplies only the
   prices, sitting inside the cells the artwork leaves for them. */
.page-zones {
  display: grid;
  grid-template-columns: var(--zcols, 1fr 1fr 1fr);
  grid-template-rows: var(--zrows, 1fr 1fr);
  gap: 0;
  flex: 1 1 auto;
  min-height: 0;
}
/* A cell of the artwork's own grid. Positioned, because the block inside is
   scaled to fit it and measures itself against this box. */
.zone { position: relative; min-width: 0; min-height: 0; padding: 0.5rem 0.9rem; overflow: hidden; }
.board .panel.bare {
  background: none; box-shadow: none; border-radius: 0; padding: 0;
  width: 100%; color: inherit; backdrop-filter: none;
}
/* Artwork like this is cream paper with navy printing, so the type follows. */
.board:has(.page-zones) .panel.bare,
.board:has(.page-zones) .panel.bare .item-name,
.board:has(.page-zones) .panel.bare table.price-matrix tbody th { color: #16305a; }
.board:has(.page-zones) .panel.bare .item-price,
.board:has(.page-zones) .panel.bare table.price-matrix tbody td { color: #0f2547; }
.board:has(.page-zones) .panel.bare table.price-matrix thead th { color: #2d5fa8; }
.board:has(.page-zones) .panel.bare .dots { opacity: 0.3; }
.board:has(.page-zones) .panel.bare tbody tr:nth-child(even) { background: rgba(22, 48, 90, 0.05); }
.board:has(.page-zones) .panel.bare .cat-sub,
.board:has(.page-zones) .panel.bare .cat-note { color: rgba(22, 48, 90, 0.7); }
.board:has(.page-zones) .band { padding: 0; background: none; }
.board:has(.page-zones) .panel-band { background: none; box-shadow: none; }
.board:has(.page-zones) .band-title { display: none; }
.board:has(.page-zones) .band-size-label { color: #2d5fa8; }
.board:has(.page-zones) .band-size-price { color: #0f2547; }
.board:has(.page-zones) .band-sizes { margin: 0 auto; }

/* ---- blocks placed by hand on artwork ---- */
.page-free { position: relative; flex: 1 1 auto; min-height: 0; }
.placed { position: absolute; overflow: hidden; }
/* A block's words start at the top of its box and are never scaled: the box
   top IS the first line, which is what lets two level boxes have level rows.
   See typography.js for why. */
.placed > .placed-inner { display: block; width: 100%; }
/* The two places a block is still measured and scaled to fit its box: a cell
   of artwork that draws its own grid (zones), and a hero card, which is a
   designed thing with its own type rather than rows of prices. */
.zone > .placed-inner,
.placed[data-fit] > .placed-inner {
  position: absolute; top: 0; left: 0;
  display: flex; align-items: center;   /* short content sits in the middle of its box */
  transform-origin: top left;
  /* No overflow:hidden here - the box around it already clips, and hiding it
     here would stop the fit measuring what is actually too big. */
}
.placed-inner > .panel { width: 100%; }
.placed-inner > .panel.bare { padding: 0; }
/* Placed blocks inherit the ink colour chosen for the page, so the same block
   reads on cream paper or on a dark photograph. */
.board.ink-dark .placed .panel.bare,
.board.ink-dark .placed .panel.bare .item-name,
.board.ink-dark .placed .panel.bare table.price-matrix tbody th,
.board.ink-dark .placed .panel.bare .item-price,
.board.ink-dark .placed .panel.bare table.price-matrix tbody td { color: #13294d; }
.board.ink-dark .placed .panel.bare table.price-matrix thead th,
.board.ink-dark .placed .panel.bare .band-size-label { color: #2c5c9e; }
.board.ink-dark .placed .panel.bare .cat-sub,
.board.ink-dark .placed .panel.bare .cat-note { color: rgba(19,41,77,.7); }
.board.ink-dark .placed .panel.bare .dots { opacity: .3; }
.board.ink-dark .placed .panel.bare .panel-title,
.board.ink-dark .placed .panel.bare .band-title,
.board.ink-dark .placed .panel.bare .kids-title { color: #13294d; }
.board.ink-dark .placed .panel.bare .band-sub { color: rgba(19, 41, 77, .75); }

.board.ink-light .placed .panel.bare,
.board.ink-light .placed .panel.bare .item-name,
.board.ink-light .placed .panel.bare table.price-matrix tbody th,
.board.ink-light .placed .panel.bare .item-price,
.board.ink-light .placed .panel.bare table.price-matrix tbody td { color: #f6f2e6; }
.board.ink-light .placed .panel.bare table.price-matrix thead th,
.board.ink-light .placed .panel.bare .band-size-label { color: #e6c479; }
.board.ink-light .placed .panel.bare .panel-title,
.board.ink-light .placed .panel.bare .band-title,
.board.ink-light .placed .panel.bare .kids-title { color: #f6f2e6; }
.board.ink-light .placed .panel.bare .band-sub { color: rgba(246, 242, 230, .8); }
.board.ink-light .placed .panel.bare .cat-sub,
.board.ink-light .placed .panel.bare .cat-note { color: rgba(246,242,230,.72); }
.board.ink-light .placed .panel.bare .dots { opacity: .34; }
/* Text on a photograph needs a shadow to stay legible over busy areas. */
.board.ink-light .placed .panel.bare { text-shadow: 0 0.07rem 0.3rem rgba(0,0,0,.55); }

/* ---- a section inside a box printed on the shop's artwork ----------------
   Plain on purpose: the artwork supplies the frame and the heading, this
   supplies the prices. Everything is in rem so the block can be scaled to fill
   whatever box it was dropped into. */
.boxed-panel { display: flex; flex-direction: column; justify-content: center; gap: 0.4rem; width: 100%; }
/* Where the words sit inside a block.
 *
 * "Spread" is what a printed price list does: the thing on the left, what it
 * costs hard against the right, so a column of prices lines up down the board
 * whatever length the names are. Left, centre and right keep each line's name
 * and price together and move the pair instead - which is what a single item
 * in sizes wants, and what several short lines want.
 */
.placed[data-align="left"] .boxed-sizes { justify-content: flex-start; }
.placed[data-align="centre"] .boxed-sizes { justify-content: center; }
.placed[data-align="right"] .boxed-sizes { justify-content: flex-end; }

.placed[data-align="left"] .boxed-list li b,
.placed[data-align="centre"] .boxed-list li b,
.placed[data-align="right"] .boxed-list li b { margin-left: 0; }
.placed[data-align="left"] .boxed-list { align-items: flex-start; }
.placed[data-align="centre"] .boxed-list { align-items: center; }
.placed[data-align="right"] .boxed-list { align-items: flex-end; }

.placed[data-align="left"] .boxed-grid { width: auto; margin-right: auto; }
.placed[data-align="centre"] .boxed-grid { width: auto; margin-inline: auto; }
.placed[data-align="right"] .boxed-grid { width: auto; margin-left: auto; }

.placed[data-align="left"] .field-line { justify-content: flex-start; }
.placed[data-align="centre"] .field-line { justify-content: center; }
.placed[data-align="right"] .field-line { justify-content: flex-end; }
.placed[data-align="left"] .field-line b,
.placed[data-align="centre"] .field-line b,
.placed[data-align="right"] .field-line b { margin-left: 0; }

/* How far apart the size columns sit.
 *
 * A board's artwork often has the columns drawn on it - a line down the middle
 * of the panel with SMALL one side and LARGE the other - and the prices have
 * to land inside those, not across the line. Widening the space between the
 * columns pushes them apart into their printed cells.
 *
 * It is set in em, so it grows and shrinks with the block's own type and the
 * fit measures it like any other space. A price itself never splits.
 */
.placed .boxed-grid th, .placed .boxed-grid td {
  padding-left: calc(0.4rem + var(--col-gap, 0) * 0.5em);
  padding-right: calc(0.4rem + var(--col-gap, 0) * 0.5em);
}
.placed .boxed-sizes { gap: calc(1.4rem + var(--col-gap, 0) * 1em); }
/* On a plain list the same handle opens up the space between a name and its
   price, which is the same job on a block with one column of prices. */
.placed .boxed-list li { gap: calc(0.6rem + var(--col-gap, 0) * 1em); }

/* Pushing the prices down away from the heading.
 *
 * Two blocks side by side rarely have headings of the same height - one wraps
 * onto two lines, one is set bigger - so their first rows start at different
 * heights and the board reads as crooked. This pushes one block's prices down
 * while its heading stays exactly where it is, so the rows can be lined up
 * across the board. It is a transform, not a margin: a margin would make the
 * block re-measure and shrink the type to pay for the gap, and the rows would
 * end up near where they were aimed rather than on it. */
.placed[data-gap] .boxed-title ~ * { transform: translateY(var(--gap-px, 0px)); }

/* The price under the name, rather than out to the right of it.
 *
 * A narrow block on a busy piece of artwork often has no room for a name and
 * a price on the same line - the type has to shrink until neither can be read
 * across a shop. Two short lines fit where one long one does not. Each row
 * follows whatever the block is aligned to, so a centred block gets the name
 * centred with its price centred underneath. */
.placed[data-stack="on"] .boxed-list li { flex-direction: column; gap: 0; line-height: 1.05; }
.placed[data-stack="on"] .boxed-list li b { margin-left: 0; }
.placed[data-stack="on"] .boxed-list li,
.placed[data-stack="on"][data-align="spread"] .boxed-list li,
.placed[data-stack="on"][data-align="left"] .boxed-list li { align-items: flex-start; }
.placed[data-stack="on"][data-align="centre"] .boxed-list li { align-items: center; }
.placed[data-stack="on"][data-align="right"] .boxed-list li { align-items: flex-end; }
.placed[data-stack="on"][data-align="spread"] .boxed-list { align-items: stretch; }

.placed[data-stack="on"] .field-line { flex-direction: column; gap: 0; line-height: 1.05; }
.placed[data-stack="on"] .field-line b { margin-left: 0; }
.placed[data-stack="on"][data-align="centre"] .field-line { align-items: center; }
.placed[data-stack="on"][data-align="right"] .field-line { align-items: flex-end; }
.placed[data-stack="on"][data-align="spread"] .field-line,
.placed[data-stack="on"][data-align="left"] .field-line { align-items: flex-start; }

/* The heading goes where the rest of the block goes. Centring a block's
   prices and leaving its name hard against the left is not what anybody
   means by centring it. "Spread" leaves the heading where reading starts. */
.placed[data-align="left"] .boxed-title { text-align: left; }
.placed[data-align="centre"] .boxed-title { text-align: center; }
.placed[data-align="right"] .boxed-title { text-align: right; }

/* A block of the shop's own words - an offer, an opening time, a note - is
   text, not a price list, so it is aligned as text. Without these, setting a
   text block to centre did nothing at all, which reads as the whole setting
   being broken rather than as one kind of block being missed. */
.placed[data-align="left"] .boxed-words,
.placed[data-align="spread"] .boxed-words { text-align: left; }
.placed[data-align="centre"] .boxed-words { text-align: center; }
.placed[data-align="right"] .boxed-words { text-align: right; }

.placed[data-align="left"] .boxed-flat { text-align: left; }
.placed[data-align="centre"] .boxed-flat { text-align: center; }
.placed[data-align="right"] .boxed-flat { text-align: right; }

/* One item with its price above it, and a price on its own. */
.placed[data-align="left"] .field-stack { align-items: flex-start; text-align: left; }
.placed[data-align="centre"] .field-stack { align-items: center; text-align: center; }
.placed[data-align="right"] .field-stack { align-items: flex-end; text-align: right; }

.placed[data-align="left"] .field-price,
.placed[data-align="left"] .field-name { text-align: left; width: 100%; }
.placed[data-align="centre"] .field-price,
.placed[data-align="centre"] .field-name { text-align: center; width: 100%; }
.placed[data-align="right"] .field-price,
.placed[data-align="right"] .field-name { text-align: right; width: 100%; }

/* Picking a face for a board and finding half the board unchanged is not what
   anybody means by picking a face. The names are set in it too, unless a shop
   says otherwise - a heavy display face is harder to read across a shop than
   a plain one, and a long name like CHEESE & ONION FRY IT is where that tells. */
.board.names-in-face .boxed-list li span,
.board.names-in-face .boxed-grid tbody th,
.board.names-in-face .boxed-sizes .bs span,
.board.names-in-face .field-line span,
.board.names-in-face .field-stack span { font-family: var(--display); }

.boxed-title {
  font-family: var(--display); font-size: calc(2.1rem * var(--title-scale, 1)); line-height: 1.14;
  text-transform: uppercase; margin: 0 0 0.2rem;
  /* A heading can be given a colour of its own - red FISH over black prices -
     and otherwise it is the same ink as the rest of the block. */
  color: var(--title-ink, inherit);
}

/* A block asked to wrap.
 *
 * Every row of a price list is normally one unbreakable line, which is what
 * lets a block be measured once and grown to fill its box exactly. It is also
 * why a block full of long names stops getting bigger: there is nowhere for
 * the words to go. Turning wrapping on for one block lets its longest name
 * take a second line, so the rest of it can be read from across a shop.
 */
.placed[data-wrap="on"] .boxed-list li span,
.placed[data-wrap="on"] .boxed-grid th,
.placed[data-wrap="on"] .boxed-grid td,
.placed[data-wrap="on"] .boxed-sizes .bs span,
.placed[data-wrap="on"] .field-line span,
.placed[data-wrap="on"] .field-stack span { white-space: normal; }
.placed[data-wrap="on"] .boxed-list li { align-items: flex-start; }
/* A price never wraps: "£5." on one line and "00" on the next is not a price. */
.placed[data-wrap="on"] .boxed-list li b,
.placed[data-wrap="on"] .boxed-grid tbody td,
.placed[data-wrap="on"] .boxed-sizes .bs b { white-space: nowrap; }
/* One scale for every block on a board.
 *
 * The three ways a block can be drawn - a row of sizes, a list, a grid - each
 * carried their own type sizes, so a price was 2.6rem in one and 1.45rem in
 * the next and the same board read as three different boards. They share
 * these now. Nothing is made smaller by it: each block is still grown to fill
 * the box it was given, so a short block still ends up large - it just ends up
 * large in proportion to the others rather than by accident.
 */
.board {
  --board-price: 1.6rem;
  --board-name: 1.35rem;
  --board-label: 1rem;
}

.boxed-sizes { display: flex; gap: 1.4rem; justify-content: space-around; align-items: flex-end; }
.boxed-sizes .bs { display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
.boxed-sizes .bs span {
  font-size: calc(var(--board-label) * var(--item-scale, 1)); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75;
  white-space: nowrap;
}
.boxed-sizes .bs b { font-family: var(--display); font-size: calc(var(--board-price) * var(--item-scale, 1)); line-height: 1; white-space: nowrap; }

.boxed-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.18rem; }
.boxed-list li { display: flex; align-items: baseline; gap: 0.6rem; font-size: calc(var(--board-name) * var(--item-scale, 1)); line-height: 1.15; }
.boxed-list li span { white-space: nowrap; text-transform: uppercase; font-weight: 600; }
.boxed-list li b { margin-left: auto; font-family: var(--display); font-size: calc(var(--board-price) * var(--item-scale, 1)); white-space: nowrap; }
/* Room at the end of a line for a face that leans.
 *
 * A placed block clips at its box, and a price is pushed hard against the
 * right-hand edge of it. Upright faces stop where their text box stops, so it
 * never showed. A handwriting face leans past the end of its own advance
 * width, so the tail of the last digit was being sliced off: every price on a
 * chalkboard read £5.7 instead of £5.70.
 *
 * A tenth of the price's own size, so it scales with the type and costs an
 * upright face a couple of pixels nobody will see. */
.placed .boxed-list li b,
.placed .boxed-grid tbody td,
.placed .rows tbody td,
.placed .field-line b,
.placed .field-stack b { padding-right: 0.1em; }

/* The price in its own colour, the way every printed board in the trade does
   it. The eye finds the number first and reads the name second. */
.board.prices-lit .boxed-list li b,
.board.prices-lit .boxed-grid tbody td,
.board.prices-lit .rows tbody td,
.board.prices-lit .field-line b,
.board.prices-lit .field-stack b { color: var(--price-ink, var(--price, var(--accent))); }

/* Names set as they are written. Capitals everywhere is a lot of shouting for
   a list of fish, and it costs the words their shape: a reader picks
   "Homemade Fish Cake" out of a column far faster than HOMEMADE FISH CAKE. */
/* The words arrive in the case the shop asked for, so nothing here has
   another go at them. Only the kinds of word the shop can choose: a badge, a
   tag and the ticker are ours and stay as they are drawn. */
.board.letters-set .boxed-list li span,
.board.letters-set .boxed-grid tbody th,
.board.letters-set .rows tbody th,
.board.letters-set .rows thead th,
.board.letters-set .field-line span,
.board.letters-set .field-stack span,
.board.letters-set .item-name,
.board.letters-set .item-desc,
.board.letters-set .panel-title,
.board.letters-set .pb-label,
.board.letters-set .tile-name,
.board.letters-set .card-body h3,
.board.letters-set table.price-matrix tbody th,
.board.letters-set table.price-matrix thead th { text-transform: none; }

.board.names-sentence .boxed-list li span,
.board.names-sentence .boxed-grid tbody th,
.board.names-sentence .rows tbody th,
.board.names-sentence .field-line span,
.board.names-sentence .field-stack span,
.board.names-sentence .item-name { text-transform: none; letter-spacing: 0; }

.boxed-flat { font-family: var(--display); font-size: 2.4rem; margin-top: 0.3rem; }
.boxed-flat span { font-family: inherit; font-size: 1.1rem; opacity: 0.8; }

.boxed-grid { width: 100%; border-collapse: collapse; font-size: calc(var(--board-name) * var(--item-scale, 1)); }
.boxed-grid th, .boxed-grid td { padding: 0.12rem 0.4rem; white-space: nowrap; }
.boxed-grid thead th {
  font-size: var(--board-label); letter-spacing: 0.05em; text-transform: uppercase; opacity: 0.75;
}
.boxed-grid tbody th { text-align: left; text-transform: uppercase; font-weight: 600; }
.boxed-grid tbody td { text-align: right; font-family: var(--display); font-size: calc(var(--board-price) * var(--item-scale, 1)); }

/* Every price column the same width.
 *
 * Left to itself a table makes each column as wide as the widest thing in it,
 * so a section whose large kebab is £10.50 gets a wider right-hand column than
 * the one beside it whose large kebab is £8.70 - and the two columns no longer
 * sit over the same printed lines on the artwork.
 *
 * A price column is given the width of six figures, which is £999.99 and more
 * than any of this. Prices are tabular figures, so six of them is the same
 * width whatever the numbers are, and a column holding £8.70 comes out exactly
 * as wide as the one holding £10.50.
 *
 * NOT table-layout:fixed, which was the first attempt. A fixed table wants a
 * width to divide up, and a block on artwork is measured at its natural width
 * to work out how big it can be drawn - so the table was sizing itself from a
 * measurement that was itself waiting on the table. The board came out at the
 * smallest size it is allowed to be.
 */
.placed .boxed-grid tbody td,
.placed .boxed-grid thead th { min-width: 6ch; }

/* ---- one shape for every block placed on artwork ----------------------
 *
 * The board carries three numbers (typography.js): how tall a row is, how tall
 * a heading slot is, how wide a price column is. `--u` is one per cent of the
 * board's height in pixels and `--uw` one per cent of its width, both written
 * by fitStage(). Every block is a heading slot of exactly --head rows and a
 * table whose every row is exactly --row tall, top to bottom, unscaled. Two
 * blocks with level tops therefore have every row level, by construction.
 *
 * The faces inside a row are fixed shares of the row (FACE in typography.js),
 * so they are the same size on every block. Nothing here is ever measured.
 */
.page-free {
  --row: 4; --head: 6; --col: 6.5;      /* overwritten per page from page.type */
  --rowpx: calc(var(--u, 1px) * var(--row) * var(--size, 1));
  --headpx: calc(var(--u, 1px) * var(--head) * var(--size, 1));
  --colpx: calc(var(--u, 1px) * var(--col));   /* in u, like the rows: no shape maths */
}
/* A block asked to be bigger than the board carries its own --size; the rows
   inside it grow together so it still reads as one block. */
.placed { --size: 1; }
.placed > .placed-inner > .boxed-panel {
  display: block; justify-content: normal; gap: 0;
  --rowpx: calc(var(--u, 1px) * var(--row) * var(--size, 1));
  --headpx: calc(var(--u, 1px) * var(--head) * var(--size, 1));
}

.slot-head {
  height: var(--headpx); line-height: var(--headpx);
  /* Each of the four kinds of word on a board answers to its own three
     variables, written on the box by parts.js and only when asked for. A box
     that says nothing about its headings inherits the board's. */
  font-family: var(--head-face, var(--display));
  font-size: calc(var(--headpx) * var(--head-scale, 0.72));
  text-transform: uppercase; margin: 0; padding: 0;
  white-space: nowrap; overflow: hidden;
  /* A heading can be given a colour of its own - red FISH over black prices. */
  color: var(--head-ink, var(--title-ink, inherit));
}

.rows { width: 100%; border-collapse: collapse; table-layout: fixed; }
.rows tr { height: var(--rowpx); }
.rows th, .rows td {
  padding: 0; height: var(--rowpx); line-height: var(--rowpx);
  vertical-align: middle; white-space: nowrap; overflow: hidden;
}
/* Every price column the same width, by number rather than by content. The
   widths sit on <col>, which a fixed-layout table honours even when a row
   above spans several columns. */
.rows col.price { width: var(--colpx); }
.rows td, .rows thead th:not(:first-child) { text-align: right; }
.rows th:first-child { text-align: left; }
.rows tbody th {
  text-transform: uppercase; font-weight: 600;
  font-size: calc(var(--rowpx) * var(--name-scale, 0.62));
  color: var(--name-ink, inherit);
}
.rows tbody td {
  font-family: var(--price-face, var(--display));
  font-size: calc(var(--rowpx) * var(--price-scale, 0.72));
  color: var(--price-ink, inherit);
}
.rows thead th {
  font-size: calc(var(--rowpx) * 0.46); letter-spacing: 0.05em; text-transform: uppercase; opacity: 0.75;
}
/* A price sits a touch left of the column's edge, so it is not hard against a
   divider printed on the artwork. */
.rows td { padding-right: calc(var(--rowpx) * 0.18); }
.rows td:last-child { padding-right: 0; }
/* The shop's own words: one row a line, in the body face. */
.rows.words tbody th { text-transform: none; font-weight: 500; font-size: calc(var(--rowpx) * 0.62); }
.board.names-in-face .rows tbody th { font-family: var(--name-face, var(--display)); }
/* A name given a face of its own is in that face whether or not the board
   sets its names in the display one. */
.rows tbody th { font-family: var(--name-face, inherit); }

/* Where the words sit. Spread is a price list: name left, price hard right. */
.placed[data-align="left"] .rows,
.placed[data-align="centre"] .rows,
.placed[data-align="right"] .rows { width: auto; table-layout: auto; }
.placed[data-align="left"] .rows th:first-child,
.placed[data-align="centre"] .rows th:first-child,
.placed[data-align="right"] .rows th:first-child { padding-right: calc(var(--rowpx) * 0.5); }
.placed[data-align="centre"] .rows { margin-inline: auto; }
.placed[data-align="right"] .rows { margin-left: auto; }
.placed[data-align="left"] .slot-head { text-align: left; }
.placed[data-align="centre"] .slot-head { text-align: center; }
.placed[data-align="right"] .slot-head { text-align: right; }

/* Price under the name: two rows an item, the second one the price alone. */
.rows tr.under td { text-align: left; }
.placed[data-align="right"] .rows tr.under td { text-align: right; }
.placed[data-align="centre"] .rows tr.under td { text-align: center; }

/* Wrap text: a long name may take a second row. Rows below it in this block
   move down, which the design screen points out. A price never wraps. */
.placed[data-wrap="on"] .rows tbody th { white-space: normal; line-height: 1.1; }

/* A line squeezed sideways to fit its cell, so a long name never pushes a
   row. Set by fitInk(); the rows themselves never move. */
.ink-squeeze { display: inline-block; transform-origin: left center; }
.placed[data-align="right"] .ink-squeeze { transform-origin: right center; }
.placed[data-align="centre"] .ink-squeeze { transform-origin: center; }

/* Ink follows the page, exactly as the hand-placed blocks do. */
.board.ink-dark .boxed-panel { color: #13294d; }
.board.ink-dark .boxed-sizes .bs span, .board.ink-dark .boxed-grid thead th { color: #2c5c9e; }
.board.ink-light .boxed-panel { color: #f6f2e6; }
.board.ink-light .boxed-sizes .bs span, .board.ink-light .boxed-grid thead th { color: #e6c479; }

/* ---- smart text dropped on a background ---------------------------------
   One line tied to one thing on the menu. It is sized to the box it was
   dropped into, so the only decisions left are where it goes and what it says. */
.panel-field { display: flex; align-items: center; width: 100%; }
.field-line { display: flex; align-items: baseline; gap: 0.8rem; width: 100%; }
/* The name on a block of its own is a heading, not a line in a price list, so
   it is set in the same face as a section heading. FISH beside ALL DAY
   SPECIALS in two different typefaces reads as a mistake, because it is one. */
.field-line span {
  font-family: var(--display); font-size: 2.1rem; line-height: 1.14;
  text-transform: uppercase; letter-spacing: 0.005em; white-space: nowrap;
}
.field-line b { margin-left: auto; font-family: var(--display); font-size: 2.2rem; white-space: nowrap; }
.field-stack { display: flex; flex-direction: column; gap: 0.1rem; width: 100%; }
.field-stack span {
  font-family: var(--display); font-size: 2rem; line-height: 1.14;
  text-transform: uppercase; letter-spacing: 0.005em; white-space: nowrap;
}
.field-stack b { font-family: var(--display); font-size: 3rem; line-height: 1; white-space: nowrap; }
.field-price { font-family: var(--display); font-size: 3.4rem; line-height: 1; white-space: nowrap; }
.field-name {
  font-family: var(--display); font-size: 2.6rem; line-height: 1;
  text-transform: uppercase; white-space: nowrap;
}
.field-size { display: inline-flex; flex-direction: column; align-items: center; margin-left: 1rem; }
.field-size i {
  font-style: normal; font-size: 0.9rem; letter-spacing: 0.06em;
  text-transform: uppercase; opacity: 0.75;
}
.boxed-words { font-size: 1.5rem; line-height: 1.25; }

.board.ink-dark .panel-field, .board.ink-dark .boxed-words { color: #13294d; }
.board.ink-dark .field-size i { color: #2c5c9e; }
.board.ink-light .panel-field, .board.ink-light .boxed-words { color: #f6f2e6; }
.board.ink-light .field-size i { color: #e6c479; }

/* A colour chosen for a board, or for one block on it, wins over the page's
   light-or-dark default - it was picked while looking at the artwork. */
.board .panel[style*="color"] .boxed-title,
.board .panel[style*="color"] .boxed-list li,
.board .panel[style*="color"] .boxed-list li b,
.board .panel[style*="color"] .boxed-sizes .bs b,
.board .panel[style*="color"] .boxed-grid th,
.board .panel[style*="color"] .boxed-grid td,
.board .panel[style*="color"] .boxed-words,
.board .panel[style*="color"] .field-line span,
.board .panel[style*="color"] .field-line b,
.board .panel[style*="color"] .field-stack span,
.board .panel[style*="color"] .field-stack b,
.board .panel[style*="color"] .field-price,
.board .panel[style*="color"] .field-name { color: inherit; }
.board .panel[style*="color"] .boxed-sizes .bs span,
.board .panel[style*="color"] .boxed-grid thead th,
.board .panel[style*="color"] .field-size i { color: inherit; opacity: .78; }

/* ...except a heading that was given a colour of its own. Same weight as the
   rule above and written after it, so the more particular choice wins. */
.board .panel[style*="color"] .boxed-title { color: var(--title-ink, inherit); }


/* A picture placed on the board. It takes the whole box it was drawn, and
   keeps its own shape inside it unless it was told to fill. */
.panel-picture { width: 100%; height: 100%; display: block; overflow: hidden; }
.panel-picture img { display: block; width: 100%; height: 100%; }
.panel-picture .pic-fit { object-fit: contain; }
.panel-picture .pic-fill { object-fit: cover; }
.placed:has(.panel-picture) .placed-inner { padding: 0; }

/* Movement, kept quiet on purpose: a board over a counter is read, not
   watched, and anything livelier than this reads as a fault with the screen. */
.move-fade { animation: boardFade 9s ease-in-out infinite; }
.move-float { animation: boardFloat 7s ease-in-out infinite; }
.move-pulse { animation: boardPulse 6s ease-in-out infinite; }
@keyframes boardFade { 0%, 100% { opacity: 1 } 50% { opacity: .55 } }
@keyframes boardFloat { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-3%) } }
@keyframes boardPulse { 0%, 100% { transform: scale(1) } 50% { transform: scale(1.04) } }
@media (prefers-reduced-motion: reduce) {
  .move-fade, .move-float, .move-pulse { animation: none; }
}
