* {
  box-sizing: border-box;
  /* Kill the grey tap-flash mobile browsers paint on tappable elements. */
  -webkit-tap-highlight-color: transparent;
}

:root {
  --primary-color: #4c7cf5;
  --primary-light: #e8f1ff;
  --gray-50: #f9fafb;
  --gray-100: #f3f4f6;
  --gray-200: #e5e7eb;
  --gray-300: #d1d5db;
  --gray-400: #9ca3af;
  --gray-500: #6b7280;
  --gray-600: #4b5563;
  --gray-700: #374151;
  --gray-800: #1f2937;
  --gray-900: #111827;
  --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}

html,
body {
  min-height: 100%;
}

body {
  margin: 0;
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--gray-900);
  background: var(--gray-50);
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input {
  font: inherit;
}

/* Tappable elements: no long-press text selection or iOS callout menu.
   Body text (headings, bio description) stays selectable for copying. */
a,
button {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.bio-public {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 0;
  background: var(--gray-50);
}

/* Desktop-only scan-to-open QR badge. Hidden by default (mobile, editor
   preview); the desktop layout block (min-width:760px) overrides to flex. */
.bio-qr-badge { display: none; }

.phone-inner {
  --primary-color: var(--bio-accent, #4c7cf5);
  width: 100%;
  max-width: none;
  min-height: 100vh;
  padding: max(28px, env(safe-area-inset-top)) 14px max(22px, env(safe-area-inset-bottom));
  background: linear-gradient(180deg, #eef4ff 0%, #fff 320px);
  position: relative;
}

#populatedContent {
  width: min(100%, 360px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  /* Vertical spacing between blocks. Driven by the Theme "Block spacing"
     slider via --bio-gap (10–38px); 14px when unset. */
  gap: var(--bio-gap, 14px);
}

.phone-inner[data-theme-bg="gradient-blue"] {
  background: linear-gradient(180deg, #eef4ff 0%, #fff 320px);
}

.phone-inner[data-theme-bg="white"] {
  background: #fff;
}

.phone-inner[data-theme-bg="dark"] {
  background: linear-gradient(135deg, #1f2937, #111827);
}

.phone-inner[data-theme-bg="peach"] {
  background: linear-gradient(135deg, #fce7f3, #fbcfe8);
}

/* Preset themes (each pairs with a coupled accent set via the theme picker) */
.phone-inner[data-theme-bg="indigo"] { background: linear-gradient(160deg, #e9edff, #f5f7ff); }
.phone-inner[data-theme-bg="mono"]   { background: linear-gradient(160deg, #eef0f3, #fafafa); }
.phone-inner[data-theme-bg="sunset"] { background: linear-gradient(160deg, #fde8d8, #fbf3ea); }
.phone-inner[data-theme-bg="mint"]   { background: linear-gradient(160deg, #e2f5ec, #f1faf6); }
.phone-inner[data-theme-bg="rose"]   { background: linear-gradient(160deg, #fde2ec, #fbecf3); }

/* Custom background: the editor stores ONE base colour (--bio-bg) and we
   auto-derive the soft two-stop gradient from it (lighten ~30% toward white),
   mirroring the look of the named presets above without a second picker. */
.phone-inner[data-theme-bg="custom"] {
  background: linear-gradient(160deg, var(--bio-bg, #eef4ff), color-mix(in srgb, var(--bio-bg, #eef4ff) 70%, #fff));
}

.phone-inner[data-theme-font="sans"] {
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.phone-inner[data-theme-font="serif"] {
  font-family: Georgia, "Times New Roman", serif;
}

/* Curated Google Font ("g:Family") — the stack is supplied inline as
   --bio-font-family by BioPageRenderer::themeAttrs (closed allowlist). These
   render at face value; the handwritten optical tuning above does not apply. */
.phone-inner[data-theme-font="custom"] {
  font-family: var(--bio-font-family, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
}

/* Heading (primary) font — an optional SECOND family scoped to the profile name
   and every block/section heading. The stack is supplied inline as
   --bio-heading-font-family by BioPageRenderer::themeAttrs (closed allowlist,
   legacy or curated). When the var is absent (no heading font chosen), `inherit`
   falls back to the body font cascading from .phone-inner — the historic
   single-font look, so existing bios are unaffected.
   Scope = name + titles (matches the editor's "Name & titles" sub-label):
   the Heading text block (.section-header), the links-group title, and the
   heading of every block that has one (email capture, contact, tour, music,
   video, calendly). Link-button labels stay on the body font for readability. */
.bio-name,
.bio-link-section-title,
.section-header,
.bio-email-heading,
.bio-contact-title,
.bio-tour-title,
.bio-music-title,
.bio-video-title,
.bio-cald-title {
  font-family: var(--bio-heading-font-family, inherit);
}

.phone-inner[data-theme-font="display"] {
  font-family: Poppins, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.phone-inner[data-theme-font="mono"] {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

.phone-inner[data-theme-font="handwritten"] {
  font-family: Caveat, "Comic Sans MS", cursive;
  /* Caveat is a light, slanted script — at the same px it reads smaller and
     fainter than the bold sans/display families, so the name and labels look
     undersized. font-size-adjust forces the x-height up, which acts on the font
     itself and so lifts EVERY element uniformly (--bio-fs body text and fixed-px
     widget labels alike), not just the ones that opt into the size variable.
     0.62 ≈ Caveat's natural ratio (~0.5) scaled up ~1.2x to match the prominence
     of the other families. Tune to taste; lower = smaller. Browsers without
     support fall back to the un-bumped look. */
  font-size-adjust: 0.62;
}

/* Icon fonts must opt OUT of the handwritten optical scale. font-size-adjust is
   inherited, and icon glyphs (Material Symbols, Font Awesome) fill the em box —
   a large intrinsic x-height — so the adjustment shrinks them. This bites the
   editor chrome too (FABs, block tools, chevrons all live inside .phone-inner).
   Pin icons back to their literal font-size in every font mode. */
.phone-inner .material-symbols-outlined,
.phone-inner .material-symbols-rounded,
.phone-inner .material-symbols-sharp,
.phone-inner i {
  font-size-adjust: none;
}

/* Caveat is a slanted script: its descenders (g, y, p, parens) ink just past a
   tight numeric line-box, so on titles that clip (overflow:hidden, needed for
   the line-clamp / ellipsis) the tails get sheared off — "Sugar" reads as
   "Suqar". A roomier line-height seats the descender INSIDE the line-box.
   Handwritten only; the other families fit their line-box and are spacing-tuned.
   No padding-bottom here — that extends the clip region past the box and exposes
   the top of the next clamped line. */
.phone-inner[data-theme-font="handwritten"] .bio-music-title,
.phone-inner[data-theme-font="handwritten"] .amazon-title,
.phone-inner[data-theme-font="handwritten"] .bio-video-title,
.phone-inner[data-theme-font="handwritten"] .bio-link-row-title {
  line-height: 1.6;
}

.bio-profile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  /* --bio-profile-top (px, default 0) is the user-set "space above" the
     avatar/name/bio cluster, additive to the layout's default top padding. */
  padding: calc(10px + var(--bio-profile-top, 0px)) 0 14px;
  text-align: center;
}

/* Hero cover banner — full-bleed 4:1 image above the avatar. The avatar
   pulls up to overlap the banner's lower edge (Linktree-style header). */
.bio-cover {
  /* Bleed past the .phone-inner padding (28px top / 14px sides) so the
     banner reaches the device edges. #populatedContent is ≤360px centered,
     so 100vw is clamped by max-width — calc against the content box. */
  width: calc(100% + 28px);
  margin: -28px -14px 0;
  aspect-ratio: 4 / 1;
  background-size: cover;
  background-position: center;
  background-color: var(--gray-100);
}

.bio-profile--hero {
  padding-top: 0;
  gap: 8px;
}

.bio-profile--hero .bio-avatar {
  margin-top: -36px;
}

/* Portrait layout — the header photo IS the header: a full-bleed image with the
   avatar/name/tagline/socials cluster overlaid at its BOTTOM over a dark
   gradient. Built as a 1-cell grid: a ::before spacer holds the photo's crop
   ratio (the MINIMUM height) and the .bio-portrait-content cluster shares that
   same cell, pinned to the bottom (align-self:end). Because a grid cell sizes to
   max(spacer, content), a short bio sits at the bottom over a tall photo, and a
   long bio grows the box DOWNWARD instead of clipping the avatar off the top.
   The photo itself is an absolute layer (.bio-portrait, inset:0) that fills the
   grown box. */
.bio-profile--portrait {
  position: relative;
  display: grid;
  /* Bleed past the .phone-inner padding, same mechanics as .bio-cover. */
  width: calc(100% + 28px);
  margin: -28px -14px 0;
  /* No padding/gap here — the cluster carries its own; the spacer owns height. */
  padding: 0;
  overflow: hidden;
}

/* Ratio spacer: enforces the photo's height as the cell MINIMUM (so the box is
   at least one crop tall) while letting the cluster grow it. The ratio arrives
   inline as --bio-portrait-ratio (per cfg.portrait_ratio); 4/5 is the no-JS
   fallback. Must NOT live as aspect-ratio on the box itself, or the box height
   becomes definite and stops growing with the content. */
.bio-profile--portrait::before {
  content: '';
  grid-area: 1 / 1;
  aspect-ratio: var(--bio-portrait-ratio, 4 / 5);
}

/* Cluster wrapper: inert everywhere by default (children act as direct
   .bio-profile items → classic/banner layouts unchanged). The --portrait rule
   below promotes it to the real bottom-pinned grid cell. */
.bio-portrait-content { display: contents; }

.bio-profile--portrait .bio-portrait-content {
  grid-area: 1 / 1;          /* same cell as the spacer → cell = max(both) */
  align-self: end;           /* pin the cluster to the photo's bottom */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  /* Top padding becomes the avatar's margin from the top edge once a long bio
     grows the box; bottom padding lifts the socials off the edge.
     --bio-profile-top (px, default 0) adds the user-set "space above". */
  padding: calc(28px + var(--bio-profile-top, 0px)) 14px 18px;
  text-align: center;
  position: relative;        /* paint above the absolute .bio-portrait photo */
  z-index: 1;
}

.bio-profile--portrait .bio-avatar { margin-bottom: 4px; }

.bio-portrait {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--gray-100);
  /* Dissolve the photo's bottom edge into whatever theme background sits
     behind it (mask reveals the real bg, so gradients work too) instead of
     ending on a hard line — the link.me look. Eased over ~160px: still ~55%
     opaque under the socials, fully gone at the edge; the name/tagline zone
     above stays fully backed. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 160px), rgb(0 0 0 / 0.55) calc(100% - 64px), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 160px), rgb(0 0 0 / 0.55) calc(100% - 64px), transparent);
}

.bio-portrait-vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.bio-portrait-vid.is-fallback {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  /* Kill the motion up front (before the JS fallback runs); the poster still —
     the .bio-portrait background-image — shows through. */
  .bio-portrait-vid { display: none; }
}

.bio-portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 45%, rgb(0 0 0 / 0.72));
  pointer-events: none;
}

/* Combined layout — a ratio-driven full-bleed HEADER photo at the TOP (the
   portrait look as a finite in-flow block, not an absolute fill), a circular
   avatar straddling its lower edge, then name/tagline/socials in NORMAL flow
   below on the theme background. Unlike --portrait the text is NOT over the
   image, so no mask/scrim and no white-text override — derived when BOTH a
   header image and an avatar are present. */
.bio-profile--combined {
  padding-top: 0;            /* header bleeds to the top edge, like --hero */
  gap: 8px;
}

/* Header block: in-flow (NOT absolute), full-bleed, ratio-driven height. Same
   bleed mechanics as --portrait (28px top / 14px sides). The aspect-ratio is
   supplied inline per cfg.portrait_ratio; 4/5 here is just a no-JS fallback. */
.bio-profile--combined .bio-portrait {
  /* relative (not static): stays in normal flow as a top band, but is the
     containing block for the absolute .bio-portrait-vid — otherwise a header
     video escapes to #populatedContent / .phone-inner and covers the page. */
  position: relative;
  inset: auto;
  width: calc(100% + 28px);
  margin: -28px -14px 0;
  aspect-ratio: 4 / 5;
  /* No dissolve: the header ends on the theme background as a clean block edge,
     because content sits below it rather than over it. */
  -webkit-mask-image: none;
          mask-image: none;
}

/* Drop the dark portrait scrim — text no longer sits on the image. */
.bio-profile--combined .bio-portrait::after { content: none; }

/* Avatar straddles the header's bottom edge (same overlap idiom as --hero):
   half of it over the seam, so the overlap scales with the chosen size. */
.bio-profile--combined .bio-avatar {
  margin-top: calc(var(--bio-av, 84px) / -2);
  position: relative;
  z-index: 1;
}

/* Avatar diameter is a per-profile size tier (cfg.avatar_size → bio-av-* class
   on .bio-profile). Applies the same in classic / overlay / banner. */
.bio-profile.bio-av-small  { --bio-av: 64px; }
.bio-profile.bio-av-medium { --bio-av: 108px; }
.bio-profile.bio-av-large  { --bio-av: 152px; }

.bio-avatar {
  width: var(--bio-av, 84px);
  height: var(--bio-av, 84px);
  flex-shrink: 0;   /* never let the flex column squash it into an ellipse/sliver */
  border-radius: 50%;
  background: linear-gradient(135deg, #667eea, #764ba2);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: calc(var(--bio-fs, 1) * 26px);
  font-weight: 600;
  border: 3px solid #fff;
  box-shadow: var(--shadow);
}

.bio-name {
  /* --bio-name-scale (per-element, 0.7–1.6) multiplies on top of the theme's
     overall --bio-fs so the name scales proportionally with the chosen text size. */
  font-size: calc(var(--bio-fs, 1) * var(--bio-name-scale, 1) * 17px);
  line-height: 1.25;
  font-weight: 700;
  /* Colour precedence: per-element --bio-name-color → legacy combined
     --bio-profile-text → theme body colour --bio-text → default ink. */
  color: var(--bio-name-color, var(--bio-profile-text, var(--bio-text, var(--gray-900))));
}

.bio-tagline {
  padding: 0 20px;
  font-size: calc(var(--bio-fs, 1) * var(--bio-bio-scale, 1) * 13px);
  line-height: 1.4;
  color: var(--bio-bio-color, var(--bio-profile-text, var(--bio-text, var(--gray-600))));
  white-space: pre-line;   /* keep user's line breaks; collapse runs of spaces */
}

.bio-socials {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 6px;
}

.bio-social {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  text-decoration: none;
  color: var(--gray-700, #374151);
  transition: transform .12s ease, opacity .12s ease;
}
.bio-social:hover { transform: translateY(-1px); opacity: .85; }
.bio-social i { font-size: 20px; line-height: 1; color: inherit; }
.bio-social img { width: 24px; height: 24px; object-fit: contain; }
.bio-social .bio-social-mk {
  width: 22px; height: 22px; display: inline-block;
  background: currentColor;
  -webkit-mask: var(--mk) center / contain no-repeat;
          mask: var(--mk) center / contain no-repeat;
}
/* Icon glyph/image is decoration — let the tap fall through to the anchor so
   long-press can't fire the iOS image callout, selection, or a drag ghost. */
.bio-social i,
.bio-social img,
.bio-social .bio-social-mk {
  pointer-events: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* A — colour tiles: white tile + full-colour logo / brand-colour mark */
.bio-socials[data-social-style="tiles"] .bio-social {
  background: #fff;
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.18);
  color: var(--brand, #374151);
}

/* B — brand tiles: brand-colour tile + white glyph */
.bio-socials[data-social-style="brand"] .bio-social {
  background: var(--brand, #374151);
  color: var(--glyph, #fff);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.18);
}

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

.block {
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  border-radius: var(--r-card, 14px);
  padding: 0;
  position: relative;
  overflow: hidden;
}

.bio-public-block {
  background: transparent;
  border: 0;
  box-shadow: none;
}

.section-header {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 700;
  color: var(--bio-text, var(--gray-900));
  margin: 0 4px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.section-header + .block {
  margin-top: -16px;
}

.bio-profile + .block {
  margin-top: 0;
}

.section-header--large {
  font-size: calc(var(--bio-fs, 1) * 15px);
}

.section-header--medium {
  font-size: calc(var(--bio-fs, 1) * 13px);
}

.section-header--small {
  font-size: calc(var(--bio-fs, 1) * 11px);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--gray-700);
}

/* Heading Bold toggle: bold (700) is the default; an explicit "normal" override
   drops the heading to a regular weight. */
.section-header--weight-normal {
  font-weight: 400;
}

.section-header--align-left {
  justify-content: flex-start;
}

.section-header--align-center {
  justify-content: center;
}

.section-header--align-right {
  justify-content: flex-end;
}

/* Heading block: per-block pixel size, optional colour, and optional
   icon/emoji (applies on the editor canvas and the public page alike). The
   attribute selector outranks the legacy .section-header--large/medium/small
   font-size so old bios that still carry those classes are unaffected. */
.section-header[style*="--bio-head-fs"] { font-size: var(--bio-head-fs); }
.bio-head-iconwrap { display: inline-flex; align-items: center; line-height: 0; }
.bio-head-iconwrap .material-symbols-outlined { font-size: 1.1em; line-height: 1; }

/* Label alignment (theme.align = center). Centers ONLY widget titles, product
   names, plain-link labels and the email box — the page layout (profile, link
   rows, images, dividers) is deliberately untouched, and the plain-only
   selector never matches a thumbnail row. The Heading / Text widgets keep their
   own per-block align: their .section-header / .bio-text-body sit in a
   .bio-flat-block, not the widget blocks targeted here. */
/* Widget block titles render as a .section-header SIBLING immediately before the
   media block (not inside it) — so target via :has(+ block). This also excludes
   the Heading widget, whose .section-header is nested in a .bio-flat-block and is
   never the direct previous sibling of a media block. Browsers without :has()
   just leave these titles left-aligned (graceful). */
.phone-inner[data-theme-align="center"] .section-header:has(+ .bio-amazon-block),
.phone-inner[data-theme-align="center"] .section-header:has(+ .bio-product-block),
.phone-inner[data-theme-align="center"] .section-header:has(+ .bio-video-block),
.phone-inner[data-theme-align="center"] .section-header:has(+ .bio-music-block) {
  justify-content: center;
}
.phone-inner[data-theme-align="center"] .bio-link-section-title,
.phone-inner[data-theme-align="center"] .amazon-title,
.phone-inner[data-theme-align="center"] .product-price,
.phone-inner[data-theme-align="center"] .bio-email-heading,
.phone-inner[data-theme-align="center"] .bio-email-description {
  text-align: center;
}
.phone-inner[data-theme-align="center"] .bio-link-block--layout-plain .bio-link-row-title {
  text-align: center;
}

.bio-text-body {
  /* Per-block font size set via --bio-text-fs (editor slider + renderer),
     still scaled by the theme's global --bio-fs. Falls back to 13px. */
  font-size: calc(var(--bio-fs, 1) * var(--bio-text-fs, 13px));
  line-height: 1.5;
  /* Key the body colour on the theme's text colour so it adapts to dark
     palettes (Midnight, Charcoal, …). Palette themes set --bio-text; legacy
     pages without it fall back to gray-700 (and the [data-theme-bg=dark]
     override below still flips plain text to white). */
  color: var(--bio-text, var(--gray-700));
  white-space: pre-wrap;
}

/* Body-text lists (ul/ol/li from the WYSIWYG). white-space:normal inside the
   list so pre-wrap newlines around it don't add stray gaps; explicit list-style
   restores bullets/numbers in case a theme reset stripped them. Alignment is
   inherited from the .bio-text-body--align-* class. */
.bio-text-body ul,
.bio-text-body ol {
  white-space: normal;
  margin: 4px 0;
  padding-left: 24px;
  text-align: inherit;
}
.bio-text-body ul { list-style: disc; }
.bio-text-body ol { list-style: decimal; }
.bio-text-body li { margin: 2px 0; }
/* Centre / right alignment: pull the marker inside the text flow and drop the
   left indent so the bullet/number rides with the centred (or right-aligned)
   text instead of floating at the far left. text-align is inherited from the
   .bio-text-body--align-* class on the block. */
.bio-text-body--align-center ul,
.bio-text-body--align-center ol,
.bio-text-body--align-right ul,
.bio-text-body--align-right ol {
  list-style-position: inside;
  padding-left: 0;
}

.bio-text-body--small {
  font-size: calc(var(--bio-fs, 1) * 11px);
}

.bio-text-body--medium {
  font-size: calc(var(--bio-fs, 1) * 13px);
}

.bio-text-body--large {
  font-size: calc(var(--bio-fs, 1) * 15px);
}

.bio-text-body--bold {
  font-weight: 700;
}

.bio-text-body--align-left {
  text-align: left;
}

.bio-text-body--align-center {
  text-align: center;
}

.bio-text-body--align-right {
  text-align: right;
}

/* Flat widgets (heading, divider, plain text): no white .block surface and
   no overflow clipping — they're too short for the editor toolbar otherwise. */
.block.bio-flat-block {
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}

/* Plain text sits on the PAGE background (not on a card) — key its colour on
   the page bg, not the card style: immersive cards can pair with light pages. */
.phone-inner[data-theme-bg="dark"] .bio-text-block--plain .bio-text-body,
.phone-inner[data-theme-bg="linktwin"] .bio-text-block--plain .bio-text-body {
  color: rgb(255 255 255 / .85);
}

.bio-text-body--bg-card {
  /* Theme-adaptive card fill: a translucent tint of the page's text colour, so
     it reads as a subtle card on EVERY palette — a soft dark tint on light
     pages, a soft light tint on dark palettes (Midnight/Charcoal/…). Replaces
     the old hardcoded white --gray-50, which clashed on dark custom palettes
     (they set data-theme-bg="custom", not "dark", so the override below never
     fired). Falls back to gray-900 for legacy pages with no --bio-text. */
  background: color-mix(in srgb, var(--bio-text, var(--gray-900)) 8%, transparent);
  border-radius: 10px;
  padding: 12px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);
}

/* Legacy keyword "dark"/"linktwin" pages carry no --bio-text, so the
   color-mix above would tint dark-on-dark (invisible). Keep the explicit
   glassy white card for them. */
.phone-inner[data-theme-bg="dark"] .bio-text-body--bg-card,
.phone-inner[data-theme-bg="linktwin"] .bio-text-body--bg-card {
  background: rgb(255 255 255 / .12);
  color: #fff;
  box-shadow: none;
}

/* Free background colour (chosen via the formatting toolbar). The colour
   itself is set inline (background-color); this only supplies the card
   padding/radius so text doesn't sit flush against the fill edge. Applies
   to both the Text body and the Heading. */
.bio-text-body--bg-custom,
.section-header--bg-custom {
  border-radius: 10px;
  padding: 12px;
}

.bio-divider {
  padding: 6px 0;
}

.bio-divider .bio-divider-line {
  /* Reads as a real separator, not a barely-there hairline. .14 was too soft
     for the job people use a divider for. */
  --bio-divider-color: rgb(0 0 0 / .55);
  border: 0;
  /* Theme-keyed hairline: a translucent version of the page foreground, NOT a
     fixed gray. Light pages get a subtle dark rule; dark/immersive pages flip
     to a translucent white one below — same convention as .bio-text-block.
     Neutral on purpose (accent-tinted dividers read as noise). */
  border-top: 1px solid var(--bio-divider-color);
  margin: 0;
  /* Neutralise Bootstrap's `hr` reboot (the25-app.css, loaded by the dashboard
     layout on the editor page only — the public page doesn't load Bootstrap).
     Bootstrap sets `background-color: currentColor; opacity: .25` on every
     <hr>, which fills this element's box with a solid currentColor bar and
     drowns the dashed/dotted/space variants. The public page is unaffected
     (no Bootstrap), so these are a harmless no-op there. */
  background-color: transparent;
  opacity: 1;
}

.phone-inner[data-theme-bg="dark"] .bio-divider .bio-divider-line,
.phone-inner[data-theme-bg="linktwin"] .bio-divider .bio-divider-line {
  /* .20 read too soft on the dark gradient — bumped so it registers as a real
     separator against #1f2937/#111827. */
  --bio-divider-color: rgb(255 255 255 / .75);
  border-top-color: var(--bio-divider-color);
}

.bio-divider--solid .bio-divider-line {
  border-top-style: solid;
}

.bio-divider--dashed .bio-divider-line {
  border-top-style: dashed;
}

.bio-divider--dotted .bio-divider-line {
  border-top: 0;
  height: 3px;
  background-image: radial-gradient(circle, var(--bio-divider-color) 1px, transparent 1.25px);
  background-repeat: repeat-x;
  background-position: left center;
  background-size: 7px 3px;
}

.bio-divider--space .bio-divider-line {
  border-top: 0;
  height: 14px;
}

/* "Space" is a blank gap, so border-top-width (what Thickness normally sets) is
   invisible — the control would be dead. Map Thickness to the gap HEIGHT instead
   so thin/medium/thick actually change the spacing. Compound parent selector
   (0,3,0) outranks the plain --space rule (0,2,0) above. */
/* Heights sit ON TOP of the 26px inter-block flex gap (#populatedContent), so
   small values barely register — thin was ~10px against a 26px gap = "no
   effect". Stepped up so each tier is clearly perceptible. */
/* Thin "Empty space" is meant as a barely-there gap — the 12px line on top of
   the block's own 6px 0 padding read as a full separator. Drop the padding and
   shrink the line so Thin is a genuine hairline-of-space. Medium/thick stay
   tall (they're for deliberate breathing room). */
.bio-divider--space.bio-divider--thin   { padding: 0 !important; }
.bio-divider--space.bio-divider--thin   .bio-divider-line { height: 0; }
.bio-divider--space.bio-divider--medium .bio-divider-line { height: 16px; }
.bio-divider--space.bio-divider--thick  .bio-divider-line { height: 30px; }

.bio-divider--thin .bio-divider-line {
  border-top-width: 1px;
}

.bio-divider--medium .bio-divider-line {
  border-top-width: 2px;
}

.bio-divider--thick .bio-divider-line {
  border-top-width: 3px;
}

.bio-divider--dotted.bio-divider--thin .bio-divider-line {
  height: 3px;
  background-size: 7px 3px;
  background-image: radial-gradient(circle, var(--bio-divider-color) 1px, transparent 1.25px);
}

.bio-divider--dotted.bio-divider--medium .bio-divider-line {
  height: 4px;
  background-size: 9px 4px;
  background-image: radial-gradient(circle, var(--bio-divider-color) 1.5px, transparent 1.75px);
}

.bio-divider--dotted.bio-divider--thick .bio-divider-line {
  height: 5px;
  background-size: 11px 5px;
  background-image: radial-gradient(circle, var(--bio-divider-color) 2px, transparent 2.25px);
}

.bio-link-block {
  padding: 0;
  min-width: 0;
}

.bio-link-section-title {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  color: var(--gray-700);
  padding: 4px 8px 8px;
}

.bio-link-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.bio-link-block--layout-grid .bio-link-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

.bio-link-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--gray-100);
  border-radius: var(--r-row, 8px);
  background: var(--bio-card, #fff);
  min-width: 0;
  overflow: hidden;
  /* Keep a consistent row height whether or not a thumbnail is shown:
     32px thumb + 8px+8px padding + 2px borders = 50px (border-box). The
     thumbnail-less row floors here; taller content (large font) still grows. */
  min-height: 50px;
}

.bio-link-block--layout-grid .bio-link-row {
  flex-direction: column;
  text-align: center;
}

.bio-link-thumb {
  width: 32px;
  height: 32px;
  border-radius: var(--r-thumb, 8px);
  background: var(--gray-100);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--gray-500);
  flex-shrink: 0;
  overflow: hidden;
}

.bio-link-thumb img,
.amazon-thumb img,
.bio-video-thumb img,
.bio-music-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.bio-link-block--layout-grid .bio-link-thumb {
  width: 40px;
  height: 40px;
}

.bio-link-body {
  min-width: 0;
  flex: 1;
  max-width: 100%;
}

.bio-link-row-title {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  color: var(--bio-text, var(--gray-700));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.bio-link-block--layout-plain .bio-link-thumb {
  display: none;
}

/* ─── Single Link block ───────────────────────────────────────────────────
   One link with optional per-link colours. The wrapper carries the user's
   palette as --bl-* custom props (BioPageRenderer::styleAttr / editor JS);
   each override is gated behind an attribute-presence selector so it ONLY
   fires when the user actually set that property — leaving the theme cascade
   (dark/immersive row styling) untouched otherwise. */
.bio-link-block--single .bio-link-list { gap: 0; }
.bio-link-block--single[style*="--bl-bg"]     .bio-link-row { background: var(--bl-bg); }
.bio-link-block--single[style*="--bl-text"]   .bio-link-row,
.bio-link-block--single[style*="--bl-text"]   .bio-link-row-title { color: var(--bl-text); }
.bio-link-block--single[style*="--bl-border"] .bio-link-row { border-color: var(--bl-border); border-style: solid; }
.bio-link-block--single[style*="--bl-bw"]     .bio-link-row { border-width: var(--bl-bw); border-style: solid; }
/* Per-link text alignment (Colors toolbar). Wins over the theme-level
   label-alignment rule via the .phone-inner prefix + later source order. */
.phone-inner .bio-link-block--single[style*="--bl-align"] .bio-link-row-title { text-align: var(--bl-align); width: 100%; }

/* ─── Multi-link blocks: per-link colours ──────────────────────────────────
   Each link inside a Links block can carry its OWN palette
   (config.links[].style → --bl-* on the .bio-link-row element, see
   BioPageRenderer::links + editor bioApplyStyleVarsTo). Same attribute-gated
   pattern as single links, but the vars live on each row so one block can mix
   differently-styled links. The .phone-inner prefix matches the single-link
   rules' specificity so an explicit choice wins over the base row styling. */
.phone-inner .bio-link-row[style*="--bl-bg"]     { background: var(--bl-bg); }
.phone-inner .bio-link-row[style*="--bl-text"]   { color: var(--bl-text); }
.phone-inner .bio-link-row[style*="--bl-text"]   .bio-link-row-title { color: var(--bl-text); }
.phone-inner .bio-link-row[style*="--bl-border"] { border-color: var(--bl-border); border-style: solid; }
.phone-inner .bio-link-row[style*="--bl-bw"]     { border-width: var(--bl-bw); border-style: solid; }
.phone-inner .bio-link-row[style*="--bl-align"]  { text-align: var(--bl-align); }
.phone-inner .bio-link-row[style*="--bl-align"] .bio-link-row-title { text-align: var(--bl-align); width: 100%; }

/* ─── Alignment follows the icon/thumbnail too ─────────────────────────────
   text-align (above) only shifts the LABEL; the thumbnail is a separate flex
   item, so without this the icon stays pinned left when the row is centered or
   right-aligned. --*-flex carries the same choice mapped to a flexbox keyword
   (BioPageRenderer::styleAttrFromStyle + editor bioApplyStyleVars). For ROW
   layouts it drives justify-content (the icon+label group shifts together) and
   the body stops growing so it can; for the GRID's COLUMN cells it drives
   align-items (the stacked icon/label shift on the cross axis). */
/* Single link — vars live on the block wrapper */
.bio-link-block--single[style*="--bl-flex"] .bio-link-row  { justify-content: var(--bl-flex); }
.bio-link-block--single[style*="--bl-flex"] .bio-link-body { flex: 0 1 auto; }
/* Multi-link rows in a horizontal layout (thumbnail / dropdown) — per-row vars */
.phone-inner .bio-link-block:not(.bio-link-block--layout-grid) .bio-link-row[style*="--bl-flex"]                 { justify-content: var(--bl-flex); }
.phone-inner .bio-link-block:not(.bio-link-block--layout-grid) .bio-link-row[style*="--bl-flex"] .bio-link-body { flex: 0 1 auto; }
/* Grid section alignment + per-link alignment inside a grid (column cells) */
.bio-link-block--grid[style*="--bs-flex"] .bio-link-row        { align-items: var(--bs-flex); }
.bio-link-block--layout-grid .bio-link-row[style*="--bl-flex"] { align-items: var(--bl-flex); }

/* Consecutive single links sit a constant ~10px apart — tighter than the
   inter-block gap — so a stack of individual links reads as a tight group, not
   scattered cards. The negative margin tracks --bio-gap so the net stays 10px
   whatever the Theme "Block spacing" slider is set to (never overlaps). */
.bio-link-block--single + .bio-link-block--single { margin-top: calc(10px - var(--bio-gap, 14px)); }

/* ─── Links grid: no white card surface by default ─────────────────────────
   The grid's cells (.bio-link-row) are the visible surface; the wrapper must
   stay transparent so only the grid tiles show against the page background
   (the white .block card peeked through the inter-cell gaps otherwise). A
   user-set section background (--bs-bg, below) re-introduces a panel on top. */
.bio-link-block--grid {
  background: transparent;
  box-shadow: none;
}

/* ─── Links grid: section-level colours (a panel behind the grid) ───────── */
.bio-link-block--grid[style*="--bs-bg"]     { background: var(--bs-bg); }
.bio-link-block--grid[style*="--bs-border"] { border-color: var(--bs-border); border-style: solid; }
.bio-link-block--grid[style*="--bs-bw"]     { border-width: var(--bs-bw); border-style: solid; }
.bio-link-block--grid[style*="--bs-pad"]    { padding: var(--bs-pad); border-radius: 14px; }
.bio-link-block--grid[style*="--bs-text"] .bio-link-section-title,
.bio-link-block--grid[style*="--bs-text"] .bio-link-row-title { color: var(--bs-text); }
/* Per-section text alignment (Colors toolbar) — overrides the grid's default
   centered cells (and the section title) when the user picks an alignment. */
.bio-link-block--grid[style*="--bs-align"] .bio-link-row,
.bio-link-block--grid[style*="--bs-align"] .bio-link-row-title,
.bio-link-block--grid[style*="--bs-align"] .bio-link-section-title { text-align: var(--bs-align); }

/* ─── Multi-links: Dropdown layout ─────────────────────────────────────────
   A "main link" header (looks like a single link) that toggles a panel of the
   block's links below it. the25-bio-public.js flips [hidden] + aria-expanded on
   tap; in the editor canvas the panel renders open so the owner sees the rows. */
.bio-link-block--dropdown .bio-link-dropdown-head,
.bio-calendly-block--button .bio-link-dropdown-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  min-height: 50px;
  border: 1px solid var(--gray-100);
  border-radius: var(--r-row, 8px);
  background: var(--bio-card, #fff);
  color: var(--bio-text, var(--gray-700));
  font: inherit;
  cursor: pointer;
  text-align: left;
  box-sizing: border-box;
}
.bio-link-block--dropdown .bio-link-dropdown-title,
.bio-calendly-block--button .bio-link-dropdown-title {
  flex: 1;
  min-width: 0;
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bio-link-block--dropdown .bio-link-dropdown-caret,
.bio-calendly-block--button .bio-link-dropdown-caret {
  display: flex;
  align-items: center;
  /* Inherit the header's (theme/section-adaptive) text colour so the chevron is
     always legible against the card background; dim it slightly for hierarchy.
     The SVG strokes with currentColor, so this is what it picks up. */
  color: inherit;
  opacity: .6;
  transition: transform .2s ease;
  flex-shrink: 0;
}
.bio-link-block--dropdown .bio-link-dropdown-head[aria-expanded="true"] .bio-link-dropdown-caret,
.bio-calendly-block--button .bio-link-dropdown-head[aria-expanded="true"] .bio-link-dropdown-caret {
  transform: rotate(180deg);
}
/* Calendly "Booking button" panel: same expand/collapse animation as the
   multi-link dropdown, but it wraps the embed instead of link rows (so no
   row gap / list padding). */
.bio-calendly-block--button .bio-link-dropdown-panel.bio-calendly-panel {
  box-sizing: border-box;
  overflow: hidden;
  transition: height .28s ease, opacity .28s ease;
}
/* Spacing below the button lives on the child (not panel padding) so a collapsed
   height:0 panel clips it completely — no leftover sliver under the button. */
.bio-calendly-block--button .bio-calendly-panel > * { margin-top: 8px; }
.bio-calendly-block--button .bio-link-dropdown-panel[hidden] { display: none; }
/* Collapsed booking-button panel: clipped to 0 height but still display:block so
   the preloaded Calendly embed keeps real layout dimensions (and stays sized). */
.bio-calendly-block--button .bio-link-dropdown-panel.bio-calendly-panel--collapsed { height: 0; opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .bio-calendly-block--button .bio-link-dropdown-panel.bio-calendly-panel { transition: none; }
}

/* ── Calendly demo (editor-only placeholder) ─────────────────────────────────
   A static mock of Calendly's "Select a Day" view shown before the owner adds
   their link. Themed via the bio --bio-* vars so it doubles as a colour preview.
   pointer-events:none so a click falls through to the block's edit handler. */
.bio-calendly-demo {
  position: relative;
  background: var(--bio-card, #fff);
  color: var(--bio-text, #1a1a1a);
  border: 1px solid var(--gray-100);
  border-radius: var(--r-card, 12px);
  padding: 18px 16px 14px;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}
.bio-calendly-demo .bio-cald-ribbon {
  position: absolute;
  top: 14px;
  right: -34px;
  transform: rotate(45deg);
  background: #4d5055;
  color: #fff;
  padding: 4px 40px;
  text-align: center;
  line-height: 1.1;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
}
.bio-calendly-demo .bio-cald-ribbon span { display: block; font-size: 6px; letter-spacing: .5px; opacity: .85; text-transform: uppercase; }
.bio-calendly-demo .bio-cald-ribbon strong { display: block; font-size: 10px; font-weight: 700; }
.bio-calendly-demo .bio-cald-title { text-align: center; font-weight: 700; font-size: calc(var(--bio-fs, 1) * 15px); margin-bottom: 12px; }
.bio-calendly-demo .bio-cald-nav { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 10px; }
.bio-calendly-demo .bio-cald-month { font-weight: 600; font-size: calc(var(--bio-fs, 1) * 13px); }
.bio-calendly-demo .bio-cald-arrow { font-size: 18px; line-height: 1; color: var(--gray-300); }
.bio-calendly-demo .bio-cald-arrow--on { color: var(--bio-accent, #5b6ef5); }
.bio-calendly-demo .bio-cald-dow,
.bio-calendly-demo .bio-cald-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.bio-calendly-demo .bio-cald-dow { margin-bottom: 4px; }
.bio-calendly-demo .bio-cald-dow span { text-align: center; font-size: 9px; font-weight: 600; color: var(--gray-400); text-transform: capitalize; }
.bio-calendly-demo .bio-cald-day {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  font-size: calc(var(--bio-fs, 1) * 12px);
  color: var(--gray-400);
  border-radius: 50%;
}
.bio-calendly-demo .bio-cald-day.is-avail {
  color: var(--bio-accent, #5b6ef5);
  font-weight: 600;
  background: color-mix(in srgb, var(--bio-accent, #5b6ef5) 13%, transparent);
}
.bio-calendly-demo .bio-cald-dot {
  position: absolute;
  bottom: 4px;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--bio-accent, #5b6ef5);
}
.bio-calendly-demo .bio-cald-tz { margin-top: 12px; font-size: 11px; color: var(--gray-500); display: flex; align-items: center; gap: 6px; }
.bio-calendly-demo .bio-cald-globe { font-size: 12px; }
.bio-calendly-demo-hint {
  margin-top: 8px;
  text-align: center;
  font-size: 11px;
  color: var(--gray-500);
  pointer-events: none;
}
/* Editor-only hint under the Booking button (the calendar isn't shown in the
   editor — it expands on the live page). */
.bio-calendly-editor-hint {
  margin-top: 8px;
  text-align: center;
  font-size: 11px;
  color: var(--gray-500);
  pointer-events: none;
}
/* Expanded panel: a single-column list of regular-looking link rows beneath the
   header, regardless of the grid layout class on the wrapper (3-class selectors
   beat the grid's 2-class rules). */
.bio-link-block--dropdown .bio-link-dropdown-panel.bio-link-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* padding (not margin) so the top spacing is clipped along with the rows when
     the panel animates to height:0 — no lingering gap above a collapsed panel. */
  padding-top: 6px;
  /* Indent the sub-links so they read as nested under the dropdown header. */
  padding-left: 16px;
  box-sizing: border-box;
  overflow: hidden;
  transition: height .28s ease, opacity .28s ease;
}
.bio-link-block--dropdown .bio-link-dropdown-panel[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  .bio-link-block--dropdown .bio-link-dropdown-panel.bio-link-list { transition: none; }
}
.bio-link-block--dropdown .bio-link-dropdown-panel .bio-link-row {
  flex-direction: row;
  text-align: left;
}
.bio-link-block--dropdown .bio-link-dropdown-panel .bio-link-thumb {
  width: 32px;
  height: 32px;
}
/* Editor-only placeholder rows (shown before the owner adds real links). */
.bio-link-block--dropdown .bio-link-dropdown-panel--demo .bio-link-row { opacity: .6; }
/* Section colour overrides extend to the dropdown header. */
.bio-link-block--grid[style*="--bs-text"] .bio-link-dropdown-title,
.bio-link-block--grid[style*="--bs-text"] .bio-link-dropdown-head { color: var(--bs-text); }

/* ─── Dropdown Title header: per-block colours (--dt-*) ─────────────────────
   Set on the .bio-link-dropdown-head element itself (config.titleStyle), so
   the collapsed header can be styled independently of the section panel and
   the links inside. Attribute-gated like every other bio palette. */
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-bg"]     { background: var(--dt-bg); }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-text"]   { color: var(--dt-text); }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-text"] .bio-link-dropdown-title { color: var(--dt-text); }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-border"] { border-color: var(--dt-border); border-style: solid; }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-bw"]     { border-width: var(--dt-bw); border-style: solid; }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-align"]  { text-align: var(--dt-align); }
.bio-link-block--dropdown .bio-link-dropdown-head[style*="--dt-align"] .bio-link-dropdown-title { text-align: var(--dt-align); }

.bio-thumb-fallback {
  font-size: calc(var(--bio-fs, 1) * 10px);
  font-weight: 700;
  text-transform: uppercase;
}

.bio-thumb-fallback i {
  font-size: 22px;
  line-height: 1;
  color: var(--primary-color, #4c7cf5);
  opacity: .92;
}

.bio-music-block--layout-compact .bio-thumb-fallback i,
.bio-link-thumb .bio-thumb-fallback i {
  font-size: 16px;
}

/* Edge-to-edge media like the video block: no white .block surface (it made the
   immersive white caption invisible and peeked out at the rounded corners);
   overflow visible so the hover lift/shadow isn't clipped. */
.bio-image {
  margin: 0;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}

/* No frame background: a light fill bleeds through the rounded-corner
   antialiasing as a halo around dark images. The empty-state placeholder
   carries its own gray fill instead. */
.bio-image .bio-image-frame {
  width: 100%;
  overflow: hidden;
  /* Corner roundness: numeric px radius set per-block via --bio-img-radius
     (editor slider + BioPageRenderer); falls back to 12px. */
  border-radius: var(--bio-img-radius, 12px);
  transition: transform .18s ease, box-shadow .18s ease;
}

.bio-image .bio-image-placeholder {
  background: var(--gray-100);
}

.bio-image-link {
  /* Must fill the frame: .bio-image-img uses height:100%, which only
     resolves against a sized parent. When an image has a click
     destination it's wrapped in this <a> (the editor never wraps), so
     without an explicit size here the anchor collapses to auto height,
     the img falls back to natural size, and object-fit:cover is moot —
     the image renders small/letterboxed instead of filling the frame. */
  display: block;
  width: 100%;
  height: 100%;
}

/* Hover lift only when the image is clickable (has a destination link). */
.bio-image:has(.bio-image-link):hover .bio-image-frame {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px -8px rgb(0 0 0 / .35);
}

.bio-image .bio-image-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bio-image--aspect-original .bio-image-frame {
  aspect-ratio: auto;
}

.bio-image--aspect-original .bio-image-img {
  height: auto;
  object-fit: contain;
}

.bio-image--aspect-square .bio-image-frame {
  aspect-ratio: 1 / 1;
}

.bio-image--aspect-landscape .bio-image-frame {
  aspect-ratio: 16 / 9;
}

.bio-image--aspect-tall .bio-image-frame {
  aspect-ratio: 9 / 16;
}

.bio-image .bio-image-caption {
  padding: 8px 12px 12px;
  font-size: calc(var(--bio-fs, 1) * 12px);
  color: var(--gray-500);
  text-align: center;
}

/* The Amazon block is a frame-less container — its product cards are the cards,
   so the wrapper drops the white .block surface/shadow and lets them float on
   the page background (and not clip their own shadows). */
.bio-amazon-block {
  background: transparent;
  box-shadow: none;
  overflow: visible;
  padding: 0;
}

.amazon-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}

.bio-amazon-block--layout-carousel .amazon-grid {
  grid-template-columns: none;
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  gap: 8px;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 4px;
}

.bio-amazon-block--layout-carousel .amazon-card {
  flex: 0 0 110px;
  scroll-snap-align: start;
}

.bio-amazon-block--layout-list .amazon-grid {
  grid-template-columns: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bio-amazon-block--layout-list .amazon-card {
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  padding: 8px 10px;
}

.bio-amazon-block--layout-list .amazon-thumb {
  width: 44px;
  height: 44px;
  aspect-ratio: 1;
  margin: 0;
  flex-shrink: 0;
}

.bio-amazon-block--layout-list .amazon-title {
  margin-top: 0;
  min-height: 0;
  -webkit-line-clamp: 2;
}

.amazon-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--bio-card, #fff);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  border-radius: var(--r-card, 14px);
  padding: 8px;
  text-align: left;
  text-decoration: none;
  color: inherit;
  min-width: 0;
  overflow: hidden;
}

/* immersive backdrop — hidden in the default white style */
.amazon-bg { display: none; }

.amazon-thumb {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--r-media, 10px);
  overflow: hidden;
  background: linear-gradient(135deg, #f4f6f9, #e9edf2);
}
.amazon-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* neutral fallback tile (no product image) — faint Amazon smile, not the old yellow box */
/* Standard product tile (no image / no icon) — a NEUTRAL box icon, not the
   Amazon logo, so the cards don't read as Amazon-branded. */
.amazon-thumb--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #eef1f6, #e3e8f0);
}
.amazon-thumb--empty::before {
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  content: "\f466";   /* fa-box */
  font-size: 40px;
  color: var(--primary-color, #4c7cf5);
  opacity: .9;
}
.bio-amazon-block--layout-list .amazon-thumb--empty::before { font-size: 24px; }

/* Category-icon tile — a clean, compliant alternative to scraped product images */
.amazon-thumb--icon { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #eef1f6, #e3e8f0); }
.amazon-thumb--icon i { font-size: 40px; line-height: 1; color: var(--primary-color, #4c7cf5); opacity: .92; }
.bio-amazon-block--layout-list .amazon-thumb--icon i { font-size: 24px; }

.amazon-title {
  font-size: calc(var(--bio-fs, 1) * 12px);
  font-weight: 600;
  color: var(--bio-text, var(--gray-800));
  line-height: 1.32;
  padding: 0 2px 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Amazon smile badge — bottom-left of the image */
.amz-smile {
  position: absolute;
  left: 7px;
  bottom: 7px;
  width: 30px;
  height: 22px;
  z-index: 2;
  border-radius: 7px;
  background: rgba(255, 255, 255, .94) url(../images/optimized/amazon_tinylogo.svg) center / 16px no-repeat;
  box-shadow: 0 2px 7px rgba(0, 0, 0, .16);
}

.amz-badge {
  position: absolute;
  z-index: 2;
  font-size: calc(var(--bio-fs, 1) * 9px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .05em;
}

/* "Ad" marker — top-right of the image */
.amz-badge.amz-ad {
  top: 7px;
  right: 7px;
  padding: 2px 6px;
  background: rgba(20, 26, 34, .55);
  color: #fff;
  border-radius: 6px;
}
.amz-badge.amz-ad.amz-ad-section {
  position: static;
  background: rgb(17 24 39 / 0.78);
  text-transform: uppercase;
  padding: 2px 5px;
}

/* List layout uses a horizontal row */
.bio-amazon-block--layout-list .amazon-card { flex-direction: row; align-items: center; gap: 12px; }
.bio-amazon-block--layout-list .amazon-thumb { width: 58px; height: 58px; flex: 0 0 58px; border-radius: var(--r-media, 10px); }
.bio-amazon-block--layout-list .amazon-title { flex: 1 1 auto; padding: 0; }
.bio-amazon-block--layout-list .amz-smile { left: 4px; bottom: 4px; width: 22px; height: 16px; background-size: 12px; border-radius: 5px; }
.bio-amazon-block--layout-list .amz-badge.amz-ad { top: 4px; right: 4px; }

/* Product block price line — sits under .amazon-title in grid/carousel.
   margin-top tucks it against the title (the card's flex gap is 8px). */
.product-price {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 700;
  color: var(--bio-text, var(--gray-900));
  line-height: 1;
  padding: 0 2px 2px;
  margin-top: -4px;
}
/* List layout: card is a flex row with the title at flex:1 — the price
   right-aligns naturally. */
.bio-amazon-block--layout-list .product-price {
  margin: 0 0 0 auto;
  padding: 0;
  white-space: nowrap;
  flex-shrink: 0;
}

/* =====================================================================
   IMMERSIVE card style (theme-wide, set via data-theme-card on .phone-inner).
   Every clickable surface — link rows, Amazon cards, tour events, email —
   becomes the same dark accent-glow card: rich accent base + a LINEAR accent
   highlight (aspect-robust: grid squares AND wide rows look the same) + a
   depth vignette + a floating thumb. Media (image/video) is light-touch only.
   Music card is already dark, so it is intentionally NOT in this group.
   ===================================================================== */
.phone-inner[data-theme-card="immersive"] .bio-link-row,
.phone-inner[data-theme-card="immersive"] .amazon-card,
.phone-inner[data-theme-card="immersive"] .bio-tour-event,
.phone-inner[data-theme-card="immersive"] .bio-email-block {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  color: #fff;
  /* border: 0, not transparent — the ::before/::after overlays only cover the
     padding box, so a kept-but-transparent border ring leaks the raw gradient
     as a 1px light line at the card edge. */
  border: 0;
  background: linear-gradient(155deg,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 84%, #fff) 0%,
    var(--primary-color, #4c7cf5) 48%,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 80%, #000) 100%);
  box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--primary-color, #4c7cf5) 58%, transparent);
}
.phone-inner[data-theme-card="immersive"] .bio-link-row::before,
.phone-inner[data-theme-card="immersive"] .amazon-card::before,
.phone-inner[data-theme-card="immersive"] .bio-tour-event::before,
.phone-inner[data-theme-card="immersive"] .bio-email-block::before {
  content: "";
  position: absolute;
  /* -1px overshoot: at fractional DPR the hover translateY can leave a
     1-device-px sliver of raw gradient at the edge if the overlay snaps
     differently than the background. overflow:hidden clips the excess. */
  inset: -1px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(170deg, rgba(255, 255, 255, .14) 0%, rgba(255, 255, 255, 0) 24%);
  opacity: 1;
}
.phone-inner[data-theme-card="immersive"] .bio-link-row::after,
.phone-inner[data-theme-card="immersive"] .amazon-card::after,
.phone-inner[data-theme-card="immersive"] .bio-tour-event::after,
.phone-inner[data-theme-card="immersive"] .bio-email-block::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, .20) 100%);
}
/* Content sits above the glow/vignette layers */
.phone-inner[data-theme-card="immersive"] .bio-link-thumb,
.phone-inner[data-theme-card="immersive"] .bio-link-body,
.phone-inner[data-theme-card="immersive"] .amazon-thumb,
.phone-inner[data-theme-card="immersive"] .amazon-title,
.phone-inner[data-theme-card="immersive"] .product-price,
.phone-inner[data-theme-card="immersive"] .bio-tour-main,
.phone-inner[data-theme-card="immersive"] .bio-tour-map,
.phone-inner[data-theme-card="immersive"] .bio-email-preview { position: relative; z-index: 1; }
/* Text → white / light */
.phone-inner[data-theme-card="immersive"] .bio-link-row-title,
.phone-inner[data-theme-card="immersive"] .amazon-title,
.phone-inner[data-theme-card="immersive"] .product-price,
.phone-inner[data-theme-card="immersive"] .bio-tour-venue,
.phone-inner[data-theme-card="immersive"] .bio-tour-day,
.phone-inner[data-theme-card="immersive"] .bio-email-heading,
.phone-inner[data-theme-card="immersive"] .bio-email-description,
.phone-inner[data-theme-card="immersive"] .bio-email-success,
.phone-inner[data-theme-card="immersive"] .bio-video-title { color: #fff; }
.phone-inner[data-theme-card="immersive"] .bio-tour-month,
.phone-inner[data-theme-card="immersive"] .bio-tour-city { color: rgb(255 255 255 / .72); }
/* Caption sits on the PAGE bg (image block is frame-less) — key on page bg. */
.phone-inner[data-theme-bg="dark"] .bio-image-caption,
.phone-inner[data-theme-bg="linktwin"] .bio-image-caption { color: rgb(255 255 255 / .72); }
/* Thumbnails: glassy on dark; amazon thumb floats */
.phone-inner[data-theme-card="immersive"] .bio-link-thumb { background: rgb(255 255 255 / .12); color: #fff; }
.phone-inner[data-theme-card="immersive"] .amazon-thumb { box-shadow: 0 16px 32px -12px rgba(0, 0, 0, .7); }
.phone-inner[data-theme-card="immersive"] .amazon-thumb--empty,
.phone-inner[data-theme-card="immersive"] .amazon-thumb--icon { background: rgba(255, 255, 255, .16); }
.phone-inner[data-theme-card="immersive"] .amazon-thumb--empty::before,
.phone-inner[data-theme-card="immersive"] .bio-thumb-fallback i,
.phone-inner[data-theme-card="immersive"] .amazon-thumb--icon i { color: #fff; opacity: .9; }
/* "Past" tour tag on dark */
.phone-inner[data-theme-card="immersive"] .bio-tour-past { background: rgb(255 255 255 / .14); color: rgb(255 255 255 / .7); }
/* Tour LIST rows re-cardify under immersive (no flat hairline) */
.phone-inner[data-theme-card="immersive"] .bio-tour-block--layout-list .bio-tour-event { border-bottom: 0; border-radius: var(--r-card, 12px); margin-bottom: 6px; }
/* Email inputs stay readable (glassy) */
.phone-inner[data-theme-card="immersive"] .bio-email-block input { background: rgba(255, 255, 255, .12); color: #fff; border-color: rgba(255, 255, 255, .2); }
.phone-inner[data-theme-card="immersive"] .bio-email-block input::placeholder { color: rgb(255 255 255 / .6); }
/* No extra 1px light border on near-black backgrounds: the accent-gradient
   cards are visible on their own, and the rim read as an "edge leak". */

.bio-amazon-meta-row {
  display: flex;
  align-items: center;
  justify-content: center;     /* center the chip so it never clips the rounded edge */
  margin-top: 10px;
}

.deeplink-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: calc(var(--bio-fs, 1) * 11px);
  font-weight: 600;
  color: var(--primary-color);
}

.bio-amazon-count {
  font-size: calc(var(--bio-fs, 1) * 11px);
  color: var(--gray-400);
}

.bio-video-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.bio-video-card {
  position: relative;
  border-radius: var(--r-media, 12px);
  overflow: hidden;
  display: block;
  cursor: pointer;
  transition: transform .15s, box-shadow .15s;
}
.bio-video-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px -8px rgb(0 0 0 / 0.25); }

/* Video blocks render edge-to-edge (no boxy .block frame); overflow:visible so the hover lift/shadow isn't clipped. */
.block.bio-video-block { background: transparent; border: none; overflow: visible; }

.bio-video-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  background: linear-gradient(135deg, #1e293b, #0f172a);
  overflow: hidden;
}

.bio-video-thumb::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 34px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28.57 20'%3E%3Cpath d='M27.97 3.12C27.64 1.89 26.68.93 25.45.6 23.22 0 14.29 0 14.29 0S5.35 0 3.12.6C1.89.93.93 1.89.6 3.12 0 5.35 0 10 0 10s0 4.65.6 6.88c.33 1.23 1.3 2.19 2.53 2.52C5.35 20 14.29 20 14.29 20s8.93 0 11.16-.6c1.23-.33 2.19-1.3 2.52-2.52.6-2.23.6-6.88.6-6.88s0-4.65-.6-6.88Z' fill='%23FF0000'/%3E%3Cpath d='M11.43 14.29 18.85 10l-7.42-4.29v8.57Z' fill='%23fff'/%3E%3C/svg%3E") center/contain no-repeat;
  opacity: 0.85;
  transition: opacity .15s, transform .15s;
  pointer-events: none;
}
.bio-video-card:hover .bio-video-thumb::after { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }

.bio-video-block--no-play .bio-video-thumb::after {
  display: none;
}

/* Inline-embed hero card: hide the play badge once the iframe is playing. */
.bio-video-card.is-playing .bio-video-thumb::after { display: none; }
.bio-video-card--embed { cursor: pointer; }

.bio-video-body {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 24px 12px 10px;
  background: linear-gradient(to top, rgb(0 0 0 / 0.75) 0%, rgb(0 0 0 / 0.35) 60%, transparent 100%);
}

.bio-video-title {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  color: #fff;
  line-height: 1.3;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.4);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.bio-video-rotator-tag {
  margin-top: 4px;
  font-size: calc(var(--bio-fs, 1) * 10px);
  font-weight: 600;
  color: rgb(255 255 255 / 0.8);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.bio-video-block--layout-compact .bio-video-list {
  gap: 8px;
}

.bio-video-block--layout-compact .bio-video-card {
  display: flex;
  align-items: center;
  background: #fff;
  border: 1px solid var(--gray-200);
}

.bio-video-block--layout-compact .bio-video-thumb {
  flex: 0 0 96px;
}

.bio-video-block--layout-compact .bio-video-thumb::after {
  width: 32px;
  height: 22px;
}

.bio-video-block--layout-compact .bio-video-body {
  position: static;
  background: none;
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
}

.bio-video-block--layout-compact .bio-video-title {
  color: var(--gray-800);
  text-shadow: none;
}

.bio-video-block--layout-compact .bio-video-rotator-tag {
  color: var(--primary-color);
}

.bio-video-block--layout-grid .bio-video-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

.bio-video-block--layout-grid .bio-video-title {
  font-size: calc(var(--bio-fs, 1) * 12px);
}

.bio-video-block--layout-grid .bio-video-thumb::after {
  width: 36px;
  height: 25px;
}

.bio-music-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The music cards are the cards — the wrapper drops the white .block surface
   (it bled through at the dark cards' rounded corners, same artifact as the
   image block) and stays overflow-visible so card shadows aren't clipped. */
.block.bio-music-block {
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}

.bio-music-card {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-card, 14px);
  background: linear-gradient(135deg, #1e293b, #0f172a);
  color: #fff;
  min-width: 0;
  isolation: isolate;
}

.bio-music-card::before {
  content: "";
  position: absolute;
  inset: -10%;
  background-image: var(--bio-music-cover, none);
  background-size: cover;
  background-position: center;
  filter: blur(28px) saturate(1.3) brightness(0.55);
  transform: scale(1.18);
  z-index: 0;
  pointer-events: none;
}

.bio-music-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgb(0 0 0 / 0.15), rgb(0 0 0 / 0.55));
  z-index: 0;
  pointer-events: none;
}

.bio-music-card > * {
  position: relative;
  z-index: 1;
}

/* Music follows the theme-wide card style.
   IMMERSIVE = cover-art blur (::before) washed with the accent tint (::after) —
   so it keeps album texture but reads as the theme, matching the other cards. */
.phone-inner[data-theme-card="immersive"] .bio-music-card {
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 44%, #111a2e) 0%,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 20%, #080c16) 100%);
}
.phone-inner[data-theme-card="immersive"] .bio-music-card::after {
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 72%, transparent) 0%,
    color-mix(in srgb, var(--primary-color, #4c7cf5) 40%, transparent) 56%,
    rgba(8, 12, 22, .58) 100%);
}
/* WHITE = clean white card; drop the blur + vignette, dark text, light chips. */
.phone-inner[data-theme-card="white"] .bio-music-card {
  background: var(--bio-card, #fff);
  color: var(--bio-text, #1d2430);
  box-shadow: 0 1px 3px rgba(16, 24, 40, .08);
}
.phone-inner[data-theme-card="white"] .bio-music-card::before,
.phone-inner[data-theme-card="white"] .bio-music-card::after { display: none; }
.phone-inner[data-theme-card="white"] .bio-music-title { color: var(--bio-text, #1d2430); }
.phone-inner[data-theme-card="white"] .bio-music-artist { color: #7b8694; }
.phone-inner[data-theme-card="white"] .bio-music-thumb { background: #f1f3f6; }
.phone-inner[data-theme-card="white"] .bio-music-dsp-pill { background: #f1f3f6; }
/* Drop the dark-card text-shadow on the white card (else title/artist look embossed). */
.phone-inner[data-theme-card="white"] .bio-music-title,
.phone-inner[data-theme-card="white"] .bio-music-artist { text-shadow: none; }
.bio-music-thumb {
  border-radius: var(--r-thumb, 10px);
  overflow: hidden;
  flex-shrink: 0;
  background: rgb(255 255 255 / 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgb(255 255 255 / 0.85);
  font-size: calc(var(--bio-fs, 1) * 11px);
  font-weight: 700;
  text-transform: uppercase;
}

.bio-music-body {
  flex: 1;
  min-width: 0;
}

.bio-music-title {
  font-size: calc(var(--bio-fs, 1) * 14px);
  font-weight: 700;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.25;
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
}

.bio-music-artist {
  font-size: calc(var(--bio-fs, 1) * 12px);
  color: rgb(255 255 255 / 0.75);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
}

.bio-music-dsps {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.bio-music-dsp-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  background: #fff;
  border: 0;
  box-shadow: 0 2px 6px rgb(0 0 0 / 0.18);
  overflow: hidden;
}

.bio-music-dsp-pill img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  flex-shrink: 0;
}

/* Per-DSP visual size tweaks for the round-chip context (moved here from the
   editor stylesheet — single source). Brand SVGs ship with very different
   internal padding, so each is nudged to a consistent visual weight. The
   dsp-list row variant resets the transform below. */
.bio-music-dsp-pill.bio-music-dsp-spotify     img { transform: scale(1.10); }
.bio-music-dsp-pill.bio-music-dsp-youtube     img { transform: scale(0.65); }
.bio-music-dsp-pill.bio-music-dsp-tidal       img { transform: scale(0.80); }
.bio-music-dsp-pill.bio-music-dsp-deezer      img { transform: scale(0.85); }
.bio-music-dsp-pill.bio-music-dsp-amazonStore img { transform: scale(0.85); }
.bio-music-dsp-pill.bio-music-dsp-soundcloud  img { transform: scale(0.85); }

.bio-music-dsp-label {
  display: none;
}

.bio-music-block--layout-big .bio-music-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 18px 16px 16px;
}

.bio-music-block--layout-big .bio-music-thumb {
  width: 65%;
  max-width: 200px;
  aspect-ratio: 1 / 1;
  border-radius: var(--r-media, 14px);
  box-shadow: 0 12px 28px -8px rgb(0 0 0 / 0.55);
  margin-bottom: 14px;
}

.bio-music-block--layout-big .bio-music-body {
  width: 100%;
  text-align: center;
  margin-bottom: 14px;
}

.bio-music-block--layout-big .bio-music-title {
  font-size: calc(var(--bio-fs, 1) * 16px);
  -webkit-line-clamp: 1;
}

.bio-music-block--layout-big .bio-music-dsps {
  justify-content: center;
  gap: 8px;
}

.bio-music-block--layout-big .bio-music-dsp-pill {
  width: 36px;
  height: 36px;
}

.bio-music-block--layout-compact .bio-music-card {
  display: grid;
  grid-template-columns: 64px 1fr;
  grid-template-rows: auto auto;
  gap: 4px 12px;
  padding: 12px;
  align-items: center;
}

.bio-music-block--layout-compact .bio-music-thumb {
  grid-row: 1 / span 2;
  width: 64px;
  height: 64px;
  box-shadow: 0 4px 10px -4px rgb(0 0 0 / 0.4);
}

.bio-music-block--layout-compact .bio-music-body,
.bio-music-block--layout-compact .bio-music-dsps {
  grid-column: 2;
}

.bio-music-block--layout-compact .bio-music-title {
  font-size: calc(var(--bio-fs, 1) * 15px);
  -webkit-line-clamp: 1;
}

.bio-music-block--layout-compact .bio-music-dsp-pill {
  width: 26px;
  height: 26px;
}

.bio-music-block--dsp-list .bio-music-dsps {
  grid-column: 1 / -1;
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 8px;
  width: 100%;
  margin-top: 8px;
}

.bio-music-block--dsp-list .bio-music-dsp-pill {
  justify-content: flex-start;
  width: 100%;
  height: auto;
  padding: 12px 16px;
  border-radius: var(--r-row, 14px);
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  color: var(--gray-900);
  gap: 12px;
}

.bio-music-block--dsp-list .bio-music-dsp-pill img {
  width: 24px;
  height: 24px;
  transform: none; /* neutralize the per-DSP chip scale nudges in row context */
}

.bio-music-block--dsp-list .bio-music-dsp-label {
  display: inline-block;
  flex: 1;
  text-align: left;
}

.bio-music-block--dsp-list .bio-music-dsp-pill::after {
  content: "›";
  font-size: calc(var(--bio-fs, 1) * 22px);
  font-weight: 400;
  color: var(--gray-400);
  line-height: 1;
  margin-left: auto;
}

.bio-email-block {
  background: var(--bio-card, #fff);
  border: 1px solid var(--gray-200);
  border-radius: var(--r-card, 14px);
  overflow: hidden;
}

.bio-email-preview {
  padding: 16px;
}

.bio-email-heading {
  font-weight: 600;
  font-size: calc(var(--bio-fs, 1) * 15px);
  color: var(--bio-text, var(--gray-900));
  margin-bottom: 4px;
}

.bio-email-description {
  font-size: calc(var(--bio-fs, 1) * 13px);
  color: var(--gray-600);
  margin-bottom: 10px;
  line-height: 1.4;
}

.bio-email-preview input {
  display: block;
  width: 100%;
  height: 36px;
  border: 1px solid var(--gray-200);
  border-radius: var(--r-row, 8px);
  background: #fff;
  margin-bottom: 8px;
  padding: 8px 12px;
  color: var(--gray-900);
  font-size: calc(var(--bio-fs, 1) * 13px);
}

.bio-email-preview input::placeholder {
  color: var(--gray-400);
}

.bio-email-btn {
  display: block;
  width: 100%;
  border: 0;
  color: #fff;
  text-align: center;
  padding: 9px;
  border-radius: var(--r-row, 8px);
  font-weight: 600;
  font-size: calc(var(--bio-fs, 1) * 14px);
  background: var(--bio-accent, #5b6cff);
  cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease, filter .15s ease;
}

.bio-email-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
  box-shadow: 0 6px 16px -4px color-mix(in srgb, var(--bio-accent, #5b6cff) 55%, transparent);
}

/* Loading: hide label, spin a ring in its place. Text stays in flow (transparent)
   so button height doesn't jump. */
.bio-email-btn.is-loading {
  position: relative;
  color: transparent;
  pointer-events: none;
}

.bio-email-btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid rgba(255, 255, 255, .45);
  border-top-color: #fff;
  border-radius: 50%;
  animation: bio-email-spin .6s linear infinite;
}

@keyframes bio-email-spin {
  to { transform: rotate(360deg); }
}

.bio-email-success {
  text-align: center;
  padding: 12px 0;
  font-weight: 600;
  font-size: calc(var(--bio-fs, 1) * 14px);
}

.bio-email-success-check {
  width: 36px;
  height: 36px;
  line-height: 36px;
  margin: 0 auto 8px;
  border-radius: 50%;
  background: var(--bio-accent, #5b6cff);
  color: #fff;
  font-size: 18px;
}

.bio-email-status {
  min-height: 16px;
  margin-top: 8px;
  font-size: calc(var(--bio-fs, 1) * 12px);
  color: var(--gray-500);
}

/* Don't reserve a dead strip below the button before there's a message. */
.bio-email-status:empty {
  display: none;
}

/* Compact layout: optional heading above a single inline row. The email
   field flexes to fill, the Subscribe button hugs its label. Without this
   the card-form rules above (.bio-email-preview input + .bio-email-btn are
   both display:block; width:100%) make input and button split the row
   ~50/50 and the input's margin-bottom offsets it from the button centre. */
.bio-email-compact {
  display: flex;
  gap: 8px;
  align-items: center;
}
.bio-email-compact input {
  flex: 1;
  min-width: 0;
  width: auto;
  margin-bottom: 0;
}
.bio-email-compact .bio-email-btn {
  width: auto;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 9px 16px;
}

.bio-amazon-disclosure,
.bio-disclosure,
.bio-footer {
  text-align: center;
  /* --bio-footer-color is emitted for custom backgrounds (luminance-derived in
     BioPageRenderer) so footer text stays readable on any bg; presets fall back
     to gray-500 and keep their own per-theme overrides below. */
  color: var(--bio-footer-color, var(--gray-500));
  /* Legal/footer chrome is pinned to a fixed system font + size so a theme's
     custom font family or font-size scale (--bio-font-family / --bio-fs) never
     enlarges or restyles the disclosure & footer links. */
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 11px;
  line-height: 1.4;
  margin: 16px 10px 0;
}

.bio-footer a {
  font-weight: 700;
}

.phone-inner[data-theme-bg="dark"] .bio-name,
.phone-inner[data-theme-bg="dark"] .bio-link-row-title,
.phone-inner[data-theme-bg="dark"] .bio-link-section-title,
.phone-inner[data-theme-bg="dark"] .section-header {
  /* --bio-name-color (a per-profile name override) wins over the dark default;
     it only resolves under .bio-profile, so the other grouped titles keep #f3f4f6. */
  color: var(--bio-name-color, #f3f4f6);
}

.phone-inner[data-theme-bg="dark"] .bio-tagline,
.phone-inner[data-theme-bg="dark"] .bio-text-body,
.phone-inner[data-theme-bg="dark"] .bio-footer,
.phone-inner[data-theme-bg="dark"] .bio-disclosure {
  color: var(--bio-bio-color, #9ca3af);
}

.phone-inner[data-theme-bg="dark"][data-theme-card="white"] .bio-link-row {
  background: rgb(255 255 255 / 0.08);
  border-color: rgb(255 255 255 / 0.1);
}

/* Plain style on a dark theme → light marks (tiles/brand carry their own contrast). */
.phone-inner[data-theme-bg="dark"] .bio-socials[data-social-style="plain"] .bio-social {
  color: #e5e7eb;
}

/* ===== LinkTwin preset — dark hero look: glowing navy + blue→cyan gradient ===== */
.phone-inner[data-theme-bg="linktwin"] {
  background:
    radial-gradient(at 22% 16%, rgba(0, 110, 255, .34) 0%, transparent 48%),
    radial-gradient(at 84% 62%, rgba(0, 194, 255, .24) 0%, transparent 52%),
    radial-gradient(at 58% 110%, rgba(0, 102, 255, .18) 0%, transparent 50%),
    linear-gradient(160deg, #111c34 0%, #0A0F1C 58%);
}
.phone-inner[data-theme-bg="linktwin"] .bio-tagline,
.phone-inner[data-theme-bg="linktwin"] .bio-text-body,
.phone-inner[data-theme-bg="linktwin"] .bio-footer,
.phone-inner[data-theme-bg="linktwin"] .bio-disclosure { color: var(--bio-bio-color, #9ca3af); }
.phone-inner[data-theme-bg="linktwin"] .bio-link-row-title { color: #fff; }
.phone-inner[data-theme-bg="linktwin"] .bio-socials[data-social-style="plain"] .bio-social { color: #e5e7eb; }
/* Signature: gradient name + gradient buttons (double attr beats the button-style rules) */
.phone-inner[data-theme-bg="linktwin"] .bio-name {
  /* Signature gradient name — unless a per-profile --bio-name-color is set, in
     which case it fills the text with that solid colour (text-fill-color wins). */
  background: var(--bio-name-color, linear-gradient(135deg, #4aa6ff, #00C2FF));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: var(--bio-name-color, transparent);
}
.phone-inner[data-theme-bg="linktwin"][data-theme-card="white"] .bio-link-row {
  background: linear-gradient(135deg, #0066FF, #00C2FF);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 10px 24px -12px rgba(0, 140, 255, .55);
}

.phone-inner[data-theme-btn] .section-header,
.phone-inner[data-theme-btn] .bio-link-section-title {
  color: var(--bio-accent, #4c7cf5);
}

/* Portrait (Overlay) layout: the name + bio sit on the photo. They now follow
   the theme body colour (--bio-text) — and an optional per-profile override
   (--bio-profile-text) — instead of being forced white, so they match the rest
   of the page. Placed AFTER the dark/linktwin theme blocks and at matching
   specificity so it wins the cascade; the explicit background + text-fill reset
   still undoes the linktwin gradient-name treatment, and the text-shadow keeps
   the text legible over the photo whatever colour is chosen. */
.phone-inner .bio-profile--portrait .bio-name {
  color: var(--bio-name-color, var(--bio-profile-text, var(--bio-text, var(--gray-900))));
  background: none;
  -webkit-text-fill-color: var(--bio-name-color, var(--bio-profile-text, var(--bio-text, var(--gray-900))));
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.4);
}

.phone-inner .bio-profile--portrait .bio-tagline {
  color: var(--bio-bio-color, var(--bio-profile-text, var(--bio-text, var(--gray-600))));
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.4);
}

.phone-inner .bio-profile--portrait .bio-socials[data-social-style="plain"] .bio-social {
  color: #fff;
}

/* Ambient backdrop (portrait layout, public render): the header photo blurred
   behind the desktop card — link.me's glow. Hidden on mobile (the card fills
   the viewport); never emitted in the editor canvas. */
.bio-ambient {
  display: none;
}

/* Shape = corner radius only (Rounded default / Pill / Square). Fill is owned
   by the White/Immersive card style above. The shape sets four radius vars and
   every card surface reads var(--r-*, <its own px>): rows/inputs go full pill,
   big cards & media soften (never circle), small in-row thumbs go circular.
   Rounded sets NOTHING — the per-element fallbacks ARE today's exact values,
   so legacy pages render pixel-identical. Identity elements (avatar, social
   circles, badges/tags) deliberately don't participate. */
.phone-inner[data-theme-btn="pill"] {
  --r-row: 999px;
  --r-card: 26px;
  --r-media: 22px;
  --r-thumb: 50%;
}

.phone-inner[data-theme-btn="square"] {
  --r-row: 0;
  --r-card: 0;
  --r-media: 0;
  --r-thumb: 0;
}

/* Font size (theme.fontSize → data-theme-size). Medium sets nothing — text
   declarations read calc(var(--bio-fs, 1) * <px>), so absent attr = today. */
.phone-inner[data-theme-size="small"] { --bio-fs: .9; }
.phone-inner[data-theme-size="large"] { --bio-fs: 1.15; }

@media (min-width: 760px) {
  /* Full-bleed theme background. */
  .bio-public {
    padding: 0;
    align-items: flex-start;
  }
  .bio-public .phone-inner {
    min-height: 100vh;
    padding: 0;
    border-radius: 0;
    box-shadow: none;
    /* Center the content card vertically AND horizontally. min-height (not
       height) keeps the page scrollable: when the card is taller than the
       viewport the container grows to fit and the centering becomes a no-op
       instead of clipping the top of the card. */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  /* The SURROUND ("rest of the background") reads as frosted glass over the
     theme background. Scoped to .bio-public so the editor phone preview (no
     .bio-public wrapper) keeps the plain mobile look. */
  .bio-public .phone-inner::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: rgb(255 255 255 / 0.5);
    -webkit-backdrop-filter: blur(40px) saturate(120%);
            backdrop-filter: blur(40px) saturate(120%);
  }
  .bio-public .phone-inner[data-theme-bg="dark"]::after {
    background: rgb(0 0 0 / 0.4);
  }

  /* Portrait glow: blurred header photo fills the surround. Painted above the
     element's theme background but below the ::after veil (pseudo paints after
     children at the same z-index), which we swap from frosted-white to a soft
     dark scrim so the glow shows through. */
  .bio-public .bio-ambient {
    display: block;
    position: fixed;
    inset: -80px;            /* overscan so the blur has no bright edges */
    z-index: 0;
    background-size: cover;
    background-position: center;
    filter: blur(64px) saturate(1.2) brightness(0.85);
    pointer-events: none;
  }
  .bio-public .phone-inner:has(.bio-ambient)::after {
    background: rgb(0 0 0 / 0.3);
  }
  /* Header videos already animate a large media layer. In some Chromium builds
     backdrop-filter makes the whole desktop page jank, so video-header pages use
     the same dark veil without sampling/blurring the pixels behind it. */
  .bio-public .phone-inner:has(#populatedContent.bio-content--portrait-video)::after {
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }

  /* The content CARD shows the chosen theme background CRISP (same as the
     phone), lifted off the frosted surround with rounded corners + shadow.
     Corners are cut with clip-path (NOT overflow:hidden): overflow clips the
     background and the full-bleed photo through different antialiasing
     paths, which leaves a bright 1px halo of card background along the
     rounded corners when a dark image sits on a light theme. clip-path cuts
     everything through one identical path. The shadow must be a drop-shadow
     filter — clip-path would clip an outer box-shadow away. */
  .bio-public #populatedContent {
    position: relative;
    z-index: 1;
    width: min(100%, 480px);
    margin: 48px auto;
    padding: 32px 26px;
    border-radius: 30px;
    clip-path: inset(0 round 30px);
    filter: drop-shadow(0 20px 35px rgb(0 0 0 / 0.28));
  }
  /* Desktop video headers are sensitive to filtered ancestors in some Chromium
     forks. Keep static pages on the crisper clip-path/drop-shadow path, but put
     animated portrait videos on cheaper overflow clipping + box-shadow. */
  .bio-public #populatedContent.bio-content--portrait-video {
    overflow: hidden;
    clip-path: none;
    filter: none;
    box-shadow: 0 20px 35px rgb(0 0 0 / 0.28);
  }
  /* When the video unloads to its poster (stutter/reduced-motion), the page is
     static again — JS adds .bio-portrait-video-fallback to <html>, so restore
     the crisper clip-path/drop-shadow path the static-photo headers use. */
  .bio-portrait-video-fallback .bio-public #populatedContent.bio-content--portrait-video {
    overflow: visible;
    clip-path: inset(0 round 30px);
    filter: drop-shadow(0 20px 35px rgb(0 0 0 / 0.28));
    box-shadow: none;
  }
  /* Cover must bleed past the desktop card's 32px/26px padding (not the mobile 28/14). */
  .bio-public #populatedContent .bio-cover {
    width: calc(100% + 52px);
    margin: -32px -26px 0;
  }
  /* Portrait bleeds 1px PAST the padding on top/sides so the card background
     can't peek through as a hairline at fractional zoom/viewport widths. */
  .bio-public #populatedContent .bio-profile--portrait {
    width: calc(100% + 54px);
    margin: -33px -27px 0;
  }
  /* Combined: the bleed lands on the in-flow header band, not the profile box. */
  .bio-public #populatedContent .bio-profile--combined .bio-portrait {
    width: calc(100% + 54px);
    margin: -33px -27px 0;
  }
  .bio-public .phone-inner[data-theme-bg="gradient-blue"] #populatedContent { background: linear-gradient(180deg, #eef4ff 0%, #fff 320px); }
  .bio-public .phone-inner[data-theme-bg="white"]         #populatedContent { background: #fff; }
  .bio-public .phone-inner[data-theme-bg="dark"]          #populatedContent { background: linear-gradient(135deg, #1f2937, #111827); }
  .bio-public .phone-inner[data-theme-bg="peach"]         #populatedContent { background: linear-gradient(135deg, #fce7f3, #fbcfe8); }
  .bio-public .phone-inner[data-theme-bg="indigo"]        #populatedContent { background: linear-gradient(160deg, #e9edff, #f5f7ff); }
  .bio-public .phone-inner[data-theme-bg="mono"]          #populatedContent { background: linear-gradient(160deg, #eef0f3, #fafafa); }
  .bio-public .phone-inner[data-theme-bg="sunset"]        #populatedContent { background: linear-gradient(160deg, #fde8d8, #fbf3ea); }
  .bio-public .phone-inner[data-theme-bg="mint"]          #populatedContent { background: linear-gradient(160deg, #e2f5ec, #f1faf6); }
  .bio-public .phone-inner[data-theme-bg="rose"]          #populatedContent { background: linear-gradient(160deg, #fde2ec, #fbecf3); }
  .bio-public .phone-inner[data-theme-bg="linktwin"]      #populatedContent { background: radial-gradient(at 22% 16%, rgba(0,110,255,.34) 0%, transparent 48%), radial-gradient(at 84% 62%, rgba(0,194,255,.24) 0%, transparent 52%), radial-gradient(at 58% 110%, rgba(0,102,255,.18) 0%, transparent 50%), linear-gradient(160deg, #111c34 0%, #0A0F1C 58%); }
  .bio-public .phone-inner[data-theme-bg="custom"]        #populatedContent { background: var(--bio-bg, #eef4ff); }

  /* "View on mobile" badge — desktop only. Deliberately quiet: a small bare
     QR tucked into the bottom-right corner, dimmed at rest so the eye stays on
     the links in the centre. It brightens to full on hover for anyone who
     actually wants to scan it. No card / shadow — just the QR's own white
     quiet zone, which is all a scanner needs. */
  .bio-qr-badge {
    display: flex;
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 40;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    opacity: .7;
    transition: opacity .25s ease;
  }
  .bio-qr-badge:hover {
    opacity: 1;
  }
  .bio-qr-badge__code {
    display: block;
    width: 92px;
    height: 92px;
    border-radius: 6px;
    box-shadow: 0 1px 4px rgb(0 0 0 / 0.12);
  }
  .bio-qr-badge__label {
    margin: 0;
    color: #fff;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: .02em;
    text-align: center;
    text-shadow: 0 1px 3px rgb(0 0 0 / 0.55), 0 0 1px rgb(0 0 0 / 0.4);
  }
}

/* ============================================================
   Rendered-element hover / interaction states.
   Ported from the25-bio-editor.css so the public page renders
   the bio with the same hover lift / press affordances the
   editor preview shows. ADDITIVE — new selectors only; none of
   the rules above are modified. (Video hovers were already in
   this file; these complete amazon cards + music.)
   ============================================================ */

/* Amazon product card — hover lift + accent border */
.amazon-card {
  transition: transform .15s, box-shadow .15s;
}
.amazon-card:hover {
  box-shadow: 0 4px 12px -4px rgba(0, 0, 0, .15);
  transform: translateY(-1px);
}

/* Music card: NO whole-card hover — the card itself isn't clickable (the
   title, DSP pills and CTA inside are; they keep their own hover states).
   Lift = "tap me" everywhere else (link rows, amazon, video, tour, image). */

/* Music DSP icon chip (big/compact round chips) — hover scale */
.bio-music-dsp-pill {
  transition: transform .12s;
}
.bio-music-dsp-pill:hover {
  transform: scale(1.08);
}

/* Music DSP list rows ("Listen on X") — hover lift + shadow */
.bio-music-block--dsp-list .bio-music-dsp-pill:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 14px -4px rgb(0 0 0 / 0.25);
}

/* ---------- Tour dates block ---------- */
/* Frame-less wrapper like amazon/video/music: the tour EVENT rows are the
   cards. overflow:visible so the row hover glow isn't clipped at the wrapper
   edge (the clip rendered as a hard light line under the hovered row). */
.block.bio-tour-block { padding: 0; min-width: 0; background: transparent; border: none; box-shadow: none; overflow: visible; }
.bio-tour-title {
  font-size: calc(var(--bio-fs, 1) * 13px);
  font-weight: 600;
  color: var(--gray-700);
  padding: 4px 8px 8px;
}
.bio-tour-list { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bio-tour-event {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--gray-100);
  border-radius: var(--r-card, 8px);
  background: var(--bio-card, #fff);
  color: var(--bio-text, var(--gray-900));
  min-width: 0;
}
.bio-tour-main { display: flex; align-items: center; gap: 12px; flex: 1 1 auto; min-width: 0; text-decoration: none; color: inherit; }
.bio-tour-date { flex: 0 0 auto; width: 42px; text-align: center; line-height: 1.1; }
.bio-tour-day { display: block; font-size: calc(var(--bio-fs, 1) * 18px); font-weight: 700; color: var(--bio-text, var(--gray-900)); }
.bio-tour-month { display: block; font-size: calc(var(--bio-fs, 1) * 10px); font-weight: 600; color: var(--bio-text, var(--gray-500)); text-transform: uppercase; letter-spacing: .03em; }
.bio-tour-body { flex: 1 1 auto; min-width: 0; }
.bio-tour-venue { font-weight: 600; font-size: calc(var(--bio-fs, 1) * 14px); color: var(--bio-text, var(--gray-900)); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.bio-tour-city { font-size: calc(var(--bio-fs, 1) * 12px); color: var(--bio-text, var(--gray-600)); margin-top: 2px; }
.bio-tour-soldout,
.bio-tour-past {
  font-size: calc(var(--bio-fs, 1) * 9px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
}
.bio-tour-soldout { background: #fee2e2; color: #b91c1c; }
.bio-tour-past { background: var(--gray-100); color: var(--gray-500); }

/* Compact list layout — flat, tighter rows divided by a hairline */
.bio-tour-block--layout-list .bio-tour-list { gap: 0; }
.bio-tour-block--layout-list .bio-tour-event {
  border: 0;
  border-radius: 0;
  border-bottom: 1px solid var(--gray-100);
  background: transparent;
  box-shadow: none;
  padding: 8px 2px;
}
.bio-tour-block--layout-list .bio-tour-event:last-child { border-bottom: 0; }
.bio-tour-block--layout-list .bio-tour-main { gap: 10px; }
.bio-tour-block--layout-list .bio-tour-date { width: 32px; }
.bio-tour-block--layout-list .bio-tour-day { font-size: calc(var(--bio-fs, 1) * 15px); }
.bio-tour-block--layout-list .bio-tour-month { font-size: calc(var(--bio-fs, 1) * 9px); }
.bio-tour-block--layout-list .bio-tour-venue { font-size: calc(var(--bio-fs, 1) * 13px); }
.bio-tour-block--layout-list .bio-tour-city { font-size: calc(var(--bio-fs, 1) * 11px); }

/* Ticket / map icons + interactive hover (card opens tickets, 📍 opens the map) */
.bio-tour-ico { font-size: 15px; line-height: 1; flex: 0 0 auto; transition: transform .12s ease; }
/* Icons react only to a DIRECT hover, not to hovering the row. */
.bio-tour-ico:hover { transform: scale(1.18); }
.bio-tour-map { flex: 0 0 auto; display: inline-flex; align-items: center; text-decoration: none; padding: 4px; border-radius: 8px; transition: transform .12s ease; }
a.bio-tour-map:hover { transform: scale(1.18); }
.bio-tour-event--link { transition: box-shadow .12s ease, transform .12s ease; }
.bio-tour-event--link:hover { box-shadow: 0 6px 18px -6px rgba(0,0,0,.22); transform: translateY(-1px); }
/* Immersive cards carry an accent glow at rest — hovering must deepen that
   glow, not swap it for a flat dark shadow (the glow visibly "popped off"). */
.phone-inner[data-theme-card="immersive"] .bio-tour-event--link:hover,
.phone-inner[data-theme-card="immersive"] .amazon-card:hover {
  box-shadow: 0 14px 30px -12px color-mix(in srgb, var(--primary-color, #4c7cf5) 72%, transparent);
}

/* ============================================================================
   PAGE CHROME — the "Join @alias on LinkTwin" pill, the Join/Share modals, and
   the Report·Privacy legal footer. (The Share BUTTON is now a deletable body
   block — see .bio-share-block / BioPageRenderer::share().)
   Rendered by BioPageRenderer::joinCta()/legalFooter()/chromeModals(); driven by
   the25-bio-chrome.js. The modals are neutral white cards (legible on any theme).
   ============================================================================ */

/* #populatedContent is the offset parent for absolutely-positioned children
   (ambient backdrop, cover bleed, QR badge). */
#populatedContent { position: relative; }

/* --- Modal shell (Join + Share share this)
   Scoped to [data-bio-modal] so this never collides with the bio EDITOR's
   own `.bio-modal` card class — the editor page loads this public stylesheet
   for its live preview, and an unscoped `.bio-modal` here leaked
   align-items/justify-content/position onto the editor's modal, breaking its
   docked-panel layout. Public modals always carry data-bio-modal. */
.bio-modal[data-bio-modal] {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.bio-modal[data-bio-modal].is-open { display: flex; }
.bio-modal__overlay {
  position: absolute;
  inset: 0;
  background: rgba(8, 10, 16, .55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: bioFade .18s ease;
}
.bio-modal__card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 400px;
  max-height: calc(100vh - 32px);
  overflow: auto;
  background: #fff;
  color: #1f2430;
  border-radius: 22px;
  padding: 26px 22px;
  box-shadow: 0 24px 60px -12px rgba(0, 0, 0, .4);
  font-size: 15px;
  line-height: 1.45;
  font-size-adjust: none;
  animation: bioPop .2s cubic-bezier(.2, .8, .25, 1);
}
.bio-modal__close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #6b7280;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.bio-modal__close:hover { background: #f3f4f6; color: #111; }
@keyframes bioFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes bioPop { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes bioSheet { from { transform: translateY(100%); } to { transform: none; } }

/* --- Join (acquisition) modal */
.bio-modal__card--join { text-align: left; }
.bio-modal__logo { height: 30px; width: auto; max-width: 160px; display: block; margin-bottom: 18px; }
.bio-modal__logo-text { font-size: 20px; font-weight: 800; margin-bottom: 18px; color: #11141b; }
.bio-modal__title { font-size: 24px; line-height: 1.2; font-weight: 800; margin: 0 0 8px; color: #11141b; }
.bio-modal__sub { margin: 0 0 18px; color: #5b6472; font-size: 15px; }
/* App logos — the destinations LinkTwin opens straight inside the native app. */
.bio-modal__apps {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 22px;
}
.bio-modal__app {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 11px;
  background: #f3f4f6;
  color: #11141b;
  font-size: 19px;
}
.bio-modal__app--youtube   { color: #ff0000; }
.bio-modal__app--spotify   { color: #1db954; }
.bio-modal__app--amazon    { color: #ff9900; }
.bio-modal__app--tiktok    { color: #010101; }
.bio-modal__app--instagram { color: #e1306c; }
.bio-modal__app--apple     { color: #fc3c44; }
.bio-modal__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 13px 18px;
  border-radius: 999px;
  background: #11141b;
  color: #fff;
  font-weight: 700;
  text-decoration: none;
  transition: transform .12s ease, opacity .12s ease;
}
.bio-modal__cta:hover { transform: translateY(-1px); opacity: .92; }
.bio-modal__link {
  display: inline-block;
  margin-top: 14px;
  color: #11141b;
  font-weight: 600;
  font-size: 14px;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- Share modal */
.bio-modal__card--share { text-align: center; }
.bio-share__head { font-size: 17px; font-weight: 800; color: #11141b; margin-bottom: 16px; }
.bio-share__preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: #0f1320;
  color: #fff;
  border-radius: 16px;
  padding: 20px 16px;
  margin-bottom: 18px;
}
.bio-share__avatar { width: 54px; height: 54px; border-radius: 50%; background-size: cover; background-position: center; margin-bottom: 8px; }
.bio-share__name { font-weight: 800; font-size: 16px; }
.bio-share__handle { font-size: 13px; opacity: .7; }
.bio-share__copy {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  border: 1px solid #e3e6ec;
  border-radius: 12px;
  background: #f7f8fa;
  padding: 10px 12px;
  margin-bottom: 16px;
  cursor: pointer;
  text-align: left;
}
.bio-share__copy-url { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: #5b6472; }
.bio-share__copy-btn { flex: 0 0 auto; font-weight: 700; font-size: 13px; color: #11141b; white-space: nowrap; }
.bio-share__copy.is-copied { border-color: #16a34a; }
.bio-share__copy.is-copied .bio-share__copy-btn { color: #16a34a; }

.bio-share__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px 8px; }
.bio-share__item { display: flex; flex-direction: column; align-items: center; gap: 6px; text-decoration: none; color: #3a4150; }
.bio-share__icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #0f1320;
  color: #fff;
  font-size: 20px;
  transition: transform .12s ease;
}
.bio-share__item:hover .bio-share__icon { transform: translateY(-2px); }
.bio-share__label { font-size: 11px; font-weight: 600; }
.bio-share__item--x .bio-share__icon { background: #000; }
.bio-share__item--facebook .bio-share__icon { background: #1877f2; }
.bio-share__item--whatsapp .bio-share__icon { background: #25d366; }
.bio-share__item--linkedin .bio-share__icon { background: #0a66c2; }
.bio-share__item--telegram .bio-share__icon { background: #26a5e4; }
.bio-share__item--reddit .bio-share__icon { background: #ff4500; }
.bio-share__item--messenger .bio-share__icon { background: #0084ff; }
.bio-share__item--email .bio-share__icon { background: #6b7280; }
.bio-share__native {
  margin-top: 18px;
  width: 100%;
  padding: 12px;
  border: 1px solid #e3e6ec;
  border-radius: 12px;
  background: #fff;
  color: #11141b;
  font-weight: 700;
  cursor: pointer;
}
.bio-share__native:hover { background: #f7f8fa; }

/* --- Bottom Share button (sits just above the marketing Join pill). A ghost
   pill that inherits the theme text colour so it reads on any background. */
/* Share BLOCK — a deletable body block (BioPageRenderer::share). The wrapper
   centers the pill button within the full-width block; inter-block spacing comes
   from the canvas/page --bio-gap, so the button itself carries no margin. */
.bio-share-block { display: flex; justify-content: center; }
.bio-share-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 22px;
  border-radius: 999px;
  background: transparent;
  color: #1f2937;
  border: 1.5px solid color-mix(in srgb, currentColor 26%, transparent);
  font-family: inherit;
  font-weight: 700;
  font-size: calc(var(--bio-fs, 1) * 13px);
  cursor: pointer;
  transition: transform .12s ease, background .12s ease, border-color .12s ease;
}
.bio-share-cta:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, currentColor 44%, transparent);
  background: color-mix(in srgb, currentColor 7%, transparent);
}
.bio-share-cta svg { flex: 0 0 auto; }

/* --- Bottom "Join @alias on LinkTwin" pill */
.bio-join-cta {
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 34px auto 0;
  padding: 11px 20px;
  border-radius: 999px;
  background: rgba(17, 20, 27, .9);
  color: #fff;
  /* Pinned to a fixed system font + size: the "Join @alias on LinkTwin" pill is
     LinkTwin chrome, so a theme's custom font / font-scale must not restyle it. */
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: 13px;
  text-decoration: none;
  border: 0;
  cursor: pointer;
  box-shadow: 0 6px 18px -8px rgba(0, 0, 0, .5);
  transition: transform .12s ease, opacity .12s ease;
}
.bio-join-cta:hover { transform: translateY(-1px); opacity: .95; }

/* --- Editor-only: trash/restore control wrapping the branding pill ----------
   ONLY rendered in the bio EDITOR (BioPageRenderer::brandingEditorWrap) — the
   public page emits the bare .bio-join-cta. Lets a paid owner hide the pill
   from their live page (cfg.hideBranding); free owners get an upgrade modal. */
.bio-branding-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 34px;
}
.bio-branding-wrap .bio-join-cta { margin: 0; }
.bio-branding-wrap.is-branding-hidden .bio-join-cta { opacity: .4; }
.bio-branding-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(17, 20, 27, .06);
  color: var(--gray-500, #6b7280);
  cursor: pointer;
  line-height: 0;
  transition: background .15s ease, color .15s ease;
}
.bio-branding-remove:hover { background: rgba(220, 38, 38, .12); color: #dc2626; }
.bio-branding-wrap.is-branding-hidden .bio-branding-remove:hover { background: rgba(17, 20, 27, .1); color: var(--gray-700, #374151); }
.bio-branding-remove .material-symbols-outlined { font-size: 18px; }
.bio-branding-hidden-note {
  flex-basis: 100%;
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  color: var(--gray-400, #9ca3af);
}

/* --- Report · Privacy legal footer (always shown) */
.bio-legal {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 14px auto 4px;
  /* Pinned to a fixed system font + size — Report · Privacy are platform links,
     not user content, so the theme's font / font-scale must not affect them. */
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 11px;
  color: var(--bio-footer-color, var(--gray-500));
}
.bio-legal a { color: inherit; text-decoration: none; }
.bio-legal a:hover { text-decoration: underline; }

/* Dark / LinkTwin themes: flip the pill to a light chip and lighten the legal text */
.phone-inner[data-theme-bg="dark"] .bio-join-cta,
.phone-inner[data-theme-bg="linktwin"] .bio-join-cta {
  background: rgba(255, 255, 255, .92);
  color: #11141b;
}
.phone-inner[data-theme-bg="dark"] .bio-legal,
.phone-inner[data-theme-bg="linktwin"] .bio-legal { color: #9ca3af; }
.phone-inner[data-theme-bg="dark"] .bio-share-cta,
.phone-inner[data-theme-bg="linktwin"] .bio-share-cta { color: #e5e7eb; }

body.bio-modal-open { overflow: hidden; }

/* Mobile: modals slide up as bottom sheets */
@media (max-width: 560px) {
  .bio-modal[data-bio-modal] { align-items: flex-end; padding: 0; }
  .bio-modal__card {
    max-width: none;
    max-height: 90vh;
    border-radius: 22px 22px 0 0;
    padding-bottom: max(26px, env(safe-area-inset-bottom));
    animation: bioSheet .22s cubic-bezier(.2, .8, .25, 1);
  }
}

/* ============================================================
   Phone, Google Maps & Contact form blocks
   ============================================================ */

/* ---------- Phone number block ---------- */
.bio-phone-block { width: 100%; }
.bio-phone-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  padding: 13px 18px;
  border-radius: var(--r-row, 12px);
  background: var(--bio-card, #fff);
  color: var(--bio-text, #1f2937);
  border: 1.5px solid color-mix(in srgb, currentColor 22%, transparent);
  font-family: inherit;
  font-weight: 600;
  font-size: calc(var(--bio-fs, 1) * 14px);
  text-decoration: none;
  cursor: pointer;
  transition: transform .12s ease, background .12s ease, border-color .12s ease;
}
.bio-phone-cta:hover { transform: translateY(-1px); border-color: color-mix(in srgb, currentColor 40%, transparent); }
.bio-phone-cta svg { flex: 0 0 auto; }
.bio-phone-cta__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Google Maps block ---------- */
.bio-map-block { width: 100%; }
.bio-map-frame {
  display: block;
  width: 100%;
  border-radius: var(--r-row, 12px);
  overflow: hidden;
  background: var(--gray-100, #f3f4f6);
}
.bio-map-demo {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 180px;
  border-radius: var(--r-row, 12px);
  background: var(--gray-100, #f3f4f6);
  border: 1px dashed var(--gray-300, #d1d5db);
  color: var(--gray-500, #6b7280);
}
.bio-map-demo-pin { font-size: 34px; color: var(--bio-accent, #ef4444); }
.bio-map-demo-hint { font-size: calc(var(--bio-fs, 1) * 12px); }

/* ---------- Contact form block ---------- */
.bio-contact-block { width: 100%; }
.bio-contact-card {
  background: var(--bio-card, #fff);
  border: 1px solid color-mix(in srgb, var(--bio-text, #1f2937) 12%, transparent);
  border-radius: var(--r-row, 12px);
  padding: 16px;
}
.bio-contact-title {
  font-weight: 700;
  font-size: calc(var(--bio-fs, 1) * 15px);
  color: var(--bio-text, #1f2937);
  margin-bottom: 12px;
}
.bio-contact-field { margin-bottom: 10px; }
.bio-contact-label {
  display: block;
  font-size: calc(var(--bio-fs, 1) * 12px);
  font-weight: 600;
  color: color-mix(in srgb, var(--bio-text, #1f2937) 75%, transparent);
  margin-bottom: 4px;
}
.bio-contact-req { color: var(--bio-accent, #ef4444); }
.bio-contact-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: var(--r-row, 10px);
  border: 1px solid color-mix(in srgb, var(--bio-text, #1f2937) 18%, transparent);
  background: color-mix(in srgb, var(--bio-card, #fff) 92%, #fff);
  color: var(--bio-text, #1f2937);
  font-family: inherit;
  font-size: calc(var(--bio-fs, 1) * 13px);
  outline: none;
  transition: border-color .12s ease;
}
.bio-contact-input:focus { border-color: var(--bio-accent, #6366f1); }
textarea.bio-contact-input { resize: vertical; min-height: 84px; }
.bio-contact-submit {
  width: 100%;
  margin-top: 6px;
  padding: 11px 18px;
  border: none;
  border-radius: var(--r-row, 10px);
  background: var(--bio-accent, #6366f1);
  color: #fff;
  font-family: inherit;
  font-weight: 700;
  font-size: calc(var(--bio-fs, 1) * 14px);
  cursor: pointer;
  transition: filter .12s ease;
}
.bio-contact-submit:hover { filter: brightness(1.05); }
.bio-contact-submit.is-loading { opacity: .6; cursor: progress; }
.bio-contact-status {
  margin-top: 8px;
  font-size: calc(var(--bio-fs, 1) * 12px);
  color: var(--bio-accent, #ef4444);
  text-align: center;
}
.bio-contact-success {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px;
  text-align: center;
  color: var(--bio-text, #1f2937);
  font-size: calc(var(--bio-fs, 1) * 14px);
}
.bio-contact-success-check {
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: #10b981; color: #fff;
  font-size: 20px; font-weight: 700;
}

/* Contact: button + modal layout */
.bio-contact-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  padding: 13px 18px;
  border-radius: var(--r-row, 12px);
  background: var(--bio-card, #fff);
  color: var(--bio-text, #1f2937);
  border: 1.5px solid color-mix(in srgb, currentColor 22%, transparent);
  font-family: inherit;
  font-weight: 600;
  font-size: calc(var(--bio-fs, 1) * 14px);
  cursor: pointer;
  transition: transform .12s ease, border-color .12s ease;
}
.bio-contact-trigger:hover { transform: translateY(-1px); border-color: color-mix(in srgb, currentColor 40%, transparent); }
.bio-contact-editor-hint {
  margin-top: 6px;
  font-size: calc(var(--bio-fs, 1) * 11px);
  color: var(--gray-500, #6b7280);
  text-align: center;
}
.bio-contact-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.bio-contact-overlay[hidden] { display: none; }
.bio-contact-overlay__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,.5);
  backdrop-filter: blur(2px);
}
.bio-contact-overlay__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 420px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--bio-card, #fff);
  border-radius: 16px;
  padding: 22px 20px 20px;
  box-shadow: 0 20px 60px rgba(0,0,0,.35);
  animation: bioContactPop .2s cubic-bezier(.2,.8,.25,1);
}
@keyframes bioContactPop { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
.bio-contact-overlay__close {
  position: absolute;
  top: 10px; right: 12px;
  border: none;
  background: none;
  font-size: 26px;
  line-height: 1;
  color: var(--gray-400, #9ca3af);
  cursor: pointer;
}
.bio-contact-overlay__close:hover { color: var(--bio-text, #1f2937); }
.bio-contact-overlay__panel .bio-contact-card { border: none; padding: 0; background: transparent; }

/* Editor preview: keep the inline contact card inert (clicks open the modal) */
.bio-contact-block .bio-contact-card[style*="pointer-events"] { opacity: .96; }

/* Honeypot: hidden from humans (and assistive tech) but present for bots.
   Off-screen rather than display:none so naive bots still "see" + fill it. */
.bio-hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
/* Turnstile interaction-only widget: no footprint until a challenge appears. */
.bio-contact-captcha:empty { display: none; }
.bio-contact-captcha { margin-top: 6px; }
