/*
 * MyPreferredSources — custom "preferred source" button.
 *
 * This is the HOSTED single source of truth for the custom button's look. The
 * generator's live preview and the copy-paste embed BOTH load this exact file
 * (the preview via /btn.css on our own origin, the embed via the absolute
 * https://mypreferredsources.com/btn.css). Same file + same markup ⇒ the
 * preview is exactly what a publisher copies, hover animation included.
 *
 * Hosting the stylesheet (instead of inlining it in every snippet) is what keeps
 * the pasted embed tiny — a single <a> + <link>. The trade-off: a publisher's
 * button depends on this file staying reachable. The markup degrades to readable
 * text + favicon if it ever 404s.
 *
 * Look is config-driven by MODIFIER CLASSES, not a generated string:
 *   .mps-pref-btn                 base (defaults to medium + light)
 *   .mps-sz-sm | .mps-sz-lg       size step (md is the base default)
 *   .mps-th-dark                  dark surface (light is the base default)
 * The serializer (src/lib/widget-code.ts → customButtonClasses) emits the class
 * list; change a size/color/spacing token HERE and both preview and embed move
 * together. The two icons are pulled in BY this stylesheet (so they never bloat
 * the snippet): the leading star via `mask` + `currentColor` so it tracks the
 * button text per theme; the trailing 4-color Google G via `background-image`.
 */

/* ---------------------------------------------------------------------------
 * CSS isolation — neutralize the HOST PAGE's styles.
 *
 * These buttons get pasted into arbitrary publisher pages. Without a Shadow DOM
 * boundary (which would force JS, or declarative-shadow markup that CMSes drop
 * when they inject the snippet via innerHTML), the host's global CSS cascades
 * INTO the button and drifts it away from the on-site preview: `* { box-sizing }`,
 * `a { color / text-decoration }`, `img { max-width }`, and inherited typography
 * (`font`, `color`, `line-height`, `letter-spacing`, …).
 *
 * `all: revert` rolls every property on the button subtree back to the
 * user-agent defaults — dropping the host's AUTHOR declarations — and the rules
 * below (also author-level, but later in source order, and a class beats a bare
 * `a`/`span`/`img` selector on specificity) re-establish the intended look. (One
 * deliberate exception: the inline-button roots' OWN box is spared from the revert
 * so the host grid can still place it — see the NB on the reset rule below.) The
 * `--mps-*` custom properties survive (`all` never resets variables). The few
 * INHERITED props `revert` can still pull from the host parent are pinned
 * explicitly on each root, so nothing typographic leaks through inheritance.
 *
 * Specificity: every selector below leads with a DOUBLED class
 * (`.mps-pref-btn.mps-pref-btn`) so the button's own rules sit at 0,2,0 — high
 * enough to beat the content-area link rules CMS themes ship, e.g. Ghost's
 * `.post-content a { text-decoration: underline; color: … }` at 0,1,1, which a
 * single class (0,1,0) loses to (this was the real-world bug: a pasted button
 * came back underlined and re-colored on a Ghost blog). Residual: a host
 * `!important` declaration, or a selector even more specific than 0,2,0, can
 * still win — the price of not using Shadow DOM, now rare. Because this lives in
 * the HOSTED stylesheet, deploying it fixes every already-pasted button at once.
 * ------------------------------------------------------------------------- */
/* NB: the two INLINE-button roots — `.mps-pref-btn` / `.mps-store-btn` themselves —
   are deliberately ABSENT from this list. Their ::before/::after and their whole
   descendant subtree ARE reset, but the root <a> is NOT `all: revert`-ed. Reverting
   the root used to wipe the host grid's `grid-column` placement off it (our reset
   and the theme's `… > * { grid-column }` tie at 0,2,0, and our later-loading sheet
   won the tie), dumping the button into a narrow gutter where it got crushed.
   Sparing the root lets the theme place it in its content column like native prose
   (see the `grid-column` note further below). The root's look is fully re-set by the
   explicit doubled-class rules + the inherited-prop pin below — both already out-
   specify the host's content rules (e.g. Ghost's `.post-content a`, 0,1,1) — so
   isolation holds WITHOUT reverting the root's box. (The position:fixed `.mps-fab`
   root is never a host grid item, so it keeps full revert.) */
.mps-pref-btn.mps-pref-btn::before,
.mps-pref-btn.mps-pref-btn::after,
.mps-pref-btn.mps-pref-btn *,
.mps-pref-btn.mps-pref-btn *::before,
.mps-pref-btn.mps-pref-btn *::after,
.mps-store-btn.mps-store-btn::before,
.mps-store-btn.mps-store-btn::after,
.mps-store-btn.mps-store-btn *,
.mps-store-btn.mps-store-btn *::before,
.mps-store-btn.mps-store-btn *::after,
.mps-fab.mps-fab,
.mps-fab.mps-fab::before,
.mps-fab.mps-fab::after,
.mps-fab.mps-fab *,
.mps-fab.mps-fab *::before,
.mps-fab.mps-fab *::after {
  /* `A *` never matches A itself, so each reset root's OWN ::before/::after are
     listed explicitly — otherwise a host `a::after { content: "…" }` injects a
     glyph into the button (the inline button root IS the <a>, and although its box
     is now spared, its pseudos are still reset here). `all: revert` resets their
     `content` to `normal`, deleting it. */
  all: revert;
  box-sizing: border-box;
}

/* Pin the inherited typographic props on each root: `revert` still inherits
   these from the host parent, so set them explicitly. Leaf rules below override
   where needed (e.g. .mps-store-lead → uppercase, .mps-store-name → nowrap). */
.mps-pref-btn.mps-pref-btn,
.mps-store-btn.mps-store-btn,
.mps-fab.mps-fab {
  font-style: normal;
  font-variant: normal;
  letter-spacing: normal;
  word-spacing: normal;
  text-transform: none;
  text-align: left;
  text-indent: 0;
  text-shadow: none;
  white-space: normal;
}

/* Sit like NATIVE CONTENT inside CMS content areas that are CSS GRIDS. Modern
   wrappers (Ghost, WordPress, Gantry "content grid" themes) make `.post-content` a
   grid with named columns — `[full] [wide] [regular 800px] [wide] [full]` — and
   place known children into the centre `regular` track via `… > * { grid-column:
   regular }`. Our pasted <a> matches that `> *`, so the theme ALREADY wants it in
   the content column. The bug was self-inflicted: the `all: revert` reset used to
   wipe that placement back to `auto`, dropping the button into a narrow gutter
   where `max-width` then crushed it into a sliver. The reset above now SPARES the
   inline-button roots (see the note there), so the theme's `grid-column` survives
   and the button lands in the content column — lined up with the prose. Here we
   only un-stretch it: `width: fit-content` makes it hug its label instead of
   filling the 800px track, and `justify-self: start` left-aligns it within the
   column (matching the paragraphs' own left edge). Both are no-ops on a plain block
   page (the button is just an inline-flex pill in normal flow). Trade-off: this
   defers placement to the host, so on a (rare, self-broken) grid theme that does
   NOT route its content children into a content column, the button could still land
   in a gutter — but any theme with content gutters routes its own prose, so in
   practice this tracks the content. */
.mps-pref-btn.mps-pref-btn,
.mps-store-btn.mps-store-btn {
  width: fit-content;
  max-width: 100%;
  justify-self: start;
}

.mps-pref-btn.mps-pref-btn {
  /* medium + light defaults (overridden by the modifier classes below) */
  --mps-pad: 11px 18px;
  --mps-fs: 14px;
  --mps-gap: 11px;
  --mps-ic: 20px;
  --mps-gic: 18px;
  --mps-rad: 11px;
  --mps-bg: #ffffff;
  --mps-fg: #202124;
  --mps-bd: #dadce0;
  --mps-bdh: #d2d5d9;
  --mps-dv: rgba(60, 64, 67, 0.18);
  --mps-sh: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);

  display: inline-flex;
  align-items: center;
  gap: var(--mps-gap);
  padding: var(--mps-pad);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: var(--mps-fs);
  font-weight: 500;
  line-height: 1.2;
  color: var(--mps-fg);
  background: var(--mps-bg);
  border: 1px solid var(--mps-bd);
  border-radius: var(--mps-rad);
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.mps-pref-btn.mps-pref-btn.mps-sz-sm {
  --mps-pad: 9px 14px;
  --mps-fs: 13px;
  --mps-gap: 10px;
  --mps-ic: 18px;
  --mps-gic: 16px;
  --mps-rad: 10px;
}

.mps-pref-btn.mps-pref-btn.mps-sz-lg {
  --mps-pad: 13px 22px;
  --mps-fs: 15px;
  --mps-gap: 12px;
  --mps-ic: 22px;
  --mps-gic: 20px;
  --mps-rad: 12px;
}

.mps-pref-btn.mps-pref-btn.mps-th-dark {
  --mps-bg: #202124;
  --mps-fg: #e8eaed;
  --mps-bd: #5f6368;
  --mps-bdh: #80868b;
  --mps-dv: rgba(255, 255, 255, 0.18);
  --mps-sh: 0 1px 3px rgba(0, 0, 0, 0.45), 0 1px 4px 1px rgba(0, 0, 0, 0.3);
}

/* Leading mark — favicon (<img>) when enabled. */
.mps-pref-btn.mps-pref-btn .mps-ic {
  width: var(--mps-ic);
  height: var(--mps-ic);
  flex-shrink: 0;
  display: block;
  border-radius: 4px;
  object-fit: contain;
}

/* Leading mark — default star, tinted by currentColor so it tracks the text. */
.mps-pref-btn.mps-pref-btn .mps-star {
  width: var(--mps-ic);
  height: var(--mps-ic);
  flex-shrink: 0;
  display: block;
  background: currentColor;
  -webkit-mask: url(/marks/cards-star.svg) center / contain no-repeat;
  mask: url(/marks/cards-star.svg) center / contain no-repeat;
}

/* Single-line text that slide-swaps to the hover line; both lines share one grid
   cell sized to the wider line, so the button never reflows. */
.mps-pref-btn.mps-pref-btn .mps-swap {
  display: inline-grid;
  overflow: hidden;
  align-items: center;
  justify-items: start;
}

.mps-pref-btn.mps-pref-btn .mps-swap > span {
  grid-area: 1 / 1;
  white-space: nowrap;
  transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.26s ease;
}

.mps-pref-btn.mps-pref-btn .mps-swap .mps-hover {
  transform: translateY(115%);
  opacity: 0;
}

/* Trailing Google-G trust mark behind a hairline divider. */
.mps-pref-btn.mps-pref-btn .mps-tm {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding-left: var(--mps-gap);
  border-left: 1px solid var(--mps-dv);
}

.mps-pref-btn.mps-pref-btn .mps-g {
  width: var(--mps-gic);
  height: var(--mps-gic);
  display: block;
  background: var(--wpr-bg-4d45ccd3-bfb0-48f2-b495-02cb89a8e749) center / contain no-repeat;
}

.mps-pref-btn.mps-pref-btn:hover {
  border-color: var(--mps-bdh);
  transform: translateY(-1px);
  box-shadow: var(--mps-sh);
}

.mps-pref-btn.mps-pref-btn:hover .mps-swap .mps-rest {
  transform: translateY(-115%);
  opacity: 0;
}

.mps-pref-btn.mps-pref-btn:hover .mps-swap .mps-hover {
  transform: translateY(0);
  opacity: 1;
}

/* =========================================================================
 * Stacked "preferred source" badge — the App-Store / Google-Play shape.
 *
 * A simpler, "basic" alternative to .mps-pref-btn: the 4-color Google G LEADS
 * (the platform mark), then two stacked text lines — a small uppercase lead-in
 * over a large name line. No hover text-swap, no favicon, no editable copy; the
 * size/theme modifier classes (.mps-sz-*, .mps-th-*) drive it just like the
 * custom button. Same hosted stylesheet ⇒ preview equals copy, snippet stays
 * tiny. Reuses /marks/google-g.svg (no new asset).
 * ====================================================================== */

.mps-store-btn.mps-store-btn {
  /* medium + light defaults (overridden by the modifier classes below) */
  --mps-pad: 9px 18px;
  --mps-gap: 12px;
  --mps-gic: 26px;
  --mps-lead-fs: 12px;
  --mps-name-fs: 18px;
  --mps-rad: 12px;
  --mps-bg: #ffffff;
  --mps-fg: #202124;
  --mps-fg2: #5f6368;
  --mps-bd: #dadce0;
  --mps-bdh: #d2d5d9;
  --mps-sh: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);

  display: inline-flex;
  align-items: center;
  gap: var(--mps-gap);
  padding: var(--mps-pad);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  color: var(--mps-fg);
  background: var(--mps-bg);
  border: 1px solid var(--mps-bd);
  border-radius: var(--mps-rad);
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.mps-store-btn.mps-store-btn.mps-sz-sm {
  --mps-pad: 7px 14px;
  --mps-gap: 10px;
  --mps-gic: 22px;
  --mps-lead-fs: 10px;
  --mps-name-fs: 15px;
  --mps-rad: 10px;
}

.mps-store-btn.mps-store-btn.mps-sz-lg {
  --mps-pad: 11px 22px;
  --mps-gap: 14px;
  --mps-gic: 30px;
  --mps-lead-fs: 13px;
  --mps-name-fs: 21px;
  --mps-rad: 14px;
}

.mps-store-btn.mps-store-btn.mps-th-dark {
  --mps-bg: #202124;
  --mps-fg: #e8eaed;
  --mps-fg2: #9aa0a6;
  --mps-bd: #5f6368;
  --mps-bdh: #80868b;
  --mps-sh: 0 1px 3px rgba(0, 0, 0, 0.45), 0 1px 4px 1px rgba(0, 0, 0, 0.3);
}

/* Leading mark — one of three (selected by markup): the 4-color Google G, the
   star (tinted to the text color), or the site favicon. All share one box. */
.mps-store-btn.mps-store-btn .mps-store-g,
.mps-store-btn.mps-store-btn .mps-store-star,
.mps-store-btn.mps-store-btn .mps-store-ic {
  width: var(--mps-gic);
  height: var(--mps-gic);
  flex-shrink: 0;
  display: block;
}

/* Google G — flat 4-color, full color (the platform mark). */
.mps-store-btn.mps-store-btn .mps-store-g {
  background: var(--wpr-bg-571453ed-9c6c-44f8-ba5f-d21fc303e6a0) center / contain no-repeat;
}

/* Star — tinted by currentColor so it tracks the button text per theme. */
.mps-store-btn.mps-store-btn .mps-store-star {
  background: currentColor;
  -webkit-mask: url(/marks/cards-star.svg) center / contain no-repeat;
  mask: url(/marks/cards-star.svg) center / contain no-repeat;
}

/* Favicon — the site's own icon (an <img>), rounded like an app icon. */
.mps-store-btn.mps-store-btn .mps-store-ic {
  border-radius: 5px;
  object-fit: contain;
}

/* Two stacked text lines (lead-in over name). */
.mps-store-btn.mps-store-btn .mps-store-tx {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1;
}

.mps-store-btn.mps-store-btn .mps-store-lead {
  font-size: var(--mps-lead-fs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--mps-fg2);
}

.mps-store-btn.mps-store-btn .mps-store-name {
  margin-top: 3px;
  font-size: var(--mps-name-fs);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
  white-space: nowrap;
  color: var(--mps-fg);
}

.mps-store-btn.mps-store-btn:hover {
  border-color: var(--mps-bdh);
  transform: translateY(-1px);
  box-shadow: var(--mps-sh);
}

/* =========================================================================
 * Floating FAB — the script-injected, position:fixed widget.
 *
 * Shape: a rounded pill `[ ★ Add … as a preferred source ]`, or — when
 * `.mps-collapsed` — an icon-only bubble that smoothly extends on hover/focus to
 * reveal the label. Pinned to the VERTICAL CENTRE of the right or left edge, with
 * a small ✕ dismiss affordance. Injected by public/float.js; mirrored in the
 * React preview (WidgetPreview → FloatingPreview). The ROOT class `.mps-fab` is
 * the positioned WRAPPER (not the anchor) — an <a> can't legally nest the ✕
 * <button>, so the anchor + ✕ are siblings inside the wrapper, and the one
 * `.mps-fab *` reset above isolates the whole subtree from the host page.
 *
 * Smooth expand: the button has NO explicit width (content-driven), so it follows
 * the label without the `width:auto` JUMP (`auto` isn't interpolatable). The label
 * reveals by animating `max-width` 0↔26rem — a hard cap that collapses reliably in
 * EVERY engine. (A grid `0fr↔1fr` track was tried first but Safari keeps a `0fr`
 * track at the nowrap text's min-content width, so the pill never became a circle.)
 *
 * Inward growth + never clipped: the wrapper pins only ONE horizontal edge
 * (right OR left) and is shrink-to-fit, so the expand runs INWARD (toward page
 * centre). The anchor is then capped to `100vw − 2·inset`, which forces left ≥
 * inset and right ≤ 100vw − inset — it CAN'T leave the screen — and the label is
 * allowed to WRAP, so a long line on a narrow viewport becomes two lines instead
 * of being cut off. The single-line circle↔pill reveal keeps `nowrap` only while
 * collapsed (where it matters for the animation); a permanent pill wraps freely.
 * On touch (no hover) the FAB instead PEEKS the full label, then float.js shrinks
 * it to an icon bubble (`.is-mini`) so a phone screen is never hogged — see the
 * `@media (hover: none)` block at the foot of this file. Reuses the shared
 * .mps-sz-* / .mps-th-* modifiers + the mask star + google-g.svg.
 * ====================================================================== */

.mps-fab.mps-fab {
  /* medium + light defaults (overridden by the modifier classes below) */
  --mps-fab-h: 48px;        /* pill height / collapsed bubble diameter */
  --mps-fab-fs: 14px;
  --mps-fab-ic: 22px;       /* leading-mark square (matches FAB_ICON_PX in widget-code.ts) */
  --mps-fab-pad: 13px;      /* symmetric padding → collapsed circle: 2·13 + 22 = 48 = h */
  --mps-fab-gap: 10px;      /* mark → label gap */
  --mps-fab-inset: 18px;    /* distance from the pinned edge */
  --mps-bg: #ffffff;
  --mps-fg: #202124;
  --mps-bd: #dadce0;
  --mps-bdh: #d2d5d9;
  --mps-sh: 0 6px 20px rgba(60, 64, 67, 0.16), 0 1px 4px rgba(60, 64, 67, 0.2);
  --mps-shh: 0 10px 28px rgba(60, 64, 67, 0.22), 0 2px 6px rgba(60, 64, 67, 0.22);
  --mps-ease: cubic-bezier(0.4, 0, 0.2, 1);

  position: fixed;
  top: 50%;
  z-index: 9999;
  display: block;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;

  /* Entrance: hidden + nudged toward the edge until float.js adds .is-in, then it
     slides IN from the side and fades up. translateY(-50%) keeps it centred. */
  opacity: 0;
  transform: translate(0, -50%);
  transition: opacity 0.32s var(--mps-ease), transform 0.42s var(--mps-ease);
}

/* Edge pin — exactly one horizontal edge so the box stays shrink-to-fit and the
   label reveal grows INWARD (toward page centre), never off the pinned edge. The
   FAB sits at the vertical middle of that edge; the pre-entrance offset nudges it
   toward the edge (overridden by .is-in below). */
.mps-fab.mps-fab.mps-pos-right {
  right: calc(var(--mps-fab-inset) + env(safe-area-inset-right, 0px));
  left: auto;
  transform: translate(16px, -50%);
}
.mps-fab.mps-fab.mps-pos-left {
  left: calc(var(--mps-fab-inset) + env(safe-area-inset-left, 0px));
  right: auto;
  transform: translate(-16px, -50%);
}

.mps-fab.mps-fab.is-in {
  opacity: 1;
  transform: translate(0, -50%);
}

.mps-fab.mps-fab.mps-sz-sm {
  --mps-fab-h: 40px;
  --mps-fab-fs: 13px;
  --mps-fab-ic: 19px;
  --mps-fab-pad: 10.5px; /* 2·10.5 + 19 = 40 */
  --mps-fab-gap: 9px;
  --mps-fab-inset: 14px;
}

.mps-fab.mps-fab.mps-sz-lg {
  --mps-fab-h: 56px;
  --mps-fab-fs: 15px;
  --mps-fab-ic: 24px;
  --mps-fab-pad: 16px;   /* 2·16 + 24 = 56 */
  --mps-fab-gap: 12px;
  --mps-fab-inset: 22px;
}

.mps-fab.mps-fab.mps-th-dark {
  --mps-bg: #202124;
  --mps-fg: #e8eaed;
  --mps-bd: #5f6368;
  --mps-bdh: #80868b;
  --mps-sh: 0 6px 22px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.4);
  --mps-shh: 0 12px 30px rgba(0, 0, 0, 0.6), 0 2px 6px rgba(0, 0, 0, 0.45);
}

/* The pill anchor. Width is CONTENT-DRIVEN (no explicit width), so it follows the
   reveal grid smoothly — nothing snaps to/from `auto`. */
.mps-fab.mps-fab .mps-fab-btn {
  display: inline-flex;
  align-items: center;
  /* min-height (not a fixed height) so the pill can grow to two lines when the
     label wraps; still a perfect circle when collapsed (content = the lone mark). */
  min-height: var(--mps-fab-h);
  /* Hard viewport cap: with one edge pinned at the inset, this guarantees the
     button is fully on-screen on ANY width — it can never run off the edge. */
  max-width: calc(100vw - 2 * var(--mps-fab-inset));
  padding: 0 var(--mps-fab-pad);
  border-radius: calc(var(--mps-fab-h) / 2);
  background: var(--mps-bg);
  color: var(--mps-fg);
  border: 1px solid var(--mps-bd);
  box-shadow: var(--mps-sh);
  font-size: var(--mps-fab-fs);
  font-weight: 500;
  line-height: 1.25;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  box-sizing: border-box;
  transition: box-shadow 0.25s var(--mps-ease), border-color 0.25s var(--mps-ease);
}

/* Hover/focus: a gentle, button-like shadow-deepen (no springy transform). */
.mps-fab.mps-fab .mps-fab-btn:hover,
.mps-fab.mps-fab .mps-fab-btn:focus-visible {
  border-color: var(--mps-bdh);
  box-shadow: var(--mps-shh);
}

/* Leading mark — one of three (selected by markup), one shared box. */
.mps-fab.mps-fab .mps-fab-mark {
  width: var(--mps-fab-ic);
  height: var(--mps-fab-ic);
  flex-shrink: 0;
  display: block;
}
.mps-fab.mps-fab .mps-fab-mark.mps-m-star {
  background: currentColor;
  -webkit-mask: url(/marks/cards-star.svg) center / contain no-repeat;
  mask: url(/marks/cards-star.svg) center / contain no-repeat;
}
.mps-fab.mps-fab .mps-fab-mark.mps-m-google {
  background: var(--wpr-bg-f71adfb5-7831-46eb-9dfc-881857df2f29) center / contain no-repeat;
}
.mps-fab.mps-fab img.mps-fab-mark {
  border-radius: 5px;
  object-fit: contain;
}

/* Label — collapses to zero width via `max-width` (a hard cap that works in every
   engine). NB: a grid `0fr` track is NOT used here on purpose — Safari keeps such a
   track at the nowrap text's min-content width, so the pill never shrinks to a
   circle. `max-width` has no such dependency. The button width is content-driven,
   so it follows the label smoothly with no `width:auto` jump. The 24rem cap is
   generous (covers a long "Add domain.com as a preferred source"); for shorter
   labels the reveal lands a touch early, which reads as the motion settling. */
.mps-fab.mps-fab .mps-fab-label {
  max-width: 26rem;
  margin-left: var(--mps-fab-gap);
  overflow: hidden;
  /* Wrap by default so a long label on a narrow viewport becomes two lines rather
     than being cut. Collapsed mode re-applies `nowrap` below for its single-line
     reveal; `overflow-wrap: anywhere` lets an over-long domain break too. */
  white-space: normal;
  overflow-wrap: anywhere;
  color: var(--mps-fg);
  opacity: 1;
}

/* Collapsed: an icon-only circle at rest (label clipped to zero); expands on
   hover/focus. Symmetric padding keeps the lone mark centred in the circle.
   The transitions are ASYMMETRIC (declared on each target state):
   • EXPAND (hover): the pill widens, but the label's opacity is held at 0 and only
     fades in AFTER the widen is nearly done (the 0.24s delay) — so the text is
     never shown mid-reveal with its end clipped by the still-narrow box.
   • COLLAPSE (un-hover): the text fades out fast, then the pill narrows. */
.mps-fab.mps-fab.mps-collapsed .mps-fab-label {
  max-width: 0;
  margin-left: 0;
  opacity: 0;
  /* nowrap only while collapsed: the reveal slides ONE line out smoothly. */
  white-space: nowrap;
  transition: max-width 0.3s var(--mps-ease), margin-left 0.3s var(--mps-ease),
    opacity 0.12s ease;
}
.mps-fab.mps-fab.mps-collapsed .mps-fab-btn:hover .mps-fab-label,
.mps-fab.mps-fab.mps-collapsed .mps-fab-btn:focus-visible .mps-fab-label {
  max-width: 26rem;
  margin-left: var(--mps-fab-gap);
  opacity: 1;
  transition: max-width 0.32s var(--mps-ease), margin-left 0.32s var(--mps-ease),
    opacity 0.2s ease 0.24s;
}

/* Dismiss ✕ — a real <button>, sibling of the anchor, pinned to the outer corner. */
.mps-fab.mps-fab .mps-fab-x {
  position: absolute;
  top: -7px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--mps-bg);
  color: var(--mps-fg);
  border: 1px solid var(--mps-bd);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.mps-fab.mps-fab.mps-pos-right .mps-fab-x {
  right: -7px;
}
.mps-fab.mps-fab.mps-pos-left .mps-fab-x {
  left: -7px;
}
.mps-fab.mps-fab:hover .mps-fab-x,
.mps-fab.mps-fab:focus-within .mps-fab-x {
  opacity: 1;
  pointer-events: auto;
}

/* Keyboard focus ring. */
.mps-fab.mps-fab .mps-fab-btn:focus-visible,
.mps-fab.mps-fab .mps-fab-x:focus-visible {
  outline: 2px solid #1a73e8;
  outline-offset: 2px;
}

/* Preview-only: anchor the FAB to the generator's mock frame instead of the
   viewport. NEVER emitted in the embed — the live FAB is position:fixed. */
.mps-fab.mps-fab.mps-fab-static {
  position: absolute;
}

/* Touch: no hover, so the hover-to-expand reveal can't fire. Instead the FAB
   PEEKS — it appears as the full pill (label visible, WRAPPING to two lines on a
   narrow phone rather than clipping) so the reader sees what it is, then float.js
   adds `.is-mini` after a few seconds to shrink it to an icon bubble, freeing the
   vertical space a permanent pill eats on mobile (tapping the bubble still
   navigates — the whole pill is the <a>). `.is-mini` is a RUNTIME class, not part
   of fabClasses(), so the React preview doesn't carry it — same role as `.is-in`.
   Keep this gated on `(hover: none)` in lock-step with float.js's `isTouch()`
   check that adds the class. ✕ stays visible throughout. */
@media (hover: none) {
  /* Peek: even a publisher-collapsed FAB shows its label at rest on touch… */
  .mps-fab.mps-fab.mps-collapsed .mps-fab-label {
    max-width: 26rem;
    margin-left: var(--mps-fab-gap);
    opacity: 1;
    white-space: normal;
  }
  /* …until the peek ends and float.js adds `.is-mini`, collapsing it to an icon.
     Equal specificity to the peek rule but later in source (and out-specifies the
     base label rule), so it wins for BOTH the always-pill and collapsed cases. */
  .mps-fab.mps-fab.is-mini .mps-fab-label {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    white-space: nowrap;
    transition: max-width 0.4s var(--mps-ease), margin-left 0.4s var(--mps-ease),
      opacity 0.2s ease;
  }
  .mps-fab.mps-fab .mps-fab-x {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Respect reduced-motion: no entrance/expand animation (kept later in source so
   it wins on equal specificity). */
@media (prefers-reduced-motion: reduce) {
  .mps-fab.mps-fab,
  .mps-fab.mps-fab .mps-fab-btn,
  .mps-fab.mps-fab .mps-fab-label,
  .mps-fab.mps-fab .mps-fab-x {
    transition: none;
  }
}
