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

/*
 * Help me design: a quiet outlined pill beside the brand.
 *
 * The order button at the foot of the page is the filled green one, and this
 * must not compete with it: the border and the words are the header's own
 * greys, and only the mark carries WhatsApp's colour, which is what makes it
 * readable at a glance without shouting. Hover and focus bring the border up
 * to the green rather than filling the shape.
 *
 * Below 560px the words go and the pill becomes a round button the size of
 * undo and redo, because the header already gives up the brand at 380px and
 * three interface names sit in the middle of it. The mark is WhatsApp's rather
 * than a question mark on purpose: at that size the icon is the whole message,
 * and a question mark does not say what pressing it does.
 */
.nd-help {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  padding: 7px 13px 7px 10px;
  border: 1px solid var(--nd-line);
  border-radius: 999px;
  background: var(--nd-panel);
  color: var(--nd-muted);
  font-size: .82rem;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  transition: border-color .15s ease, color .15s ease;
}

.nd-help svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  fill: var(--nd-whatsapp);
}

.nd-help:hover,
.nd-help:focus-visible {
  border-color: var(--nd-whatsapp);
  color: var(--nd-ink);
}

/* The header gives the word up at the same width the help button gives up its
   own: hidden from the eye, kept for a screen reader, which still names the
   select. */
@media (max-width: 560px) {
  .nd-mode-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

@media (max-width: 560px) {
  .nd-help {
    width: 38px;
    height: 38px;
    padding: 0;
    justify-content: center;
    gap: 0;
  }
  .nd-help span { display: none; }
}

@media (max-width: 380px) {
  .nd-help { width: 34px; height: 34px; }
  .nd-help svg { width: 15px; height: 15px; }
}


/* The mode picker: a setting, not the page's headline.

   It sits at the right of the header with the history buttons, because that is
   where a setting belongs and because the middle of the header was drawing the
   eye before the shelf did. Quiet by default (muted text, no fill, no border
   until it is approached), and it uses the same chevron as the panel's own
   selects so it reads as the same kind of control. */
.nd-mode {
  margin-left: auto;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  /* Tight, because the pair is one control: the word and its answer. At arm's
     length they read as two unrelated words in a header. */
  gap: 2px;
}

/* Quieter than the value it labels, so the two read as one setting: a word and
   its answer, the way the bottom bar sets a price over a size. */
.nd-mode-label {
  color: var(--nd-muted);
  font-size: .78rem;
  font-weight: 500;
  white-space: nowrap;
}

/* The control itself: the value, the chevron, and the real select lying over
   both of them. Padding is even on both sides so the box does not look hung
   off the value, and the border only arrives on hover or focus. */
.nd-mode-field {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 34px;
  padding: 0 7px;
  border: 1px solid transparent;
  border-radius: var(--nd-radius);
  color: var(--nd-ink);
  cursor: pointer;
}

.nd-mode-value {
  font-size: .85rem;
  font-weight: 600;
  white-space: nowrap;
}

.nd-mode-chevron {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  color: var(--nd-muted);
}

.nd-mode-field:hover,
.nd-mode-field:focus-within {
  border-color: var(--nd-line);
  background: var(--nd-soft);
}

/* Focus belongs to the field, because the select inside it is invisible. */
.nd-mode-field:focus-within { outline: 2px solid var(--nd-accent); outline-offset: 2px; }

/* The real control: over the whole field, invisible, and still the thing that
   takes the click, the focus and the screen reader's attention. */
.nd-mode-select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}

.nd-mode-select:focus-visible { outline: none; }

.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 mode picker and undo/redo have to share one
 * row on a page that cannot scroll sideways. Everything gives up a little, and
 * the mark gives up the most: the picker and the history are the tool, and the
 * logo is a link to a homepage that the back button also reaches. (The row was
 * overflowing at 386px when the modes were three tabs; the picker is one word,
 * so there is more room now than these rules were written for.)
 */
@media (max-width: 380px) {
  .nd-top { gap: 6px; padding-left: 8px; padding-right: 8px; }
  .nd-brand { display: none; }
  .nd-mode-field { padding: 0 6px; }
  .nd-mode-value { font-size: .8rem; }
  .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%;
  /* Literal white, not --nd-panel: the ring is what carries the marker against
     a dark Charcoal post, where the accent fill alone is 1.2:1. */
  border: 2px solid #fff;
  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); }


/* A marker that opens a list instead of placing something. In Advanced these
   stand beside markers that place on the second tap, so the difference has to
   be visible before the tap -- that is the whole complaint this pass started
   from. A second ring, not a second colour: it reads as "there is more behind
   this one" and costs two pixels.

   Opaque, for the same reason the border above is literal white: at 32% it
   composited to rgb(33,35,35) over the Charcoal finish, which is to say it
   vanished on exactly the shelf that needed it most. */
.nd-plus.is-menu {
  box-shadow: 0 0 0 2px var(--nd-accent), 0 2px 8px rgba(32, 36, 36, .3);
}

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

/*
 * A landscape phone's stage is about 270px tall, and five 38px tools stacked
 * from the top reach 224px -- 8px into the options button standing at the foot
 * of the same edge. On a short stage the rail becomes two columns, hugging the
 * frame (rtl puts the first tool on the right), which lands zoom in and zoom out
 * side by side and ends the rail at 136px. Portrait phones never reach this.
 */
@media (max-height: 460px) {
  .nd-stage-tools {
    display: grid;
    grid-template-columns: repeat(2, 38px);
    gap: 6px;
    direction: rtl;
  }
  .nd-stage-tools button { direction: ltr; }
}

/*
 * What the tool says back: a confirmation, or a refusal.
 *
 * A pill of the same family as .nd-busy below, because the two are the same
 * kind of object and used to look nothing alike. It was grey type on 92% white
 * with no border, which over a Charcoal shelf composited to about #eee and put
 * --nd-muted at 4.28:1 -- so the same sentence passed or failed depending on
 * where the shelf happened to be framed. An opaque fill is contrast that does
 * not depend on the geometry behind it: white on this ink is 13:1 anywhere.
 *
 * It sits above the floating buttons, derived from their own inset and height
 * rather than the 66px that used to encode them by hand, and above them in the
 * stack, so a sentence about a marker can never be painted under one.
 */
.nd-hint {
  position: absolute;
  left: 50%;
  bottom: calc(12px + var(--nd-tap) + 8px);
  transform: translateX(-50%);
  z-index: 5;
  width: max-content;
  /* 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), 36ch);
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(32, 36, 36, .92);
  color: #fff;
  font-size: .85rem;
  line-height: 1.4;
  /* Balanced, or the first-run sentence leaves "it." alone on a second line
     at 375px. Ignored where unsupported, which only costs the widow back. */
  text-wrap: balance;
  box-shadow: 0 2px 10px rgba(32, 36, 36, .28);
  pointer-events: none;
}
.nd-hint[hidden] { display: none; }
/* One shape, two fills. Opaque for the same reason as above: 6.3:1 on any
   geometry, where the old pale wash was only safe over the empty stage. */
.nd-hint.is-error { background: var(--nd-danger); color: #fff; }

.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; }

/* Wide enough for the longest of its three labels, so the button does not
   change size as it changes meaning -- only the glyph does. */
.nd-fab-add { left: 12px; min-width: 134px; justify-content: center; }

/* One icon, three states: the cross is drawn while a piece is waiting for a
   home and nothing has been placed with it yet, the tick once something has, so
   the control never changes size or place as it changes meaning. "Cancel" and
   "Done" are different offers — after a placement, cancelling would read as
   undoing the part just put down. */
.nd-fab .nd-fab-cross,
.nd-fab .nd-fab-tick { display: none; }
.nd-fab-add.is-cancel,
.nd-fab-add.is-done { background: var(--nd-ink); }
.nd-fab-add.is-cancel .nd-fab-plus,
.nd-fab-add.is-done .nd-fab-plus { display: none; }
.nd-fab-add.is-cancel .nd-fab-cross { display: inline; }
.nd-fab-add.is-done .nd-fab-tick { display: inline; }

/* The quiet pair at the bottom right: options, and bookends. White and muted
   because they finish a design rather than build it -- the accent fill belongs
   to the one control that adds pieces. */
.nd-fab-gear,
.nd-fab-quiet {
  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,
.nd-fab-quiet:hover { color: var(--nd-ink); }

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

/* Beside the gear, close enough to read as the pair they are. 6px rather than
   the 12px the pair keeps from the frame: at 320px in Advanced the Add button
   ends at x=146, and with a wide fallback face loaded before Red Hat Display
   arrives an 8px gap left only 10px between the two -- less than a thumb's
   margin for error between "add a piece" and "bookends". */
.nd-fab-bookends {
  right: calc(12px + var(--nd-tap) + 6px);
  /* 13px, not the 15 a symmetric pill would take: in a wide fallback face the
     Add button's "Add a piece" outgrows its 134px minimum, and the gap between
     the two fell to 9px. */
  padding: 0 13px;
  white-space: nowrap;
}

/* 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;
}
/* The panel's own name. It is the heading of the half of the screen where the
   shelf is actually made, so it is set in the page's ink rather than the grey
   used for captions: with the mode picker quietened at the top, this is what
   the eye should find first after the shelf itself. */
.nd-panel-head h2 {
  margin: 0;
  font-size: .84rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--nd-ink);
  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;
}
/* And the same trap as .nd-present-code above: a display rule beats the hidden
   attribute, so "View in AR" would show before a design has a code to link to. */
a.nd-button[hidden] { display: none; }
/* Waiting on the save that the page it points at has to read. It looks like a
   disabled button and, having no href, cannot be followed or focused. */
a.nd-button[aria-disabled="true"] {
  opacity: .4;
  cursor: default;
  pointer-events: 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-muted);
  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). */
/* Sticky, because the body is the scroller: with two dozen rows, scrolling to
   the bottom took the field you were filtering with off the screen. */
.nd-search {
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
  min-height: var(--nd-tap);
  margin-bottom: 8px;
  padding: 0 12px;
  border: 1px solid var(--nd-line-strong);
  border-radius: var(--nd-radius);
  background: var(--nd-panel);
  -webkit-appearance: none;
}

/*
 * The pieces reached for most recently.
 *
 * One row, scrolling sideways rather than wrapping, so the sheet's height does
 * not change with how much someone has done. Above the search box because it is
 * the shortcut past it.
 */
.nd-chips {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
  padding-bottom: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}
.nd-chips::-webkit-scrollbar { display: none; }
.nd-chip {
  flex: 0 0 auto;
  min-height: var(--nd-tap);
  padding: 0 12px;
  border: 1px solid var(--nd-accent);
  border-radius: 999px;
  background: var(--nd-accent-soft);
  color: var(--nd-accent);
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
}

.nd-list { display: flex; flex-direction: column; gap: 4px; }

/* Two kinds of answer to one question — carry on along this run, or turn off
   it. They used to be two markers on the model, 18px apart and identical. */
.nd-list-heading {
  margin: 8px 0 2px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--nd-muted);
}
.nd-list-heading:first-child { margin-top: 0; }

.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 {
  position: relative;
  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; }
/* 44px of thumb over a 32px control: it is the only way into "what is in it",
   which is the list a buyer checks before they order. Same trick below for
   Present, which drops to an icon at 420px. */
.nd-summary-toggle::after,
.nd-present::after {
  content: "";
  position: absolute;
  inset: -6px;
}
.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 {
  position: relative;
  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; }
}
