/* ==========================================================================
   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.

  Two ways to have no stage, and both must be caught (slice X9c). The stage can
  be *hidden* — a kebab with no photograph — or **absent**, since slice X9b
  stopped rendering it at all when a superadmin switches the artwork off. A rule
  that only knew the first left the panel split in two with a white void where
  the picture used to be, and a light chip on it.
*/
.builder__panel:has(.builder__stage[hidden]) .builder__close,
.builder__panel:not(:has(.builder__stage)) .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.

  **Hidden and absent are two different states and the collapse must answer
  both** (slice X9c). Slice X9b moved the superadmin's switch server-side, so a
  platform with the artwork off has no `.builder__stage` element at all — and a
  rule written only for `[hidden]` stopped matching, which left the dialog cut
  down the middle with an empty half. That was the owner's report the day it
  went live, and it is the standing cost of keying a layout on a child that may
  simply not be there.
*/
.builder__stage[hidden] { display: none; }

.builder__panel:has(.builder__stage[hidden]),
.builder__panel:not(:has(.builder__stage)) {
  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; }

/* Scoped to the dialog since slice Y-C, and not for tidiness: `checkout.php`
   marks five of its own fieldsets `.builder__group` and never loaded this
   stylesheet, so they have always shown the browser's default fieldset border.
   Y12 puts builder.css on that page for the dialog's sake, and an unscoped rule
   would have restyled the whole checkout as a side effect of adding an edit
   button. Whether those fieldsets should lose their border is a design question,
   not a consequence of this slice. */
.builder .builder__group { border: none; padding: 0; margin: 1.35rem 0 0; }
.builder .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` and `.size-pill` moved to app.css on 2026-08-15 — they are shared by
   the checkout, which does not load this file. See the block there. */

/*
  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; }
}
