/* ==========================================================================
   Pizza builder.

   The stage is dark while the rest of the storefront is light paper. That is
   deliberate and functional: pale toppings — mozzarella, onion, pineapple —
   disappear against a light background and read clearly against a dark one.
   It also puts a spotlight on the thing being built.
   ========================================================================== */

.builder {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: end center;
  background: rgb(12 9 8 / 62%);
  backdrop-filter: blur(3px);
  animation: builder-in 180ms ease both;
}

.builder[hidden] { display: none; }

@keyframes builder-in { from { opacity: 0; } to { opacity: 1; } }

.builder__panel {
  position: relative;
  display: grid;
  grid-template-areas: "stage" "controls" "foot";
  grid-template-rows: auto 1fr auto;
  width: min(100%, 980px);
  max-height: 94vh;
  background: var(--surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  overflow: hidden;
  animation: builder-rise 260ms cubic-bezier(.2, .9, .3, 1) both;
}

@keyframes builder-rise { from { transform: translateY(28px); } to { transform: none; } }

@media (min-width: 880px) {
  .builder { place-items: center; }

  .builder__panel {
    grid-template-areas: "stage controls" "stage foot";
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr auto;
    border-radius: var(--radius-lg);
    max-height: 88vh;
  }
}

/*
  The chip is opaque, never translucent: it must not borrow contrast from
  whatever happens to be behind it. Which ground it sits on decides its colour,
  and there are exactly two — the dark stage, and the white panel of an item
  with no stage. Both are handled below.
*/
.builder__close {
  position: absolute;
  top: 0.6rem;
  right: 0.7rem;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  /*
    Light on dark. This chip sits on the --night stage, and in --ink it was a
    near-black button on a near-black gradient: present, working, and invisible
    on every pizza and every burger since the builder shipped. Customers read
    the dialog as forcing a purchase, which was the correct reading of what
    they could see.
  */
  background: var(--paper);
  color: var(--ink);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.92;
  box-shadow: 0 2px 10px rgb(0 0 0 / 35%);
  transition: opacity 140ms ease, transform 140ms ease;
}

.builder__close:hover { opacity: 1; transform: scale(1.06); }

/* No stage means a white panel, so the chip flips back to dark on light. */
.builder__panel:has(.builder__stage[hidden]) .builder__close {
  background: var(--ink);
  color: var(--paper);
  box-shadow: none;
}

/* --- Stage ---------------------------------------------------------------- */

.builder__stage {
  grid-area: stage;
  display: grid;
  place-items: center;
  gap: 0.75rem;
  padding: 1.75rem 1.25rem;
  background:
    radial-gradient(circle at 50% 42%, #2c211c 0%, var(--night) 62%);
}

.builder__size-note {
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgb(247 244 239 / 55%);
}

.pizza {
  position: relative;
  width: min(58vw, 340px);
  aspect-ratio: 1;
  transition: transform 340ms cubic-bezier(.34, 1.35, .64, 1);
  transform-origin: center;
  will-change: transform;
  filter: drop-shadow(0 18px 26px rgb(0 0 0 / 45%));
}

@media (min-width: 880px) { .pizza { width: min(34vw, 380px); } }

/*
  The burger's stage. Sized to the same box the pizza occupies so the dialog does
  not change shape between kinds, and squared off rather than round — it is a
  photograph of a burger, not a pie.
*/
.builder__photo {
  width: min(58vw, 340px);
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
  background: var(--night-soft);
  box-shadow: 0 18px 26px rgb(0 0 0 / 45%);
}

@media (min-width: 880px) { .builder__photo { width: min(34vw, 380px); } }

/* A base rule that sets a size still needs its own [hidden]: the element is
   display:inline by default, so the UA rule wins here — but .pizza above sets
   display through its own box, and the pair must hide the same way. */
.builder__photo[hidden] { display: none; }
.pizza[hidden] { display: none; }

/*
  A stage with nothing on it is not a stage.

  A kebab has no layer stack and usually no photograph either, and an empty half
  of the dialog reads as an image that failed to load rather than as an item
  nobody has photographed. The panel keeps its whole width for the questions
  instead. A shop that uploads a picture gets the stage back.
*/
.builder__stage[hidden] { display: none; }

.builder__panel:has(.builder__stage[hidden]) {
  grid-template-areas: 'controls' 'foot';
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  /* Half the panel was the picture. Without it, 980px of white around three
     rows of chips reads as a page that failed to finish loading. */
  width: min(100%, 620px);
}

.pizza__layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  opacity: 1;
  transform: none;
  transition: opacity 200ms ease, transform 200ms ease;
}

/*
  Half and half. The artwork is one round topping layer and half of it is simply
  not drawn — a clip, not a second image, so every topping in the library works
  on a split pizza the day it is added with no extra file.

  The clip is on the layer and never on the stack: .pizza carries a transform
  for diameter, and clipping the scaled parent would cut the pizza itself rather
  than the topping. A layer with no data-half is drawn whole, which is every
  order placed before this existed.
*/
.pizza__layer[data-half="left"]  { clip-path: inset(0 50% 0 0); }
.pizza__layer[data-half="right"] { clip-path: inset(0 0 0 50%); }

/* The seam, so a half-topped pizza reads as deliberate rather than broken. */
.pizza--split::after {
  content: '';
  position: absolute;
  top: 8%;
  bottom: 8%;
  left: 50%;
  border-left: 1px dashed rgb(0 0 0 / 22%);
  z-index: 99;
  pointer-events: none;
}

/* Start position for a topping about to drop in. */
.pizza__layer--dropping {
  opacity: 0;
  transform: translateY(-120px) scale(1.25) rotate(-6deg);
}

/* The settle: overshoot past rest and back. Reads as pieces landing. */
.pizza__layer--settling {
  opacity: 1;
  transform: none;
  transition: opacity 180ms ease, transform 420ms cubic-bezier(.34, 1.4, .64, 1);
}

.pizza__layer--leaving {
  opacity: 0;
  transform: translateY(28px) scale(.94);
  transition: opacity 200ms ease, transform 200ms ease;
}

/* --- Whole or half and half ----------------------------------------------- */

/*
  Both of these set `display`, which outranks the UA's [hidden] { display: none }
  — without their own [hidden] rule the mode switch and the chips would render on
  every whole pizza. Cost paid once already, on the slot picker.
*/
.modes {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.9rem;
}

.modes[hidden] { display: none; }

.mode-pill {
  flex: 1;
  min-width: 0;
  padding: 0.45rem 0.7rem;
  border: 1px solid rgb(247 244 239 / 22%);
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.mode-pill.is-active {
  background: rgb(247 244 239 / 92%);
  color: #191512;
  font-weight: 650;
}

/* Two columns on every width. The halves are a pair, and stacking them loses
   exactly the thing the control is trying to say. */
.halves {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.halves[hidden] { display: none; }

.half {
  min-width: 0;
  border: 1px solid rgb(247 244 239 / 16%);
  border-radius: 0.6rem;
  padding: 0.45rem;
}

.half.is-editing { border-color: rgb(247 244 239 / 70%); }

.half__pick {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  padding: 0;
  cursor: pointer;
}

.half__side {
  display: block;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.6;
}

.half__name {
  display: block;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.half__swap {
  width: 100%;
  margin-top: 0.35rem;
  font: inherit;
  font-size: 0.8rem;
  padding: 0.2rem;
  border-radius: 0.35rem;
  border: 1px solid rgb(247 244 239 / 22%);
  background: rgb(25 21 18 / 60%);
  color: inherit;
}

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

.builder__controls {
  grid-area: controls;
  padding: 1.35rem 1.35rem 0.5rem;
  overflow-y: auto;
}

.builder__controls h2 {
  font-size: var(--step-3);
  letter-spacing: -0.03em;
}

.builder__desc { color: var(--ink-soft); font-size: var(--step--1); margin-top: 0.15rem; }

.builder__group { border: none; padding: 0; margin: 1.35rem 0 0; }
.builder__group legend { padding: 0; margin-bottom: 0.5rem; }
.builder__hint { font-size: var(--step--1); color: var(--ink-faint); margin: -0.25rem 0 0.6rem; }

/* The shop's own questions. A plain container: each group inside it is a
   .builder__group and already carries its own spacing. */
.optiongroups { display: grid; }

/* A base rule that sets `display` outranks the UA's [hidden] { display: none },
   so a container that hides itself has to say so. Paid for once already, by a
   slot picker that rendered a whole week on screen. */
.optiongroups[hidden] { display: none; }

.sizes { display: flex; gap: 0.4rem; flex-wrap: wrap; }

.size-pill {
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-weight: 650;
  font-size: var(--step--1);
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}

.size-pill:hover { border-color: var(--ink-soft); }

.size-pill.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/*
  Wide enough for the longest name this menu can hold.

  It was 9,5rem, which fits "Kinkku +0,90 €" and stops fitting somewhere around
  "Vegaanijuusto +1,50 €" — so the longest names pushed their price past the
  chip edge, and once the chip was allowed to wrap they broke mid-word instead:
  "Valkosipulik / astike". Finnish compounds are single words, so no wrapping
  rule can rescue a column that is too narrow. The column is the fix.
*/
.toppings {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.4rem;
}

.topping {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
  cursor: pointer;
  transition: border-color 140ms ease, background-color 140ms ease;
}

.topping:hover { border-color: var(--ink-soft); }

.topping.is-on {
  border-color: var(--ink);
  background: #fbf7f0;
}

/* A filled square marks what is on the pizza — visible without relying on colour. */
.topping::before {
  content: "";
  order: -1;
  width: 0.85rem;
  height: 0.85rem;
  border: 1.5px solid var(--ink-faint);
  border-radius: 3px;
  flex: none;
  transition: background-color 140ms ease, border-color 140ms ease;
}

.topping.is-on::before {
  background: var(--brand);
  border-color: var(--brand);
  box-shadow: inset 0 0 0 2px var(--surface);
}

/* min-width: 0 so a long name shrinks instead of shoving the price out of the
   chip. A flex item's floor is its content width until it is told otherwise —
   and shrinking the box is only half the job: without a wrap rule the text
   itself still runs out over the price. "Valkosipulikastike" is the one that
   proves it, on a two-column grid. */
.topping__name {
  flex: 1;
  min-width: 0;
  /* The last resort, not the plan: the column above is wide enough for the
     longest name on the standard library, and this only catches a longer one. */
  overflow-wrap: break-word;
  font-size: var(--step--1);
  font-weight: 600;
}

.topping__price {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--ink-faint);
  white-space: nowrap;
}

.topping.is-on .topping__price { color: var(--ink-soft); }

/* --- Foot ----------------------------------------------------------------- */

.builder__foot {
  grid-area: foot;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  padding: 0.9rem 1.35rem;
  border-top: 1px solid var(--line);
  background: var(--surface);
}

/* A long Finnish label is not a wrapping opportunity. */
#builder-add { white-space: nowrap; }

/* .builder__price already carries the margin-left:auto that pushes the primary
   action to the right edge — see further down this file. */

.builder__cancel { border-color: transparent; color: var(--ink-soft); }
.builder__cancel:hover { color: var(--ink); }

.builder__qty {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.builder__qty .btn { min-height: 2.25rem; padding: 0.35rem 0.7rem; }
.builder__qty span { min-width: 1.75rem; text-align: center; font-weight: 700; }

.builder__price {
  margin-left: auto;
  font-size: var(--step-2);
  font-weight: 750;
  /* "6,90 €" broke across two lines in the desktop panel, whose foot only ever
     gets half of 980px — narrower than the phone breakpoint ever tests. */
  white-space: nowrap;
}

/* The price nudges when it changes, so a topping tap is felt as well as seen. */
.builder__price.is-bumped { animation: price-bump 320ms cubic-bezier(.34, 1.4, .64, 1); }

@keyframes price-bump {
  0%   { transform: none; }
  35%  { transform: translateY(-4px) scale(1.06); }
  100% { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .pizza__layer--dropping { opacity: 0; transform: none; }
  .pizza__layer--settling,
  .pizza__layer--leaving { transition: opacity 120ms linear; transform: none; }
  .pizza { transition: none; }
}

/* --- The foot on a phone ---------------------------------------------------
   Four controls do not fit on one line. Cancel and quantity take the first row,
   price and the primary action the second.

   This block is last in the file on purpose: .builder__price sets
   margin-left:auto further up, and an equal-specificity rule declared after a
   media query wins inside it too.
   -------------------------------------------------------------------------- */

@media (max-width: 560px) {
  .builder__foot { flex-wrap: wrap; gap: 0.5rem 0.75rem; }
  .builder__cancel { order: 1; }
  .builder__qty { order: 2; margin-left: auto; }
  .builder__price { order: 3; margin-left: 0; }
  #builder-add { order: 4; margin-left: auto; }
}
