/* ==========================================================================
   Email Signature Generator — interface styles (v2)
   --------------------------------------------------------------------------
   Nothing in this file may be relied on by the signature itself. Mail clients
   strip <style> blocks, so signature markup carries inline styles only; these
   rules dress the *tool* around it.

   Layout follows the approved mockup on desktop and reflows to a single
   column with a floating bottom bar on small screens.
   ========================================================================== */

/* The `hidden` attribute is a UA-stylesheet `display:none`, so *any* author
   rule that sets `display` on the same element silently defeats it. Several
   components here do exactly that (badges, drawers, pro-only fields), so the
   attribute is promoted once, globally, rather than re-patched per component.
   Anything that must stay laid out while hidden should use another mechanism. */
[hidden] { display: none !important; }

/* --------------------------------------------------------------- Tokens -- */

:root {
  --blue:        #2563eb;
  --blue-dark:   #1d4ed8;
  --blue-wash:   #eff4ff;
  --blue-ring:   rgba(37, 99, 235, .14);
  /* Text that sits *on* a --blue fill. Light mode wants white; dark mode
     lightens --blue far enough that white stops passing, so it flips to ink. */
  --on-blue:     #ffffff;

  --ink:         #111827;
  --ink-2:       #374151;
  --muted:       #6b7280;
  --faint:       #9ca3af;
  --placeholder: #c3c9d2;

  --paper:       #ffffff;
  --page:        #f5f7fa;
  --line:        #e5e7eb;
  --line-soft:   #eef1f5;
  --line-2:      #d6dbe3;   /* borders that darken on hover */
  --line-dash:   #cfd6e0;   /* dashed drop zones */
  --well:        #fafbfc;   /* insets: drop zones, mailbox toolbar */

  --green:       #16a34a;
  --green-ring:  rgba(22, 163, 74, .16);
  --red:         #dc2626;
  --red-wash:    #fee2e2;

  /* Translucent surfaces. The floating tab bar sits over scrolling content, so
     it cannot use a flat --paper without the text behind it showing through. */
  --glass:       rgba(255, 255, 255, .94);
  --glass-blur:  rgba(255, 255, 255, .82);
  --scrim:       rgba(17, 24, 39, .45);   /* dialog backdrop      */
  /* Lighter than --scrim on purpose: the walkthrough also blurs what it
     covers, and the two effects stack. Dim it as hard as a dialog and the
     page behind stops reading as a page. */
  --tour-scrim:  rgba(17, 24, 39, .32);
  --veil:        rgba(255, 255, 255, .58); /* busy overlay        */
  --veil-2:      rgba(255, 255, 255, .18); /* ...at pulse peak    */
  --lock-bg:     rgba(17, 24, 39, .82);   /* Pro badge on a tile  */
  --lock-fg:     #ffffff;

  /* PixelGeometry's own colours, sampled from the supplied logo. Distinct from
     --blue, which is the *tool's* accent: the mockup's accent is blue, but the
     brand mark is purple, and the mark should stay true to the brand. */
  --brand:      #6b56dd;
  --brand-deep: #17123a;

  --r-sm: 8px;
  --r:    12px;
  --r-lg: 16px;

  --shadow:    0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
  --shadow-lg: 0 12px 32px rgba(16, 24, 40, .12);

  --appbar-h: 64px;
  --tabbar-h: 64px;

  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  color-scheme: light;
}

/* ------------------------------------------------------------------ Dark -- */

/* app.js resolves the theme before first paint and always writes an explicit
   data-theme, so there is no @media (prefers-color-scheme) rule here: the
   system preference is read once in JS and becomes the default choice. One
   source of truth beats two that can disagree.

   Only the tokens move. Every rule below this block is written against tokens,
   so dark mode costs nothing to maintain — with three deliberate exceptions,
   each marked further down: the brand chip, the mailbox, and the Pro Annual
   card, which holds one fixed colour scheme in both themes on purpose. */
[data-theme="dark"] {
  --blue:        #6f9bff;
  --blue-dark:   #8ab1ff;   /* hover goes *lighter* on a dark ground */
  --blue-wash:   #1a2440;
  --blue-ring:   rgba(111, 155, 255, .22);
  --on-blue:     #0b0f16;

  --ink:         #e7ebf2;
  --ink-2:       #bcc4d2;
  --muted:       #8d97a8;
  --faint:       #6d7787;
  --placeholder: #5c6675;

  --paper:       #171b23;
  --page:        #0e1117;
  --line:        #2b3240;
  --line-soft:   #232936;
  --line-2:      #3a4354;
  --line-dash:   #3a4354;
  --well:        #12161e;

  --green:       #3ecf7f;
  --green-ring:  rgba(62, 207, 127, .20);
  --red:         #ff7676;
  --red-wash:    #3a1d1d;

  --glass:       rgba(23, 27, 35, .94);
  --glass-blur:  rgba(23, 27, 35, .76);
  --scrim:       rgba(0, 0, 0, .62);
  --tour-scrim:  rgba(0, 0, 0, .48);
  --veil:        rgba(23, 27, 35, .62);
  --veil-2:      rgba(23, 27, 35, .22);
  /* The badge sits on a tile that is now dark, so it inverts to stay legible. */
  --lock-bg:     rgba(231, 235, 242, .90);
  --lock-fg:     #0b0f16;

  /* The mark is purple against #17123a. Lift it just enough to read on the
     dark UI without drifting off-brand. */
  --brand:      #9b8bff;

  --shadow:    0 1px 2px rgba(0, 0, 0, .40), 0 1px 3px rgba(0, 0, 0, .32);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, .55);

  color-scheme: dark;
}

/* ---------------------------------------------------------------- Reset -- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 400 14px/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }

img { max-width: 100%; height: auto; }

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

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

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Icons inherit colour so one sprite symbol works everywhere. */
.ico {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ico--sm { width: 14px; height: 14px; }
.ico--lg { width: 28px; height: 28px; stroke-width: 1.4; }

.link {
  color: var(--blue);
  background: none;
  border: 0;
  padding: 0;
  font-weight: 500;
  cursor: pointer;
}
.link:hover { text-decoration: underline; }
.link--sm { display: block; margin: 10px 0 4px; font-size: 13px; }

/* --------------------------------------------------------------- Appbar -- */

.appbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--appbar-h);
  padding: 0 20px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.appbar__brand {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
}

/* The chip restores the dark ground the mark was drawn against, so its white
   facets keep their shape. Do not flatten this to a bare <img>. */
.appbar__chip {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--brand-deep);
}
.appbar__chip img { display: block; width: 21px; height: auto; }

.appbar__word {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.015em;
  color: var(--ink);
  white-space: nowrap;
}
.appbar__word b { font-weight: 700; color: var(--brand); }

.appbar__rule {
  width: 1px;
  height: 26px;
  background: var(--line);
}

.appbar__title {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 8px;
}

.appbar__title .crown {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .02em;
  color: #6b4c05;
  background: linear-gradient(135deg, #fde08a, #e8b422 55%, #c8930d);
  border: 1px solid rgba(140, 100, 6, .35);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}

.appbar__title .crown .ico {
  width: 13px;
  height: 13px;
}

[data-theme="dark"] .appbar__title .crown {
  color: #2a1e02;
  background: linear-gradient(135deg, #ffd977, #e0ab1c 55%, #b8850a);
  border-color: rgba(255, 220, 130, .45);
}

.appbar__actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
  align-items: center;
}

/* Signed-in label is an email address, so it has no natural ceiling — clamp it
   rather than let one long address push Copy Signature off the bar. */
.appbar__actions [data-role="account-label"] {
  display: inline-block;
  max-width: 168px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* -------------------------------------------------------------- Buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 14px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  transition: background-color .15s, border-color .15s, color .15s;
}

.btn--primary { background: var(--blue); border-color: var(--blue); color: var(--on-blue); }
.btn--primary:hover { background: var(--blue-dark); border-color: var(--blue-dark); }

.btn--ghost { background: var(--paper); border-color: var(--line); color: var(--ink-2); }
.btn--ghost:hover { background: var(--page); border-color: var(--line-2); }

.btn--outline { background: var(--paper); border-color: var(--blue); color: var(--blue); font-weight: 600; }
.btn--outline:hover { background: var(--blue-wash); }

.btn--dashed {
  border: 1px dashed var(--line-dash);
  color: var(--muted);
  padding: 11px 14px;
}
.btn--dashed:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-wash); }

.btn--block { width: 100%; }

/* Square, label-less. Padding is set so it lands on the same height as its
   lettered neighbours rather than a hand-picked px value that drifts when the
   button metrics change. */
.btn--icon {
  padding: 9px;
  position: relative;
}

/* Both glyphs ship in the markup and the theme picks one, so the swap costs no
   layout and no second sprite lookup. The hidden one is removed from the box
   entirely — stacking them would widen the button by an icon.

   The glyph shows the *destination*, matching the aria-label ("Switch to dark
   theme" wears a moon). Showing the current state instead would leave the icon
   and the label saying opposite things on the same control. With no JS there is
   no data-theme, the page is light, and the moon offers dark — still true. */
.btn--icon .ico--sun { display: none; }
[data-theme="dark"] .btn--icon .ico--moon { display: none; }
[data-theme="dark"] .btn--icon .ico--sun { display: inline-block; }

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .5;
  pointer-events: none;
}

.iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
}
.iconbtn:hover { background: var(--page); color: var(--ink); }

/* ---------------------------------------------------------------- Shell -- */

.shell {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
  gap: 16px;
  max-width: 1680px;
  margin: 0 auto;
  padding: 16px;
  /* Room for the floating bar so the last control is never trapped under it. */
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 24px);
}

.main { min-width: 0; }

/* One column on phones, and here the areas follow the numbered steps rather
   than the desktop arrangement: install is step 5 and belongs last. It only
   climbs to sit under the preview once there is a second column to hold it. */
.cols {
  display: grid;
  gap: 16px;
  grid-template-columns: 1fr;
  grid-template-areas: "details" "preview" "design" "install";
}

#panel-details          { grid-area: details; }
.panel--preview         { grid-area: preview; }
.panel--design          { grid-area: design; }
#panel-install          { grid-area: install; }

/* ----------------------------------------------------------------- Rail -- */

.rail {
  order: 2;                 /* below the tool on mobile; beside it on desktop */
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.rail__label {
  padding: 0 4px;
  color: var(--faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.steps {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}

.step {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: var(--r-sm);
  color: inherit;
  text-decoration: none;
  transition: background-color .15s;
}
.step:hover { background: var(--page); text-decoration: none; }

.step__ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 9px;
  background: var(--page);
  color: var(--muted);
  transition: background-color .15s, color .15s;
}

.step__txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.step__txt b { font-size: 13px; font-weight: 600; }
.step__txt small { color: var(--muted); font-size: 11.5px; }

.step.is-active { background: var(--blue-wash); }
.step.is-active .step__ico { background: var(--blue); color: var(--on-blue); }
.step.is-active .step__txt b { color: var(--blue); }

/* Steps already filled in get a quiet tick rather than a colour change, so
   "done" never competes with "you are here". */
.step.is-done .step__ico { color: var(--green); }

/* --------------------------------------------------------- Upgrade card -- */

.promo {
  padding: 16px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}

.promo__head {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--blue);
  font-size: 14px;
  font-weight: 600;
}

.promo__price {
  margin: 10px 0 14px;
  color: var(--muted);
  font-size: 13px;
}
.promo__price b {
  color: var(--ink);
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -.02em;
}

.promo__list {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-bottom: 16px;
  font-size: 13px;
}
.promo__list li { display: flex; align-items: center; gap: 9px; }
.promo__list .ico { color: var(--green); stroke-width: 2.4; width: 15px; height: 15px; }

.promo__note {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 9px;
  color: var(--faint);
  font-size: 11.5px;
}
.promo__note .ico { width: 13px; height: 13px; }

.promo__body { margin: 8px 0 14px; color: var(--muted); font-size: 13px; }

.promo--lapsed .promo__head { color: var(--ink); }

/* The one card on the page that keeps its own colours in both themes. It is
   the only upsell in the interface, and it earns its emphasis by *not*
   matching the cards around it — a dark blue that complements the tool's
   --blue without competing with the purple brand mark.

   Because the surface no longer follows --paper, everything inside it is
   redeclared against local tokens. Nothing in here may inherit --ink, --line
   or --green: those flip with the theme and would drop out against a ground
   that does not. Contrast is checked against #12224d, not against --paper. */
.promo--pro {
  --pro-ground:  #12224d;
  --pro-line:    rgba(255, 255, 255, .16);
  --pro-ink:     #f3f7ff;
  --pro-ink-2:   #c6d5f2;
  --pro-faint:   #94a9d2;
  --pro-accent:  #93b7ff;
  --pro-check:   #5fd99a;

  position: relative;
  overflow: hidden;
  border-color: var(--pro-line);
  background:
    radial-gradient(130% 120% at 0% 0%, #1d356e 0%, rgba(29, 53, 110, 0) 62%),
    var(--pro-ground);
  box-shadow: 0 10px 30px rgba(9, 20, 51, .30);
}

/* A hairline of light across the top edge, so the card reads as raised rather
   than as a hole punched in the rail. */
.promo--pro::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(255, 255, 255, 0), rgba(255, 255, 255, .40), rgba(255, 255, 255, 0));
}

.promo--pro .promo__head  { color: var(--pro-accent); }
.promo--pro .promo__price { color: var(--pro-faint); }
.promo--pro .promo__price b,
.promo--pro .promo__list  { color: var(--pro-ink); }
.promo--pro .promo__list .ico { color: var(--pro-check); }
.promo--pro .promo__note  { color: var(--pro-faint); }

.promo__mail {
  color: var(--pro-ink-2);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.promo__mail:hover { color: var(--pro-ink); }

/* The CTA keeps its .btn--outline class — the markup and the click handler are
   shared with the lapsed card — but an outline drawn in --line is invisible on
   this ground, so it is overridden to a solid light button here. */
.promo--pro .btn--outline {
  background: var(--pro-ink);
  border-color: var(--pro-ink);
  color: #12224d;
  font-weight: 600;
}
.promo--pro .btn--outline:hover { background: #ffffff; border-color: #ffffff; }
.promo--pro .btn--outline:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(147, 183, 255, .55);
}

/* The rail copy of the card. Same ground and the same CTA, trimmed to fit the
   room the five steps leave below them: three benefits instead of five, no
   payment/support footnotes, and a smaller price. The drawer still carries the
   full pitch, which is what "See everything in Pro" opens. */
.promo--rail {
  margin-top: auto;         /* sits at the foot of the rail when it stretches */
  padding: 14px;
}
.promo--rail .promo__price   { margin: 8px 0 12px; }
.promo--rail .promo__price b { font-size: 24px; }
.promo--rail .promo__list    { gap: 8px; margin-bottom: 13px; font-size: 12.5px; }
.promo--rail .promo__body    { margin: 6px 0 12px; font-size: 12.5px; }

/* Reads as a link, behaves as a button — it opens the drawer rather than
   navigating, so an <a href="#"> would be the wrong element. */
.promo__more {
  display: block;
  width: 100%;
  margin-top: 9px;
  padding: 2px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--pro-ink-2);
  font: inherit;
  font-size: 11.5px;
  text-align: center;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.promo__more:hover { color: var(--pro-ink); }
.promo__more:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(147, 183, 255, .55);
}

/* Below the desktop breakpoint the rail drops under the tool, where a second
   pitch this far down the page is just noise — the edge tab is still pinned to
   the viewport and does the job there. */
@media (max-width: 1179px) {
  .promo--rail { display: none; }
}

/* ------------------------------------------------------- System status -- */

/* Items 12 + 13. The fixed bottom strip is gone, and with the counters and the
   sparkline went the only reason it needed a bar of its own. What survives is
   the one line anyone ever read — is the service up — parked under the tour
   button in the rail, where it costs a single row, scrolls with the rest of
   the rail, and stops fighting the floating tabbar for the bottom edge. */

.railstat {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 2px 10px 0;
  color: var(--faint);
  font-size: 11.5px;
  letter-spacing: .02em;
  white-space: nowrap;
}
/* The dot carries --green so its ring token resolves; the label beside it is
   deliberately quieter than the state it is labelling. */
.railstat .status__dot { color: var(--green); flex: 0 0 auto; }
.railstat b { color: var(--green); font-weight: 600; }

.status__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 3px var(--green-ring);
}

/* Item 8 removed the right-edge Pro tab and its slide-over drawer, and about
   200 lines of styling for the pair went with them. The pitch now lives once,
   in .promo--rail above, so nothing here needs to dress a second copy. */

/* --------------------------------------------------------------- Panels -- */

.panel {
  min-width: 0;
  padding: 18px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  /* Anchored jumps from the rail and the bottom bar must clear the appbar. */
  scroll-margin-top: calc(var(--appbar-h) + 12px);
}

.panel__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.panel__h {
  color: var(--faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.panel__bar .panel__h { margin: 0; }
.panel > .panel__h { margin-bottom: 14px; }

.panel__h--sub {
  margin-top: 24px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  scroll-margin-top: calc(var(--appbar-h) + 12px);
}

.panel__h3 {
  margin: 20px 0 10px;
  color: var(--faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.hint {
  margin-top: 9px;
  color: var(--faint);
  font-size: 12px;
}
.hint:empty { display: none; }
.hint--warn { color: var(--red); }

/* --------------------------------------------------------------- Fields -- */

.fields { display: flex; flex-direction: column; gap: 10px; }

/* Until the split kicks in these are ordinary block boxes, so the children keep
   their original stacking and margin behaviour untouched. */
.details__col { min-width: 0; }

.field {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  align-items: center;
  gap: 0 11px;
  padding: 8px 12px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: text;
  transition: border-color .15s, box-shadow .15s;
}

.field:hover { border-color: var(--line-2); }
.field:focus-within {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-ring);
}

.field__ico {
  grid-column: 1;
  grid-row: 1 / 3;
  color: var(--faint);
}
.field:focus-within .field__ico { color: var(--blue); }

.field__lab {
  color: var(--muted);
  font-size: 11px;
  font-weight: 500;
}
.field__lab em { font-style: normal; color: var(--faint); font-weight: 400; }

.field__in {
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-size: 14px;
  line-height: 1.35;
}
.field__in:focus { outline: none; }
.field__in::placeholder { color: var(--placeholder); }

/* A paragraph of legal text needs room; inherit the font so it matches inputs. */
.field__in--area {
  font-family: inherit;
  resize: vertical;
  min-height: 54px;
  color: inherit;
}

.field--invalid { border-color: var(--red); }
.field--invalid .field__ico { color: var(--red); }

/* The grid above reserves column 1 for .field__ico. A field built without one
   drops its label into that 20px column, where "Email address" wraps to two
   lines next to the input instead of sitting above it. Collapse to a single
   column so an icon-less field still stacks correctly. */
.field:not(:has(.field__ico)) { grid-template-columns: minmax(0, 1fr); }

/* Repeat rows (extra phones / sites on Pro) get a remove control. */
[data-repeat] { display: flex; flex-direction: column; gap: 10px; }

/* The remove button opens a third track. Both it and .field__ico span rows
   1-3, and grid places definite-row items *before* the auto-flow ones — left
   to choose their own columns they take 1 and 2, shunting the label and the
   input out into the 22px gutter. Pin each to an explicit column and the
   label/input flow back into the 1fr track. -2 / -1 is the last explicit
   column either way, so this holds with or without an icon. */
.field:has(.field__rm) { grid-template-columns: 20px minmax(0, 1fr) 22px; }
.field:not(:has(.field__ico)):has(.field__rm) {
  grid-template-columns: minmax(0, 1fr) 22px;
}

.field__rm {
  grid-column: -2 / -1;
  grid-row: 1 / 3;
  align-self: center;
  justify-self: end;
  border: 0;
  background: none;
  padding: 2px;
  color: var(--faint);
  line-height: 0;
  cursor: pointer;
}
.field__rm:hover { color: var(--red); }

/* -------------------------------------------------------- Photo dropzone -- */

.drop {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 128px;
  padding: 18px;
  border: 1px dashed var(--line-dash);
  border-radius: var(--r);
  background: var(--well);
  text-align: center;
  transition: border-color .15s, background-color .15s;
}
.drop:hover, .drop.is-over { border-color: var(--blue); background: var(--blue-wash); }

.drop__in {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.drop__empty { display: grid; gap: 5px; justify-items: center; color: var(--muted); }
.drop__empty .ico { color: var(--faint); }
.drop__empty p { font-size: 13px; }
.drop__empty b { color: var(--ink); font-weight: 600; }
.drop__empty small { color: var(--faint); font-size: 11.5px; }

.drop__preview {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: var(--shadow);
}
.drop.is-square .drop__preview { border-radius: var(--r-sm); }
.drop.has-photo .drop__empty { display: none; }

.drop__clear {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
}

/* Both of these sit above .drop__in, which covers the whole zone — without a
   stacking order the invisible file input would swallow the click. */
.drop__adjust {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 1;
  padding: 3px 9px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-2);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.6;
  cursor: pointer;
  box-shadow: var(--shadow);
}
.drop__adjust:hover { border-color: var(--blue); color: var(--blue); }

/* -------------------------------------------------------------- Socials -- */

.socials { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }

.social {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) 26px;
  align-items: center;
  gap: 9px;
  padding: 6px 8px 6px 6px;
  border: 1px solid var(--line);
  border-radius: 10px;
  transition: border-color .15s, box-shadow .15s;
}
.social:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-ring); }

.social__ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-sm);
  background: var(--page);
}
.social__ico img { width: 17px; height: 17px; }

.social__in {
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-size: 13px;
}
.social__in:focus { outline: none; }
.social__in::placeholder { color: var(--placeholder); }

.social__rm {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--faint);
}
.social__rm:hover { background: var(--red-wash); color: var(--red); }

/* --------------------------------------------------------- Live preview -- */

.seg {
  display: inline-flex;
  padding: 3px;
  background: var(--page);
  border-radius: 9px;
}

.seg--wide { display: flex; width: 100%; }
.seg--wide .seg__b { flex: 1 1 0; }

.seg__b {
  padding: 6px 13px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  transition: background-color .15s, color .15s;
}
.seg__b:hover { color: var(--ink); }
.seg__b.is-on {
  background: var(--paper);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--shadow);
}

/* Fake compose window. Decorative framing so the signature is judged in
   context rather than floating on a blank card. */
/* The compose window does NOT follow the theme, in either direction.
   --------------------------------------------------------------------------
   Everything else here is our interface and can be recoloured freely. This is
   not interface — it is a rehearsal of the recipient's inbox. The signature
   inside carries hard-coded inline colours (email clients strip <style>, so
   the markup has no choice), all of them written against a white ground. Show
   that on a dark card and we would be lying about the deliverable, and the
   darker designs would go unreadable on top of it.

   So the light tokens are re-declared here and the subtree ignores the switch.
   Redeclaring rather than hard-coding each rule keeps the mailbox chrome in
   step with the rest of the light theme if those values are ever retuned. */
.mailbox {
  --ink:         #111827;
  --ink-2:       #374151;
  --muted:       #6b7280;
  --faint:       #9ca3af;
  --paper:       #ffffff;
  --line:        #e5e7eb;
  --line-soft:   #eef1f5;
  --well:        #fafbfc;
  color-scheme: light;
  color: var(--ink);

  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  transition: max-width .25s ease;
  max-width: 100%;
  margin: 0 auto;
}
.mailbox[data-width="mobile"] { max-width: 380px; }

.mailbox__row {
  display: flex;
  gap: 10px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.mailbox__row span { color: var(--faint); min-width: 48px; }
.mailbox__row i { color: var(--ink-2); font-style: normal; }

.mailbox__tools {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--well);
  color: var(--muted);
  overflow-x: auto;
  scrollbar-width: none;
}
.mailbox__tools::-webkit-scrollbar { display: none; }

.tool {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 7px;
  border-radius: 5px;
  font-size: 12px;
  white-space: nowrap;
}
.tool--wide { border: 1px solid var(--line); }
.tool--sep { width: 1px; height: 16px; padding: 0; background: var(--line); }

.mailbox__body {
  min-height: 210px;
  padding: 18px;
  overflow-x: auto;
}
/* min-height is only a floor — a tall signature still renders in full and pushes
   the panel down. 210 was sizing the empty state, which meant the one column that
   has to hold both the preview and the install box was reserving 40px of white
   space for a signature that had not been built yet. */
@media (min-width: 900px) {
  .mailbox__body { min-height: 170px; }
}
.mailbox__body:empty::after {
  content: 'Start typing — your signature appears here.';
  color: var(--faint);
  font-size: 13px;
}

.legal {
  display: flex;
  gap: 9px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.55;
}
.legal .ico { color: var(--green); margin-top: 1px; }
.legal b { color: var(--ink-2); font-weight: 600; }
/* Hosting/usage terms sit right under the Terms line, so they share that
   paragraph's divider instead of drawing a second one 14px below it. */
.legal--policy { margin-top: 8px; padding-top: 0; border-top: 0; }

/* ------------------------------------------------------- Template picker -- */

.tpick {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  text-align: left;
  transition: border-color .15s, box-shadow .15s;
}
.tpick:hover { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-ring); }
.tpick .ico { margin-left: auto; color: var(--faint); }

.tpick__thumb {
  display: block;
  width: 62px;
  flex: 0 0 auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  overflow: hidden;
}

.tpick__txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tpick__txt b { font-size: 13px; font-weight: 600; }
.tpick__txt small { color: var(--muted); font-size: 11.5px; }

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

.swatch {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  margin-bottom: 9px;
  font-size: 13px;
}
.swatch > span { color: var(--ink-2); }

.swatch input[type="color"] {
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: none;
  cursor: pointer;
}
.swatch input[type="color"]::-webkit-color-swatch-wrapper { padding: 3px; }
.swatch input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 4px; }
.swatch input[type="color"]::-moz-color-swatch { border: 0; border-radius: 4px; }

.swatch output {
  grid-column: 2;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--muted);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}

/* Colour + hex sit on one line to the right of the label. */
.swatch { grid-template-columns: minmax(0, 1fr) auto auto; }
.swatch output { grid-column: auto; }

.select {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-size: 13px;
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%236b7280' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6.5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 32px;
}
.select:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-ring); }

.reset {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 22px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--red);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.reset:hover { text-decoration: underline; }

/* -------------------------------------------------------- Install strip -- */

/* Item 8: this is a grid child of .cols now, not a block trailing after it, so
   the row gap supplies the spacing that margin-top used to. The width cap is
   the "shorter width" part — six install guides in one wide row read as a
   toolbar; capped, they wrap into a tidy block under the preview. */
.install {
  max-width: 660px;
  margin-inline: auto;
  padding: 24px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  scroll-margin-top: calc(var(--appbar-h) + 12px);
}

.install__intro { margin-bottom: 18px; text-align: center; }
.install__intro h2 { font-size: 18px; font-weight: 600; letter-spacing: -.01em; }
.install__intro p { margin-top: 5px; color: var(--muted); font-size: 13px; }

.install__grid {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.guide {
  display: grid;
  align-content: start;
  justify-items: center;
  gap: 3px;
  padding: 18px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: inherit;
  text-align: center;
  text-decoration: none;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.guide:hover {
  border-color: var(--blue);
  box-shadow: var(--shadow-lg);
  text-decoration: none;
  transform: translateY(-2px);
}
.guide img { margin-bottom: 7px; width: 34px; height: 34px; object-fit: contain; }
.guide b { font-size: 14px; font-weight: 600; }
.guide small { color: var(--muted); font-size: 11.5px; }
.guide__go {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 9px;
  color: var(--blue);
  font-size: 12.5px;
  font-weight: 500;
}

/* Desktop is where vertical room is scarce and horizontal room is not, so the
   guides collapse to a single row of four and shed the copy that was only
   padding them out: "Step-by-step guide" said the same thing on all four cards
   under a heading that already promised guides, and "View Guide" restated the
   fact that the whole card is a link. Phones keep the fuller card — they scroll
   anyway, and there the extra line is the only thing distinguishing the rows.

   This block MUST stay below the base .guide rules. A media query adds no
   specificity, so up above it lost every tie to the plain selectors and quietly
   did nothing at all. */
@media (min-width: 900px) {
  .install { padding: 20px; }
  .install__grid { grid-template-columns: repeat(4, 1fr); gap: 10px; }
  .guide { padding: 13px 10px; }
  .guide img { margin-bottom: 5px; }
  .guide small,
  .guide__go { display: none; }

  /* The sub-line promised "easy guides to add your signature to your favourite
     email platform" immediately above four cards named after email platforms.
     The heading keeps the intent; the cards are the sentence. */
  .install__intro { margin-bottom: 12px; }
  .install__intro p { display: none; }
}

.deliver {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 9px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--line-soft);
}

/* ------------------------------------------------- Floating bottom bar -- */

.tabbar {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 50;
  display: flex;
  gap: 2px;
  padding: 6px;
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: var(--shadow-lg);
}

@supports (backdrop-filter: blur(12px)) {
  .tabbar { background: var(--glass-blur); backdrop-filter: blur(12px); }
}

.tab {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
  padding: 7px 2px;
  border: 0;
  border-radius: 13px;
  background: none;
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 500;
  transition: background-color .15s, color .15s;
}
.tab span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.tab.is-on { background: var(--blue-wash); color: var(--blue); font-weight: 600; }

.peek {
  position: fixed;
  right: 16px;
  bottom: calc(var(--tabbar-h) + 22px + env(safe-area-inset-bottom));
  z-index: 49;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 11px 16px;
  border: 0;
  border-radius: 999px;
  background: var(--blue);
  color: var(--on-blue);
  font-size: 13px;
  font-weight: 600;
  box-shadow: var(--shadow-lg);
}
.peek:hover { background: var(--blue-dark); }

/* ------------------------------------------------------ Template sheet -- */

.sheet {
  width: min(960px, calc(100vw - 24px));
  max-height: min(86vh, 780px);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
/* Column layout must be scoped to [open]: a bare `display:flex` would beat the
   UA's `display:none` and leave every closed dialog on the page. The column is
   what gives .sheet__body a bounded height, so bar/foot stay put and only the
   body scrolls. */
.sheet[open] {
  display: flex;
  flex-direction: column;
}
.sheet::backdrop { background: var(--scrim); }

.sheet__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.sheet__bar h2 { font-size: 16px; font-weight: 600; }

.sheet__filters {
  display: flex;
  gap: 7px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--line-soft);
}

.chip {
  padding: 6px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
}
.chip:hover { border-color: var(--line-2); color: var(--ink); }
.chip.is-on { background: var(--blue); border-color: var(--blue); color: var(--on-blue); }

.tgrid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  padding: 18px;
  overflow-y: auto;
  /* Sized by the sheet's flex column instead of a calc() that hardcoded the
     bar + filters height, which silently broke whenever either row grew. */
  flex: 1 1 auto;
  min-height: 0;
}

.tpl-option {
  position: relative;
  display: grid;
  gap: 4px;
  padding: 9px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  text-align: left;
  transition: border-color .15s, box-shadow .15s;
}
.tpl-option:hover { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-ring); }
.tpl-option.is-on { border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue); }

.tpl-option__name { margin-top: 7px; font-size: 13px; font-weight: 600; }
.tpl-option__meta { color: var(--muted); font-size: 11.5px; }

.tpl-option__lock {
  position: absolute;
  top: 15px;
  right: 15px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--lock-bg);
  color: var(--lock-fg);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.tpl-option__lock .ico { width: 11px; height: 11px; }

/* ---------------------------------------------------------------- Toast -- */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tabbar-h) + 28px + env(safe-area-inset-bottom));
  z-index: 60;
  transform: translateX(-50%);
  max-width: calc(100vw - 32px);
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 13px;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
}
.toast--bad { background: var(--red); }

/* ==========================================================================
   Runtime components
   --------------------------------------------------------------------------
   Everything below is built by js/app.js rather than written into index.html:
   the extra image slots, the repeat-group footers, the account and upgrade
   dialogs, and the locked-template placeholder. Kept in one block so the
   markup in app.js has a single place to be checked against.
   ========================================================================== */

/* ---------------------------------------------------- Extra image slots -- */

/* Logo and banner sit under the main photo drop and appear only for templates
   that declare the slot, so this container is often empty. */
.slots {
  display: grid;
  gap: 10px;
  margin-top: 10px;
}
.slots:empty { display: none; }

/* These stay stacked. Two side by side looks like the obvious saving and is not:
   in a 232px column each card is ~111px wide, "Company logo — drop or browse"
   breaks over five lines, and the pair ends up 187px tall against 194px stacked.
   Seven pixels is not worth reading a label down a ladder. */

.drop--sm {
  min-height: 92px;
  padding: 14px;
}
.drop--sm .drop__empty .ico { width: 22px; height: 22px; }
.drop--sm .drop__empty p { font-size: 12.5px; }

/* Logos and banners are not portraits: never mask them into a circle. */
.drop--sm .drop__preview {
  width: auto;
  max-width: 100%;
  height: 56px;
  border-radius: var(--r-sm);
  object-fit: contain;
  background: var(--paper);
}

/* Mid-upload. The input is covered so a second file cannot be dropped onto a
   slot that is already talking to the server. */
.drop.is-busy { border-style: solid; border-color: var(--blue); background: var(--blue-wash); }
.drop.is-busy .drop__in { pointer-events: none; }
.drop.is-busy::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  background: var(--veil);
  animation: pulse 1.1s ease-in-out infinite;
}

@keyframes pulse { 50% { background: var(--veil-2); } }

/* The chosen template has no photo slot. The zone stays in place so the panel
   does not jump, but it is plainly inert; the hint underneath says why. */
.drop.is-idle {
  opacity: .45;
  filter: grayscale(1);
}
.drop.is-idle .drop__in { pointer-events: none; }
.drop.is-idle:hover { border-color: var(--line-dash); background: var(--well); }

/* --------------------------------------------------------- Repeat rows -- */

.repeat__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

/* .link--sm carries its own margin for standalone use; inside the footer the
   flexbox owns the spacing. */
.repeat__foot .link--sm { margin: 6px 0 0; }

.repeat__note {
  margin-top: 6px;
  color: var(--faint);
  font-size: 11.5px;
}

/* ------------------------------------------------------------ Pro fields -- */

/* Injected into the details panel once a Pro template is chosen. The rule is
   the same as .fields; it exists so the group can be targeted and animated
   as a unit when it appears. */
.pro-fields { margin-top: 10px; }

/* A field the chosen template does not render. Same visual language as an
   unused image slot (.drop.is-idle), minus the grayscale — there is no colour
   to drain from a text input. Stays focusable and editable on purpose: the
   value is kept in the draft and returns when a design that uses it is
   picked, so locking it would only throw away typing. */
.pro-fields .field.is-idle { opacity: .5; }
.pro-fields .field.is-idle:focus-within { opacity: 1; }

/* ------------------------------------------------------- Small dialogues -- */

/* Account and upgrade sheets. .sheet is sized for the 30-template grid, so
   these narrow it and let the body scroll instead of the grid. */
.sheet--sm {
  width: min(420px, calc(100vw - 24px));
  max-height: min(88vh, 640px);
}

.sheet__body {
  padding: 18px;
  overflow-y: auto;
  /* min-height:0 lets this shrink below its content so the scrollbar lands
     here rather than pushing the footer out of the sheet. */
  flex: 1 1 auto;
  min-height: 0;
}
.sheet__body .field { margin-bottom: 10px; }

.sheet__foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 18px;
  border-top: 1px solid var(--line-soft);
  background: var(--page);
  flex-shrink: 0;
}

/* ---------------------------------------------------- Crop & resize tool -- */

.sheet--crop { width: min(400px, calc(100vw - 24px)); }

.crop__body { display: grid; justify-items: center; gap: 12px; }

.crop__note {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
}
.crop__note b { color: var(--ink); font-weight: 600; }

.crop__stage {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--well);
  box-shadow: inset 0 0 0 1px var(--line);
}

.crop__canvas {
  display: block;
  cursor: grab;
  /* The pointer handlers have to beat the browser's own pan/zoom, or dragging
     scrolls the dialog instead of moving the image. */
  touch-action: none;
}
.crop__canvas.is-grabbing { cursor: grabbing; }

/* Purely a guide drawn over the canvas, so it must not eat the drag. */
.crop__mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .55);
}
/* Circle headshots get the round cut previewed here. The spread is clipped by
   the stage's overflow, so it dims the corners and nothing else. */
.crop__mask.is-round::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 9999px rgba(17, 24, 39, .38);
}

.crop__zoom {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(100%, 300px);
}

.crop__step {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.crop__step:hover { border-color: var(--blue); color: var(--blue); }

.crop__range { flex: 1 1 auto; min-width: 0; accent-color: var(--blue); }

.crop__hint { text-align: center; }

/* Reset sits apart from Cancel/Save: it changes the frame, it does not close. */
.crop__foot { align-items: center; }
.crop__spacer { flex: 1 1 auto; }

/* ------------------------------------------- Terms of Use / Privacy Policy -- */

/* Wider than the guide sheet: these are long enough that a narrow measure
   turns them into a ribbon of scrolling. */
.sheet--legal {
  width: min(560px, calc(100vw - 24px));
  max-height: min(88vh, 720px);
}

.legal-doc {
  font-size: 13.5px;
  line-height: 1.62;
  color: var(--ink);
}
.legal-doc p { margin: 0 0 12px; }

.legal-doc__lead {
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--muted);
}

.legal-doc h3 {
  margin: 22px 0 8px;
  font-size: 13.5px;
  font-weight: 650;
  color: var(--ink);
}
/* The lead already carries the gap under the rule. */
.legal-doc h3:first-of-type { margin-top: 0; }

.legal-doc ul {
  margin: 0 0 12px;
  padding-left: 20px;
}
.legal-doc li { margin-bottom: 6px; }

.legal-doc__foot {
  margin: 22px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
  font-size: 12.5px;
  color: var(--muted);
}
.legal-doc a { color: var(--brand); }

/* Install guides. Slightly wider than the account sheet because numbered
   steps wrap badly at 420px. */
.sheet--guide { width: min(480px, calc(100vw - 24px)); }

.guide-lead {
  margin-bottom: 14px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

.guide-sub {
  margin: 20px 0 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

.guide-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: guide;
}
.guide-steps li {
  position: relative;
  counter-increment: guide;
  padding: 0 0 12px 30px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
}
.guide-steps li:last-child { padding-bottom: 0; }
.guide-steps li::before {
  content: counter(guide);
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--blue-ring);
  color: var(--blue);
  font-size: 11px;
  font-weight: 600;
}
.guide-steps b { font-weight: 600; }

/* Rides on .sheet__foot, so borders/padding/background come from there. This
   only lets the two buttons wrap and share the width on narrow screens. */
.guide-act { flex-wrap: wrap; }
.guide-act .btn { flex: 1 1 auto; justify-content: center; }

.dlg-note {
  margin-bottom: 14px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
}

/* Signed-in identity: address on one line, plan underneath. */
.dlg-who {
  display: grid;
  gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--page);
  margin-bottom: 14px;
}
.dlg-who b { font-size: 13.5px; word-break: break-all; }
.dlg-who small { color: var(--muted); font-size: 12px; }

/* PayPal injects an iframe here. Reserve height so the dialog does not jolt
   when the button finally paints. */
.paypal-box {
  min-height: 52px;
  margin-top: 14px;
}

/* PayPal paints an opaque white canvas inside its own cross-origin iframe, so
   in dark mode the buttons land on a white slab we cannot reach: contentDocument
   is null, and `color-scheme: dark` does not cross the frame boundary either
   (verified — PayPal pins its own scheme). Rather than fight it, we extend the
   same white behind the frame with padding and a radius, so it reads as a
   deliberate payment surface instead of a torn hole in the dialog. The padding
   also hides the iframe's square corners. */
[data-theme="dark"] .paypal-box {
  padding: 10px 12px;
  border-radius: var(--r);
  background: #fff;
}

/* ------------------------------------------------- Locked Pro placeholder -- */

/* Shown in the preview when a free account selects a Pro design. It replaces
   the signature entirely — a blurred teaser would still put the markup in the
   DOM, where it could simply be copied out. */
.locked {
  display: grid;
  gap: 8px;
  justify-items: center;
  max-width: 380px;
  margin: 0 auto;
  padding: 34px 20px;
  text-align: center;
}
.locked .ico { color: var(--faint); }
.locked h3 { font-size: 15px; font-weight: 600; }
.locked p { color: var(--muted); font-size: 13px; line-height: 1.55; }
.locked .btn { margin-top: 6px; }
.locked .link--sm { margin: 2px 0 0; }

/* Placeholder copy while the Pro pack loads, or if templates.js is missing. */
.muted-note {
  padding: 30px 16px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* --------------------------------------------------------- Copy staging -- */

/* Rich clipboard copy needs the markup laid out and selectable, so it cannot
   be display:none or visibility:hidden — either would make execCommand('copy')
   come back empty. Park it outside the viewport instead. app.js appends and
   removes it within a single synchronous block, so it is never perceivable. */
.offscreen-copy {
  position: fixed;
  top: 0;
  left: -10000px;
  width: 640px;
  pointer-events: none;
  opacity: 0;
}

/* ---------------------------------------------------------- Breakpoints -- */

/* Tablet: details and design stack in one column, preview alongside. */
@media (min-width: 900px) {
  .cols {
    grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
    grid-template-areas:
      "details preview"
      "design  preview"
      "design  install";
    align-items: start;
  }
}

/* Desktop: rail plus the mockup's three columns. */
@media (min-width: 1180px) {
  .shell {
    grid-template-columns: 248px minmax(0, 1fr);
    padding: 20px;
    gap: 20px;
  }
  .rail {
    order: 0;
    position: sticky;
    top: calc(var(--appbar-h) + 20px);
  }
  .tabbar, .peek { display: none; }
}

/* Item 8: with three columns the install block moves under the preview in the
   centre column, where it is the obvious next thing after "this is your
   signature". details and design span both rows on either side of it. */
@media (min-width: 1440px) {
  .cols {
    grid-template-columns: 344px minmax(0, 1fr) 292px;
    grid-template-areas:
      "details preview design"
      "details install design";
  }
}

/* The no-scroll breakpoint.
   Three columns still left the first one roughly twice the height of the other
   two — it carries every text field AND every upload AND the socials, so it
   alone decided how far the page scrolled. Widening it and splitting it in two
   internally turns the tallest column into two short ones and costs nothing but
   horizontal space, which at this width is the one thing there is a surplus of. */
@media (min-width: 1600px) {
  /* 520 rather than a wider split: the details column has height to spare once
     it is two columns, and the middle column does not. Every pixel handed to the
     middle buys two things at once — the four guide cards stop wrapping their
     titles onto a third line, and the delivery buttons fit on one row instead of
     two. Column one just gets slightly narrower fields, which cost nothing. */
  .cols { grid-template-columns: 520px minmax(0, 1fr) 292px; }

  #panel-details {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 18px;
    align-items: start;
  }
  /* The sub-headings lead their own column here, so the top margin that
     separated them from the block above is now just a ragged top edge. */
  #panel-details .details__col > .panel__h--sub:first-child { margin-top: 0; }
}

/* Phone: tighten the frame so the preview keeps as much width as possible. */
@media (max-width: 620px) {
  .appbar { padding: 0 12px; gap: 10px; }
  .appbar__title { display: none; }
  .appbar__rule { display: none; }
  /* Icon-only on phones, but NOT display:none — that drops the label out of
     the accessibility tree, and since every icon here is aria-hidden the
     button would be announced as just "button". Screen reader users would
     find four unlabelled controls, one of which is the only way to sign in.
     Hiding it visually while leaving it readable keeps the name. */
  .appbar__actions .btn span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .appbar__actions .btn { padding: 9px 11px; }

  .shell { padding: 12px; padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 24px); }
  .cols { gap: 12px; }
  .panel { padding: 14px; border-radius: var(--r); }
  .install { padding: 18px 14px; }
  .mailbox__body { padding: 14px; min-height: 170px; }
  .deliver .btn { flex: 1 1 auto; }
}

/* Very narrow phones: three icon buttons plus the wordmark stop fitting, and
   the buttons are the ones people actually press. The chip still identifies us. */
@media (max-width: 430px) {
  .appbar__word { display: none; }
}

/* ---------------------------------------------------------- Walkthrough -- */

/* First-visit coach marks. The dimming is four separate panels framing a gap
   rather than one overlay with a punched-out hole, because backdrop-filter
   only blurs what is painted *behind* the element — so the honest way to keep
   the spotlight sharp is to put nothing over it. The gap also lets clicks
   through, which means the thing being pointed at is still usable while it is
   being explained. */

.tour {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;   /* only the panels and the card take the mouse */
}

.tour__panel {
  position: fixed;
  background: var(--tour-scrim);
  -webkit-backdrop-filter: blur(2.5px);
  backdrop-filter: blur(2.5px);
  pointer-events: auto;
}

/* The ring is drawn on its own so the panels stay perfect rectangles: giving
   the gap a radius is otherwise impossible with four straight edges. */
.tour__ring {
  position: fixed;
  border: 2px solid var(--blue);
  border-radius: var(--r);
  box-shadow: 0 0 0 4px var(--blue-ring), 0 10px 30px rgba(17, 24, 39, .18);
  pointer-events: none;
  transition: top .22s ease, left .22s ease, width .22s ease, height .22s ease;
}

.tour__pop {
  position: fixed;
  width: min(340px, calc(100vw - 24px));
  padding: 16px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: 0 18px 44px rgba(17, 24, 39, .22);
  pointer-events: auto;
  transition: top .22s ease, left .22s ease;
}

.tour__count {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--blue);
}

.tour__h    { margin: 0 0 6px; font-size: 16px; font-weight: 700; color: var(--ink); }
.tour__body { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-2); }

.tour__foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
}

.tour__dots { display: flex; gap: 5px; margin-right: auto; }

.tour__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--line-2);
}

.tour__dot.is-on { background: var(--blue); }

/* No .btn--sm exists in the button vocabulary and one step of the tour is not
   reason enough to invent one, so the size is scoped to this footer. */
.tour__foot .btn { padding: 7px 12px; font-size: 13px; }

/* Only the dots take the free space. Two auto margins in here would split it
   and leave the buttons floating in the middle of the row. */

/* The replay handle, under the step list. Deliberately quiet: it is there for
   the rare second look, not competing with the Pro card below it. */
.rail__tour {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}

.rail__tour:hover { background: var(--blue-wash); color: var(--blue); }
.rail__tour .ico  { width: 15px; height: 15px; flex: 0 0 auto; }

/* Phones stop chasing the spotlight sideways and let the card span the width —
   there is rarely room beside a highlight on a 390px screen. The vertical flip
   in placeTour() still applies, so the card moves above or below as needed. */
@media (max-width: 720px) {
  .tour__pop {
    left: 12px !important;
    right: 12px;
    width: auto;
  }
}

/* --------------------------------------------------------- Preferences -- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .guide:hover { transform: none; }
}

/* High contrast pushes the greys apart: lines and secondary text move *away*
   from the page colour, which means darker on light and lighter on dark.
   Both selectors have the same specificity, so the dark rule has to come
   second here or the light greys would win on source order. */
@media (prefers-contrast: more) {
  :root { --line: #b9c0ca; --muted: #4b5563; --faint: #5b6472; }

  [data-theme="dark"] {
    --line: #556072; --muted: #aab3c2; --faint: #929cad;
  }
}
