/*
 * Shelf designer — Simple / Flexible / Advanced.
 *
 * One layout for all three interfaces: the same viewport, the same bottom bar,
 * the same sheets. Switching interface never moves a control to a new home, so
 * someone who starts in Simple and moves up does not have to relearn the page.
 *
 * Every interface has exactly ONE place for the options that finish a design —
 * colour, bookends, starting again. In Simple that is the control column, which
 * is Simple's whole interface. Flexible and Advanced have no column: their
 * design is made on the model, so those same fields live in a sheet behind the
 * button at the bottom right of the model, and are built by the same functions.
 * A screen shows one job at a time, which is what a phone has room for.
 *
 * Mobile-first. On a wide screen the control column moves from under the
 * viewport to beside it; the bottom bar spans both, in all three interfaces.
 */

:root {
  --nd-ink: #202424;
  --nd-muted: #68726f;
  --nd-line: #dde3e0;
  --nd-line-strong: #c3ccc8;
  --nd-panel: #ffffff;
  --nd-soft: #f4f7f5;
  --nd-accent: #245d5f;
  --nd-accent-soft: #e6f0ee;
  --nd-danger: #a34034;
  --nd-whatsapp: #25d366;
  --nd-radius: 10px;
  --nd-tap: 44px;
  --nd-shadow: 0 1px 2px rgba(32, 36, 36, .06), 0 8px 24px rgba(32, 36, 36, .08);
  --nd-panel-h: min(46vh, 400px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--nd-soft);
  color: var(--nd-ink);
  font-family: 'Red Hat Display', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; }

/* Focus is only drawn for keyboard users: a persistent ring after every tap
   reads as a stuck button on a phone. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--nd-accent); outline-offset: 2px; }

/* ---------------------------------------------------------------- shell -- */

.nd-app {
  display: flex;
  flex-direction: column;
  /* dvh tracks the collapsing mobile URL bar; vh is the fallback. */
  height: 100vh;
  height: 100dvh;
}

.nd-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--nd-panel);
  border-bottom: 1px solid var(--nd-line);
  flex: 0 0 auto;
}

.nd-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: inherit;
  flex: 0 0 auto;
}
.nd-brand img { display: block; width: 28px; height: auto; }
.nd-brand span { font-weight: 700; font-size: .95rem; letter-spacing: .01em; }

@media (max-width: 420px) {
  .nd-brand span { display: none; }
}


/* Interface switch — the one control that is identical in all three modes. */
.nd-modes {
  display: flex;
  margin: 0 auto;
  background: var(--nd-soft);
  border: 1px solid var(--nd-line);
  border-radius: 999px;
  padding: 2px;
  gap: 2px;
}

.nd-modes button {
  border: 0;
  background: transparent;
  color: var(--nd-muted);
  padding: 7px 14px;
  border-radius: 999px;
  font-size: .85rem;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}

.nd-modes button[aria-selected="true"] {
  background: var(--nd-panel);
  color: var(--nd-ink);
  font-weight: 700;
  box-shadow: 0 1px 2px rgba(32, 36, 36, .12);
}

.nd-history { display: flex; gap: 4px; flex: 0 0 auto; }

.nd-iconbutton {
  width: 38px;
  height: 38px;
  border: 1px solid var(--nd-line);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  color: var(--nd-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.nd-iconbutton:disabled { opacity: .35; cursor: default; }
.nd-iconbutton svg { width: 18px; height: 18px; }

/*
 * On a 320px phone the mark, the three interface names and undo/redo came to
 * 386px and the row overflowed, putting redo off the edge of a page that
 * cannot scroll sideways. Everything gives up a little, and the mark gives up
 * the most: the interface switch and the history are the tool, and the logo is
 * a link to a homepage that the back button also reaches.
 */
@media (max-width: 380px) {
  .nd-top { gap: 6px; padding-left: 8px; padding-right: 8px; }
  .nd-brand { display: none; }
  .nd-modes button { padding: 7px 8px; font-size: .76rem; }
  .nd-iconbutton { width: 34px; height: 34px; }
  .nd-iconbutton svg { width: 16px; height: 16px; }
}

.nd-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ---------------------------------------------------------------- stage -- */

.nd-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: #fff;
  overflow: hidden;
  /* The canvas handles its own panning, so the browser must not also scroll
     or double-tap-zoom the page under the finger. */
  touch-action: none;
}

.nd-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.nd-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.nd-overlay > * { pointer-events: auto; }

/* In-viewport "add here" affordance. */
.nd-plus {
  position: absolute;
  width: 38px;
  height: 38px;
  margin: -19px 0 0 -19px;
  border-radius: 50%;
  border: 2px solid var(--nd-panel);
  background: var(--nd-accent);
  color: #fff;
  font-size: 1.35rem;
  line-height: 1;
  font-weight: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(32, 36, 36, .3);
  padding: 0;
  transition: transform .12s ease;
}
.nd-plus:active { transform: scale(.92); }

/* Advanced: the marker whose placement is currently previewed in 3D. Tapping it
   again commits, so it says so. */
.nd-plus.is-confirm {
  background: var(--nd-ink);
  font-size: 1.05rem;
  transform: scale(1.08);
}

/* Comfortable touch target without a 44px disc cluttering the model. */
.nd-plus::after {
  content: "";
  position: absolute;
  inset: -6px;
}

.nd-actions {
  position: absolute;
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--nd-panel);
  border: 1px solid var(--nd-line);
  border-radius: var(--nd-radius);
  box-shadow: var(--nd-shadow);
}
.nd-actions button {
  border: 0;
  background: transparent;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: .85rem;
  font-weight: 500;
  min-height: 36px;
  white-space: nowrap;
}
.nd-actions button:hover { background: var(--nd-soft); }
.nd-actions button.is-danger { color: var(--nd-danger); }

/* Top-right, not bottom-right: in this isometric view a run grows towards the
   lower right, so that corner is exactly where the "add a unit" affordance
   travels to as the shelf gets wider. */
.nd-stage-tools {
  position: absolute;
  right: 10px;
  top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.nd-stage-tools button {
  width: 38px;
  height: 38px;
  border-radius: var(--nd-radius);
  border: 1px solid var(--nd-line);
  background: rgba(255, 255, 255, .94);
  color: var(--nd-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(32, 36, 36, .12);
}
.nd-stage-tools svg { width: 17px; height: 17px; }

/* Above the floating buttons, centred, so it never sits under either of them. */
.nd-hint {
  position: absolute;
  left: 50%;
  bottom: 66px;
  transform: translateX(-50%);
  /* Capped by measure as well as by the frame: over a wide model a hint set
     the width of the screen is a sentence nobody's eye tracks. */
  max-width: min(calc(100% - 24px), 46ch);
  text-align: center;
  font-size: .8rem;
  color: var(--nd-muted);
  background: rgba(255, 255, 255, .92);
  border-radius: 8px;
  padding: 6px 10px;
  pointer-events: none;
}
.nd-hint[hidden] { display: none; }
.nd-hint.is-error { color: var(--nd-danger); background: #fdeceb; }

.nd-busy {
  position: absolute;
  top: 10px;
  left: 50%;
  display: flex;
  align-items: center;
  gap: 8px;
  transform: translateX(-50%);
  background: rgba(32, 36, 36, .85);
  color: #fff;
  border-radius: 999px;
  padding: 5px 14px;
  font-size: .78rem;
  pointer-events: none;
}
.nd-busy::before {
  content: "";
  width: 12px;
  height: 12px;
  border: 2px solid rgba(255, 255, 255, .45);
  border-top-color: #fff;
  border-radius: 50%;
  animation: nd-spin .8s linear infinite;
}
.nd-busy[hidden] { display: none; }

@keyframes nd-spin {
  to { transform: rotate(360deg); }
}

.nd-app[data-loading="saved"] .nd-stage canvas { visibility: hidden; }
.nd-app[data-loading="saved"] .nd-busy {
  top: 50%;
  transform: translate(-50%, -50%);
  background: rgba(255, 255, 255, .96);
  color: var(--nd-ink);
  box-shadow: var(--nd-shadow);
}
.nd-app[data-loading="saved"] .nd-busy::before {
  border-color: rgba(36, 93, 95, .22);
  border-top-color: var(--nd-accent);
}

/* Drafting-style dimensions, drawn under the interactive overlay. */
.nd-dim-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}

.nd-dim-line {
  stroke: var(--nd-muted);
  stroke-width: 1;
  shape-rendering: crispEdges;
}
.nd-dim-arrow {
  stroke: var(--nd-muted);
  stroke-width: 1;
  stroke-linecap: round;
}
.nd-dim-witness {
  stroke: var(--nd-line-strong);
  stroke-width: 1;
  shape-rendering: crispEdges;
}
.nd-dim-text {
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  fill: var(--nd-ink);
  /* Reads over the model as well as over the background. */
  stroke: #fff;
  stroke-width: 3.5px;
  paint-order: stroke fill;
}

/* The front view frames itself, so pan/zoom/dimensions have nothing to act on. */
.nd-app[data-view="perspective"] #nd-zoom-in,
.nd-app[data-view="perspective"] #nd-zoom-out,
.nd-app[data-view="perspective"] #nd-dimensions,
.nd-app[data-view="perspective"] #nd-fit {
  opacity: .35;
  pointer-events: none;
}

.nd-stage-tools button.is-active {
  background: var(--nd-accent);
  border-color: var(--nd-accent);
  color: #fff;
}

/* ------------------------------------------------------- floating buttons -- */

/*
 * Two buttons at the bottom corners of the model, and nothing else on it.
 *
 * Add is on the left because that is the reading start of the row, and it is
 * the one that changes what the model is for: in Advanced it opens the piece
 * list, and while a piece is waiting to be placed it turns into the cancel for
 * that same decision — the button you pressed is the button you press to undo
 * pressing it. Options sits opposite, quiet, because it is finishing rather
 * than building.
 */
.nd-fab {
  position: absolute;
  bottom: 12px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: var(--nd-tap);
  padding: 0 16px 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--nd-accent);
  color: #fff;
  font-size: .88rem;
  font-weight: 600;
  box-shadow: 0 2px 10px rgba(32, 36, 36, .28);
  transition: transform .12s ease, background-color .12s ease;
}
.nd-fab svg { width: 20px; height: 20px; flex: 0 0 auto; }
.nd-fab:active { transform: scale(.96); }
.nd-fab[hidden] { display: none; }
.nd-fab:disabled { opacity: .45; cursor: default; box-shadow: none; }

.nd-fab-add { left: 12px; }

/* One icon, two states: the cross is only drawn while a piece is waiting for a
   home, so the control never changes size or place as it changes meaning. */
.nd-fab .nd-fab-cross { display: none; }
.nd-fab-add.is-cancel { background: var(--nd-ink); }
.nd-fab-add.is-cancel .nd-fab-plus { display: none; }
.nd-fab-add.is-cancel .nd-fab-cross { display: inline; }

/* Round and unlabelled: it holds settings, and its icon says so on its own. */
.nd-fab-gear {
  right: 12px;
  width: var(--nd-tap);
  padding: 0;
  justify-content: center;
  background: rgba(255, 255, 255, .95);
  color: var(--nd-muted);
  border: 1px solid var(--nd-line);
  box-shadow: 0 1px 6px rgba(32, 36, 36, .16);
}
.nd-fab-gear:hover { color: var(--nd-ink); }

/* The label is the affordance on a phone — "+" alone does not say what it adds
   — but it costs nothing on a wide screen either, so it always shows. */
.nd-fab-label { white-space: nowrap; }

.nd-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  text-align: center;
  background: var(--nd-soft);
  color: var(--nd-muted);
}
.nd-fallback[hidden] { display: none; }
.nd-fallback strong { color: var(--nd-ink); font-size: 1.05rem; }

/* ---------------------------------------------------------------- panel -- */

/*
 * The control column, which only Simple has.
 *
 * Simple's design is generated from a spec, so its interface IS a form and a
 * column is the right shape for it. Flexible and Advanced build on the model
 * itself; a column beside them held a colour picker, a piece list and a price
 * breakdown competing with the thing being designed, which on a phone left the
 * model about a third of the screen.
 */
.nd-panel {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  background: var(--nd-panel);
  border-top: 1px solid var(--nd-line);
  max-height: var(--nd-panel-h);
  min-height: 0;
}
.nd-app:not([data-mode="simple"]) .nd-panel { display: none; }

.nd-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--nd-line);
  flex: 0 0 auto;
}
.nd-panel-head h2 {
  margin: 0;
  font-size: .8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--nd-muted);
  flex: 1 1 auto;
}

.nd-collapse {
  border: 0;
  background: transparent;
  color: var(--nd-muted);
  padding: 6px;
  display: flex;
}
.nd-collapse svg { width: 18px; height: 18px; transition: transform .18s ease; }
.nd-app[data-panel="collapsed"] .nd-collapse svg { transform: rotate(180deg); }
.nd-app[data-panel="collapsed"] .nd-panel-body { display: none; }

.nd-panel-body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.nd-field { display: flex; flex-direction: column; gap: 6px; }
.nd-field-tight { gap: 0; }

/* Quiet caveat under a control, e.g. bookends not appearing in the render. */
.nd-subtext {
  font-size: .72rem;
  line-height: 1.35;
  color: var(--nd-muted);
  margin-top: -2px;
}

.nd-label {
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--nd-muted);
}

.nd-select {
  width: 100%;
  min-height: var(--nd-tap);
  padding: 0 34px 0 12px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2368726f' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>") no-repeat right 10px center;
  background-size: 16px;
  -webkit-appearance: none;
  appearance: none;
}

/* Stepper: label, value, minus/plus. */
.nd-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--nd-tap);
}
.nd-stepper .nd-label { flex: 1 1 auto; }
.nd-stepper-value {
  min-width: 4.4em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.nd-stepper-buttons { display: flex; gap: 6px; }
.nd-stepper-buttons button {
  width: var(--nd-tap);
  height: var(--nd-tap);
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  font-size: 1.2rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nd-stepper-buttons button:disabled { opacity: .35; cursor: default; }

/* Named colours: a small two-tone chip (steel over MDF) above its name. Two of
   the four finishes read similarly at chip size, and the name is what people
   actually say when they order. */
.nd-swatches { display: flex; gap: 8px; }

.nd-swatch {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--nd-tap);
  padding: 5px 2px 6px;
  border: 1.5px solid transparent;
  border-radius: var(--nd-radius);
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

.nd-swatch-chip {
  position: relative;
  display: block;
  width: 100%;
  max-width: 46px;
  height: 20px;
  border-radius: 5px;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(32, 36, 36, .18);
}
.nd-swatch-chip span {
  position: absolute;
  inset: 0;
  display: block;
}
.nd-swatch-chip span + span { left: 50%; }

.nd-swatch-name {
  font-size: .74rem;
  line-height: 1;
  color: var(--nd-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.nd-swatch[aria-pressed="true"] {
  border-color: var(--nd-ink);
  background: var(--nd-soft);
}
.nd-swatch[aria-pressed="true"] .nd-swatch-name { color: var(--nd-ink); font-weight: 700; }

.nd-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--nd-tap);
  cursor: pointer;
}
.nd-toggle input { width: 20px; height: 20px; accent-color: var(--nd-accent); flex: 0 0 auto; }
.nd-toggle span { font-size: .92rem; }
.nd-option-stack { display: flex; flex-direction: column; gap: 2px; }
.nd-toggle-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 10px;
}
.nd-inline-note {
  color: var(--nd-muted);
  font-size: .72rem;
  line-height: 1.25;
  text-align: right;
  max-width: 190px;
}

.nd-note {
  font-size: .78rem;
  line-height: 1.45;
  color: var(--nd-muted);
  margin: 0;
}

.nd-button {
  min-height: var(--nd-tap);
  padding: 0 14px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  font-size: .9rem;
  font-weight: 500;
}
.nd-button:disabled { opacity: .4; cursor: default; }
/* "Open it in Zoho" is a link wearing a button. Without this it renders as
   underlined, left-aligned text in a box the width of the sheet. */
a.nd-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
.nd-button-row { display: flex; gap: 8px; flex-wrap: wrap; }
.nd-confirm-row { margin-top: 6px; }
.nd-button.is-primary {
  background: var(--nd-accent);
  border-color: var(--nd-accent);
  color: #fff;
  font-weight: 700;
}
.nd-button-row .nd-button { flex: 1 1 auto; }

/* Download / Upload / Start again are team tools, not customer ones, so they
   read as secondary. Still a full tap target -- smaller type, not a smaller
   button. */
.nd-button.is-small {
  min-height: 34px;
  padding: 0 10px;
  font-size: .78rem;
  color: var(--nd-muted);
}
.nd-button-row-small { gap: 6px; margin-top: 2px; }

/* The created link, with its copy button. */
.nd-link-out {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.nd-link-url {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem;
  /* The code is the part that matters; let the address wrap rather than clip. */
  overflow-wrap: anywhere;
}
.nd-link-out .nd-button { flex: 0 0 auto; }

/* The design code under the share image. Set larger and wider than the link
   above, because this one gets read off a screen and typed into another
   machine -- the two characters people confuse are 0 and O. */
.nd-present-code {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}
.nd-present-code-label {
  color: var(--nd-ink-soft);
  font-size: .78rem;
}
.nd-present-code code {
  flex: 1 1 auto;
  padding: 7px 10px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .12em;
}
.nd-present-code .nd-button { flex: 0 0 auto; }
/* `display: flex` beats the hidden attribute, so it needs saying. */
.nd-present-code[hidden] { display: none; }

/* Module list (Advanced). */
.nd-search {
  width: 100%;
  min-height: var(--nd-tap);
  padding: 0 12px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  -webkit-appearance: none;
}

.nd-list { display: flex; flex-direction: column; gap: 4px; }
.nd-list-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: var(--nd-tap);
  padding: 6px 10px;
  border: 1px solid var(--nd-line);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  text-align: left;
}
.nd-list-row:hover { border-color: var(--nd-line-strong); }
.nd-list-row[aria-pressed="true"] {
  border-color: var(--nd-accent);
  background: var(--nd-accent-soft);
}
.nd-list-row b { flex: 1 1 auto; font-weight: 500; font-size: .9rem; }
/* The colour sheet reuses these rows, so a swatch has to sit in one without
   stretching the way it does in the field of four. */
.nd-list-row .nd-swatch-chip { flex: 0 0 34px; width: 34px; height: 18px; }
.nd-list-row small { color: var(--nd-muted); font-size: .8rem; font-variant-numeric: tabular-nums; }
/* Secondary detail on a row — set apart from the piece name so a spacing choice
   does not read as part of the product's name. */
.nd-list-row .nd-list-note {
  font-size: .75rem;
  font-style: italic;
  opacity: .85;
}
.nd-list-empty { color: var(--nd-muted); font-size: .85rem; padding: 8px 2px; }

/* Line-item breakdown. */
.nd-lines { display: flex; flex-direction: column; gap: 3px; }
.nd-line {
  display: flex;
  gap: 8px;
  font-size: .85rem;
  padding: 3px 0;
}
.nd-line span:first-child { flex: 1 1 auto; }
.nd-line .nd-qty { color: var(--nd-muted); font-variant-numeric: tabular-nums; }
.nd-line .nd-amount { font-variant-numeric: tabular-nums; min-width: 5.5em; text-align: right; }
.nd-line.is-unpriced .nd-amount { color: var(--nd-muted); }
/* Why the pieces in the picture outnumber the lines above it. Smaller than a
   line, because it is a footnote to the list rather than an entry in it. */
.nd-omitted-note { display: block; margin-top: 6px; font-size: .75rem; font-style: italic; }

/* --------------------------------------------------------------- summary -- */

/*
 * One line, at the foot of every interface: what is in it, what it costs, how
 * big it is, and the two things worth doing with it.
 *
 * It stays a single row at every width. The two actions are deliberately small
 * — the price and the size are what someone is reading, and Present and Order
 * are what they reach for once they have read it. Below 560px they drop their
 * labels rather than wrapping to a second line: a bar that changes height as
 * the design changes makes the model jump.
 */
.nd-summary {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-top: 1px solid var(--nd-line);
  background: var(--nd-panel);
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
}

/* The disclosure for "what is in it", left of the figures it explains. */
.nd-summary-toggle {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border: 1px solid var(--nd-line);
  border-radius: 8px;
  background: var(--nd-panel);
  color: var(--nd-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.nd-summary-toggle svg { width: 16px; height: 16px; transition: transform .18s ease; }
.nd-summary-toggle[aria-expanded="true"] {
  background: var(--nd-soft);
  color: var(--nd-ink);
}
.nd-summary-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.nd-summary-toggle:disabled { opacity: .35; cursor: default; }

.nd-total { flex: 1 1 auto; min-width: 0; }

/* --------------------------------------------------------------- what is in it -- */

/*
 * The piece list, opened from the bar and sitting directly on top of it.
 *
 * Capped rather than free-growing: a 30-piece design would otherwise push the
 * model off the screen to explain itself. On a wide screen it lays out in
 * columns, because one column of "2 x Standard Base" across 1200px is a lot of
 * white space to read a short list in.
 */
.nd-breakdown {
  flex: 0 0 auto;
  max-height: min(38vh, 300px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 12px 12px 4px;
  border-top: 1px solid var(--nd-line);
  background: var(--nd-panel);
}
.nd-breakdown[hidden] { display: none; }
.nd-breakdown .nd-label { display: block; margin-bottom: 6px; }
.nd-breakdown .nd-lines {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  column-gap: 24px;
}
.nd-total-amount {
  display: block;
  font-size: 1.05rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
/* Wraps rather than truncates: on a narrow phone the size and the VAT note do
   not fit on one line, and the size is the part people check. */
.nd-total-note {
  display: block;
  font-size: .72rem;
  line-height: 1.35;
  color: var(--nd-muted);
}

/* Secondary action beside Order: makes the client-facing image. */
.nd-present {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 38px;
  padding: 0 11px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  font-size: .82rem;
  font-weight: 600;
}
.nd-present svg { width: 16px; height: 16px; flex: 0 0 auto; }
.nd-present:disabled { opacity: .4; cursor: default; }

/* The share image itself: shown rather than downloaded, so it can be
   long-pressed on a phone or right-clicked on a desktop. */
.nd-modal-image .nd-modal-panel { max-height: 92vh; max-height: 92dvh; }
.nd-present-frame {
  display: flex;
  justify-content: center;
  background: var(--nd-soft);
  border: 1px solid var(--nd-line);
  border-radius: var(--nd-radius);
  padding: 8px;
}
.nd-present-frame img {
  display: block;
  width: 100%;
  max-width: 360px;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgba(32, 36, 36, .14);
}

.nd-order {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  padding: 0 13px;
  border-radius: var(--nd-radius);
  background: var(--nd-whatsapp);
  color: #fff;
  font-weight: 700;
  font-size: .84rem;
  text-decoration: none;
  white-space: nowrap;
}
.nd-order svg { width: 17px; height: 17px; fill: currentColor; flex: 0 0 auto; }
.nd-order[aria-disabled="true"] { opacity: .45; pointer-events: none; }

/*
 * Narrow phones: the actions keep their icons and lose their words, so the row
 * survives at 320px without wrapping. "Order on WhatsApp" also shortens to
 * "Order" first, at the width where the icon alone would be ambiguous next to
 * a second icon-only button.
 */
@media (max-width: 560px) {
  .nd-order .nd-button-text { font-size: 0; }
  .nd-order .nd-button-text::after { content: "Order"; font-size: .84rem; }
}
@media (max-width: 420px) {
  .nd-summary { gap: 8px; padding-left: 10px; padding-right: 10px; }
  .nd-present .nd-button-text { display: none; }
  .nd-present { padding: 0 10px; }
}

/* ---------------------------------------------------------------- modal -- */

.nd-modal {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(32, 36, 36, .42);
}
.nd-modal[hidden] { display: none; }

.nd-modal-panel {
  width: 100%;
  max-height: 82vh;
  max-height: 82dvh;
  display: flex;
  flex-direction: column;
  background: var(--nd-panel);
  border-radius: 16px 16px 0 0;
  box-shadow: var(--nd-shadow);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.nd-modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 14px 10px;
  flex: 0 0 auto;
}
.nd-modal-head strong { flex: 1 1 auto; font-size: 1rem; }
.nd-modal-close {
  width: 36px;
  height: 36px;
  border: 0;
  background: var(--nd-soft);
  border-radius: 50%;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--nd-muted);
}

.nd-modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 0 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ------------------------------------------------------------ full sheet -- */

/*
 * The order form takes the whole screen on a phone.
 *
 * Everything else in this tool is something you do TO the model, so it shares
 * the screen with it. This is a form: eleven fields, a searchable client list
 * and a submit that creates a real record in the books. Sharing the screen with
 * a shelf meant scrolling past the shelf to reach the fields, and a dropdown
 * with nowhere to open into.
 */
.nd-modal-full .nd-modal-panel {
  height: 100%;
  max-height: 100%;
  border-radius: 0;
}
.nd-modal-full .nd-modal-head {
  border-bottom: 1px solid var(--nd-line);
  padding-bottom: 12px;
}
.nd-modal-full .nd-modal-body { padding-top: 14px; gap: 12px; }

/* --------------------------------------------------------------- desktop -- */

@media (min-width: 860px) {
  .nd-main { flex-direction: row; }

  .nd-stage { flex: 1 1 auto; }

  .nd-panel {
    flex: 0 0 340px;
    max-height: none;
    border-top: 0;
    border-left: 1px solid var(--nd-line);
  }

  /* Collapsing is a small-screen affordance; a side column always has room. */
  .nd-collapse { display: none; }
  .nd-app[data-panel="collapsed"] .nd-panel-body { display: flex; }

  .nd-panel-body { padding: 16px; gap: 18px; }

  .nd-summary { padding: 10px 16px; gap: 14px; }
  .nd-breakdown { padding: 14px 16px 6px; }

  .nd-modal { align-items: center; }
  .nd-modal-panel {
    width: min(460px, 92vw);
    max-height: min(560px, 82vh);
    border-radius: 14px;
  }

  /* A form to work through, not a list to pick from: wider, and tall enough
     that the client dropdown opens without moving anything. */
  .nd-modal-full .nd-modal-panel {
    width: min(720px, 94vw);
    max-height: min(760px, 88vh);
  }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Staff login. Reached from the last row of the Advanced options sheet, and
   nothing about an order is rendered until the password is accepted, so there
   is nothing to read over a shoulder. Customers use Advanced too. */
.nd-staff { border-top: 1px solid var(--nd-line); padding-top: 12px; margin-top: 4px; }
.nd-button.is-quiet {
  background: none;
  border: 1px solid var(--nd-line);
  color: var(--nd-muted);
  font-weight: 500;
}
.nd-button.is-quiet:hover { color: var(--nd-ink); border-color: var(--nd-line-strong); }

.nd-staff-form { display: flex; flex-direction: column; gap: 12px; }
.nd-staff-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 10px; align-items: center; }
/* A row whose control opens something downwards must not be overlapped by the
   rows below it, and stacking context is decided by document order alone. */
.nd-staff-row.is-open { position: relative; z-index: 5; }
.nd-staff-label { font-size: .78rem; color: var(--nd-muted); }
.nd-staff-row input[type="checkbox"] { justify-self: start; width: 20px; height: 20px; }
.nd-staff-address { resize: vertical; font: inherit; padding: 10px 12px; line-height: 1.4; }
.nd-staff-window { display: flex; gap: 8px; align-items: center; }
.nd-staff-window input { flex: 1 1 auto; min-width: 0; }
.nd-note.is-error { color: var(--nd-danger); }

/* A heading inside the form, for the fields that only exist in one branch of
   it — the four that describe a client who is not in the list yet. */
.nd-staff-group {
  border: 1px solid var(--nd-line);
  border-radius: var(--nd-radius);
  padding: 10px 12px 12px;
  background: var(--nd-soft);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.nd-staff-group[hidden] { display: none; }
.nd-staff-group > .nd-label { margin-bottom: -2px; }
.nd-staff-group .nd-search { background: var(--nd-panel); }

/* Where the form's own actions sit: full width, and last. */
.nd-staff-form .nd-button.is-primary { margin-top: 4px; }

/* ------------------------------------------------------------- combo box -- */

/*
 * The client picker: a text box with a list that OVERLAYS what is under it.
 *
 * It used to be a block of buttons pushed into the form between the field and
 * the next one, so choosing a client moved every remaining field down the page
 * and, further down the form, opened below the fold with no way to reach it.
 * A dropdown is absolutely positioned for exactly this reason — the layout must
 * not know or care that it is open.
 *
 * Every client is already in the browser by the time this renders (they arrive
 * with the password check), so filtering is an array filter and there is no
 * request, no debounce and no spinner between a keystroke and the list.
 */
.nd-combo { position: relative; min-width: 0; }

.nd-combo-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 6;
  max-height: 268px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  background: var(--nd-panel);
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  box-shadow: var(--nd-shadow);
  padding: 4px;
}
.nd-combo-list[hidden] { display: none; }

.nd-combo-option {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  text-align: left;
  font-size: .9rem;
}
.nd-combo-option:hover,
.nd-combo-option.is-active {
  background: var(--nd-accent-soft);
  color: var(--nd-ink);
}
.nd-combo-option b { font-weight: 500; }
/* The matched run of characters, so a long list explains why each row is in it. */
.nd-combo-option mark { background: none; color: var(--nd-accent); font-weight: 700; }

/* "+ New client" is pinned to the bottom, always — not only when nothing
   matches. A name spelled differently in Zoho is exactly the case where
   somebody needs it and the list is not empty. */
.nd-combo-new {
  position: sticky;
  bottom: -4px;
  margin-top: 4px;
  border-top: 1px solid var(--nd-line);
  border-radius: 0 0 7px 7px;
  background: var(--nd-panel);
  color: var(--nd-accent);
  font-weight: 600;
}
.nd-combo-new:hover, .nd-combo-new.is-active { background: var(--nd-accent-soft); }

.nd-combo-empty { color: var(--nd-muted); font-size: .85rem; padding: 10px; }

/* The chosen client, shown as a fact rather than as text still in a box. */
.nd-combo-chosen {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--nd-tap);
  padding: 0 6px 0 12px;
  border: 1px solid var(--nd-accent);
  border-radius: var(--nd-radius);
  background: var(--nd-accent-soft);
}
.nd-combo-chosen b { flex: 1 1 auto; font-weight: 600; font-size: .92rem; min-width: 0; overflow-wrap: anywhere; }
.nd-combo-clear {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--nd-muted);
  font-size: 1.1rem;
  line-height: 1;
}
.nd-combo-clear:hover { background: rgba(32, 36, 36, .07); color: var(--nd-ink); }

/* ------------------------------------------------------------- segmented -- */

/*
 * Tentative / Confirmed, and nothing selected.
 *
 * Three states, and "nothing yet" is a real answer rather than a missing one —
 * most delivery dates are agreed after the invoice is raised. Pressing the
 * selected half again clears it, which is why these are buttons rather than a
 * pair of radios: a radio group has no way back to empty.
 */
.nd-segmented {
  display: flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--nd-line);
  border-radius: 999px;
  background: var(--nd-soft);
}
.nd-segmented button {
  flex: 1 1 0;
  min-height: 32px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--nd-muted);
  font-size: .78rem;
  font-weight: 500;
  white-space: nowrap;
}
.nd-segmented button[aria-pressed="true"] {
  background: var(--nd-panel);
  color: var(--nd-ink);
  font-weight: 700;
  box-shadow: 0 1px 2px rgba(32, 36, 36, .14);
}

/* A date or a time with its own Tentative/Confirmed beside it. The status is
   meaningless without the value, so it greys out until there is one. */
.nd-staff-dated,
.nd-staff-stack {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
}
.nd-staff-stack .nd-subtext { margin-top: 0; }

/* What the other live record holds, where the two disagree. Set apart rather
   than coloured as an error: it is a thing to settle, not a thing that broke. */
.nd-staff-flag {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 10px;
  border: 1px solid var(--nd-line-strong);
  border-radius: 8px;
  background: var(--nd-soft);
  font-size: .75rem;
  line-height: 1.35;
}
.nd-staff-flag[hidden] { display: none; }
.nd-staff-flag span { flex: 1 1 auto; }
.nd-staff-flag .nd-button {
  flex: 0 0 auto;
  min-height: 28px;
  padding: 0 9px;
  font-size: .72rem;
  background: var(--nd-panel);
  color: var(--nd-ink);
}
.nd-staff-dated .nd-segmented.is-idle { opacity: .45; pointer-events: none; }

/* A field the form is hiding rather than disabling — client collection removes
   the delivery fee outright, because a fee that is merely greyed still reads as
   a number somebody might mean. */
.nd-staff-row[hidden] { display: none; }

@media (max-width: 520px) {
  .nd-staff-row { grid-template-columns: 1fr; gap: 4px; align-items: start; }
  .nd-staff-row input[type="checkbox"] { margin-top: 4px; }
}
