:root {
  --bg: #f6f7fb;
  --surface: #ffffff;
  --border: #e4e7ee;
  --text: #131722;
  --muted: #626b80;
  --brand: #4f46e5;
  --brand-dark: #4338ca;
  --accent: #06b6d4;
  --danger: #dc2626;
  --ok: #059669;
  /* Accent palette. Six hues that read as one family, used wherever a run of
     sibling cards would otherwise be a wall of identical grey: the feature
     grids and the type picker cycle through them by position, and the content
     type list sets one directly per row. Anything tinted derives from --c, so a
     rule only ever names the slot, never a hex. */
  --c1: #4f46e5;
  --c2: #0ea5e9;
  --c3: #10b981;
  --c4: #f59e0b;
  --c5: #ec4899;
  --c6: #8b5cf6;
  --grad: linear-gradient(95deg, var(--brand), var(--accent) 45%, #d62976);
  --radius: 14px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 8px 24px rgba(16, 24, 40, .06);
  --maxw: 1140px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
/* Two soft colour washes behind the whole page, so the grey never reads as flat.
   They live on a fixed pseudo-element rather than on body: a background-attachment
   fixed gradient repaints on every scroll frame on mobile Safari, and a layer at
   z-index -1 stays out of the way of everything that stacks above it. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(760px 460px at 88% -6%, rgba(6, 182, 212, .13), transparent 70%),
    radial-gradient(680px 420px at 6% 2%, rgba(79, 70, 229, .12), transparent 70%);
}
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { line-height: 1.2; margin: 0 0 .5em; letter-spacing: -.02em; }
h1 { font-size: clamp(2rem, 4vw, 3rem); }
h2 { font-size: clamp(1.5rem, 2.6vw, 2rem); }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }
.muted { color: var(--muted); }
.center { text-align: center; }

/* ---------- header ---------- */
header.site {
  position: sticky; top: 0; z-index: 40;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
/* A gradient hairline along the bottom edge — the brand colours introduce
   themselves before the page does. Drawn over the border rather than replacing
   it so the header still has a solid edge if gradients fail to paint. */
header.site::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--grad); opacity: .85;
}
header.site .inner { display: flex; align-items: center; gap: 24px; height: 64px; }
.logo { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 1.1rem; color: var(--text); }
.logo:hover { text-decoration: none; }
.logo-mark {
  width: 30px; height: 30px; border-radius: 8px; display: block;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .12);
}
.logo-word { letter-spacing: -.01em; }
.logo-word em {
  font-style: normal; color: var(--brand);
  background: var(--grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
nav.main { display: flex; gap: 20px; margin-left: auto; align-items: center; }
nav.main a { color: var(--muted); font-weight: 500; font-size: .95rem; }
nav.main a:hover { color: var(--text); text-decoration: none; }
nav.main a.active { color: var(--brand); font-weight: 600; }
nav.main a.active::after {
  content: ''; display: block; height: 2px; margin-top: 3px;
  border-radius: 2px; background: var(--brand);
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px; border-radius: 10px; border: 1px solid transparent;
  font: inherit; font-weight: 600; font-size: .95rem; cursor: pointer;
  /* Gradient fill, with the flat brand colour underneath it as the fallback.
     background-size is oversized so the hover can slide the gradient sideways
     instead of cross-fading two backgrounds, which is not animatable. */
  background: var(--brand) linear-gradient(115deg, var(--brand), #6366f1 45%, var(--accent));
  background-size: 180% 100%; color: #fff;
  box-shadow: 0 2px 10px rgba(79, 70, 229, .28);
  transition: background-position .25s, transform .05s, box-shadow .15s;
}
.btn:hover {
  background-position: 100% 0; text-decoration: none; color: #fff;
  box-shadow: 0 6px 18px rgba(79, 70, 229, .34);
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn.ghost { background: rgba(255, 255, 255, .7); color: var(--text); border-color: var(--border); box-shadow: none; }
.btn.ghost:hover { background: #eef2ff; color: var(--brand-dark); border-color: #c7cbf5; box-shadow: none; }
.btn.sm { padding: 6px 12px; font-size: .85rem; }
.btn.danger { background: transparent; color: var(--danger); border-color: #f2c9c9; box-shadow: none; }
.btn.danger:hover { background: #fdecec; color: var(--danger); box-shadow: none; }
.btn.block { width: 100%; }

/* ---------- form controls ---------- */
label.field { display: block; margin-bottom: 14px; }
label.field > span { display: block; font-size: .82rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
input[type=text], input[type=url], input[type=email], input[type=password],
input[type=tel], input[type=number], input[type=datetime-local], select, textarea {
  width: 100%; padding: 11px 13px; font: inherit; color: var(--text);
  border: 1px solid var(--border); border-radius: 10px; background: #fff;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(79, 70, 229, .14);
}
textarea { min-height: 96px; resize: vertical; }
input[type=color] { width: 100%; height: 42px; padding: 3px; border: 1px solid var(--border); border-radius: 10px; background: #fff; }
input[type=range] { width: 100%; accent-color: var(--brand); }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* ---------- cards ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 24px;
}
.badge {
  display: inline-block; padding: 3px 9px; border-radius: 999px;
  font-size: .74rem; font-weight: 700; letter-spacing: .02em;
  background: #eef2ff; color: var(--brand-dark);
}
.badge.ok { background: #e7f7f0; color: var(--ok); }
.badge.off { background: #f1f3f7; color: var(--muted); }

/* ---------- hero + generator ---------- */
.hero { padding: 56px 0 28px; text-align: center; position: relative; }
.hero p.lede { font-size: 1.12rem; color: var(--muted); max-width: 620px; margin: 0 auto; }
/* The page title in the brand gradient. inline-block, not the h1 itself: a
   full-width block stretches the gradient across the whole column and the
   centred text then only ever samples its middle. */
.hero h1 {
  display: inline-block;
  background: var(--grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--brand);
}

/* ---------- hero demo ----------
   A real vCard QR Code and the contact sheet a phone offers when it reads one.
   It sits in the hero's top-right corner on a wide screen: the hero text is a
   620px column in a 1140px wrap, so there is a clear 250px gutter beside it and
   nothing has to move to make room. Narrower than that and the gutter is gone,
   so the box becomes an ordinary centred block under the lede instead. */
.demo {
  position: absolute; top: 40px; right: 20px; width: 278px; z-index: 1;
  text-align: left; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 12px 34px rgba(16, 24, 40, .1); overflow: hidden;
}
.demo-head {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 12px; font-size: .76rem; font-weight: 700; color: #fff;
  letter-spacing: .01em; background: var(--grad);
}
/* The pulsing dot reads as "this is running", which the crossfade below is. */
.demo-head .live {
  width: 7px; height: 7px; border-radius: 50%; background: #fff; flex: none;
  box-shadow: 0 0 0 0 rgba(255, 255, 255, .7); animation: demo-pulse 2s ease-out infinite;
}
@keyframes demo-pulse {
  70% { box-shadow: 0 0 0 7px rgba(255, 255, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
.demo-body { display: flex; gap: 10px; align-items: center; padding: 12px 12px 4px; }

/* 140px is about the smallest this code scans at: it is 45 modules across, so
   anything less puts a module under 3px and the camera starts guessing. */
.demo-qr { margin: 0; flex: none; width: 140px; text-align: center; }
.demo-qr-img {
  width: 140px; height: 140px; padding: 5px; border-radius: 10px;
  background: #fff; border: 1px solid var(--border);
}
/* The code has to reach the camera as drawn — smoothing a shrunk QR is what
   makes one unreadable, and this one is meant to be scanned off the screen. */
.demo-qr-img svg { width: 100%; height: 100%; display: block; shape-rendering: crispEdges; }
.demo-qr figcaption { font-size: .68rem; font-weight: 600; color: var(--muted); margin-top: 6px; line-height: 1.25; }

/* ---- the phone ---- */
.demo-phone {
  position: relative; flex: none; width: 102px; height: 168px;
  border-radius: 16px; padding: 6px; background: #131722;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16), 0 6px 16px rgba(16, 24, 40, .22);
}
.demo-phone .notch {
  position: absolute; top: 4px; left: 50%; transform: translateX(-50%);
  width: 26px; height: 4px; border-radius: 3px; background: #33384a; z-index: 2;
}
.demo-screen { position: relative; width: 100%; height: 100%; border-radius: 11px; overflow: hidden; background: #fff; }
/* Both screens are stacked on top of each other and swapped by opacity, so the
   phone never changes size between them. */
.demo-screen .scr {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px; text-align: center;
  animation: demo-cycle 7s ease-in-out infinite;
}
.demo-screen .scan { background: #1b1f2e; color: #fff; animation-delay: 0s; }
.demo-screen .contact { background: linear-gradient(180deg, #fff, #f5f7ff); animation-delay: 3.5s; opacity: 0; }
/* Each screen is opaque for its half of the loop and transparent for the other,
   with the two offset by half the duration by the delays above. */
@keyframes demo-cycle {
  0%, 42% { opacity: 1; }
  50%, 92% { opacity: 0; }
  100% { opacity: 1; }
}
.demo-screen .scan b { font-size: .6rem; font-weight: 600; letter-spacing: .04em; opacity: .8; margin-top: 30px; }
/* The camera's viewfinder: a bracketed square with a line sweeping down it. */
.demo-screen .reticle {
  position: absolute; top: 34px; left: 50%; transform: translateX(-50%);
  width: 48px; height: 48px; border-radius: 8px; border: 2px solid rgba(255, 255, 255, .85);
}
.demo-screen .beam {
  position: absolute; top: 34px; left: 50%; transform: translateX(-50%);
  width: 48px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  box-shadow: 0 0 8px var(--accent);
  animation: demo-beam 1.6s ease-in-out infinite;
}
@keyframes demo-beam {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 44px); }
}
/* Clear of the notch, and packed from the top so the sheet keeps the same
   shape whatever the fields say. */
.demo-screen .contact { justify-content: flex-start; padding: 15px 6px 10px; gap: 2px; }
.demo-screen .contact .av {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  background: var(--grad); color: #fff; font-size: .68rem; font-weight: 800; margin-bottom: 3px;
}
.demo-screen .contact b { font-size: .72rem; }
.demo-screen .contact i { font-style: normal; font-size: .56rem; color: var(--muted); }
.demo-screen .contact .line { width: 46px; height: 1px; background: var(--border); margin: 4px 0; }
.demo-screen .contact em { font-style: normal; font-size: .54rem; color: var(--text); }
.demo-screen .contact .cta {
  margin-top: auto; padding: 5px 11px; border-radius: 999px; white-space: nowrap;
  background: var(--grad); color: #fff; font-size: .55rem; font-weight: 700;
}
.demo-foot { margin: 0; padding: 8px 12px 11px; font-size: .68rem; line-height: 1.35; color: var(--muted); }

/* A loop in the corner of the eye is a nuisance to some people, so with reduced
   motion asked for the phone rests on the contact sheet — the half of the loop
   that carries the point — and nothing moves. */
@media (prefers-reduced-motion: reduce) {
  .demo-head .live, .demo-screen .beam { animation: none; }
  .demo-screen .scr { animation: none; }
  .demo-screen .scan { opacity: 0; }
  .demo-screen .contact { opacity: 1; }
}
/* The corner placement only holds while the gutter beside the 520px hero column
   is wide enough for the box. Below that the box drops under the lede, and the
   lede goes back to its full width. */
@media (min-width: 1200px) {
  .hero p.lede { max-width: 520px; }
}
@media (max-width: 1199px) {
  .demo { position: static; width: 100%; max-width: 300px; margin: 26px auto 0; }
}

/* minmax(0, …) rather than a bare 1fr: the form column holds sideways-scrolling
   strips (the frame picker), and an auto-minimum track would grow to their full
   content width and push the whole page wider than a phone screen. */
.generator { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 24px; align-items: start; padding-bottom: 56px; }
.types { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.type-btn {
  padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border);
  background: #fff; font: inherit; font-size: .88rem; font-weight: 600; color: var(--muted); cursor: pointer;
}
.type-btn:hover { border-color: #c9cede; color: var(--text); }
.type-btn.active {
  background: var(--brand) linear-gradient(115deg, var(--brand), var(--accent));
  border-color: transparent; color: #fff; box-shadow: 0 2px 10px rgba(79, 70, 229, .28);
}

.preview { position: sticky; top: 88px; text-align: center; }
.preview-frame {
  background: #fff; border-radius: 12px; padding: 16px;
  border: 1px solid var(--border); display: grid; place-items: center; min-height: 260px;
}
.preview-frame img { width: 100%; max-width: 240px; image-rendering: pixelated; }
/* Square modules stay crisp when the browser shrinks them without smoothing.
   Shaped ones are curves, and the same setting turns them into a staircase, so
   a code with a shape style chosen is scaled the ordinary way instead. */
.preview-frame.smooth img { image-rendering: auto; }
/* Says what the code on screen is before it is saved: a real, scannable code
   that carries a "save this first" note instead of your content. */
.preview-tag {
  margin: 10px 0 0; font-size: .78rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
/* ---- shape picker ---- */
.shape-h { margin: 24px 0 4px; font-size: 1rem; }
.swatches { display: grid; grid-template-columns: repeat(8, 1fr); gap: 6px; margin-bottom: 14px; }
.swatch {
  aspect-ratio: 1; padding: 6px; cursor: pointer; display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  transition: border-color .15s, background .15s;
}
.swatch:hover:not(:disabled) { border-color: #c9cede; }
.swatch.on { border-color: var(--brand); background: #eef2ff; box-shadow: 0 0 0 2px rgba(79, 70, 229, .14); }
.swatch:disabled { opacity: .55; cursor: not-allowed; }
.swatch svg { width: 100%; height: 100%; display: block; }
/* The frame swatches are little pictures in the frame's own colour, so they are
   left with the fills they were drawn with; the shape glyphs take theirs here. */
.swatch:not(.frame-swatch) path { fill: var(--text); }
.swatch.on:not(.frame-swatch) path { fill: var(--brand-dark); }
/* The picker sits next to its checkbox rather than under it: unchecked, the
   pair reads as one control that is currently switched off. */
.opt-color-row { display: flex; align-items: center; gap: 8px; }
.opt-color-row input[type=checkbox] { width: 16px; height: 16px; flex: none; }
.opt-color-row input[type=color] { flex: 1; min-width: 0; }
/* Eight swatches in a row stop being legible on a phone. */
@media (max-width: 560px) { .swatches { grid-template-columns: repeat(4, 1fr); } }

/* ---- outer frame picker ---- */
/* There are more frames than fit a row and each one is a small picture rather
   than a glyph, so they scroll sideways instead of wrapping into a block that
   would push the rest of the form off the screen. */
.frame-strip {
  display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px 10px;
  margin-bottom: 6px; scrollbar-width: thin;
}
.frame-swatch { flex: 0 0 62px; aspect-ratio: 24 / 30; padding: 7px; }
/* "No frame" is a cross rather than a picture, and follows the swatch's own
   selected state like the shape glyphs do. */
.frame-swatch svg { color: var(--text); }
.frame-swatch.on svg { color: var(--brand-dark); }
[data-frame-fields][hidden], [data-frame-color][hidden] { display: none; }

.check { display: flex; align-items: center; gap: 8px; font-size: .85rem; margin-top: 12px; }
.check input { width: 16px; height: 16px; }
.check .muted { font-weight: 400; }
.preview .err { color: var(--danger); font-size: .9rem; }
.dl { display: grid; gap: 8px; margin-top: 16px; }

.steps { display: flex; gap: 8px; justify-content: center; margin-bottom: 28px; flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: 8px; font-size: .88rem; color: var(--muted); font-weight: 600; }
.step i {
  width: 22px; height: 22px; border-radius: 50%; background: #e6e9f2; color: var(--muted);
  display: grid; place-items: center; font-size: .74rem; font-style: normal;
}
.step.on i {
  background: linear-gradient(135deg, var(--brand), var(--accent)); color: #fff;
  box-shadow: 0 2px 8px rgba(79, 70, 229, .35);
}
.step.on { color: var(--text); }
.step + .step::before {
  content: ""; width: 26px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--border), color-mix(in srgb, var(--brand) 35%, var(--border)));
}

/* ---------- marketing sections ---------- */
section.band { padding: 56px 0; position: relative; }
/* The alternating band keeps its white ground but sits under a pale tint, so the
   page reads as bands of colour rather than white/grey/white. */
section.band.alt {
  background: linear-gradient(180deg, #fff, #fbfaff 55%, #f7fbfd);
  border-block: 1px solid var(--border);
}
/* A gradient rule across the top of every band: a divider that carries colour. */
section.band + section.band::before,
section.band.alt::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--grad); opacity: .5;
}
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px; }
.feature h3 { font-size: 1.05rem; margin-bottom: 6px; }

/* Feature cards take a palette hue by position, same trick as the type picker:
   a tinted icon tile, a gradient bar that wipes in along the top on hover, and a
   shadow in the card's own colour. */
.feature { --c: var(--brand); position: relative; overflow: hidden; transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
.grid-3 .feature:nth-child(6n+1) { --c: var(--c1); }
.grid-3 .feature:nth-child(6n+2) { --c: var(--c2); }
.grid-3 .feature:nth-child(6n+3) { --c: var(--c3); }
.grid-3 .feature:nth-child(6n+4) { --c: var(--c4); }
.grid-3 .feature:nth-child(6n+5) { --c: var(--c5); }
.grid-3 .feature:nth-child(6n+6) { --c: var(--c6); }
.feature::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--c), color-mix(in srgb, var(--c) 35%, #fff));
  transform: scaleX(0); transform-origin: left; transition: transform .28s ease;
}
.feature:hover {
  transform: translateY(-3px); border-color: color-mix(in srgb, var(--c) 40%, var(--border));
  box-shadow: 0 12px 30px color-mix(in srgb, var(--c) 18%, transparent);
}
.feature:hover::before { transform: scaleX(1); }
.feature .ico {
  width: 46px; height: 46px; border-radius: 13px; margin-bottom: 12px;
  display: grid; place-items: center; font-size: 1.5rem;
  background: color-mix(in srgb, var(--c) 14%, #fff);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 26%, transparent);
}
/* An FAQ card is the same .feature markup with no icon, so the hue lands on the
   question instead: a coloured bar down the side and a tinted heading. Keyed on
   the missing icon rather than on #faq, so the answers on the pricing page get
   the same treatment without needing that id. */
.feature:not(:has(.ico)) { border-left: 3px solid var(--c); }
.feature:not(:has(.ico)) h3 { color: color-mix(in srgb, var(--c) 72%, var(--text)); }

/* Gradient headings. Applied to an inline-block span inside the heading, not the
   heading itself: a full-width block would stretch the gradient across the whole
   band and the centred text would only ever sample its middle. The -webkit-
   prefixes are still what Safari honours for background-clip: text. */
.gradient-text {
  display: inline-block;
  background: linear-gradient(95deg, var(--brand), var(--accent) 45%, #d62976);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--brand);
}

/* The content-type list. Each <li> carries its own --c (the brand colour of the
   thing it encodes) and everything tinted on the row — icon tile, hover border,
   glow — derives from that one value, so adding a type means picking a colour. */
.type-list {
  list-style: none; margin: 0 auto; padding: 0; max-width: 980px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 14px;
}
.type-list li {
  --c: var(--brand);
  display: flex; gap: 14px; align-items: flex-start;
  padding: 16px 18px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); font-size: .94rem; line-height: 1.5;
  position: relative; overflow: hidden;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
/* A colour wash that stays out of the text's way: a soft corner glow plus a
   full-height bar keyed to --c. */
.type-list li::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--c);
}
/* A radial-gradient rather than a flat circle: a solid disc clips to a hard arc
   against the card edge and cuts visibly across the text behind it. */
.type-list li::after {
  content: ""; position: absolute; top: -60px; right: -40px; width: 200px; height: 200px;
  background: radial-gradient(circle, color-mix(in srgb, var(--c) 20%, transparent), transparent 70%);
  pointer-events: none;
}
.type-list li:hover {
  transform: translateY(-2px); border-color: color-mix(in srgb, var(--c) 45%, var(--border));
  box-shadow: 0 10px 26px color-mix(in srgb, var(--c) 22%, transparent);
}
.type-list li .ico {
  flex: none; width: 40px; height: 40px; border-radius: 11px;
  display: grid; place-items: center; font-size: 1.25rem;
  background: color-mix(in srgb, var(--c) 14%, #fff);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 26%, transparent);
}
.type-list li b { display: block; color: var(--text); font-weight: 700; font-size: .98rem; margin-bottom: 2px; }

/* ---------- pricing ---------- */
/* stretch, not start: every card is the height of the tallest one in the row, so
   the feature lists line up and the buttons sit on a single baseline. */
.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px; align-items: stretch; }
.plan { position: relative; display: flex; flex-direction: column; height: 100%; }
/* the list absorbs the slack, so a shorter card pads in the middle and not below the button */
.plan ul { flex: 1 0 auto; }
.plan .btn.block { margin-top: auto; }
.plan.featured {
  border-color: transparent;
  background: linear-gradient(180deg, #fdfdff, #f7f8ff);
  box-shadow: 0 0 0 2px var(--brand), 0 16px 40px rgba(79, 70, 229, .18);
}
/* The recommended plan wears the gradient as a bar across its top edge. No
   overflow:hidden and no corner wash here: the "Available now" badge is pinned
   above the card's top edge and clipping the card would cut it in half. */
.plan.featured::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  border-radius: var(--radius) var(--radius) 0 0; background: var(--grad);
}
.plan .price {
  font-size: 2.4rem; font-weight: 700; letter-spacing: -.03em;
  background: var(--grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--brand);
}
/* The "/month" suffix stays plain text — the gradient clip would swallow it. */
.plan .price small { -webkit-text-fill-color: var(--muted); }
.plan .price small { font-size: .95rem; font-weight: 500; color: var(--muted); }
.plan ul { list-style: none; padding: 0; margin: 18px 0; display: grid; gap: 9px; }
.plan li { padding-left: 24px; position: relative; font-size: .94rem; }
.plan li::before { content: "✓"; position: absolute; left: 0; color: var(--ok); font-weight: 700; }
/* Inside the card's top-right corner rather than straddling its top edge: on the
   featured card that edge is now the gradient bar, and a pill sitting on the bar
   leaves a stray sliver of it showing past the pill's corner. */
.plan .tag { position: absolute; top: 16px; right: 16px; }
.plan.featured .tag { background: var(--grad); color: #fff; }

/* ---------- table / dashboard ---------- */
table { width: 100%; border-collapse: collapse; font-size: .93rem; }
th, td { padding: 12px 10px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: middle; }
th { font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
/* Sortable headers: the arrow is faint until the column is the active sort. */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover, th.sortable:focus-visible { color: var(--text); }
th.sortable.sorted { color: var(--text); }
.sort-ind { margin-left: 6px; font-size: .8em; opacity: .35; }
th.sortable.sorted .sort-ind { opacity: 1; }
td.actions { text-align: right; white-space: nowrap; }
.empty { padding: 48px 20px; text-align: center; color: var(--muted); }

/* ---------- support tickets ---------- */
/* A ticket is a row that opens into its conversation, so the list and the
   thread are the same screen — a complaint and the answer to it belong
   together, and a separate page per ticket would only add a back button. */
.ticket + .ticket { border-top: 1px solid var(--border); }
.ticket-head { display: flex; align-items: center; gap: 12px; padding: 14px 4px; cursor: pointer; }
.ticket-head:hover { color: var(--brand); }
.ticket-head:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 8px; }
.ticket-body { padding: 4px 4px 18px; display: grid; gap: 10px; }
.msg {
  background: #f7f8fc; border: 1px solid var(--border);
  border-radius: 12px; padding: 10px 12px; max-width: 46em;
}
/* Ours sits on the other side and in the brand tint, so a long thread reads as
   a conversation at a glance rather than a wall of identical boxes. */
.msg.from-staff { background: #eef0ff; border-color: #d9dcff; margin-left: auto; }
.reply textarea { width: 100%; }
/* A status change in the thread: something that happened to the ticket rather
   than something either side said, so it is a line and not a bubble. */
.ticket-body .event {
  font-size: .82rem; color: var(--muted); text-align: center; padding: 4px 8px;
  border-top: 1px dashed var(--border); border-bottom: 1px dashed var(--border);
}

/* ---------- account panel ---------- */
.account-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.avatar {
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  background: linear-gradient(135deg, var(--brand), var(--accent));
  color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 1.1rem;
}
.account .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
/* Same palette-by-position trick as the marketing cards, so a row of counters
   is not four identical grey boxes. */
.account .stat {
  --c: var(--brand);
  border: 1px solid color-mix(in srgb, var(--c) 26%, var(--border)); border-radius: 12px;
  padding: 14px 16px; background: linear-gradient(160deg, color-mix(in srgb, var(--c) 8%, #fff), #fff);
  display: flex; flex-direction: column; gap: 2px;
}
.account .stat:nth-child(4n+1) { --c: var(--c1); }
.account .stat:nth-child(4n+2) { --c: var(--c2); }
.account .stat:nth-child(4n+3) { --c: var(--c3); }
.account .stat:nth-child(4n+4) { --c: var(--c6); }
.account .stat .k {
  font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; font-weight: 700;
  color: color-mix(in srgb, var(--c) 62%, var(--muted));
}
.account .stat .v { font-size: 1.5rem; letter-spacing: -.02em; }
.account .stat .s { font-size: .82rem; color: var(--muted); }

/* ---------- list filters ---------- */
.toolbar {
  display: grid; gap: 12px; align-items: end;
  grid-template-columns: minmax(190px, 2fr) repeat(3, minmax(140px, 1fr)) minmax(100px, .7fr) auto;
  padding: 18px 20px;
}
.toolbar label.field { margin-bottom: 0; }
.toolbar input[type=search] {
  width: 100%; padding: 11px 13px; font: inherit; color: var(--text);
  border: 1px solid var(--border); border-radius: 10px; background: #fff;
}
.listbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.selall { display: flex; align-items: center; gap: 8px; font-size: .9rem; font-weight: 600; cursor: pointer; }
.selall input { width: 17px; height: 17px; accent-color: var(--brand); }
.pick { width: 17px; height: 17px; accent-color: var(--brand); }
tr.picked { background: #f7f8ff; }
.qr-thumb {
  width: 46px; height: 46px; display: block; border: 1px solid var(--border);
  border-radius: 8px; background: #fff; padding: 2px;
}
#listCard { overflow-x: auto; }
.pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 14px; font-size: .9rem; }
.pager[hidden] { display: none; }

@media (max-width: 880px) {
  .toolbar { grid-template-columns: 1fr 1fr; }
  .toolbar label.field:first-child, .toolbar #fReset { grid-column: 1 / -1; }
}

/* ---------- modal ---------- */
@keyframes modal-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes modal-rise {
  from { opacity: 0; transform: translateY(14px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

.modal-back {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(15, 19, 30, .5);
  backdrop-filter: blur(6px) saturate(.9);
  -webkit-backdrop-filter: blur(6px) saturate(.9);
  display: grid; place-items: center; padding: 20px;
  animation: modal-fade .18s ease-out;
}
.modal-back[hidden] { display: none; }

.modal {
  position: relative; overflow: hidden;
  background: var(--surface); color: var(--text);
  border: 1px solid rgba(255, 255, 255, .7);
  border-radius: 20px; padding: 28px;
  width: 100%; max-width: 420px;
  box-shadow: 0 24px 60px -12px rgba(16, 24, 40, .32), 0 8px 20px -8px rgba(16, 24, 40, .18);
  animation: modal-rise .24s cubic-bezier(.22, 1, .36, 1);
}
/* the brand hairline that tops every popup */
.modal::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 4px;
  background: linear-gradient(90deg, var(--brand), var(--accent));
}
.modal.danger::before { background: linear-gradient(90deg, var(--danger), #f97316); }
.modal.ok::before { background: linear-gradient(90deg, var(--ok), var(--accent)); }

.modal h3 { margin-bottom: 4px; font-size: 1.22rem; }
.modal > p.muted:first-of-type { margin-top: 0; }

/* header: optional round glyph, title block, dismiss button */
.modal-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 18px; }
.modal-head .modal-ico {
  flex: none; width: 44px; height: 44px; border-radius: 14px;
  display: grid; place-items: center; font-size: 1.3rem;
  background: #eef2ff; color: var(--brand-dark);
}
.modal.danger .modal-head .modal-ico { background: #fdecec; color: var(--danger); }
.modal.ok .modal-head .modal-ico { background: #e7f7f0; color: var(--ok); }
.modal-head .modal-title { flex: 1; min-width: 0; }
.modal-head h3 { margin: 0 0 2px; }
.modal-head p { margin: 0; font-size: .9rem; color: var(--muted); }

.modal-x {
  flex: none; width: 32px; height: 32px; margin: -4px -6px 0 0;
  border: 0; border-radius: 9px; background: transparent; cursor: pointer;
  color: var(--muted); font: inherit; font-size: 1.1rem; line-height: 1;
  display: grid; place-items: center; transition: background .15s, color .15s;
}
.modal-x:hover { background: #eef0f6; color: var(--text); }
.modal-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* body scrolls on its own so the header and footer stay put on short screens */
.modal-body { max-height: min(58vh, 520px); overflow: auto; margin: 0 -6px; padding: 0 6px; }
.modal-body::-webkit-scrollbar { width: 8px; }
.modal-body::-webkit-scrollbar-thumb { background: #d8dce8; border-radius: 999px; }
.modal-body::-webkit-scrollbar-thumb:hover { background: #c3c9da; }

.modal-text { font-size: .95rem; color: var(--muted); margin: 0; white-space: pre-line; }
.modal-text + .modal-text { margin-top: 10px; }
.modal-list {
  list-style: none; margin: 14px 0 0; padding: 14px 16px; display: grid; gap: 7px;
  background: #fbfcfe; border: 1px solid var(--border); border-radius: 12px; font-size: .9rem;
}
.modal-list li { display: flex; gap: 10px; }
.modal-list li b { flex: none; min-width: 96px; color: var(--muted); font-weight: 600; }
.modal-list li span { min-width: 0; overflow-wrap: anywhere; }

.modal-foot { display: flex; gap: 10px; margin-top: 20px; }
.modal-foot .btn { flex: 1; }
.modal-foot.stack { flex-direction: column; }
@media (max-width: 460px) {
  .modal { padding: 22px 18px; border-radius: 16px; }
  .modal-foot { flex-direction: column-reverse; }
}

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  background: rgba(19, 23, 34, .96); color: #fff;
  border: 1px solid rgba(255, 255, 255, .12);
  padding: 12px 18px; border-radius: 12px; max-width: min(460px, calc(100vw - 32px));
  font-size: .9rem; z-index: 80;
  box-shadow: 0 14px 34px -10px rgba(16, 24, 40, .45);
  animation: toast-in .26s cubic-bezier(.22, 1, .36, 1);
}
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 14px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.toast[hidden] { display: none; }
.toast::before {
  content: "✓"; flex: none; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; font-size: .72rem;
  background: var(--ok); color: #fff;
}
.toast.err { background: rgba(153, 27, 27, .97); }
.toast.err::before { content: "!"; background: #fff; color: var(--danger); font-weight: 800; }

@media (prefers-reduced-motion: reduce) {
  .modal-back, .modal, .toast { animation: none; }
}

.bars { display: flex; align-items: flex-end; gap: 3px; height: 90px; margin-top: 12px; }
.bars .bar { flex: 1; background: var(--brand); border-radius: 3px 3px 0 0; min-height: 2px; opacity: .85; }

footer.site {
  position: relative; border-top: 1px solid var(--border); padding: 32px 0;
  color: var(--muted); font-size: .9rem;
  background: linear-gradient(180deg, #fff, #f4f6fd);
}
/* Bookends the gradient hairline under the header. */
footer.site::before {
  content: ""; position: absolute; top: -1px; left: 0; right: 0; height: 2px; background: var(--grad);
}
footer.site .inner { display: flex; flex-wrap: wrap; gap: 16px; justify-content: space-between; }

@media (max-width: 880px) {
  .generator { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the preview would land under a long form and the code would be
     off-screen while you fill it in — so it moves above the form instead.
     Source order stays form-first, which is what the two-column desktop layout
     and the keyboard tab order both want. */
  .preview { position: static; order: -1; }
  /* the empty frame is a 260px hole above the form otherwise */
  .preview-frame { min-height: 180px; }
  nav.main { gap: 12px; }
  nav.main a.hide-sm { display: none; }
}

/* ==================== small screens ==================== */

/* The hamburger only exists below the nav breakpoint. */
.nav-toggle { display: none; }

@media (max-width: 760px) {
  header.site .inner { gap: 12px; }
  .nav-toggle {
    display: grid; place-content: center; gap: 5px;
    margin-left: auto; width: 42px; height: 42px; flex: none;
    padding: 0; border: 1px solid var(--border); border-radius: 10px;
    background: #fff; cursor: pointer;
  }
  .nav-toggle span {
    display: block; width: 18px; height: 2px; border-radius: 2px;
    background: var(--text); transition: transform .18s, opacity .18s;
  }
  .nav-toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle.open span:nth-child(2) { opacity: 0; }
  .nav-toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* The panel drops out of the header rather than pushing the page down, so
     opening it never moves the content you were looking at. */
  nav.main {
    position: absolute; top: 100%; left: 0; right: 0; margin: 0;
    display: none; flex-direction: column; align-items: stretch; gap: 4px;
    background: var(--surface); border-bottom: 1px solid var(--border);
    padding: 10px 20px 16px;
    box-shadow: 0 14px 28px -18px rgba(16, 24, 40, .4);
  }
  nav.main.open { display: flex; }
  nav.main a, nav.main a.hide-sm { display: block; padding: 11px 4px; font-size: 1rem; }
  nav.main a.active::after { display: none; }
  nav.main a.active { color: var(--brand); }
  nav.main .btn { width: 100%; padding: 11px 16px; font-size: .95rem; margin-top: 4px; }
}

@media (max-width: 700px) {
  .wrap { padding: 0 16px; }
  .hero { padding: 36px 0 20px; }
  section.band { padding: 40px 0; }
  .card { padding: 18px 16px; }
  .modal-back { padding: 12px; }

  /* Two-up fields are unreadable at phone width; every field takes the row. */
  .row { grid-template-columns: 1fr; }

  /* One column, tighter rows and smaller icon tiles: 320px tracks would still
     fit two-up on a wide phone and squeeze the sentences to two words a line. */
  .type-list { grid-template-columns: 1fr; gap: 10px; }
  .type-list li { padding: 13px 14px; gap: 11px; }
  .type-list li .ico { width: 34px; height: 34px; border-radius: 9px; font-size: 1.05rem; }

  .steps { gap: 6px 12px; }
  .step + .step::before { display: none; }

  /* Sitting above the form, the preview card is the only thing between the top
     of a phone screen and the first field — so it is compacted until it and the
     start of the form fit together, rather than costing a whole screen.
     The frame keeps a square-ish window for the code itself; everything around
     it (padding, stacked buttons, the note) is what gets taken back. */
  .generator { gap: 14px; }
  .preview-frame { min-height: 120px; padding: 10px; }
  .preview-frame img { max-width: 170px; }
  .preview-frame .muted { font-size: .88rem; }
  /* The two downloads are short labels; side by side they cost one row, not two. */
  .dl { grid-template-columns: 1fr 1fr; margin-top: 10px; }
  .preview-tag { font-size: .72rem; }
  #previewNote { font-size: .75rem; margin-top: 8px !important; }

  /* The phone layout for the cards themselves is further down the file, after
     the rules it has to override. */

  /* The strip is a progress hint, not a control. */
  .steps { margin-bottom: 16px; font-size: .82rem; }

  /* Filters stack; search and the reset button each take the full width. */
  .toolbar { grid-template-columns: 1fr; padding: 14px 16px; }

  /* Comfortable targets for a thumb, and room for a long email or URL. */
  .btn { padding: 11px 18px; }
  .btn.sm { padding: 8px 12px; }
  .msg { max-width: 100%; }
  .modal-list li { flex-direction: column; gap: 2px; }
  .modal-list li b { min-width: 0; }
  footer.site .inner { flex-direction: column; gap: 8px; }
}

/* ---------- dashboard list as cards ----------
   A seven-column table on a phone is either a sideways scroll or unreadable
   type, so below this width each row becomes a stacked card: the header row is
   dropped and every cell carries its own label from data-label. */
@media (max-width: 760px) {
  #listCard { overflow-x: visible; }
  /* Each row is its own card, so the panel around them would only be a second
     border drawn around the first. */
  #listCard:has(.cards) { padding: 0; background: none; border: 0; box-shadow: none; }
  .cards thead { display: none; }
  .cards, .cards tbody, .cards tr, .cards td { display: block; width: 100%; }
  .cards tr {
    position: relative; border: 1px solid var(--border); border-radius: 12px;
    padding: 12px 12px 12px 68px; margin-bottom: 12px; background: #fff;
  }
  .cards tr.picked { background: #f7f8ff; }
  .cards td { border: 0; padding: 3px 0; overflow-wrap: anywhere; }
  .cards td[data-label]::before {
    content: attr(data-label); display: block;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); font-weight: 700;
  }
  /* Checkbox and thumbnail pin to the gutter the padding above reserves. */
  .cards td.c-pick { position: absolute; top: 12px; left: 12px; padding: 0; }
  .cards td.c-thumb { position: absolute; top: 40px; left: 12px; padding: 0; }
  .cards .pick { width: 20px; height: 20px; }
  .cards td.actions {
    display: flex; flex-wrap: wrap; gap: 8px;
    text-align: left; white-space: normal; margin-top: 10px;
  }
  .cards td.actions .btn { flex: 1; min-width: 92px; }
}

/* The grids and panels below set their own display, which outranks the plain
   [hidden] rule — so hiding one has to say so louder. */
[hidden] { display: none !important; }

/* ---------- type picker grid ---------- */
.type-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px; margin-bottom: 26px;
}
.type-card {
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
  padding: 20px 12px; text-align: center; cursor: pointer; font: inherit; color: var(--text);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
  transition: border-color .15s, box-shadow .15s, transform .1s;
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px;
}
/* The picker is a dozen-plus identical tiles, so each one takes a hue from the
   palette by position and tints its icon disc, hover ring and selected ring from
   it. Cycling by nth-child rather than a colour per type keeps the registry in
   qr-types.js pure data — it is generated, and a colour there would be lost on
   the next regeneration. */
.type-card:nth-child(6n+1) { --c: var(--c1); }
.type-card:nth-child(6n+2) { --c: var(--c2); }
.type-card:nth-child(6n+3) { --c: var(--c3); }
.type-card:nth-child(6n+4) { --c: var(--c4); }
.type-card:nth-child(6n+5) { --c: var(--c5); }
.type-card:nth-child(6n+6) { --c: var(--c6); }
.type-card:hover {
  border-color: color-mix(in srgb, var(--c) 55%, var(--border)); transform: translateY(-3px);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--c) 20%, transparent);
}
.type-card.active {
  border-color: var(--c);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c) 18%, transparent),
              0 8px 20px color-mix(in srgb, var(--c) 16%, transparent);
}
.type-card .ico {
  width: 46px; height: 46px; border-radius: 50%;
  background: color-mix(in srgb, var(--c) 15%, #fff);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 25%, transparent);
  display: grid; place-items: center; font-size: 1.35rem; margin-bottom: 6px;
  transition: transform .15s ease;
}
.type-card:hover .ico { transform: scale(1.08); }
.type-card.active .ico { background: color-mix(in srgb, var(--c) 24%, #fff); }
.type-card .nm { font-weight: 700; font-size: .97rem; }
.type-card .bl { font-size: .82rem; color: var(--muted); line-height: 1.35; }
.type-card .pro {
  position: absolute; top: 8px; right: 8px; font-size: .62rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; color: var(--brand-dark);
  background: #eef2ff; border-radius: 999px; padding: 2px 7px;
}
.type-groups { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }

/* Static / Dynamic sits above the groups and reads as the bigger choice. */
.mode-tabs { display: flex; gap: 6px; margin-bottom: 18px; padding: 4px; border-radius: 12px;
  background: linear-gradient(120deg, #eef2ff, #e6f7fb); width: fit-content;
  box-shadow: inset 0 0 0 1px rgba(79, 70, 229, .1); }
.mode-tab {
  padding: 8px 22px; border-radius: 9px; border: 0; background: transparent;
  font: inherit; font-size: .95rem; font-weight: 700; color: var(--muted); cursor: pointer;
}
.mode-tab:hover { color: var(--text); }
.mode-tab.active { background: #fff; color: var(--brand-dark); box-shadow: 0 1px 3px rgba(16, 24, 40, .1); }

/* ---------- design tabs ---------- */
/* Same pill-in-a-trough as .mode-tabs, but these sit inside a card and there
   are four of them, so they stretch to the card's width instead of hugging
   their labels — and scroll sideways rather than wrapping to a second row. */
.design-tabs {
  display: flex; gap: 6px; margin: 4px 0 18px; padding: 4px;
  border-radius: 12px; background: #f1f3f7; overflow-x: auto; scrollbar-width: none;
}
.design-tabs::-webkit-scrollbar { display: none; }
.design-tab {
  flex: 1 0 auto; padding: 8px 18px; border-radius: 9px; border: 0; background: transparent;
  font: inherit; font-size: .92rem; font-weight: 700; color: var(--muted); cursor: pointer;
  white-space: nowrap;
}
.design-tab:hover { color: var(--text); }
.design-tab.active { background: #fff; color: var(--brand-dark); box-shadow: 0 1px 3px rgba(16, 24, 40, .1); }
/* The tab already names the pane, so the picker's own heading would say it
   twice — and its top margin would push the pane away from the tab strip. */
.design-pane .shape-h { display: none; }
.design-pane > :first-child > .field:first-child { margin-top: 0; }

/* Nothing to click yet, so the card drops the hover lift and the pointer. */
.type-card.soon { cursor: default; opacity: .72; }
.type-card.soon:hover { border-color: var(--border); transform: none; box-shadow: 0 1px 2px rgba(16, 24, 40, .04); }
.type-card .soon-tag {
  margin-top: 8px; font-size: .68rem; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand-dark);
  background: #eef2ff; border-radius: 999px; padding: 3px 9px;
}

/* ---------- repeaters & file inputs ---------- */
.repeat { margin-bottom: 16px; }
.repeat-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.repeat-head span { font-size: .82rem; font-weight: 600; color: var(--muted); }
.repeat-item { border: 1px solid var(--border); border-radius: 12px; padding: 14px; margin-bottom: 10px; background: #fbfcfe; }
.repeat-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; font-size: .85rem; }
.repeat-item .repeat-item { background: #fff; }
input[type=file] { width: 100%; font-size: .9rem; padding: 9px; border: 1px dashed var(--border); border-radius: 10px; background: #fff; }
.field > span { display: block; font-size: .82rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; }

.logo-status { display: flex; align-items: center; gap: 10px; font-size: .85rem; margin-top: 6px; }
.logo-thumb {
  width: 40px; height: 40px; object-fit: contain; border: 1px solid var(--border);
  border-radius: 8px; padding: 3px; background: #fff;
}

.notice {
  background: #eef2ff; border: 1px solid #d9dffb; color: var(--brand-dark);
  border-radius: 12px; padding: 14px 16px; font-size: .9rem; margin-bottom: 16px;
}
.notice b { display: block; margin-bottom: 2px; }

/* Single sign-on buttons, above the email form in the auth modal. */
.sso { margin-bottom: 4px; }
.sso-btn { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 8px; }
.sso-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; border: 1px solid var(--border);
  font-weight: 700; font-size: .8rem; color: var(--brand-dark);
}
.sso-or {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0 6px; color: var(--muted); font-size: .8rem;
}
.sso-or::before, .sso-or::after { content: ''; flex: 1; height: 1px; background: var(--border); }

/* ==================== large screens ====================
   The layout was built phone-first and then capped at 1140px, which on a wide
   monitor leaves most of the window empty while the few full-width grids
   (plans, features, stats) stretch their cards well past a comfortable width.
   The container grows in two steps, and the grids that would otherwise run to
   the edge get a column count instead of a minimum width. */
@media (min-width: 1400px) {
  :root { --maxw: 1280px; }
  .wrap { padding: 0 32px; }
  /* the preview pane can hold a bigger code once there is room for it */
  .generator { grid-template-columns: minmax(0, 1fr) 400px; gap: 32px; }
  .preview-frame img { max-width: 300px; }
  /* auto-fit would spread three cards across the whole row; pin the count so
     they keep a readable width and the row stays centred */
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
  .plans { grid-template-columns: repeat(auto-fit, minmax(260px, 320px)); justify-content: center; }
  .account .stats { grid-template-columns: repeat(auto-fit, minmax(180px, 260px)); }
  section.band { padding: 72px 0; }
  .hero { padding: 72px 0 32px; }
}

@media (min-width: 1750px) {
  :root { --maxw: 1440px; }
  /* a wider window is normally a further-away one, so the base size comes up
     a notch rather than the text staying phone-sized in the middle of it */
  body { font-size: 17px; }
  .generator { grid-template-columns: minmax(0, 1fr) 440px; }
  .preview-frame img { max-width: 340px; }
}

/* ---------- type picker on phones ----------
   This has to sit after the .type-card rules above rather than with the other
   phone rules further up: it overrides them at the same specificity, so the
   later block is the one that wins.

   The chooser is everything above the generator, and stacked
   icon-over-name-over-blurb cards were ~340px of it — enough on their own to
   push the preview and the form off the screen. As rows the same three cards
   cost a third of that, and the type names carry what the blurb repeated. */
@media (max-width: 700px) {
  .type-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 18px; }
  .type-card {
    flex-direction: row; align-items: center; justify-content: flex-start;
    gap: 10px; padding: 10px 12px; text-align: left;
  }
  .type-card .ico { width: 30px; height: 30px; font-size: 1rem; margin-bottom: 0; flex: none; }
  .type-card .nm { font-size: .92rem; }
  .type-card .bl { display: none; }
  /* Out of the corner and onto the row, so it cannot sit over the name. */
  .type-card .pro { position: static; margin-left: auto; }
  /* The dynamic cards carry the same tag as a block under the name; as a row
     item it loses that margin and rides along at the end of the line. */
  .type-card .soon-tag {
    margin-top: 0; margin-left: auto; flex: none;
    font-size: .62rem; padding: 2px 7px;
  }
}

/* One card per row below the two-column split, so the name and the tag still
   fit on a line instead of the tag wrapping under a broken-up label. */
@media (max-width: 380px) {
  .type-grid { grid-template-columns: 1fr; }
}
