/*
 * The customer's Editor.
 *
 * Deliberately quieter than the Studio. This screen has one job — make somebody's invitation
 * feel like theirs — and every piece of furniture on it competes with the card for attention.
 *
 * Written mobile-first, because most of these visitors arrive from a phone. The Studio was built
 * the other way round and every layout fault this month came from that.
 */

@font-face {
  font-family: 'QT Display';
  src: url('../packages.5ddeef3d02/font-library/fonts/cinzel-400.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'QT Text';
  src: url('../packages.5ddeef3d02/font-library/fonts/belleza-400.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  --ink: #2a1a24;
  --muted: #7a6a72;
  --paper: #fffdfd;
  --rose: #fa518b;
  --bad: #b3123c;
  --line: #efe3e8;
  --workspace: #f4f1f2;
}

* { box-sizing: border-box; }

/*
 * A NORMAL SCROLLING PAGE — which is what the Studio's customer preview is, and why that one has
 * always felt right on a phone while this one did not.
 *
 * The Studio stacks its columns below 1040px, lets the page scroll, and gives the card a
 * guaranteed share of the window with `min-height`. Nothing then competes with the invitation:
 * the wording is not squeezed into the viewport beside it, it is simply further down.
 *
 * This app locked itself to the window height and hid the overflow, so the wording had to be
 * crammed into a strip at the bottom — and a fold handle was invented to cope with the strip
 * being too small. That was a workaround for the wrong foundation, and three days of adjustments
 * were spent patching the workaround. Maria: *"the studio customer preview never shrinks the
 * card, the canvas is large for entering the text fields, and it can be scrolled to reach the
 * rest of the text."*
 *
 * The window is locked only where locking earns its keep: the two-column laptop layout at the
 * bottom of this file, where the card sits beside the wording and must stay in view while the
 * wording is scrolled.
 */
body {
  margin: 0;
  background: var(--workspace);
  color: var(--ink);
  font: 400 16px/1.5 'QT Text', ui-sans-serif, system-ui, sans-serif;
}

.ed-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
  padding: 10px 16px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  flex: none;
}

/* Everything that answers "which invitation is this", together and pushed to the right. */
.ed-choices {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-left: auto;
}

.ed-brand {
  font: 600 15px 'QT Display', serif;
  letter-spacing: .04em;
}

.ed-name {
  font-size: 13px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Loading and failure share one place, so there is never a blank screen with nothing on it. */
.ed-state {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 40px 24px;
  text-align: center;
  color: var(--muted);
}
.ed-state[hidden] { display: none; }
.ed-state.bad { color: var(--bad); }
.ed-state.bad .ed-spinner { display: none; }

/* The cause, for whoever has to fix it. Quiet enough that a customer reads past it. */
.ed-state-detail {
  margin: 0;
  font: 400 12.5px/1.5 ui-monospace, monospace;
  color: var(--muted);
  max-width: 60ch;
  word-break: break-word;
}
.ed-state-detail[hidden] { display: none; }

.ed-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--line);
  border-top-color: var(--rose);
  border-radius: 50%;
  animation: ed-spin .9s linear infinite;
}
@keyframes ed-spin { to { transform: rotate(360deg); } }

/* Some people get motion sickness; the browser already knows who. */
@media (prefers-reduced-motion: reduce) {
  .ed-spinner { animation: none; }
}

/* ── The styling toolbar ──────────────────────────────────────────────── */

/*
 * One slim bar above the invitation, acting on whichever line is chosen.
 *
 * It keeps its height with nothing selected, showing a hint instead. The stage takes whatever
 * height this leaves and the card is scaled to the stage — so a bar that appeared and vanished
 * would resize the card underneath a finger that was already moving.
 */
.ed-style {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 8px 16px;
  min-height: 46px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  flex: none;
}
.ed-style[hidden] { display: none; }

.ed-style-what {
  font: 600 13px 'QT Display', serif;
  letter-spacing: .02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 30ch;
}
.ed-style-hint { font-size: 12.5px; color: var(--muted); }
.ed-style-hint .bi { display: inline; }

.ed-style-group { display: inline-flex; align-items: center; gap: 7px; }
.ed-style-group[hidden] { display: none; }

.ed-style-label {
  font: 600 10.5px 'QT Display', serif;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--muted);
  white-space: nowrap;
}
.ed-style-label .bi { display: inline; }

.ed-style-select {
  border: 1.5px solid var(--line);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
  padding: 6px 8px;
  font: 400 14px inherit;
  max-width: 170px;
}

.ed-style-range { width: 110px; accent-color: var(--rose); }

/* Big enough for a thumb: a one-point nudge is hard to hit by dragging a slider. */
.ed-style-step {
  width: 28px;
  height: 28px;
  border: 1.5px solid var(--line);
  border-radius: 50%;
  background: #fff;
  color: var(--ink);
  font: 600 15px/1 inherit;
  cursor: pointer;
}
.ed-style-step:hover { border-color: var(--rose); color: var(--rose); }

.ed-swatches-row { display: inline-flex; gap: 5px; }

/*
 * Undo, at the end of the editing bar and always present.
 *
 * Shown disabled rather than hidden until there is something to undo: a control that appears only
 * once it is needed is one nobody knows exists until the moment they no longer do.
 */


.es-swatch {
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(42, 26, 36, .22);
  cursor: pointer;
}
.es-swatch.on { outline: 2.5px solid #d4a84b; outline-offset: 2px; }

/*
 * Round, like every other swatch on the screen.
 *
 * It is a colour input only so the shared palette can attach to it — that package claims every
 * `input[type=color]` and opens the circular palette instead of the operating system's window.
 * What the customer sees is a round swatch showing the colour they have now.
 */
.ed-style-color {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid rgba(42, 26, 36, .22);
  border-radius: 50%;
  background: #fff;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  overflow: hidden;
}
.ed-style-color::-webkit-color-swatch-wrapper { padding: 0; }
.ed-style-color::-webkit-color-swatch { border: 0; border-radius: 50%; }
.ed-style-color::-moz-color-swatch { border: 0; border-radius: 50%; }
.ed-style-color[hidden] { display: none; }

/* The chosen line keeps a frame while the toolbar points at it, so there is never any doubt
   about which text the controls are acting on. */
.ed-hit.on { box-shadow: 0 0 0 2px var(--rose); }

/*
 * The invitation's own room, measured against the WINDOW and not against what is left over.
 *
 * `min-height` is the whole trick, and it is lifted straight from the Studio. Sized by what
 * remains after the wording, the card shrank whenever the wording grew — a longer venue, a second
 * version, a field switched on. Sized against the window, it is the same every time and the
 * wording flows below it.
 */
.ed-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
}
.ed-stage[hidden] { display: none; }

/*
 * The card, sized by its HEIGHT so the whole of it is always visible.
 *
 * Sizing by width made a 5×7 taller than the window on a laptop, so the bottom of somebody's
 * invitation — where the RSVP details live — sat below the fold while they typed. The aspect
 * ratio then gives the width, which is the same arithmetic the card itself does.
 */
/*
 * Sized by the WINDOW, so it cannot shrink when something below it grows.
 *
 * `height: 100%` of whatever was left over is what made the card change size as the panel filled
 * up. A share of the viewport is a number nothing else can move. The aspect ratio then gives the
 * width, and `max-width` takes over on a screen narrower than it is tall — which is every phone
 * held upright.
 */
.ed-cardwrap {
  position: relative;
  /*
   * `dvh`, not `vh`. On a phone `vh` is measured against the window with the address bar hidden,
   * so a card sized in `vh` is taller than the screen actually showing until you scroll.
   */
  /*
   * The height is the SMALLER of a share of the window and what the width allows, so the shape
   * always holds. It used to be `78dvh` with only a `max-width` to stop it: on a narrow phone the
   * width was cut and the height was not, the card became taller than a 5×7, the picture
   * stretched to fill it while the wording did not, and every press target sat above its line
   * (Maria's Android phone, Oct 5, 2026). Whichever runs out first — as the Studio's preview does
   * (`computeScale`).
   *
   * `--card-ratio` is the design's width ÷ height, set by `main.js` `shapeCard`; until then, and
   * for every 5×7, it is the 5×7's. The small card (5.5 × 4.25, wide, MASTER-PLAN §4.4) uses the
   * same rule with its own ratio.
   */
  height: min(78dvh, calc(94vw / var(--card-ratio, calc(1596 / 2196))));
  aspect-ratio: var(--card-ratio, 1596 / 2196);
  max-width: 94vw;
  background: var(--paper);
  box-shadow: 0 10px 34px rgba(42, 26, 36, .18);
  overflow: hidden;
  flex: none;
}

.ed-preview,
.ed-text {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/*
 * The wording takes no clicks. The layout engine owns where every glyph sits — the same engine
 * draws this and the 300 DPI print file — so the text stays a picture, and the buttons below
 * catch the press instead.
 */
.ed-text { pointer-events: none; }

/*
 * The layer is transparent to the pointer; only the buttons inside it are not.
 *
 * It covers the whole card, so with the default it swallowed every press that was not on a line
 * of wording — including presses on the artwork, which should reach the canvas and put the
 * invitation back to full size. Nothing looked wrong; presses simply stopped where they were.
 */
/*
 * The movable graphics' own layers — one under the wording, one over it.
 *
 * Transparent to the pointer for now: nothing here is draggable yet, and a layer covering the
 * card would otherwise swallow presses meant for the wording underneath. Dragging turns this on
 * for the graphics themselves, never for the layer.
 */
.ed-live { position: absolute; inset: 0; pointer-events: none; }
.ed-live img { display: block; }

.ed-hits { position: absolute; inset: 0; pointer-events: none; }

/* ── Moving and resizing a graphic ────────────────────────────────────── */

/*
 * The box round a graphic the customer may move.
 *
 * Invisible until it is wanted, like the wording's targets — a card outlined in rectangles stops
 * looking like an invitation, which is the one thing this screen exists to show.
 */
.ed-gbox {
  position: absolute;
  pointer-events: auto;
  border-radius: 2px;
  touch-action: none;          /* or the browser scrolls the page instead of moving the graphic */
  transition: box-shadow .12s ease;
}

/*
 * A GRAB AREA LARGER THAN THE THING ITSELF.
 *
 * A horizontal rule is a few pixels tall. Its box was exactly that tall, so hovering it was near
 * impossible and its handles — which only appeared on hover — could not be reached at all. Maria:
 * *"I can't select the little circles at the end of the line and can't do anything with it."*
 *
 * The frame stays the true size of the graphic; only the reach around it grows. Nothing here is
 * visible, and a thin divider becomes as easy to take hold of as a floral.
 */
.ed-gbox::before {
  content: '';
  position: absolute;
  inset: -10px;
}
.ed-gbox.movable { cursor: move; }

/* The chosen one, framed exactly as a chosen line of wording is — one selection, one look. */
.ed-gbox.on { box-shadow: 0 0 0 2px var(--rose); }
.ed-gbox:hover { box-shadow: 0 0 0 1.5px rgba(250, 81, 139, .5); }

/* A finger has no hover, so a movable graphic says so quietly and permanently. */
@media (hover: none) {
  .ed-gbox { box-shadow: 0 0 0 1px rgba(250, 81, 139, .22); }
}

/*
 * Corner handles, and only corners.
 *
 * A graphic scales proportionally — a stretched floral reads as a mistake — so an edge handle
 * could do nothing a corner cannot, and four fewer targets on a small screen is four fewer things
 * to catch by accident.
 *
 * They sit ON the corners rather than inside them: a handle inside the box covers the artwork it
 * is there to resize, which is the same fault as the move grip sitting on the wording.
 */
.ed-grip {
  position: absolute;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1.5px solid var(--rose);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(42, 26, 36, .28);
  opacity: 0;
  transition: opacity .12s ease;
  touch-action: none;
}
/*
 * Handles appear once the graphic is CHOSEN, not merely hovered.
 *
 * The same lesson as the wording's move grip: hovering is not a decision, and on anything small
 * it is not even reliably possible. Selecting is, and it lasts — so the handles stay put while a
 * hand travels toward them instead of vanishing on the way.
 */
.ed-gbox.on .ed-grip,
.ed-gbox:hover .ed-grip,
.ed-gbox:focus-within .ed-grip { opacity: 1; }
@media (hover: none) {
  /* Nothing to hover with: the handles are simply there, and bigger for a fingertip. */
  .ed-grip { opacity: 1; width: 24px; height: 24px; }
}

.ed-grip.nw { left: 0; top: 0; transform: translate(-50%, -50%); cursor: nwse-resize; }
.ed-grip.ne { right: 0; top: 0; transform: translate(50%, -50%); cursor: nesw-resize; }
.ed-grip.se { right: 0; bottom: 0; transform: translate(50%, 50%); cursor: nwse-resize; }
.ed-grip.sw { left: 0; bottom: 0; transform: translate(-50%, 50%); cursor: nesw-resize; }

/*
 * A faint cross through the middle of the card, for the whole of a drag.
 *
 * Somebody centring a monogram by eye needs to know where the middle IS, not to be told after the
 * fact that they happened to land on it. Drawn on the card rather than on the element, so it
 * reads as a property of the invitation.
 */
.ed-cardwrap.centring::before,
.ed-cardwrap.centring::after {
  content: '';
  position: absolute;
  background: rgba(250, 81, 139, .3);
  pointer-events: none;
  z-index: 55;
}
.ed-cardwrap.centring::before { left: 50%; top: 0; bottom: 0; width: 1px; }
.ed-cardwrap.centring::after { top: 50%; left: 0; right: 0; height: 1px; }

@media (prefers-reduced-motion: reduce) {
  .ed-gbox, .ed-grip { transition: none; }
}

/*
 * Invisible until you go near it.
 *
 * A card covered in dotted rectangles stops looking like an invitation, which is the one thing
 * this screen exists to show. So nothing is drawn until a pointer is over a line or a keyboard
 * reaches it — then a soft frame says "this one is yours to change".
 */
.ed-hit {
  position: absolute;
  pointer-events: auto;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: text;
  border-radius: 3px;
  /* A comfortable target on a phone even where the type is small. */
  min-height: 26px;
  transition: box-shadow .12s ease, background-color .12s ease;
}
.ed-hit:hover {
  background: rgba(250, 81, 139, .07);
  box-shadow: 0 0 0 1.5px rgba(250, 81, 139, .45);
}
.ed-hit:focus-visible {
  outline: none;
  background: rgba(250, 81, 139, .07);
  box-shadow: 0 0 0 2.5px var(--rose);
}

/*
 * An EMPTY line, which is invisible on the card.
 *
 * Deleting the last letter of a line used to take the target with it, so there was no way to type
 * it back — on a field Maria had not allowed to be removed at all, a backspace could destroy part
 * of the invitation permanently. The target now survives the text, and says where it is, because
 * an outline round nothing is the only way to find nothing.
 */
.ed-hit.empty {
  box-shadow: 0 0 0 1.5px rgba(250, 81, 139, .55);
  background: rgba(250, 81, 139, .06);
  border-radius: 3px;
}

/* A pointer that cannot hover — a finger — gets a permanent whisper of a frame instead, or
   there is no way to discover that the words can be tapped at all. */
@media (hover: none) {
  .ed-hit { box-shadow: 0 0 0 1px rgba(250, 81, 139, .22); }
}

/* ── Language, in the header ──────────────────────────────────────────── */

/* No auto margin now that it sits inside .ed-choices, which does the pushing. */
.ed-langrow { display: flex; gap: 6px; }

.ed-lang {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  border-radius: 999px;
  padding: 5px 12px;
  font: 500 12px inherit;
  cursor: pointer;
}
.ed-lang.on {
  background: var(--rose);
  border-color: var(--rose);
  color: #fff;
}

/* ── The wording panel ────────────────────────────────────────────────── */

/*
 * Scrolls on its own, always. On a phone it takes the bottom of the screen and the card keeps
 * the top; on a laptop it is the right-hand column. Either way the invitation stays put while
 * the wording is filled in.
 */
/*
 * A THIRD of the screen, not nearly half.
 *
 * At 42dvh, with a header and a styling toolbar above, the card was left 234 × 323 in a 500 × 720
 * window — under half the width available, and Maria's verdict on it was "very tiny". The card is
 * sized by its height, so every strip of chrome costs width as well, and the spare width simply
 * goes unused.
 *
 * A third still shows a label and its box with room to spare, and the panel scrolls — which was
 * always the arrangement: the invitation stays in view while the wording is filled in.
 */
/*
 * Below the invitation, at whatever height it needs. Scroll to reach it.
 *
 * It was capped at a third of the window and scrolled inside itself, which is why only a line or
 * so was ever visible. That cap belongs to the laptop layout, where the panel is a column and the
 * window is deliberately locked; here there is a whole page underneath to use.
 */
.ed-panelwrap {
  background: var(--paper);
  border-top: 1px solid var(--line);
  padding: 16px 16px 40px;
}
.ed-panelwrap[hidden] { display: none; }

.ed-panel {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.ed-group { display: flex; flex-direction: column; gap: 6px; }

/*
 * The label sits ABOVE the box, never beside it.
 *
 * A two-column version was tried in the Studio and reverted: it squeezed the boxes, and these
 * are the one thing on the screen that gets typed into. Somebody checking a long venue address
 * has to be able to read the whole of it.
 */
.ed-label {
  font: 600 13px 'QT Display', serif;
  letter-spacing: .02em;
}

.ed-input {
  width: 100%;
  padding: 12px 13px;
  border: 1.5px solid var(--line);
  border-radius: 9px;
  background: #fff;
  color: var(--ink);
  /* 16px stops iOS zooming the whole page in the moment a box is focused. */
  font: 400 16px/1.4 inherit;
  resize: vertical;
}
.ed-input:focus {
  outline: none;
  border-color: var(--rose);
  box-shadow: 0 0 0 3px rgba(250, 81, 139, .15);
}

.ed-help { font-size: 12.5px; color: var(--muted); }

.ed-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--muted);
}
.ed-check input { width: 18px; height: 18px; accent-color: var(--rose); }

/*
 * Side by side once there is room; card above wording on a phone.
 *
 * The grid is exactly the height of the window — one auto row for the bar, one that takes the
 * rest — and each column scrolls inside it. That is what keeps the invitation on screen while
 * the wording is typed.
 */
@media (min-width: 900px) {
  /*
   * HERE the window is locked, and here it earns it: the card sits beside the wording, and the
   * whole point of the arrangement is that the invitation stays in view while the wording is
   * scrolled. Maria asked for exactly that — *"make sure that right side is scrollable so that
   * the invite stays in view while we scroll the right side"* — and it was right for this width
   * and wrong for a phone, which is the mistake that took three days to find.
   */
  body { height: 100dvh; overflow: hidden; }

  .ed-stage { min-height: 0; overflow: auto; }
  .ed-cardwrap { height: 100%; max-height: 100%; }
  .ed-panelwrap { overflow-y: auto; padding-bottom: 28px; }

  /*
   * THREE rows, and every child is placed in one of them.
   *
   * A grid places anything unplaced into a row of its own, after the ones that are. The styling
   * toolbar was added to the HTML between the header and the card, which is where it appears on a
   * phone — but here, with no row of its own, it was auto-placed beneath the whole layout and
   * turned up at the BOTTOM of the screen. Toolbars were moved off the bottom months ago for
   * being impractical, on phones especially, and this quietly put one back.
   *
   * So the rows are named here and nothing is left to be placed automatically.
   */
  body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 400px;
    grid-template-rows: auto auto minmax(0, 1fr);
  }
  .ed-top { grid-column: 1 / -1; grid-row: 1; }
  .ed-style { grid-column: 1 / -1; grid-row: 2; }
  .ed-stage, .ed-state { grid-column: 1; grid-row: 3; }

  .ed-panelwrap {
    grid-column: 2;
    grid-row: 3;
    border-top: 0;
    border-left: 1px solid var(--line);
    max-height: none;   /* the grid row already bounds it */
  }

  /* No bar above it here, so the card can have almost the whole column. */
  .ed-cardwrap { max-height: 100%; }

  /*
   * The card fits the column in BOTH directions. `height: 100%` alone fills the column's height,
   * and a wide card — or a 5×7 in a narrow window — is then wider than the column. Measuring the
   * stage as a container lets the card take the smaller of the column's width and its height ×
   * the ratio. A browser without container units keeps the `100%` above.
   */
  @supports (width: 1cqw) {
    .ed-stage { container-type: size; }
    .ed-cardwrap {
      height: auto;
      max-height: none;
      width: min(100cqw, calc(100cqh * var(--card-ratio, calc(1596 / 2196))));
    }
  }
}

/* ── Which side, which colour ─────────────────────────────────────────── */

.ed-sides, .ed-swatches { display: flex; align-items: center; gap: 8px; }

.ed-side {
  border: 1.5px solid var(--line);
  background: transparent;
  color: var(--ink);
  border-radius: 999px;
  padding: 7px 15px;
  font: 500 13px inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ed-side.on { border-color: var(--rose); background: #fff4f8; font-weight: 600; }

/*
 * A side not yet looked at is marked.
 *
 * The back carries the RSVP details. Somebody who personalises the front and buys has ordered a
 * print run with placeholder information on the reverse — found on paper, and not undoable. A
 * dot before the fact is worth more than a warning at checkout (§7.2d).
 */
.ed-todo { color: #d4a84b; font-size: 20px; line-height: 0; }

.ed-swatch {
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(42, 26, 36, .22);
  cursor: pointer;
}
.ed-swatch.on { outline: 2.5px solid #d4a84b; outline-offset: 2px; }

.ed-swatch-name {
  font: 600 12.5px 'QT Display', serif;
  letter-spacing: .02em;
  white-space: nowrap;
}

/*
 * On a narrow screen every pixel of chrome is a pixel off the invitation.
 *
 * The card is sized by its height, so a strip saved at the top makes it wider as well as taller.
 * The shop's name goes first — somebody who followed a link from the product page knows whose
 * shop they are in.
 */
@media (max-width: 620px) {
  .ed-brand { display: none; }
  .ed-side { padding: 6px 12px; font-size: 12.5px; }
  .ed-swatch { width: 26px; height: 26px; }

  .ed-top { padding: 6px 12px; gap: 5px 10px; }
  .ed-style { padding: 4px 12px; min-height: 30px; gap: 5px 12px; }
  .ed-stage { padding: 6px; }
  .ed-cardwrap {
    max-width: 98vw;
    height: min(78dvh, calc(98vw / var(--card-ratio, calc(1596 / 2196))));
  }

  /*
   * The invitation's name goes, and the hint shrinks.
   *
   * Both were taking a whole row each above the card — and a row above the card is width off the
   * card as well, since it is sized by its height. Somebody who followed a link from a product
   * page knows which invitation they are personalising; it is on screen behind them and on the
   * card in front of them.
   */
  .ed-name { display: none; }
  .ed-style-hint { font-size: 11.5px; line-height: 1.25; }

  /* The label is the shorter half of the styling bar; the controls are the half that is used. */
  .ed-style-what { max-width: 16ch; font-size: 12px; }
  .ed-style-range { width: 84px; }
}

/* ── Notes on a field: warnings, and the one that matters most ────────── */

/*
 * Sized to be read, not skimmed. These are the sentences that stop somebody printing a card
 * with a dead QR code on it — white paper, dark ink, and a frame that says which kind of note
 * it is (LESSONS 30).
 */
.ed-note {
  font-size: 13px;
  line-height: 1.5;
  padding: 10px 12px;
  border-radius: 8px;
  background: #fff;
  border: 1.5px solid var(--line);
}
.ed-note.warn { border-color: #d4a84b; color: #5c3f0b; }
.ed-note.bad { border-color: var(--bad); color: var(--bad); }
.ed-note .bi { display: block; }
.ed-note .bi-es { margin-top: 5px; opacity: .85; }

/* Solid, because a white button on a white panel is furniture nobody presses. */
.ed-add {
  align-self: flex-start;
  border: 0;
  border-radius: 999px;
  background: var(--rose);
  color: #fff;
  padding: 10px 18px;
  font: 600 14px inherit;
  cursor: pointer;
}

.ed-remove {
  align-self: flex-start;
  border: 0;
  background: none;
  color: var(--muted);
  padding: 4px 0;
  font: 400 12.5px inherit;
  text-decoration: underline;
  cursor: pointer;
}

/*
 * "See how it looks" — gold, not pink.
 *
 * Gold means look at it; pink will mean buy it. Keeping those two apart by colour matters more
 * than either looking good, because they sit on the same screen and one of them spends money.
 */
/*
 * The readiness block, first in the panel.
 *
 * Sized to be read rather than skimmed: these are the sentences that stop somebody printing a
 * hundred cards with a dead QR code or a name pushed past the trim.
 */
.ed-status {
  max-width: 560px;
  margin: 0 auto 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ed-status:empty { display: none; }

.ed-note.ok {
  border-color: #4b7d5a;
  color: #2f5c3c;
  background: #f3faf5;
}

.ed-status-unseen { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }

/* One problem per line, and the label first, so the line it belongs to is obvious. */
.ed-problems {
  margin: 6px 0 0;
  padding-left: 1.15em;
}
.ed-problems li { margin-bottom: 4px; }

.ed-actions {
  max-width: 560px;
  margin: 0 auto 18px;
  display: flex;
  align-items: center;
  gap: 10px;
}

/*
 * Undo, beside "See my card" and always there.
 *
 * NOT faded to a whisper when it has nothing to undo. The first version used 40% opacity and sat
 * thirty pixels wide in the corner of the toolbar, and Maria reported it as missing — which it
 * effectively was. A control that is present but unreadable is worse than one that is absent,
 * because nobody asks where it went.
 */
.ed-undo {
  flex: none;
  margin-left: auto;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  padding: 8px 16px;
  font: 600 13.5px inherit;
  cursor: pointer;
  white-space: nowrap;
}
.ed-undo::before { content: '↺ '; }
.ed-undo:hover:not(:disabled) { border-color: var(--rose); color: var(--rose); }
.ed-undo:disabled { color: var(--muted); cursor: default; }
.ed-undo .bi { display: inline; }

/* Nothing beside it now, so it takes the full width again. */
.ed-actions .ed-see { flex: 1; width: auto; }

.ed-see {
  width: 100%;
  border: 0;
  border-radius: 999px;
  background: #d4a84b;
  color: #3a2a06;
  padding: 13px 18px;
  font: 600 15px 'QT Display', serif;
  letter-spacing: .02em;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(212, 168, 75, .35);
}
.ed-see:hover { background: #c89a3b; }

/*
 * Which copy of the app this is. Quiet enough to ignore, findable when it matters.
 */

.ed-build {
  max-width: 560px;
  margin: 18px auto 0;
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--muted);
  opacity: .55;
}

/* ── A second invitation, not a second edit ───────────────────────────── */

/*
 * Set apart from the wording by a rule and a gap, because it is a different KIND of action.
 * Everything above changes this invitation; this makes another one. Somebody skimming for the
 * Spanish version has to be able to see that the two are not the same thing.
 */
.ed-versionwrap {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  gap: 10px;
}

.ed-versions { display: flex; flex-direction: column; gap: 6px; }

.ed-version {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1.5px solid var(--line);
  border-radius: 9px;
  padding: 4px 10px 4px 4px;
  background: #fff;
}
.ed-version.on { border-color: var(--rose); background: #fff4f8; }

/* The whole row is the target — these get pressed with a thumb. */
.ed-version-pick {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  text-align: left;
  border: 0;
  background: none;
  color: var(--ink);
  padding: 8px;
  font: 500 14px inherit;
  cursor: pointer;
  min-width: 0;
}
.ed-version-top {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.ed-version.on .ed-version-lang { font-weight: 600; }

/*
 * The colour version's dot. Sized to be readable as a colour rather than a bullet, with a rim so
 * a white or cream version is still visibly a disc on a white row.
 */
.ed-version-dot {
  flex: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid rgba(42, 26, 36, .25);
}

/*
 * Their own words, under the language.
 *
 * Quieter than the language because it is the tie-breaker, not the label — and clipped rather
 * than wrapped, so a long venue name cannot turn a row into a paragraph.
 */
.ed-version-hint {
  max-width: 100%;
  font-size: 12.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Outlined, not solid.
 *
 * "Add to cart" will be the solid pink button on this screen, and there must be no doubt about
 * which of the two is which — pressing "start another version" when you meant to buy is a
 * mistake somebody makes once and does not come back from.
 */
.ed-another {
  align-self: flex-start;
  border: 1.5px solid var(--rose);
  border-radius: 999px;
  background: #fff;
  color: var(--rose);
  padding: 10px 18px;
  font: 600 14px inherit;
  cursor: pointer;
}

/* == How many, and what they cost ====================================== */

/*
 * Set apart from the wording above it, the same way the versions block is.
 *
 * Everything above this line describes the invitation; this is the first thing on the screen that
 * spends money, and somebody skimming has to be able to see the join. It sits BETWEEN the wording
 * and the versions because that is the order the decision is made in: what it says, how many of
 * this one, then whether there is another one (11.2g).
 */
.ed-buywrap {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  gap: 12px;
}
.ed-buywrap:empty { display: none; }

/*
 * Format, paper and envelopes — the product page's pressable sections, redrawn here.
 *
 * Chips rather than a dropdown, for the same reason the product page uses them (11.2f): every
 * value is visible at once, so "premium" can be read beside "standard" before either is pressed.
 * `aria-pressed` carries the state, so the pressed look and what a screen reader says come from
 * the same attribute and cannot disagree. Sized by a MINIMUM, like the quantity arrows — a
 * fingertip is 44px and a chip that comes out at 37px is a miss that only a browser test finds.
 */
.ed-choice-group { display: flex; flex-direction: column; gap: 6px; }
.ed-choice-label { font-size: 12.5px; color: var(--muted); }
.ed-choice-row { display: flex; flex-wrap: wrap; gap: 8px; }
.ed-choice {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  padding: 8px 14px;
  font: 600 14px inherit;
  cursor: pointer;
}
.ed-choice:hover { border-color: var(--rose); }
/* The chosen one is filled, not tinted — Maria: "whichever they click on should show boldly." */
.ed-choice[aria-pressed="true"] {
  border-color: var(--rose);
  background: var(--rose);
  color: #fff;
}

/*
 * The sizes people actually order, one press away.
 *
 * Maria's own selling history rather than the shape of the price curve (16.3a): most orders are
 * 50, 80, 100 or 120. Wrapping rather than scrolling sideways, because a horizontal scroller hides
 * half its contents on a phone and these are the fastest route to the common answer.
 */
.ed-qty-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/*
 * Sized by a MINIMUM, not by padding.
 *
 * The first version set 10px of padding and came out 37px tall -- three pixels short of a
 * fingertip, and caught by a browser test rather than by reading the stylesheet. That is the same
 * miss as the move grip that overlapped the words on phones: a size arrived at by adding numbers
 * up is a size nobody has actually checked. Stating the minimum says what the control is FOR.
 */
.ed-qty-chip {
  min-width: 52px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  padding: 8px 14px;
  font: 600 14px inherit;
  cursor: pointer;
}
.ed-qty-chip:hover { border-color: var(--rose); }
.ed-qty-chip.on {
  border-color: var(--rose);
  background: #fff4f8;
  color: var(--rose);
}

/*
 * Ten fewer, ten more, with the number between them.
 *
 * There is no typed box anywhere on this screen and there never will be: a typed quantity cannot
 * be trusted, and the pack rule is a supplier constraint rather than a policy (11.2c). A control
 * that cannot produce 37 needs no clamping and cannot surprise anyone.
 */
.ed-qty-step {
  display: flex;
  align-items: center;
  gap: 12px;
}

.ed-qty-arrow {
  flex: none;
  width: 44px;
  height: 44px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  font: 600 20px/1 inherit;
  cursor: pointer;
}
.ed-qty-arrow:hover:not(:disabled) { border-color: var(--rose); color: var(--rose); }
/*
 * Disabled at the ends, never hidden.
 *
 * A button that vanishes at the bottom of its range takes the layout with it — and the pack
 * minimum is the exact thing this block exists to make visible, so the moment it is reached is
 * the moment the control should still be there saying so.
 */
.ed-qty-arrow:disabled { color: var(--muted); opacity: .45; cursor: default; }

.ed-qty-count {
  font: 600 15px 'QT Display', serif;
  letter-spacing: .01em;
}
.ed-qty-count .bi { display: block; }
.ed-qty-count .bi-es { font-size: 13px; color: var(--muted); }

/*
 * THE PACK PRICE IS THE BIG NUMBER, per card underneath.
 *
 * Maria's revision of 11.2f on seeing it built the other way round: leading with the per-card
 * figure put the worst per-card rate she ever charges in the largest type on the screen. The pack
 * price is what the customer is actually billed.
 */
.ed-price {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  border: 1.5px solid var(--line);
  border-radius: 11px;
  background: #fff;
}

.ed-price-big {
  font: 600 30px/1.1 'QT Display', serif;
  letter-spacing: .01em;
  color: var(--ink);
}

.ed-price-sub { font-size: 13px; color: var(--muted); }
.ed-price-sub .bi { display: block; }
/* The cart's own figure for a bundle — the number Maria checks the cart against, so not muted. */
.ed-price-total { font: 600 13px inherit; color: var(--ink); margin-top: 4px; }
.ed-price-total .bi { display: block; }

/*
 * Said beside the quantity because it is the CAUSE of what the quantity does.
 *
 * Folding shipping into the price is why the per-card rate falls steeply as the amount rises, and
 * it is the whole reason quantity is a real decision rather than a formality. Maria asked for it
 * by name (11.2f-1).
 */
.ed-shipping {
  font-size: 13px;
  font-weight: 600;
  color: #2f5c3c;
}
.ed-shipping .bi { display: block; }
.ed-shipping .bi-es { font-weight: 500; opacity: .85; }


/*
 * How many of this version, on its own row.
 *
 * Quiet and pushed to the end, because the row's job is to say WHICH invitation this is; the
 * quantity is the second question. It still has to be visible, though: each version is its own
 * print run and its own pack of ten, so two versions are two numbers (11.2c).
 */
.ed-version-qty {
  margin-left: auto;
  flex: none;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* The gap left for a price that has not landed yet. Present, and saying nothing. */
.ed-price-waiting { color: var(--line); }

/*
 * WHY there is no price, under the sentence saying there is not.
 *
 * Only ever visible on a development copy. Technical on purpose: the address that was tried and the
 * answer it gave are what turned "the Editor was never wired up" into a one-line diagnosis, twice.
 * A customer sees only the sentence above it, which is all they can act on.
 */
.ed-why {
  display: block;
  margin-top: 5px;
  font: 400 11.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0;
  word-break: break-all;
  opacity: .75;
}

/* == Into the basket =================================================== */

/*
 * PINK MEANS BUY, and nothing else on this screen wears it solid.
 *
 * "See my card" is gold because gold means look at it; "start another version" is outlined pink
 * because it is an action of the same family but not this one. The three must never be confused at
 * the moment of pressing -- pressing "start another version" when you meant to buy is a mistake
 * somebody makes once and does not come back from.
 */
.ed-buy {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 4px 0 14px;
}

.ed-buybtn {
  width: 100%;
  border: 0;
  border-radius: 999px;
  background: var(--rose);
  color: #fff;
  /* Tall, because it is the one control on this screen that completes a purchase. */
  padding: 15px 20px;
  font: 600 16px 'QT Display', serif;
  letter-spacing: .02em;
  cursor: pointer;
  box-shadow: 0 2px 12px rgba(250, 81, 139, .34);
}
.ed-buybtn:hover:not(:disabled) { background: #e8467e; }
.ed-buybtn:disabled {
  background: var(--line);
  color: var(--muted);
  box-shadow: none;
  cursor: default;
}

/*
 * The shop's own words, under our sentence.
 *
 * Kept verbatim and set apart: the bridge's messages are already written for a customer, and they
 * carry the one piece of information that says what to do next.
 */
.ed-shopsaid {
  display: block;
  margin-top: 6px;
  font-size: 12.5px;
  opacity: .85;
}

/*
 * One version's own way into the basket, inside that version's own frame.
 *
 * Smaller than the big one and still a real target -- 44px, because it is pressed with a thumb and
 * a control three pixels short of a fingertip on the screen where money is spent is an order that
 * does not get placed.
 */
.ed-addcart {
  flex: none;
  min-height: 44px;
  border: 1.5px solid var(--rose);
  border-radius: 999px;
  background: var(--rose);
  color: #fff;
  padding: 8px 16px;
  font: 600 13.5px inherit;
  cursor: pointer;
  white-space: nowrap;
}
.ed-addcart:hover:not(:disabled) { background: #e8467e; }
.ed-addcart:disabled { background: var(--line); border-color: var(--line); color: var(--muted); }

/*
 * Already bought, so it stops being a button.
 *
 * WooCommerce merges an identical addition into the existing line, so a second press does not make
 * a second line -- it makes one line of sixty. A control that still looks pressable is an invitation
 * to double an order, so this is a link to the basket instead.
 */
.ed-incart,
.ed-tocart {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border: 1.5px solid #4b7d5a;
  border-radius: 999px;
  background: #f3faf5;
  color: #2f5c3c;
  padding: 8px 16px;
  font: 600 13.5px inherit;
  text-decoration: none;
  white-space: nowrap;
}
.ed-incart::before { content: '\2713\00a0'; }
.ed-tocart { width: 100%; font-size: 15px; min-height: 50px; }

/*
 * Bought: the badge and "Order more of this" side by side, wrapping when the frame is narrow.
 * As a bare span the pair overflowed the frame on Maria's first look (Sept 22, 2026): the row's
 * flex gave the pill no room and it ran out past the edge.
 */
.ed-purchased-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex: 0 1 auto;
  min-width: 0;
}
.ed-purchased { flex: none; }
.ed-ordermore { flex: none; }

/*
 * The rows hold three things now -- which invitation, how many, and how to buy it -- so they wrap
 * rather than squeezing the label on a phone.
 */
.ed-version { flex-wrap: wrap; }
.ed-version-pick { min-width: 14ch; }

/*
 * What acts on one version, kept together.
 *
 * The row carries three things now -- which invitation, how many, and how to buy it -- and on a
 * phone it wraps. As loose siblings the Delete link and the buy button landed on separate lines
 * with the label between them, and the row stopped reading as one invitation.
 */
/*
 * The row holding Start over · Delete · the buy control.
 *
 * It WRAPS. It did not, and a bought version — which carries two controls, "Purchased" and
 * "Order more of this" — pushed the pill out past the frame and put a sideways scrollbar under
 * the whole panel (Maria, Sept 22, 2026). `flex: none` on a row whose contents can grow is a
 * promise the contents cannot keep; `min-width: 0` lets it shrink inside the frame rather than
 * setting the frame's width from its own.
 */
.ed-version-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-left: auto;
  flex: 0 1 auto;
  min-width: 0;
}

/*
 * Every amount the shop sells, between the stepper's arrows.
 *
 * Maria asked for all of them to be reachable in the Editor, and this costs no extra row on a
 * phone: the number that was already sitting between the arrows became the chooser. The chips above
 * stay as one-tap shortcuts to the sizes she actually sells.
 *
 * A select rather than anything typed into, so it cannot produce an amount the shop does not sell.
 */
.ed-qty-pick {
  min-height: 44px;
  border: 1.5px solid var(--line);
  border-radius: 9px;
  background: #fff;
  color: var(--ink);
  padding: 8px 10px;
  /* 16px stops iOS zooming the whole page in the moment the chooser is focused. */
  font: 600 16px 'QT Display', serif;
  letter-spacing: .01em;
  cursor: pointer;
}
.ed-qty-pick:focus {
  outline: none;
  border-color: var(--rose);
  box-shadow: 0 0 0 3px rgba(250, 81, 139, .15);
}

/* The unit, beside the chooser. Quieter than the number, because the number is the answer. */
.ed-qty-unit { font-size: 13.5px; color: var(--muted); }
.ed-qty-unit .bi { display: block; }

/* The count is a row of its own things now, not a single piece of text. */
.ed-qty-count { display: inline-flex; align-items: center; gap: 8px; }

/* == The two ways out ================================================== */

/*
 * Maria asked for both by name: a way to see the basket, and a way back to the invitation's own
 * page. The Editor is a full page with none of the shop's header or footer -- deliberately, since
 * the card is the only thing on this screen worth looking at -- and that left it with no exits.
 *
 * Quiet, because they are not what anybody came here to do. Findable, because without them the
 * screen is a trap.
 */
.ed-nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
}
.ed-nav:empty { display: none; }

.ed-navlink {
  display: inline-flex;
  align-items: center;
  /* A fingertip, like every other target on this screen. */
  min-height: 44px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
  white-space: nowrap;
}
.ed-navlink:hover { color: var(--rose); text-decoration: underline; }
.ed-navlink .bi { display: block; }

.ed-navback::before { content: '\2039\00a0'; }

/*
 * The basket reads slightly stronger than the way back. It is the one somebody reaches for after
 * adding something, and "where did my invitation go" is the question this screen most needs to be
 * able to answer.
 */
/*
 * Maria, Sept 21, 2026: "make the view cart button in the editor more visible and remove the
 * emoji, we use only svg's on my site and make the button pink so it stands out." A solid pink
 * pill, then — the same shape as Add to cart, because it is the same journey's next step.
 */
.ed-navcart {
  color: #fff;
  font-weight: 600;
  background: var(--rose);
  border-radius: 999px;
  padding: 0 16px;
  min-height: 40px;
}
.ed-navcart:hover { color: #fff; background: #e8467e; text-decoration: none; }

/* ── A question the customer must answer (dialog.js) ─────────────────────── */

/*
 * The page's own box, not the browser's. Maria, Sept 20, 2026, on the confirm() it replaces: "a pop
 * up from what looks like my browser, unstyled … it needs to be comprehensive customer side and
 * understandable." Centred, one sentence in both languages, and one button per answer — the
 * primary one pink because it is the action, the other plain because it is the way out.
 */
.ed-dialog {
  border: 0;
  border-radius: 14px;
  padding: 22px 22px 18px;
  width: min(92vw, 420px);
  background: #fff;
  color: var(--ink);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .25);
}
.ed-dialog::backdrop { background: rgba(20, 12, 16, .45); }
.ed-dialog-text { font: 500 15px/1.45 inherit; }
.ed-dialog-text .bi { display: block; }
.ed-dialog-text .bi-es { margin-top: 8px; color: var(--muted); font-size: 14px; }
.ed-dialog-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
  justify-content: flex-end;
}
.ed-dialog-btn {
  min-height: 44px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  padding: 8px 18px;
  font: 600 14px inherit;
  cursor: pointer;
}
.ed-dialog-btn.primary { border-color: var(--rose); background: var(--rose); color: #fff; }
.ed-dialog-btn .bi { display: block; text-align: center; }
.ed-dialog-btn .bi-es { font-weight: 400; font-size: 12.5px; opacity: .85; }

/* ── Inside a version's frame (§11.2g-2, Sept 21, 2026) ──────────────────── */

/*
 * The open version's frame holds its name, its language, its format/paper/envelopes, its quantity
 * and price, and its own Add to cart. Maria: "so each version gets the option for them to name the
 * version, choose the format, paper, envelope, and the add to cart button." The frame is the
 * version, so everything about it is inside its border and nothing about it is anywhere else.
 */
.ed-version-inside {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 6px 12px 12px;
}
.ed-version-inside .ed-label { margin-top: 4px; }
.ed-version-name { display: flex; flex-direction: column; gap: 6px; }
.ed-version-name-input { padding: 10px 12px; font-size: 15px; }
.ed-version-options:empty, .ed-version-buy:empty { display: none; }

/* On the open frame the version's own button is THE button, so it reads like one. */
.ed-version.on .ed-version-actions { padding: 0 12px 12px; }
.ed-version.on .ed-addcart { flex: 1; font-size: 15px; padding: 12px 18px; }

/* "Update in cart" — the same button, outlined, because it changes a line rather than adding one. */
.ed-addcart.ed-updatecart { background: #fff; color: var(--rose); }
.ed-addcart.ed-updatecart:hover:not(:disabled) { background: #fff4f8; }
