/* ==========================================================================
   listing-card.css

   This file mirrors the My Feline app's business detail card. It is a visual
   replica, not an independent design. Whenever the app card changes, this file
   must be updated to match, otherwise a business previewing its listing on the
   Ghost form pages will be shown something the app will not render.

   Authoritative source for every value here:
     docs/Implementations/listing-card-spec.md
   which was extracted from the Flutter code at ~/projects/MyFeline. Where the
   design system document disagrees with that spec, the spec wins.

   Deliberate departures from the app, all agreed rather than accidental:
     1. Section gaps sit on the section, so an empty section leaves no dead
        space. The app puts them outside, which leaves 48px of blank page when
        the Contact section is empty (spec 5.10, app-side issue 3).
     2. The "Get directions" label uses Quicksand rather than falling through to
        the system font (spec 12.7).
     3. Negative "No" rows are never rendered. The app shows some of them
        because a few yes/no fields are configured as plain text (spec 12.5).

   Structure: this file is visual only. It is the theme copy, loaded on the five
   business update form pages via {{asset "css/listing-card.css"}}. The markup is
   partials/listing-preview.hbs and the renderer is assets/js/listing-preview.js.

   Both @import rules from the standalone version are gone. default.hbs already
   loads Plus Jakarta Sans and Quicksand at the weights the card needs, and the
   Material Symbols subset is a <link> on the form pages so it does not have to
   queue behind this stylesheet.
   ========================================================================== */


/* Material Symbols Outlined, subset to only the icons this card uses.
   The five category icons are NOT here: they are Font Awesome (spec 7.1) and are
   inlined as an SVG sprite in the markup, so there is no second font request.
   The what3words row also uses Font Awesome (location-crosshairs, solid), so it
   is in the sprite too. No substitutions remain.
   icon_names must be in alphabetical order, or Google Fonts returns 400. */

:root {
  /* ---- Colours (spec 2.2) ---------------------------------------------- */
  --mf-white:          #FFFFFF;
  --mf-text:           #1C1A12;   /* primaryText: titles, values */
  --mf-text-2:         #2D2F28;   /* secondaryText: descriptions, labels, icons */
  --mf-bg:             #FBFAF5;   /* page and detail background */
  --mf-brand:          #4D5045;   /* links, ticks, buttons, category text */
  --mf-brand-400:      #797E6D;
  --mf-grey-200:       #F6F5ED;   /* icon tiles */
  --mf-grey-300:       #EBE9DD;   /* app bar bottom border */
  --mf-grey-500:       #AEA88C;   /* chevron on tappable rows */
  --mf-error:          #FF5963;
  --mf-badge-fill:     rgba(77, 80, 69, 0.1);
  --mf-liked-fill:     rgba(255, 89, 99, 0.1);
  /* Not from the app palette. The app has no disclaimer, so there is no colour
     to copy. Sits between secondaryText and grey500: quiet enough to recede,
     dark enough to stay legible on the page background. */
  --mf-muted:          #6B6858;

  /* ---- Spacing (spec 2.1) ---------------------------------------------- */
  --mf-space-1:  4px;
  --mf-space-2:  8px;
  --mf-space-3: 12px;
  --mf-space-4: 16px;
  --mf-space-6: 24px;

  /* ---- Radii ------------------------------------------------------------ */
  --mf-radius-sm:    10px;   /* icon tiles, distance pill */
  --mf-radius-md:    18px;   /* cards, detail category badge, buttons */
  --mf-radius-lg:    24px;
  --mf-radius-sheet: 32px;

  /* ---- Elevation -------------------------------------------------------- */
  --mf-shadow-card: 0 2px 10px rgba(0, 0, 0, 0.06);

  /* ---- Type ------------------------------------------------------------- */
  --mf-font-primary:   'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mf-font-secondary: 'Quicksand', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --mf-size-title:    24px;  /* detail title, Plus Jakarta Sans 600 */
  --mf-size-heading:  20px;  /* section heading, Plus Jakarta Sans 600 */
  --mf-size-body:     16px;  /* descriptions, values, link rows */
  --mf-size-caption:  14px;  /* field labels, category badge text */
  --mf-size-small:    12px;

  /* Flutter sets no explicit line height, so it uses the font's own metrics.
     These are the deterministic equivalents (spec 11.1). */
  --mf-leading-primary:   1.26;
  --mf-leading-secondary: 1.25;

  /* ---- Component sizes -------------------------------------------------- */
  --mf-tile-size:     40px;  /* icon tile on every detail row */
  --mf-icon-size:     20px;  /* icon inside that tile */
  --mf-chevron-size:  24px;
  --mf-badge-icon:    14px;  /* category icon in the detail badge */
  --mf-yesno-pad:     10px;  /* centres a single-line label against the tile */
  --mf-content-pad:   16px;  /* detail screen content padding */
  --mf-section-gap:   24px;  /* between sections */
  --mf-heading-gap:   12px;  /* heading to its first row */

  /* ---- Map preview (spec 5.2) ------------------------------------------- */
  --mf-map-height:    200px;
  --mf-marker-size:   40px;
  --mf-marker-icon:   20px;

  /* ---- Phone frame ------------------------------------------------------
     Frame furniture, not app design.

     The frame grows with the card rather than scrolling inside a fixed screen.
     This page is read mostly on a desktop, where a business needs the whole
     listing at a glance; a scroll box inside a phone outline hides most of it
     and invites nobody to scroll. The frame stays only to say "this is the app",
     not to simulate a device.

     Set this to 844px to get the fixed phone-screen height back, with the card
     scrolling inside it. */
  --mf-frame-height:  auto;
  --mf-frame-bezel:   12px;
  --mf-frame-radius:  44px;
  --mf-screen-radius: 32px;
  --mf-frame-colour:  #1C1A12;
}

/* ==========================================================================
   Phone frame

   Deliberately plain: a bezel and a rounded screen, no bodywork, no buttons, no
   status bar. Its only job is to stop the card reading as a web page. None of
   this is app design, so it is grouped separately and can be dropped wholesale
   when the card is embedded in the Ghost form pages.
   ========================================================================== */

.mf-phone {
  max-width: calc(430px + (var(--mf-frame-bezel) * 2));
  margin: 0 auto;
  padding: var(--mf-frame-bezel);
  border-radius: var(--mf-frame-radius);
  background: var(--mf-frame-colour);
  box-shadow: 0 18px 40px rgba(28, 26, 18, 0.28);
}

.mf-phone__screen {
  position: relative;
  border-radius: var(--mf-screen-radius);
  overflow: hidden;
  background: var(--mf-bg);
}

.mf-phone__scroll {
  height: var(--mf-frame-height);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Clearance for the home indicator, which overlays the screen, so content
     never sits underneath it. Stands in for the device safe area. */
  padding-bottom: var(--mf-space-6);
}

/* Home indicator. One line, and the only concession to looking like a handset. */
.mf-phone__indicator {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  width: 134px;
  height: 5px;
  border-radius: 3px;
  background: rgba(28, 26, 18, 0.32);
  pointer-events: none;
}

/* ==========================================================================
   App bar (spec 5.1)

   Sticks to the top of the screen while the content scrolls, as it does in the
   app. The heart is static furniture here: liking is a per-user action that a
   business previewing its own listing cannot perform.
   ========================================================================== */

.mf-appbar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--mf-space-2) var(--mf-space-2) var(--mf-space-3);
  background: var(--mf-bg);
  border-bottom: 1px solid var(--mf-grey-300);
}

.mf-appbar .mf-icon {
  font-size: var(--mf-chevron-size);
  color: var(--mf-text);
}

/* ==========================================================================
   Map preview (spec 5.2)

   A static image rather than live tiles, so the page makes no per-load external
   request and survives being printed to PDF. Full width, 200px tall, square
   corners, marker centred.
   ========================================================================== */

/* The slot paints grey200 immediately and the image arrives on top whenever it
   is ready. The map is only ever an <img> source, so it can never hold up the
   form or the card, and an image that never arrives leaves a neutral panel
   rather than a gap or a broken icon.

   In production the src is the get-listing-map endpoint:
     /functions/v1/get-listing-map?bid={id}&bt={type}
   which redirects to a stored image, generating it on the first request. Every
   failure, including Geoapify being down or rate limiting, redirects to a blank
   panel of this same colour, so the card degrades to looking mapless. */
.mf-map {
  position: relative;
  height: var(--mf-map-height);
  overflow: hidden;
  background: var(--mf-grey-200);
}

.mf-map__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Hide the marker when there is no map under it. The pin is meaningless over a
   blank panel, and worse, it asserts a location we are not showing. */
.mf-map--empty .mf-map__marker { display: none; }

/* Marker drawn here rather than baked into the image, so it stays exactly to
   spec whichever service generates the tiles. */
.mf-map__marker {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: var(--mf-marker-size);
  height: var(--mf-marker-size);
  border-radius: 50%;
  background: var(--mf-error);
  border: 2px solid var(--mf-white);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  display: grid;
  place-items: center;
}

.mf-map__marker svg {
  width: var(--mf-marker-icon);
  height: var(--mf-marker-icon);
  fill: var(--mf-white);
}

/* No attribution overlay here. Geoapify bakes its own bar into every generated
   image, "Powered by Geoapify | (c) OpenStreetMap contributors", which cannot be
   removed on the free plan and satisfies both its own and OpenStreetMap's
   requirements. A second overlay would simply duplicate it. The app shows no
   attribution at all, which is a gap in the app rather than something to copy
   (spec 11.4). */

/* ==========================================================================
   Detail card
   ========================================================================== */

.mf-detail {
  background: var(--mf-bg);
  color: var(--mf-text);
  font-family: var(--mf-font-secondary);
  -webkit-font-smoothing: antialiased;
  text-size-adjust: 100%;
}

.mf-detail *,
.mf-detail *::before,
.mf-detail *::after {
  box-sizing: border-box;
}

.mf-detail__body {
  padding: var(--mf-content-pad);
}

/* ---- Category badge (spec 5.5) ----------------------------------------- */

.mf-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--mf-space-2);
  padding: var(--mf-space-2) var(--mf-space-3);
  border-radius: var(--mf-radius-md);
  background: var(--mf-badge-fill);
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-caption);
  font-weight: 600;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-brand);
}

/* Category icon, Font Awesome, inlined as SVG. The five glyphs have different
   viewBox widths, so height is fixed and width follows the aspect ratio, which
   is how the app sizes them too (a font glyph sized by its em box).
   The markup must repeat the symbol's viewBox on the outer <svg>: a <use>
   reference alone gives the element no intrinsic ratio, and width: auto then
   falls back to the 300px default replaced-element width. */
.mf-badge__icon {
  flex: 0 0 auto;
  height: var(--mf-badge-icon);
  width: auto;
  fill: currentColor;
  color: var(--mf-brand);
}

/* Holds the category badge and, once it ships, the confirmed pill. Present in
   every panel whether or not a pill sits in it, so the badge lands in exactly
   the same place in both. */
.mf-detail__badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mf-space-2);
}

/* ---- Confirmed by business chip -----------------------------------------
   The component EXISTS in the app, at
   lib/features/area/widgets/confirmed_by_business_chip.dart. It currently
   renders from mock_listing_confirmed.dart; Marc's PR wires up the real
   listing_confirmed_at column rather than adding the component.

   Values below are the widget's detail variant exactly:
     fill      grey200, NOT the brand badge fill. The widget's own comment says
               the neutral fill is deliberate, so the chip carries the same
               visual weight as the distance pill "rather than competing with
               the brand-coloured category badge"
     radius    radiusMd, 18px
     padding   spacingSm vertical, spacingMd horizontal, so 8px by 12px
     icon      storefront_outlined, 14px, secondaryText
     gap       spacingSm, 8px, between icon and text
     text      AppTypography.caption, Quicksand 14 weight 500, secondaryText
     trailing  info_outline, 14px, secondaryText, after a spacingXs 4px gap

   Off by default. One switch: add mf-show-confirmed to any ancestor, usually
   <body>. The chip markup is also absent from the live panel entirely, so no CSS
   change can leak it into a panel meant to show only what a cat owner sees
   today. Add it there once the real column is wired up.
   ------------------------------------------------------------------------ */

.mf-confirmed {
  display: none;
  align-items: center;
  padding: var(--mf-space-2) var(--mf-space-3);
  border-radius: var(--mf-radius-md);
  background: var(--mf-grey-200);
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-caption);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text-2);
}

.mf-show-confirmed .mf-confirmed {
  display: inline-flex;
}

.mf-confirmed .mf-icon {
  font-size: var(--mf-badge-icon);
  color: var(--mf-text-2);
}

/* 8px after the leading icon, 4px before the trailing one, matching spacingSm
   and spacingXs in the widget. */
.mf-confirmed__icon { margin-right: var(--mf-space-2); }
.mf-confirmed__info { margin-left: var(--mf-space-1); }

/* ---- Title and description (spec 5.5) ---------------------------------- */

.mf-detail__title {
  margin: var(--mf-space-3) 0 0;
  font-family: var(--mf-font-primary);
  font-size: var(--mf-size-title);
  font-weight: 600;
  line-height: var(--mf-leading-primary);
  color: var(--mf-text);
}

.mf-detail__description {
  margin: var(--mf-space-3) 0 0;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text-2);
}

/* ==========================================================================
   Sections

   The 24px gap lives on the section rather than between sections, so a section
   that is not rendered contributes no height at all. This is the fix for the
   app's dead-space bug, not a reproduction of it.
   ========================================================================== */

.mf-section {
  margin-top: var(--mf-section-gap);
}

.mf-section__heading {
  margin: 0 0 var(--mf-heading-gap);
  font-family: var(--mf-font-primary);
  font-size: var(--mf-size-heading);
  font-weight: 600;
  line-height: var(--mf-leading-primary);
  color: var(--mf-text);
}

/* ==========================================================================
   Rows

   Two shapes, both built on the same 40px icon tile:
     .mf-row--link   contact, address, and any link-type field (spec 5.6)
     .mf-row--field  label above value, or a tick with a label only (spec 5.7)
   ========================================================================== */

.mf-row {
  display: flex;
  gap: var(--mf-space-3);
  padding: var(--mf-space-2) 0;
  text-decoration: none;
}

/* Link and tick rows centre against the tile. Label-and-value rows top align,
   so a wrapping value keeps the icon at the top (spec 5.7, confirmed by
   app-detail-thin-2.png where "Parent Company" wraps to two lines). */
.mf-row--link,
.mf-row--tick {
  align-items: center;
}

.mf-row--field {
  align-items: flex-start;
}

.mf-row__icon {
  flex: 0 0 var(--mf-tile-size);
  width: var(--mf-tile-size);
  height: var(--mf-tile-size);
  border-radius: var(--mf-radius-sm);
  background: var(--mf-grey-200);
  display: grid;
  place-items: center;
  color: var(--mf-text-2);
}

/* The tick is the one icon that carries the brand green (spec 7.2). */
.mf-row--tick .mf-row__icon {
  color: var(--mf-brand);
}

.mf-row__content {
  flex: 1;
  min-width: 0;
}

/* Tappable text: brand green, genuinely underlined (spec 5.6). */
.mf-row__link {
  display: block;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-brand);
  text-decoration: underline;
  overflow-wrap: anywhere;
}

/* Label sits on its own line above the value, so both must be block level. */
.mf-row__label {
  display: block;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-caption);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text-2);
}

.mf-row__value {
  display: block;
  margin-top: 2px;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text);
}

/* A tick row is a label only. The extra vertical padding centres a single line
   of 16px text against the 40px tile (spec 5.7). */
.mf-row--tick .mf-row__label-only {
  display: block;
  padding: var(--mf-yesno-pad) 0;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text);
}

/* Font Awesome rows (currently just what3words) size like the Material ones. */
.mf-row__icon-svg {
  height: var(--mf-icon-size);
  width: auto;
  fill: currentColor;
}

.mf-row__chevron {
  flex: 0 0 auto;
  align-self: center;
  color: var(--mf-grey-500);
}

.mf-row__chevron .mf-icon {
  font-size: var(--mf-chevron-size);
}

/* ==========================================================================
   "Could look like" state

   Used on one panel only, never on the panel that mirrors the live listing. The
   app has no placeholder state at all: it hides empty fields outright (spec
   5.10). So every rule below is presentation for the preview, and none of it
   describes app behaviour.

   Three constraints shape it:
     1. A placeholder appears only where a real row could appear. A field the app
        would never render must not be offered here, or the panel promises
        something publishing cannot deliver.
     2. A placeholder keeps the icon, label, section and position its populated
        counterpart would have, so the two panels read as the same card.
     3. The four My Feline Partnership fields are NEVER offered as placeholders,
        whatever their editability says:
          my_feline_partner, my_feline_verified,
          accepts_my_feline_referrals, offers_my_feline_discount
        They describe a commercial arrangement My Feline controls, not business
        detail, so inviting a business to add them would invite it to assert
        something only we can confer. They still render normally when populated,
        because they are genuinely part of the card. This is a hard exclusion,
        not a consequence of those columns currently being admin-only, so it must
        survive any later change to which fields the forms can reach.
   ========================================================================== */

.mf-row--placeholder .mf-row__icon {
  color: var(--mf-grey-500);
}

.mf-row--placeholder .mf-row__label,
.mf-row--placeholder .mf-row__value {
  color: var(--mf-grey-500);
}

/* The invitation itself. Same size and weight as a real value, so the row keeps
   its shape and the two panels stay aligned; only the colour differs. */
.mf-row__value--invite {
  color: var(--mf-grey-500);
}

/* A faded row that maps to a field on this form is a control: it scrolls to that
   field and focuses it. Only rows that actually do something get this, so the
   affordance is never a lie. The whole row is the target, not just the text,
   because at this size a text-only hit area is unkind on touch. */
.mf-row--jump {
  cursor: pointer;
  border-radius: var(--mf-radius-sm);
  transition: background-color 150ms ease;
}

.mf-row--jump:hover,
.mf-row--jump:focus-visible {
  background: var(--mf-grey-200);
  outline: none;
}

.mf-row--jump:focus-visible {
  box-shadow: 0 0 0 2px var(--mf-brand);
}

.mf-row--jump:hover .mf-row__value--invite,
.mf-row--jump:focus-visible .mf-row__value--invite,
.mf-row--jump:hover .mf-row__label,
.mf-row--jump:focus-visible .mf-row__label {
  color: var(--mf-text-2);
}

@media (prefers-reduced-motion: reduce) {
  .mf-row--jump { transition: none; }
}

.mf-detail__description--placeholder {
  color: var(--mf-grey-500);
}

/* ==========================================================================
   Completeness line

   A single plain line above the card, driven by completeness.form_editable from
   get-public-listing. It reports the fields this form can actually change, not
   all whitelisted fields: a vet can reach 20 of the app's 40, so counting out of
   40 would report a business as permanently incomplete for reasons it cannot act
   on.
   ========================================================================== */

.mf-completeness {
  margin: 0 0 var(--mf-space-3);
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-caption);
  font-weight: 500;
  line-height: var(--mf-leading-secondary);
  color: var(--mf-text-2);
}

.mf-completeness strong {
  font-weight: 600;
  color: var(--mf-text);
}

/* ==========================================================================
   Disclaimer

   Sits below the panels. Deliberately quiet: it qualifies the preview without
   competing with it. "A close representation" rather than an exact one, because
   fonts, line breaking and the map all differ slightly from the app, and the
   card is a replica that can drift whenever the app changes.
   ========================================================================== */

.mf-disclaimer {
  margin: var(--mf-space-4) 0 0;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-small);
  font-weight: 500;
  line-height: 1.5;
  color: var(--mf-muted);
}

/* ==========================================================================
   Get directions button (spec 5.9)

   The app leaves this label unstyled, so it renders in the system font. That is
   a bug and is not reproduced here (spec 12.7).
   ========================================================================== */

.mf-directions {
  margin-top: var(--mf-section-gap);
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--mf-space-2);
  padding: var(--mf-space-3);
  border: 0;
  border-radius: var(--mf-radius-md);
  background: var(--mf-brand);
  color: var(--mf-white);
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 600;
  line-height: var(--mf-leading-secondary);
  cursor: pointer;
}

.mf-directions .mf-icon {
  font-size: var(--mf-chevron-size);
  color: var(--mf-white);
}

/* ==========================================================================
   Icons
   ========================================================================== */

.mf-icon {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: var(--mf-icon-size);
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  user-select: none;
}


/* ==========================================================================
   Page layout

   Not card design. This is how the preview sits alongside the form on the five
   Ghost business update pages, and it is the only part of this file that knows
   anything about the page around it.
   ========================================================================== */

.mf-preview {
  margin: 0 0 var(--mf-space-6);
}

.mf-preview__intro {
  margin: 0 0 var(--mf-space-4);
}

.mf-preview__intro h2 {
  margin: 0 0 var(--mf-space-2);
  font-family: var(--mf-font-primary);
  font-size: var(--mf-size-heading);
  font-weight: 600;
  line-height: var(--mf-leading-primary);
  color: var(--mf-text);
}

.mf-preview__intro p {
  margin: 0;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-body);
  font-weight: 500;
  line-height: 1.6;
  color: var(--mf-text-2);
}

.mf-preview__panels {
  display: flex;
  flex-direction: column;
  gap: var(--mf-space-6);
}

.mf-preview__panel {
  min-width: 0;
}

.mf-preview__caption {
  margin: 0 0 var(--mf-space-3);
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-caption);
  font-weight: 600;
  color: var(--mf-text-2);
}

/* Credit for Font Awesome, whose icons are CC BY 4.0 and therefore require
   attribution. It lives on the page, not in the sprite comment, because a
   comment is not visible credit. Do not remove it while those icons are in use. */
.mf-preview__credit {
  margin: var(--mf-space-2) 0 0;
  font-family: var(--mf-font-secondary);
  font-size: var(--mf-size-small);
  font-weight: 500;
  line-height: 1.5;
  color: var(--mf-muted);
}

.mf-preview__credit a { color: inherit; }

/* Desktop: preview beside the form, so a business can see both at once and does
   not have to scroll between what it is typing and what it changes.

   The breakpoint is 1200px, not 1024px, because the form pages cap
   .form-container at 720px. Two columns inside 720px squeezed the form to 128px.
   .form-container--wide lifts that cap, but only where there is genuinely room:
   at 1200px the form column still gets around 600px. Below that the preview
   stacks above the form at full width. */
@media (min-width: 1200px) {
  .form-container--wide {
    max-width: 1280px;
  }

  .mf-form-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 520px);
    gap: var(--mf-space-6);
    align-items: start;
  }

  /* Preview is first in the source order so it stacks above the form on mobile.
     Here it moves to the right-hand column. */
  .mf-form-layout > .mf-preview {
    grid-column: 2;
    grid-row: 1;
    margin-bottom: 0;
  }

  .mf-form-layout > .mf-form-column {
    grid-column: 1;
    grid-row: 1;
  }
}
