.dashboard-page .dash-site {
  width: min(1440px, 96%);
  padding-bottom: 60px;
}

.nav-btn-ghost {
  background: transparent;
  border: 1px solid var(--panel-border);
  color: var(--ink);
  box-shadow: none;
}

.dash-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 20px;
}

.dash-title {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  letter-spacing: -0.02em;
  margin-top: 4px;
}

.dash-link-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 10px 16px;
  border-radius: 999px;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  box-shadow: var(--shadow);
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--ink);
}

.dash-link-domain {
  position: relative;
}

.dash-link-domain-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  padding: 4px 6px;
  border-radius: 8px;
  cursor: pointer;
}

.dash-link-domain-btn:hover {
  background: rgba(14, 165, 233, 0.1);
}

.dash-link-domain-btn:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
}

.dash-link-domain-btn i {
  font-size: 0.62em;
  color: var(--muted);
  transition: transform 0.18s ease;
}

.dash-link-domain.is-open .dash-link-domain-btn i {
  transform: rotate(180deg);
}

.dash-link-domain-list {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  z-index: 20;
  margin: 0;
  padding: 6px;
  min-width: 100%;
  width: max-content;
  list-style: none;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  box-shadow: var(--shadow);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px) scale(0.97);
  transform-origin: top left;
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s;
}

.dash-link-domain.is-open .dash-link-domain-list {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

.dash-link-domain-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 10px;
  border-radius: 10px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}

.dash-link-domain-option:hover,
.dash-link-domain-option:focus-visible {
  background: rgba(14, 165, 233, 0.12);
  color: var(--ink);
  outline: none;
}

.dash-link-domain-option.is-selected {
  color: var(--brand-blue);
}

.dash-link-domain-option.is-selected::after {
  content: '\2713';
  font-weight: 800;
}

.billing-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 18px;
  padding: 18px 20px;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(14, 165, 233, 0.3);
  background: linear-gradient(115deg, rgba(255, 95, 109, 0.14), rgba(14, 165, 233, 0.14));
  box-shadow: var(--shadow);
}

.billing-card.is-renewal-due {
  border-color: var(--warning-border);
  background: var(--warning-bg);
}

.billing-card-copy {
  flex: 1 1 380px;
}

.billing-card-copy .auth-kicker {
  margin-bottom: 4px;
}

.billing-card-copy h2 {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.25rem;
  letter-spacing: -0.02em;
  margin-bottom: 5px;
}

.billing-card-copy p:last-child {
  color: var(--ink-soft);
  font-size: 0.84rem;
  line-height: 1.5;
  max-width: 68ch;
}

.billing-card-actions {
  display: flex;
  align-items: stretch;
  gap: 8px;
  flex: 0 0 auto;
}

.billing-plan-btn {
  min-width: 104px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 9px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
  color: #fff;
  cursor: pointer;
  font-family: "Sora", sans-serif;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.billing-plan-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(15, 23, 42, 0.18);
}

.billing-plan-btn span {
  font-size: 0.72rem;
  opacity: 0.82;
}

.billing-plan-btn strong {
  font-size: 0.84rem;
}

.billing-plan-monthly {
  background: linear-gradient(135deg, #0ea5e9, #2563eb);
}

.billing-plan-lifetime {
  background: linear-gradient(135deg, #f97316, #e11d48);
}

.billing-plan-concierge {
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
}

.billing-plan-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--success-text);
  font-size: 0.84rem;
  font-weight: 800;
  padding: 10px 13px;
  border: 1px solid var(--success-border);
  border-radius: 999px;
}

.billing-plan-badge.billing-plan-badge-warning {
  color: var(--warning-text);
  border-color: var(--warning-border);
}

.billing-manage-btn {
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--field-bg);
  color: var(--ink);
  font-family: "Sora", sans-serif;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 9px 13px;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.billing-manage-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(15, 23, 42, 0.14);
}

/* Overrides .billing-plan-btn/.billing-plan-concierge's bold two-line gradient "pricing card"
   look (right, for the actual pricing-picker contexts - the "buy Pro" cards and the Concierge
   upsell modal) down to the same compact single-line pill as .billing-manage-btn, just with a
   purple accent - as a lone action floating in an otherwise plain Subscription details card, the
   busier gradient style stood out as oversized and out of place. Keeps the .billing-plan-btn/
   data-billing-plan wiring (assets/js/dashboard.js's checkout click handler selects by that
   class) while visually matching its neutral sibling instead. */
.billing-concierge-inline-btn {
  min-width: 0;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  border: 1px solid rgba(124, 58, 237, 0.35);
  border-radius: 999px;
  background: var(--field-bg);
  padding: 9px 13px;
  color: #7c3aed;
  font-size: 0.8rem;
  font-weight: 700;
}

.billing-concierge-inline-btn:hover {
  border-color: #7c3aed;
}

/* Same reasoning/shape as .billing-concierge-inline-btn above, applied to the Subscription
   details card's Monthly/Lifetime buttons too - they were still using the bold two-line gradient
   card look, which was the actual inconsistency once Concierge got its own compact pill: three
   buttons that can appear in this same plain card, only one of them shaped differently. */
.billing-monthly-inline-btn {
  min-width: 0;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  border: 1px solid rgba(14, 165, 233, 0.4);
  border-radius: 999px;
  background: var(--field-bg);
  padding: 9px 13px;
  color: #0ea5e9;
  font-size: 0.8rem;
  font-weight: 700;
}

.billing-monthly-inline-btn:hover {
  border-color: #0ea5e9;
}

.billing-lifetime-inline-btn {
  min-width: 0;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  border: 1px solid rgba(249, 115, 22, 0.4);
  border-radius: 999px;
  background: var(--field-bg);
  padding: 9px 13px;
  color: #f97316;
  font-size: 0.8rem;
  font-weight: 700;
}

.billing-lifetime-inline-btn:hover {
  border-color: #f97316;
}

.dash-copy-btn {
  border: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  background: linear-gradient(135deg, var(--brand-coral), var(--brand-gold));
  color: #111827;
  transition: transform 0.18s ease;
}

.dash-copy-btn:hover {
  transform: scale(1.06);
}

.dash-grid {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr) 320px;
  gap: 18px;
  align-items: start;
}

.dash-col[hidden] {
  display: none;
}

.dash-col {
  display: grid;
  gap: 16px;
  /* .dash-col is itself a nested grid (display: grid, above) with the default overflow: visible,
     so its automatic min-width resolves to its own content's min-content size - the shortlinks
     table's, in .dash-grid-security's case - completely bypassing the outer grid's
     minmax(0, 1fr) track and blowing the whole page out horizontally instead of letting
     .admin-table-wrap's overflow-x: auto handle it internally like it's supposed to. */
  min-width: 0;
}

.dash-grid-builder {
  grid-template-columns: 76px var(--builder-links-col, 300px) minmax(0, 1fr) var(--builder-design-col, 320px);
}

/* Only one of Links/Design is ever visible at a time - whichever is [hidden] still reserved its
   full fixed-width column in the grid, so the Live Preview was permanently squeezed by an empty
   320px (or 300px) gap it could otherwise use. Collapsing the hidden side's track to 0 here lets
   the preview's 1fr track claim that space instead. */
.dash-grid-builder:has(#builderPanelLinks[hidden]) {
  --builder-links-col: 0px;
}
.dash-grid-builder:has(#builderPanelDesign[hidden]) {
  --builder-design-col: 0px;
}

/* Pin each panel to its intended column explicitly - otherwise hiding a sibling (e.g. the
   Links panel when Design is active) makes CSS Grid auto-placement collapse the remaining
   panels into the front-most tracks, shrinking the Live Preview and misplacing Branding. */
.dash-grid-builder #builderRail { grid-column: 1; }
.dash-grid-builder #builderPanelLinks { grid-column: 2; }
.dash-grid-builder .dash-col-preview { grid-column: 3; }
.dash-grid-builder #builderPanelDesign { grid-column: 4; }

/* Security's 2-card layout was silently inheriting .dash-grid's 3-column template, leaving a
   dead empty column - it only ever has two cards, so it gets its own explicit column count. */
.dash-grid-security {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/* The Access Rules/Shield row and the Redirect Mode card below it are separate elements (the
   grid only ever holds the first two), so nothing but ordinary block-flow margin sits between
   them - same 18px .dash-grid already uses for gap, so this row-to-row spacing reads as
   consistent with everything above it instead of tighter. */
.dash-grid-security + .dash-card {
  margin-top: 18px;
}

.see-plans-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  border: 0;
  border-radius: 999px;
  padding: 10px 18px;
  margin-bottom: 16px;
  background: linear-gradient(135deg, rgba(255, 95, 109, 0.16), rgba(14, 165, 233, 0.16));
  cursor: pointer;
  text-align: left;
}

.see-plans-bar-label {
  color: var(--ink);
  font-size: 0.82rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}

.see-plans-bar-label i {
  color: #ff5f6d;
}

.see-plans-bar-cta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #ff5f6d;
  color: #fff;
  font-size: 0.78rem;
  font-weight: 800;
  padding: 7px 14px;
  border-radius: 999px;
  white-space: nowrap;
}

.builder-rail {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--panel-border);
  border-radius: 18px;
  background: var(--panel-bg);
  box-shadow: var(--shadow);
  height: fit-content;
}

.builder-rail-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 4px;
  border-radius: 14px;
  border: 1px solid transparent;
  background: none;
  color: var(--ink-soft);
  font-size: 0.7rem;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.builder-rail-btn i {
  font-size: 1.05rem;
}

.builder-rail-btn:hover {
  background: var(--field-bg);
  color: var(--ink);
}

.builder-rail-btn.is-active {
  background: #ff5f6d;
  color: #fff;
}

.builder-rail-btn.is-active:hover {
  background: #ff5f6d;
  color: #fff;
}

.dash-card {
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  padding: 18px;
  box-shadow: var(--shadow);
  /* Same automatic-minimum-size issue as .dash-col above, one level deeper: .dash-col is itself
     display: grid, so its direct child .dash-card gets its own min-content-sized floor (the
     shortlinks table's, here) unless told otherwise, overflowing past .dash-col's already-fixed
     0 minimum right back out again. */
  min-width: 0;
}

.dash-card-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.dash-card-title {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  margin-bottom: 12px;
}

.dash-card-title-row .dash-card-title {
  margin-bottom: 0;
}

/* Design tab specific - that panel has enough distinct sections (Colors, Backdrop, Buttons,
   Typography, Avatar, Header) that showing all of them expanded at once made the column run far
   longer than the phone preview next to it. Collapsed by default (one exception - see JS) like
   the Links panel's own block cards, so opening just the section you want to edit is a click
   away instead of always scrolling past everything else. */
.dash-card-collapsible .dash-card-title-row {
  margin-bottom: 0;
  cursor: pointer;
}

.dash-card-collapse-toggle {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  font-size: 0.8rem;
  transition: background-color 0.16s ease, transform 0.16s ease;
}

.dash-card-collapse-toggle:hover {
  background: rgba(14, 165, 233, 0.14);
  color: #0ea5e9;
}

.dash-card-collapsible.is-expanded .dash-card-collapse-toggle {
  transform: rotate(180deg);
}

.dash-card-collapsible-body {
  display: none;
}

.dash-card-collapsible.is-expanded .dash-card-collapsible-body {
  display: grid;
  gap: 12px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--panel-border);
}

.dash-card-hint {
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.5;
  margin-bottom: 10px;
}

.map-block-editor-preview {
  width: 100%;
  height: 240px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--field-bg);
  border: 1px solid var(--panel-border);
  display: flex;
  align-items: center;
  justify-content: center;
}

.map-search-results {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 160px;
  overflow-y: auto;
}

.map-search-result {
  text-align: left;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  cursor: pointer;
  font-size: 0.82rem;
  color: inherit;
}

.map-search-result:hover {
  background: var(--panel-bg);
}

.map-search-result.is-active {
  border-color: #ff5f6d;
  box-shadow: 0 0 0 2px rgba(255, 95, 109, 0.28);
}

.swatch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.dash-card-steps {
  color: var(--ink);
  font-size: 0.84rem;
  line-height: 1.6;
  margin: 0 0 12px;
  padding-left: 20px;
}

.dash-card-steps li + li {
  margin-top: 4px;
}

.save-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--success-text);
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--success-bg);
  border: 1px solid var(--success-border);
  transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}

.save-indicator.is-saving {
  color: var(--muted);
  background: transparent;
  border-color: var(--panel-border);
}

.save-indicator.is-error {
  color: var(--error-text);
  background: var(--error-bg);
  border-color: var(--error-border);
}

/* Quick add */
.quick-add-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.quick-add-btn[hidden] {
  display: none;
}

/* Social icons row and Link block are both more "structural" block types than a single square
   Link/Gallery/Music/Video tile, so they get a wider, row-layout button (icon beside the label,
   not stacked above it) instead of squeezing into the 2-column grid - spanning full-width also
   means an odd button count (e.g. 5 when one of Social icons row/Link block is hidden or when
   there's an odd number of square tiles) never leaves a lone tile stretched or an empty cell
   beside it. */
/* Compound selector (not just .quick-add-btn--wide alone) so this reliably outranks the plain
   .quick-add-btn rule below on flex-direction/padding regardless of source order - two rules of
   equal specificity would otherwise have the later one in the file win, which is exactly what
   silently broke this the first time (grid-column applied since .quick-add-btn never sets it, but
   flex-direction/padding still lost to .quick-add-btn's own column/16px-8px values). */
.quick-add-btn.quick-add-btn--wide {
  grid-column: 1 / -1;
  flex-direction: row;
  justify-content: flex-start;
  padding: 12px 16px;
}

.quick-add-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 8px;
  border-radius: 14px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--ink);
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

.pro-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #ff5f6d;
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: 999px;
  line-height: 1.4;
  flex: 0 0 auto;
}

.pro-badge-corner {
  position: absolute;
  top: 8px;
  right: 8px;
}

.nav-brand-group {
  display: flex;
  align-items: center;
}

.tier-indicator {
  margin-left: 8px;
}

.tier-indicator-pro {
  font-size: 0.68rem;
  gap: 5px;
}

/* fa-star isn't in the self-hosted FA subset (assets/css/fa-subset.css) - an empty <i> for it
   still reserves its own inline box, which read as lopsided padding ahead of "PRO" with nothing
   visible in it. Inline SVG instead, sized to sit on the text baseline like a real icon would. */
.tier-indicator-star {
  width: 0.68em;
  height: 0.68em;
  flex: 0 0 auto;
}

.tier-indicator-free {
  display: inline-flex;
  align-items: center;
  background: rgba(15, 23, 42, 0.06);
  color: var(--ink, #0f172a);
  font-size: 0.72rem;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid rgba(15, 23, 42, 0.12);
  cursor: pointer;
  transition: background 0.16s ease;
}

.tier-indicator-free:hover {
  background: rgba(15, 23, 42, 0.1);
}

.tier-indicator-concierge {
  display: inline-flex;
  align-items: center;
  background: #7c3aed;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border-radius: 999px;
  border: 0;
  cursor: pointer;
  transition: background 0.16s ease;
}

.tier-indicator-concierge:hover {
  background: #6d28d9;
}

.access-rule-label-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.color-picker-trigger {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  cursor: pointer;
  padding: 0;
}

.quick-add-btn i {
  font-size: 1.2rem;
  background: linear-gradient(135deg, var(--brand-coral), var(--brand-blue));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.quick-add-btn:hover {
  transform: translateY(-2px);
  border-color: rgba(14, 165, 233, 0.5);
  box-shadow: 0 10px 20px rgba(14, 165, 233, 0.16);
}

.quick-add-btn:disabled {
  cursor: not-allowed;
  opacity: 0.52;
  filter: grayscale(0.4);
  transform: none;
  box-shadow: none;
}

/* Icon catalog */
.icon-search {
  width: 100%;
  border-radius: 12px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--field-text);
  padding: 9px 12px;
  font-size: 0.85rem;
  margin-bottom: 12px;
  font-family: "Sora", sans-serif;
}

.icon-search-wrap {
  position: relative;
}

.icon-search-wrap .icon-search {
  padding-left: 34px;
}

.icon-search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 0.85rem;
  pointer-events: none;
}

.icon-category-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

.icon-category-tab {
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--ink-soft);
  font-size: 0.74rem;
  font-weight: 700;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.icon-category-tab:hover {
  border-color: #0ea5e9;
  color: var(--ink);
}

.icon-category-tab.is-active {
  background: #ff5f6d;
  border-color: transparent;
  color: #fff;
}

.icon-catalog {
  max-height: 560px;
  overflow-y: auto;
  display: grid;
  gap: 14px;
  padding-right: 4px;
}

.icon-category-label {
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

.icon-tile-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.icon-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 4px;
  border-radius: 12px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  cursor: grab;
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}

.icon-tile:active {
  cursor: grabbing;
}

.icon-tile img {
  width: 26px;
  height: 26px;
  object-fit: contain;
  border-radius: 6px;
}

.icon-tile span {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-align: center;
  line-height: 1.2;
}

.icon-tile:hover {
  transform: translateY(-2px);
  border-color: rgba(14, 165, 233, 0.5);
  box-shadow: 0 8px 16px rgba(14, 165, 233, 0.16);
}

/* Live phone preview */
.preview-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: sticky;
  top: 16px;
}

.preview-card-label {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 700;
  margin-bottom: 14px;
}

.phone-stage {
  --accent: #ff5f6d;
  --secondary: #0ea5e9;
  position: relative;
  /* Establishes its own stacking context so the blur-clip child's z-index:-1 (below) is scoped
     locally - without this, position:relative alone doesn't create one, so that negative z-index
     escapes to a distant ancestor context and can end up painting below unrelated content instead
     of just behind .phone-stage's own background, which showed as "the gradient covers my photo"
     even though background_style was correctly set to photo. */
  isolation: isolate;
  border-radius: 20px;
  padding: 24px;
  transition: background 0.3s ease;
}

/* The blur layer's transform:scale(1.15) (avoids transparent blur-edge fringing) needs to be
   clipped to the stage's rounded shape - but overflow:hidden on .phone-stage itself would also
   clip .phone-frame's own drop shadow (a sibling, 30-60px of blur that needs room to breathe).
   A dedicated wrapper scoped to just this layer clips the overflow without touching that shadow. */
.phone-stage-blur-clip {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  z-index: -1;
}

.phone-stage[data-background="gradient"] {
  background: linear-gradient(165deg, var(--accent), var(--secondary));
}

.phone-stage[data-background="solid"] {
  background: var(--accent);
}

.phone-stage[data-background="midnight"] {
  background: linear-gradient(165deg, #0b0f1a, #1b2036 60%, #101425);
}

.phone-stage[data-background="photo"] {
  background: linear-gradient(165deg, var(--accent), var(--secondary));
}

.phone-frame {
  --canvas-color: #ff5f6d;
  --canvas-secondary-color: #0ea5e9;
  width: min(540px, 100%);
  /* Was a fixed near-viewport height regardless of content amount, which read as a huge slab of
     empty background under any account that hasn't filled the page yet (like a fresh account with
     one placeholder block). Sizing to content instead, with a floor so it never looks collapsed -
     no ceiling, so a long page just makes the frame taller instead of scrolling (and clipping)
     inside it. */
  height: auto;
  min-height: 600px;
  border-radius: 38px;
  border: 10px solid #0b0f1a;
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.32), inset 0 0 0 2px rgba(255, 255, 255, 0.08);
  overflow-x: hidden;
  position: relative;
  z-index: 0;
  padding-bottom: 26px;
  transition: background 0.3s ease;
}

.phone-card {
  position: relative;
  background-size: cover;
  background-repeat: no-repeat;
  margin-bottom: -26px;
  padding-bottom: 26px;
}

.phone-card.no-banner .phone-identity {
  margin-top: 0;
  padding-top: 20px;
}

.phone-card[data-card-background="solid"] {
  background-color: var(--card-color, #ffffff);
}

.phone-card[data-card-background="gradient"] {
  background-image: linear-gradient(165deg, var(--card-color, #ffffff), var(--card-secondary-color, #0ea5e9));
}

.phone-frame[data-canvas-background="solid"] {
  background-color: var(--canvas-color, #ff5f6d);
}

.phone-frame[data-canvas-background="gradient"] {
  background-image: linear-gradient(165deg, var(--canvas-color, #ff5f6d), var(--canvas-secondary-color, #0ea5e9));
}

.phone-frame[data-canvas-background="image"] {
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}

.phone-frame-blur-layer {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-size: cover;
  background-position: center;
  filter: blur(30px);
  transform: scale(1.15);
  pointer-events: none;
}

.phone-frame[data-font="sora"] { font-family: "Sora", sans-serif; }
.phone-frame[data-font="bricolage"] { font-family: "Bricolage Grotesque", sans-serif; }
.phone-frame[data-font="poppins"] { font-family: "Poppins", sans-serif; }
.phone-frame[data-font="manrope"] { font-family: "Manrope", sans-serif; }

.phone-banner {
  width: 100%;
  /* Matches the reposition editor's stage ratio (2.8/1) and the real public page's actual banner
     proportions (480px card / 170px banner ~= 2.8/1) - was a flat 120px before, which came out
     much wider/shorter than either of those, so a position/zoom picked in the editor landed
     differently here than it would actually render on the public page. */
  aspect-ratio: 2.8 / 1;
  height: auto;
  border: 0;
  cursor: pointer;
  background-color: rgba(255, 255, 255, 0.14);
  background-repeat: no-repeat;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

/* Pre-existing gap, unrelated to the background-image change above: applyBannerStyleToPreview()
   (assets/js/dashboard.js) toggles this button's `hidden` attribute when switching to a Solid/
   Gradient banner style with no photo uploaded, but the plain `display: grid` two rules up is an
   author-origin rule that beats the browser's own `[hidden] { display: none }` UA-origin rule
   regardless of specificity - so hidden alone never actually hid it. */
.phone-banner[hidden] {
  display: none;
}

.phone-banner video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.phone-banner-placeholder {
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.78rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.phone-banner-wrap {
  position: relative;
}

.phone-banner-solid {
  width: 100%;
  aspect-ratio: 2.8 / 1;
  height: auto;
}

.phone-banner-solid[data-banner-style="gradient"] {
  background: linear-gradient(150deg, var(--accent, #ff5f6d), var(--secondary, #0ea5e9));
}

.phone-banner-solid[data-banner-style="solid"] {
  background: var(--accent, #ff5f6d);
}

.phone-banner-remove {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 11px;
  border: 1px solid rgba(248, 113, 113, 0.5);
  border-radius: 9px;
  background: rgba(30, 8, 8, 0.65);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #fecaca;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s, background-color 0.18s ease, border-color 0.18s ease;
}

.phone-banner-wrap:hover .phone-banner-remove,
.phone-banner-remove:focus-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.phone-banner-remove:hover {
  background: rgba(127, 29, 29, 0.85);
  border-color: rgba(248, 113, 113, 0.85);
}

.phone-banner-reposition {
  position: absolute;
  right: 10px;
  bottom: 48px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 11px;
  border: 1px solid rgba(255, 255, 255, 0.32);
  border-radius: 9px;
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #fff;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s, background-color 0.18s ease;
}

.phone-banner-wrap:hover .phone-banner-reposition,
.phone-banner-reposition:focus-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.phone-banner-reposition:hover {
  background: rgba(15, 23, 42, 0.82);
}

.phone-identity {
  padding: 0 20px;
  margin-top: -36px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.phone-avatar.no-avatar-border {
  border: none;
}

.phone-avatar {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  border-style: solid;
  border-width: var(--avatar-border-width, 4px);
  border-color: var(--avatar-border-color, #fff);
  background-color: #e2e8f0;
  cursor: pointer;
  overflow: hidden;
  display: grid;
  place-items: center;
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.25);
  margin-bottom: 10px;
}

.phone-avatar.is-square {
  border-radius: 16px;
}

.phone-avatar-photo {
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
}

.phone-avatar-fallback {
  color: #64748b;
  font-size: 1.1rem;
}

.phone-avatar-wrap {
  position: relative;
}

.phone-avatar-reposition {
  position: absolute;
  right: 4px;
  bottom: 8px;
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.32);
  background: rgba(15, 23, 42, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #fff;
  font-size: 0.68rem;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s, background-color 0.18s ease;
}

.phone-avatar-wrap:hover .phone-avatar-reposition,
.phone-avatar-reposition:focus-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.phone-avatar-reposition:hover {
  background: rgba(15, 23, 42, 0.9);
}

.phone-avatar-remove {
  position: absolute;
  left: 4px;
  bottom: 8px;
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.32);
  background: rgba(15, 23, 42, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #fff;
  font-size: 0.68rem;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s, background-color 0.18s ease;
}

.phone-avatar-wrap:hover .phone-avatar-remove,
.phone-avatar-remove:focus-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.phone-avatar-remove:hover {
  background: rgba(239, 68, 68, 0.85);
}

/* Avatar variant slots (Design tab, "Profile photo variants") - deliberately plain circles with
   no crop/border styling of their own, unlike .phone-avatar above: variants share the main
   photo's border/shape/crop wholesale (see includes/profile.php), there's nothing per-slot to
   preview here beyond which photo is uploaded. */
.avatar-variants-row {
  display: flex;
  gap: 16px;
  margin: 4px 0 14px;
}

.avatar-variant-slot {
  position: relative;
}

.avatar-variant-btn {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 2px dashed var(--panel-border);
  background: var(--field-bg);
  cursor: pointer;
  overflow: hidden;
  display: grid;
  place-items: center;
  padding: 0;
}

.avatar-variant-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.avatar-variant-fallback {
  color: var(--muted);
  font-size: 0.9rem;
}

.avatar-variant-remove {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.32);
  background: rgba(15, 23, 42, 0.85);
  color: #fff;
  font-size: 0.6rem;
  cursor: pointer;
}

.avatar-variant-remove:hover {
  background: rgba(239, 68, 68, 0.85);
}

.shortlink-custom-code-row {
  display: flex;
  align-items: center;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  /* Deliberately NOT overflow: hidden - the domain half of this row is a dropdown now, and
     clipping to the row would cut its menu off. Nothing needed the clipping anyway: the input
     inside carries no border or background of its own to bleed past the rounded corners. */
}

/* The domain prefix is a picker now, styled to still read as the static text prefix it replaced
   (same muted colour, size and left padding) - just with a chevron and a hover state to
   advertise that it opens. */
.shortlink-domain-prefix-btn {
  padding: 9px 2px 9px 10px;
  gap: 4px;
  border-radius: 10px 0 0 10px;
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 400;
  white-space: nowrap;
}

.shortlink-domain-prefix-btn:hover {
  color: var(--ink);
}

.shortlink-domain-prefix-slash {
  color: var(--ink-soft);
}

/* Anchored to the field's left edge rather than the button's, so the menu lines up with the
   input it belongs to. */
.shortlink-domain-select .dash-link-domain-list {
  left: -1px;
}

.shortlink-custom-code-row input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 9px 10px 9px 2px;
  font-size: 0.85rem;
  font-family: "Sora", sans-serif;
  color: var(--field-text);
}

.shortlink-custom-code-row input:focus {
  outline: none;
}

.shortlink-destination-cell {
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shortlink-row-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* The Shortener tab's "Create a short link" form and its "Your short links" table used to sit
   side by side in a 2-column grid - since the form is much shorter than the table can grow, that
   left a tall dead gap under whichever card finished first. Stacking them instead means each
   card is only ever as tall as its own content, and the table gets the full row width to work
   with instead of being squeezed into a half-width column. */
.dash-grid-shortlinks {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}

.shortlink-create-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 14px;
}

@media (max-width: 720px) {
  .shortlink-create-grid {
    grid-template-columns: 1fr;
  }
}

.shortlink-expired-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(239, 68, 68, 0.14);
  color: #ef4444;
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.admin-users-table tr.is-expired td {
  opacity: 0.6;
}

.phone-name-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

#phoneVerifiedBadge {
  color: #3b82f6;
  font-size: 1rem;
  flex: 0 0 auto;
  /* Now a <button> (was a plain <span>) so it can open the "what does this badge mean" modal -
     strip the native button chrome back down to how the span used to render. */
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
}

.phone-name-input {
  width: 100%;
  text-align: center;
  background: transparent;
  border: 0;
  color: var(--name-color, #fff);
  font-family: "Bricolage Grotesque", sans-serif;
  font-weight: 800;
  font-size: 1.15rem;
  margin-bottom: 2px;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

.phone-name-input::placeholder {
  color: rgba(255, 255, 255, 0.7);
}

.phone-name-input:focus {
  outline: none;
}

.phone-slug {
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.78rem;
  font-weight: 600;
  margin-bottom: 8px;
}

.phone-bio-input {
  width: 100%;
  text-align: center;
  resize: none;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  color: var(--name-color, #fff);
  font-size: 0.82rem;
  padding: 8px 10px;
  font-family: inherit;
  margin-bottom: 14px;
}

.phone-bio-input::placeholder {
  color: rgba(255, 255, 255, 0.7);
}

.phone-blocks {
  padding: 0 18px;
  display: grid;
  gap: 10px;
  min-height: 120px;
}

.frame-empty-state {
  text-align: center;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.85);
  border: 1.5px dashed rgba(255, 255, 255, 0.4);
  border-radius: 14px;
  padding: 22px 12px;
}

/* Block cards */
.block-card {
  background: var(--panel-bg);
  border-radius: 16px;
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.14);
  overflow: hidden;
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}

.block-card.sortable-ghost {
  opacity: 0.4;
}

.block-card.is-block-hidden {
  opacity: 0.55;
}

.block-card.is-block-hidden .block-title-input {
  text-decoration: line-through;
  text-decoration-color: var(--muted);
}

.block-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 10px;
}

.block-drag-handle {
  color: var(--muted);
  cursor: grab;
  padding: 4px;
}

.block-type-badge {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, #ff5f6d, #0ea5e9);
  color: #fff;
  flex: 0 0 auto;
  overflow: hidden;
}

.block-icon-btn {
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.block-icon-btn img {
  width: 20px;
  height: 20px;
  object-fit: contain;
}

.block-title-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--ink);
}

.block-title-input:focus {
  outline: none;
}

.block-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}

.block-action-btn {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 0.78rem;
  transition: background-color 0.16s ease, color 0.16s ease;
}

.block-footer-flag {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(255, 95, 109, 0.12);
  color: #ff5f6d;
  display: grid;
  place-items: center;
  font-size: 0.68rem;
  cursor: help;
}

.block-action-btn:hover {
  background: rgba(14, 165, 233, 0.14);
  color: #0ea5e9;
}

.block-action-danger:hover {
  background: rgba(239, 68, 68, 0.14);
  color: #ef4444;
}

.block-body {
  display: none;
  padding: 0 12px 14px;
  border-top: 1px solid var(--panel-border);
  padding-top: 12px;
}

.block-card.is-expanded .block-body {
  display: grid;
  gap: 10px;
}

.block-card.is-expanded .block-action-btn[title="Edit"] i {
  transform: rotate(180deg);
}

.social-footer-slots {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}

.social-footer-slot-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.social-footer-slot-topline {
  display: flex;
  align-items: center;
  gap: 8px;
}

.social-footer-chip-number {
  font-size: 0.7rem;
  font-weight: 800;
  color: var(--muted);
  min-width: 14px;
  text-align: center;
  flex: 0 0 auto;
}

.social-footer-slot-picker {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
  padding: 6px 8px;
  font-size: 0.82rem;
  font-family: "Sora", sans-serif;
  background: var(--field-bg);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}

.social-footer-slot-picker:hover {
  border-color: rgba(14, 165, 233, 0.5);
}

.social-footer-slot-picker-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  background: rgba(148, 163, 184, 0.18);
  overflow: hidden;
  font-size: 0.75rem;
  color: var(--muted);
}

.social-footer-slot-picker-avatar img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.social-footer-slot-picker-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.social-footer-slot-url {
  width: 100%;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
  padding: 8px 10px;
  font-size: 0.82rem;
  font-family: "Sora", sans-serif;
  background: var(--field-bg);
  color: var(--ink);
  box-sizing: border-box;
}

.social-footer-chip-remove {
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 2px;
  flex: 0 0 auto;
}

.social-footer-chip-remove:hover {
  color: #ef4444;
}

/* [hidden] must be listed before the bare class - same reasoning as .block-checkbox-row[hidden]
   below: equal specificity, and the class rule's own `display: grid` would otherwise win by
   source order, leaking a row (e.g. the featured-title custom font upload button) into view even
   while its own element.hidden = true is set. */
.block-field-group[hidden] {
  display: none;
}

.block-field-group {
  display: grid;
  gap: 5px;
}

.block-field-group label {
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--muted);
}

.block-input {
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--ink);
  padding: 8px 10px;
  font-size: 0.85rem;
  font-family: "Sora", sans-serif;
}

.block-missing-url-flag {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--warning-bg);
  color: var(--warning-text);
  display: grid;
  place-items: center;
  font-size: 0.68rem;
  cursor: help;
}

.block-missing-url-flag[hidden] {
  display: none;
}

.block-field-error {
  margin: 0;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--warning-text);
}

.block-field-error[hidden] {
  display: none;
}

.branding-field-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.branding-field-row[hidden] {
  display: none;
}

.photo-backdrop-thumb {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  object-fit: cover;
  flex: 0 0 auto;
}

.music-cover-preview {
  width: 52px;
  height: 52px;
  border-radius: 12px;
  object-fit: cover;
  display: block;
  margin-bottom: 8px;
}

.music-tracks-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 10px;
}

.music-track-card {
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 12px;
  background: var(--field-bg);
}

.music-track-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.music-track-label {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.block-secondary-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: 10px;
  /* Was hardcoded to dark-on-transparent colors, which is only ever legible on the phone
     preview's own always-light card background - everywhere else this class is used (Branding
     panel controls in the dashboard's own dark theme, the reposition modal) it was invisible. */
  border: 1px dashed var(--panel-border);
  background: transparent;
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 700;
  padding: 8px 10px;
  cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}

.block-secondary-btn:hover {
  border-color: #0ea5e9;
  color: #0ea5e9;
}

.segmented-control {
  display: inline-flex;
  flex-wrap: wrap;
  max-width: 100%;
  border-radius: 999px;
  border: 1px solid var(--panel-border);
  padding: 3px;
  gap: 3px;
  width: fit-content;
}

.segmented-option {
  border: 0;
  background: transparent;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
}

.segmented-option-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  flex: 0 0 auto;
}

.segmented-option-icon img,
.segmented-option-icon i {
  width: 18px;
  height: 18px;
  object-fit: contain;
  font-size: 0.95rem;
}

.segmented-option.is-active {
  background: linear-gradient(135deg, #ff5f6d, #0ea5e9);
  color: #fff;
}

/* Per-registrar custom-domain setup guide (account.php's domain card) - the segmented-control
   tabs above just toggle which one of these is visible. */
.registrar-guide {
  margin-top: 10px;
}

/* The Canvas/Background rail is the entry point for "which surface am I editing" - full-width
   and evenly split reads as a rail you pick a lane on, not just another small toggle among many. */
#surfaceRailToggle {
  width: 100%;
  margin-bottom: 16px;
}

#surfaceRailToggle .segmented-option {
  flex: 1 1 0;
  text-align: center;
  padding: 9px 14px;
  font-size: 0.82rem;
}

.gallery-dimension-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.gallery-dimension-field {
  display: grid;
  gap: 5px;
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 700;
}

.gallery-dimension-field .block-input {
  appearance: textfield;
}

.gallery-dimension-field .block-input::-webkit-inner-spin-button,
.gallery-dimension-field .block-input::-webkit-outer-spin-button {
  opacity: 1;
}

.gallery-image-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.gallery-thumb {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  aspect-ratio: 1 / 1;
}

.gallery-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery-thumb-remove {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 0;
  background: rgba(15, 23, 42, 0.72);
  color: #fff;
  cursor: pointer;
  line-height: 1;
  font-size: 0.8rem;
}

.link-stack-items-wrap {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 10px;
}

.link-stack-item-card {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 8px;
  background: var(--field-bg);
}

.link-stack-item-handle {
  cursor: grab;
  color: var(--muted);
  flex: 0 0 auto;
  padding: 4px;
}

.link-stack-item-thumb-btn {
  position: relative;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  border: 1px dashed var(--panel-border);
  background: transparent;
  color: var(--muted);
  overflow: hidden;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.link-stack-item-thumb-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.link-stack-item-remove-image {
  position: relative;
  margin-left: -34px;
  align-self: flex-start;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 0;
  background: rgba(15, 23, 42, 0.72);
  color: #fff;
  cursor: pointer;
  line-height: 1;
  font-size: 0.7rem;
  flex: 0 0 auto;
}

.link-stack-item-fields {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.link-stack-item-fields .block-input {
  width: 100%;
}

.link-stack-item-remove {
  flex: 0 0 auto;
}

.block-audio-preview,
.block-video-preview,
.block-image-preview {
  width: 100%;
  border-radius: 10px;
}

.block-image-preview {
  display: block;
  max-height: 160px;
  object-fit: cover;
  margin-bottom: 8px;
}

/* [hidden] must be listed before the bare class - both have equal specificity, and the class
   rule's own `display: flex` would otherwise win by source order, leaking this row (e.g. the
   image-position editor's avatar-only "Show a border ring" controls) into purposes that hide it
   via element.hidden = true. Same reasoning as .upload-progress-overlay[hidden] above. */
.block-checkbox-row[hidden] {
  display: none;
}

.block-checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.block-checkbox-row input {
  width: 15px;
  height: 15px;
  accent-color: #0ea5e9;
}

.access-rule-row {
  align-items: flex-start;
  color: var(--ink);
  font-weight: 700;
  padding: 8px 0;
  border-bottom: 1px solid var(--panel-border);
}

.access-rule-row:last-child {
  border-bottom: 0;
}

.access-rule-row input {
  margin-top: 3px;
  flex: 0 0 auto;
}

.access-rule-row span {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.access-rule-row small {
  color: var(--ink-soft);
  font-weight: 500;
  font-size: 0.76rem;
  line-height: 1.4;
}

/* Locked (PRO) rows need to visibly read as unavailable, but low-contrast enough to be hard to
   read isn't "disabled," it's just illegible - explicit muted-but-readable colors here rather
   than an opacity fade on the whole row, since a blanket opacity would also wash out the PRO
   badge that's supposed to be the most visible thing in the row. */
.access-rule-row.is-locked-row {
  color: var(--muted);
  cursor: not-allowed;
}

.access-rule-row.is-locked-row small {
  color: var(--muted);
  opacity: 0.85;
}

.access-rule-row.is-locked-row input[type="checkbox"] {
  opacity: 0.7;
}

.locked-feature-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 8px 0;
  border: 0;
  border-bottom: 1px solid var(--panel-border);
  background: none;
  font: inherit;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}

.locked-feature-row:last-child {
  border-bottom: 0;
}

.locked-feature-row:hover .locked-feature-row-label {
  color: #0ea5e9;
}

.locked-feature-row-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-weight: 700;
}

.locked-feature-row-label i {
  color: var(--ink-soft);
  margin-right: 2px;
}

.locked-feature-row-label small {
  color: var(--ink-soft);
  font-weight: 500;
  font-size: 0.76rem;
  line-height: 1.4;
}

/* Branding */
.branding-field {
  display: grid;
  gap: 6px;
  margin-bottom: 12px;
}

.branding-field[hidden] {
  display: none;
}

.branding-field label {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-soft);
}

.branding-field input[type="color"] {
  width: 100%;
  height: 38px;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  padding: 3px;
  cursor: pointer;
}

.branding-field select {
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--field-text);
  padding: 9px 10px;
  font-size: 0.85rem;
  font-family: "Sora", sans-serif;
}

.swatch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.swatch-grid-font {
  grid-template-columns: repeat(2, 1fr);
}

.swatch-grid-icon {
  grid-template-columns: repeat(3, 1fr);
}

.swatch-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  border-radius: 12px;
  padding: 10px 8px;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.swatch-btn:hover {
  border-color: #0ea5e9;
}

.swatch-btn.is-active {
  border-color: #ff5f6d;
  box-shadow: 0 0 0 2px rgba(255, 95, 109, 0.28);
}

.is-premium-locked {
  opacity: 0.45;
  cursor: not-allowed;
  position: relative;
}

/* .is-premium-locked's position:relative (needed for the swatch-btn lock badge below) has the
   same specificity as these overlay buttons' own position:absolute and comes later in the
   file, so it was winning the cascade and knocking the avatar/banner reposition+remove buttons
   out of their anchored corner into normal flow whenever guardPremiumControl() locked them.
   Higher-specificity selectors restore the absolute positioning these buttons actually need. */
.phone-avatar-reposition.is-premium-locked,
.phone-avatar-remove.is-premium-locked,
.phone-banner-reposition.is-premium-locked,
.phone-banner-remove.is-premium-locked {
  position: absolute;
}

.is-premium-locked:hover {
  border-color: var(--panel-border) !important;
}

.swatch-btn.is-premium-locked::after {
  content: "\f023";
  font-family: "Font Awesome 6 Free";
  font-weight: 900;
  position: absolute;
  top: 6px;
  right: 6px;
  font-size: 0.62rem;
  color: var(--muted);
}

.swatch-label {
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 700;
}

.swatch-preview {
  width: 100%;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
}

.swatch-preview-bg[data-bg-preview="gradient"] {
  background: linear-gradient(135deg, var(--accent, #ff5f6d), var(--secondary, #0ea5e9));
}

.swatch-preview-bg[data-bg-preview="solid"] {
  background: var(--accent, #ff5f6d);
}

.swatch-preview-bg[data-bg-preview="midnight"] {
  background: linear-gradient(135deg, #0b0f1a, #1e293b);
}

.swatch-preview-bg[data-bg-preview="photo"] {
  background: linear-gradient(135deg, var(--accent, #ff5f6d), var(--secondary, #0ea5e9));
  background-size: cover;
  background-position: center;
  filter: blur(2px);
}

.swatch-preview-bg[data-canvas-preview="gradient"] {
  background: linear-gradient(135deg, var(--canvas-color, #ff5f6d), var(--canvas-secondary-color, #0ea5e9));
}

.swatch-preview-bg[data-canvas-preview="solid"] {
  background: var(--canvas-color, #ff5f6d);
}

.swatch-preview-bg[data-canvas-preview="image"] {
  background: linear-gradient(135deg, var(--canvas-color, #ff5f6d), var(--canvas-secondary-color, #0ea5e9));
  background-size: cover;
  background-position: center;
}

.swatch-preview-bg[data-banner-preview="none"] {
  background: transparent;
  border-style: dashed;
}

.swatch-preview-bg[data-banner-preview="solid"] {
  background: var(--accent, #ff5f6d);
}

.swatch-preview-bg[data-banner-preview="gradient"] {
  background: linear-gradient(135deg, var(--accent, #ff5f6d), var(--secondary, #0ea5e9));
}

.swatch-preview-button {
  background: #64748b;
}

.swatch-radius-rounded {
  border-radius: 8px;
}

.swatch-radius-pill {
  border-radius: 999px;
}

.swatch-radius-square {
  border-radius: 2px;
}

.swatch-fill-solid {
  border-radius: 8px;
  background: #64748b;
}

.swatch-fill-outline {
  border-radius: 8px;
  background: transparent;
  border: 2px solid #64748b;
}

.swatch-preview-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  font-size: 1rem;
}

/* Filled chip, matching how icon_style=color actually renders (a brand-color circle baked
   behind the glyph) - distinguishes it at a glance from the no-background Logo preview below. */
.swatch-icon-color {
  background: #9146ff;
  color: #ffffff;
}

.swatch-icon-mono {
  color: var(--ink-soft);
}

.swatch-icon-logo {
  color: #9146ff;
}

.swatch-btn-font {
  padding: 12px 8px;
}

.swatch-font-glyph {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--field-text);
}

.bg-image-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.bg-image-row.is-stacked {
  flex-direction: column;
  align-items: stretch;
}

/* Shield */
.shield-field {
  display: grid;
  gap: 6px;
  margin: 12px 0;
}

.shield-field label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-soft);
}

.shield-field label small {
  font-weight: 500;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--ink-soft);
  opacity: 0.85;
}

.shield-field select,
.shield-field input[type="text"],
.shield-field input[type="url"],
.shield-field input[type="number"],
.shield-field input[type="datetime-local"] {
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--field-bg);
  color: var(--field-text);
  padding: 9px 10px;
  font-size: 0.85rem;
  font-family: "Sora", sans-serif;
}

/* Chrome/Edge render the datetime-local calendar-picker glyph in solid black by default, which
   goes invisible against this app's dark-mode field backgrounds - matches how .shield-field
   input[type="date"] would need the same treatment if one existed here. */
.shield-field input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  filter: invert(0.5);
  cursor: pointer;
}

.shield-field[hidden] {
  display: none;
}

.meta-radar-panel {
  margin: 4px 0 14px;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--field-bg);
  border: 1px solid var(--panel-border);
}

.meta-radar-panel[hidden] {
  display: none;
}

.meta-radar-panel .dash-card-hint {
  margin: 0 0 8px;
}

/* Redirect Mode's active-state panel (dashboard.php's Security tab) - visually flagged with an
   accent-colored left edge, distinct from the neutral .meta-radar-panel styling above, since this
   state means the creator's live page is currently sending every visitor elsewhere. */
.redirect-active-status {
  margin: 4px 0 14px;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--field-bg);
  border: 1px solid var(--panel-border);
  border-left: 3px solid var(--brand-blue, #2563eb);
}

.redirect-active-status .dash-card-hint {
  margin: 0 0 8px;
}

.redirect-active-status .dash-card-hint:last-of-type {
  margin-bottom: 12px;
}

.meta-radar-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
  max-height: 220px;
  overflow-y: auto;
}

.meta-radar-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  font-size: 0.78rem;
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(127, 127, 127, 0.08);
}

.meta-radar-time {
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
}

.meta-radar-ua {
  color: var(--muted);
  word-break: break-word;
}

.meta-radar-empty {
  text-align: center;
  font-size: 0.8rem;
  color: var(--muted);
  border: 1.5px dashed var(--panel-border);
  border-radius: 12px;
  padding: 16px 12px;
  margin: 0;
}

.bg-image-preview {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--field-bg);
  border: 1px solid var(--panel-border);
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  color: var(--muted);
}

.bg-image-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Mirrors .pub-link-featured's own box model (creator.css) - full width, 150px tall. The public
   card's image is a CSS background-image (background-size: cover), so its rendered shape never
   depends on the uploaded photo's own dimensions - a square upload and a wide upload both fill
   the exact same box. This preview has to hold that same fixed height (not height: auto), or its
   <img> tag's own intrinsic aspect ratio leaks through instead: with an auto-height parent, the
   base .bg-image-preview img rule's height: 100% doesn't resolve against anything, so the box
   collapses to the image's own aspect ratio - a 512x512 upload measures as a square, a wide
   upload measures as a short strip, neither matching what the public page actually renders. That
   mismatched box was then handed to the crop tool as matchElementRatio, so a square upload made
   the reposition modal itself go square instead of staying pinned to the real card shape. */
.bg-image-preview.is-featured-card {
  width: 100%;
  height: 150px;
  flex: 0 0 auto;
  position: relative;
}

/* Mirrors .pub-link-featured-small's own 64px (creator.css). */
.bg-image-preview.is-featured-card.is-small-size {
  height: 64px;
}

/* Mirrors .pub-link-featured-full's own 200px (creator.css) - keeps this thumbnail's box model
   in sync with whichever size the creator picked, same reasoning as the 150px case above. */
.bg-image-preview.is-featured-card.is-full-size {
  height: 200px;
}

/* Mirrors .pub-link-featured-tall's own 340px (creator.css). */
.bg-image-preview.is-featured-card.is-tall-size {
  height: 340px;
}

.bg-image-preview.is-featured-card .featured-icon-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 1;
  width: 32px;
  height: 32px;
  pointer-events: none;
}

.bg-image-preview.is-featured-card .featured-icon-badge img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.55));
}

/* Once an image is uploaded, the title text controls layer their preview (.pub-link-featured-
   scrim/-label, from creator.css) directly onto this same box instead of a second one underneath -
   position: absolute pulls both out of the parent's grid layout entirely, so the placeholder-icon
   centering above is untouched when there's no image yet. */
.bg-image-preview.is-featured-card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The label is a normal (non-absolute) flex child here, same as .pub-link-featured itself in
   creator.css - NOT position: absolute with left/right stretching it full-width, which was the
   previous approach. That stretch made the invisible box around the text much wider than the
   text itself, so transform-based animations (pulse's scale, wiggle's rotate, ...) pivoted around
   the center of the whole card instead of the word - the animation looked like it was skewing off
   to one side instead of animating the text in place. Sizing to content instead of the full width
   keeps the transform-origin centered on the actual glyphs, matching how it animates for real.  */
.bg-image-preview.is-featured-card.has-title-overlay {
  display: flex;
  align-items: flex-end;
  padding: 14px;
  box-sizing: border-box;
}

.bg-image-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;
}

.bg-image-actions .block-secondary-btn {
  width: 100%;
}

/* Account tab */
.dashboard-tabs {
  display: inline-flex;
  gap: 6px;
  padding: 4px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--panel-bg);
}

.dashboard-tab {
  padding: 9px 13px;
  font-size: 0.82rem;
  box-shadow: none;
  /* Sized to fit the longest label ("Shortener", now wider than "Analytics" was when this was
     first set to 112px) rather than each button's own text, so all five read as one consistent
     row instead of visibly different widths - re-measure and bump this again if a future tab's
     label ends up wider still. */
  min-width: 120px;
}

.dashboard-tab.is-active {
  background: linear-gradient(135deg, #111827, #33335c);
  color: #fff;
  box-shadow: 0 8px 18px rgba(17, 24, 39, 0.2);
}

.dashboard-view[hidden] {
  display: none;
}

.account-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 20px;
}

.account-subtitle {
  color: var(--ink-soft);
  font-size: 0.88rem;
  margin-top: 7px;
}

.account-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: 16px;
  align-items: stretch;
}

.account-card {
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow);
}

.account-card-heading,
.admin-card-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
}

.account-eyebrow {
  color: var(--brand-blue);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  margin-bottom: 5px;
}

.account-card h2 {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.25rem;
  letter-spacing: -0.02em;
}

.account-avatar {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  color: #fff;
  background: linear-gradient(135deg, var(--brand-coral), var(--brand-blue));
}

.account-details {
  display: grid;
  gap: 13px;
}

.account-details > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  border-bottom: 1px solid var(--panel-border);
  padding-bottom: 10px;
}

.account-details dt {
  color: var(--muted);
  font-size: 0.78rem;
}

.account-details dd {
  color: var(--ink);
  font-size: 0.84rem;
  font-weight: 700;
  text-align: right;
  overflow-wrap: anywhere;
  /* Flex items default to min-width: auto, which stops the row from ever shrinking down far
     enough for overflow-wrap above to actually kick in - a long value (the IP lookup modal's ISP/
     Organization field is the realistic case) pushes .account-details > div past its container,
     widening .modal-panel and the whole page with it (the "have to zoom out to see it" symptom on
     mobile). min-width: 0 lets the flex item actually shrink to the row's available width first. */
  min-width: 0;
}

.account-detail-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 24px;
}

.account-card-note,
.account-danger-card p:last-of-type {
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.5;
  margin-top: 16px;
}

.subscription-status,
.admin-count,
.admin-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  padding: 6px 9px;
  font-size: 0.7rem;
  font-weight: 800;
  white-space: nowrap;
}

.subscription-status.is-active,
.premium-active,
.table-status.is-active {
  color: var(--success-text);
}

.subscription-status.is-inactive {
  color: var(--muted);
  background: var(--chip-bg);
}

.account-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

.seat-purchase-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 14px;
}

.seat-stepper {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}

.seat-stepper-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--chip-bg);
  color: var(--ink);
  font-size: 1.1rem;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}

.seat-stepper-btn:hover {
  border-color: #0ea5e9;
  color: #0ea5e9;
}

.seat-stepper-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.seat-stepper-value {
  min-width: 2.5ch;
  text-align: center;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--ink);
}

.seat-stepper-label {
  color: var(--muted);
  font-size: 0.8rem;
}

.seat-price-estimate {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--ink);
}

.account-danger-card {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  border-color: var(--error-border);
  background: var(--error-bg);
}

.account-danger-card h2 {
  font-size: 1.05rem;
}

.account-danger-card p:last-of-type {
  color: var(--error-text);
  margin-top: 6px;
}

.account-danger-btn,
.grant-premium-btn,
.sotocore-badge-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--error-border);
  border-radius: 10px;
  background: transparent;
  color: var(--error-text);
  font-family: "Sora", sans-serif;
  font-size: 0.78rem;
  font-weight: 800;
  padding: 10px 13px;
  cursor: pointer;
  white-space: nowrap;
}

.account-danger-btn:hover {
  background: rgba(239, 68, 68, 0.12);
}

.duplicate-settings-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
}

.duplicate-target-select {
  width: auto;
  max-width: 160px;
  font-size: 0.82rem;
  padding: 6px 8px;
}

.admin-card {
  margin-top: 16px;
}

.admin-count {
  color: var(--ink-soft);
  background: var(--chip-bg);
}

#liveActivityStatus,
.live-status-dot {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

#liveActivityStatus::before,
.live-status-dot::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #22c55e;
  animation: liveActivityPulse 1.6s ease-in-out infinite;
}

#liveVisitorCount .analytics-kpi-value {
  color: #22c55e;
}

@keyframes liveActivityPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.live-activity-row-new {
  animation: liveActivityRowFlash 1.6s ease-out;
}

@keyframes liveActivityRowFlash {
  0% { background: rgba(34, 197, 94, 0.18); }
  100% { background: transparent; }
}

.admin-search-wrap {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: var(--field-bg);
  color: var(--muted);
  padding: 0 12px;
  margin-bottom: 14px;
}

.admin-search-wrap input {
  width: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--field-text);
  font: inherit;
  font-size: 0.82rem;
  padding: 11px 0;
}

.admin-table-wrap {
  overflow-x: auto;
}

/* Shrinks a text+icon action button (Grant lifetime, Payout, View as, Delete, etc.) down to
   just its icon - used on the admin User accounts table, where every row repeats 5+ of these
   buttons and the full text labels were the main reason that table needed horizontal scrolling
   even on a 1920px-wide screen. The label stays in the DOM (visually hidden, not display:none)
   so screen readers still announce it, and it still surfaces on hover/focus via the button's
   title attribute. */
.icon-only-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  gap: 0;
  flex: 0 0 auto;
}

.icon-only-btn .icon-only-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.admin-users-table,
.analytics-table {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-size: 0.78rem;
}

/* The Most-visited-pages/Top-users-by-clicks leaderboards only ever have 3 narrow columns (#,
   name, a number), but they sit in .analytics-chart-grid's half-width cards - the 760px min-width
   above (sized for the much wider admin/analytics tables) forced a horizontal scrollbar on them
   even though the actual content never needs anywhere near that much room. */
.analytics-chart-card .analytics-table {
  min-width: 0;
}

.admin-users-table th,
.analytics-table th {
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  padding: 0 12px 10px;
  text-align: left;
  text-transform: uppercase;
}

.admin-users-table td,
.analytics-table td {
  border-top: 1px solid var(--panel-border);
  color: var(--ink-soft);
  padding: 12px;
  vertical-align: middle;
  white-space: nowrap;
}

.admin-users-table td:first-child {
  min-width: 210px;
  white-space: normal;
}

#adminAssetsTable td:first-child {
  min-width: 280px;
}

.org-asset-file {
  display: flex;
  align-items: center;
  gap: 10px;
}

.org-asset-thumb {
  width: 72px;
  height: 72px;
  border-radius: 10px;
  object-fit: cover;
  flex: 0 0 auto;
  background: rgba(15, 23, 42, 0.06);
}

.org-asset-thumb-icon {
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 1.4rem;
}

.org-asset-filename {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
  color: var(--muted);
  font-size: 0.75rem;
  text-decoration: none;
}

.org-asset-filename:hover {
  text-decoration: underline;
}

.org-asset-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 14px;
}

.org-asset-pagination-label {
  font-size: 0.8rem;
  color: var(--muted);
}

.analytics-table td:first-child {
  min-width: 220px;
  white-space: normal;
}

.country-flag-icon {
  vertical-align: middle;
  margin-right: 2px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--panel-border);
}

.analytics-table td strong,
.analytics-table td small {
  display: block;
}

.admin-users-table td strong,
.admin-users-table td small {
  display: block;
}

.admin-users-table td strong,
.analytics-table td strong {
  color: var(--ink);
}

.admin-users-table td small,
.analytics-table td small {
  color: var(--muted);
  font-size: 0.7rem;
  margin-top: 3px;
  overflow-wrap: anywhere;
  white-space: normal;
}

/* Same visitor_id string shown here (admin Live activity's User cell) and in a given account's
   own Recent visits table - monospace makes it easy to eyeball-match the two, and selectable/
   copyable in full for pasting into Recent visits' own search box. */
.live-activity-visitor-id {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

.global-visit-ip-btn {
  border: 0;
  background: transparent;
  padding: 0;
  color: #0ea5e9;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 0.78rem;
  text-decoration: underline dotted;
  cursor: pointer;
}

.global-visit-ip-btn:hover {
  color: #0284c7;
}

.admin-tag {
  color: var(--brand-blue);
  background: rgba(14, 165, 233, 0.12);
  margin-top: 6px;
}

.member-concierge-nudge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 3px 9px;
  font-size: 0.66rem;
  font-weight: 700;
  white-space: nowrap;
  color: #7c3aed;
  background: rgba(124, 58, 237, 0.12);
  margin-left: 8px;
}

.table-status {
  color: var(--muted);
}

.premium-active {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.73rem;
  font-weight: 800;
}

.grant-premium-btn {
  border-color: rgba(14, 165, 233, 0.38);
  color: var(--brand-blue);
  padding: 7px 9px;
}

.grant-premium-btn:hover {
  background: rgba(14, 165, 233, 0.1);
}

.sotocore-badge-btn {
  border-color: var(--panel-border);
  color: var(--muted);
  padding: 7px 9px;
}

.sotocore-badge-btn:hover {
  background: var(--chip-bg);
}

.sotocore-badge-btn.is-active {
  border-color: rgba(212, 160, 23, 0.4);
  color: #d4a017;
  background: rgba(212, 160, 23, 0.12);
}

.sotocore-badge-btn.is-active:hover {
  background: rgba(212, 160, 23, 0.18);
}

.admin-toggle-btn.is-active {
  border-color: rgba(99, 102, 241, 0.4);
  color: #6366f1;
  background: rgba(99, 102, 241, 0.12);
}

.admin-toggle-btn.is-active:hover {
  background: rgba(99, 102, 241, 0.18);
}

.sotocore-badge-star {
  width: 0.85em;
  height: 0.85em;
  flex: 0 0 auto;
}

/* Manager "manage-as" banner shown at the top of the dashboard while impersonating a seat member. */
.manage-as-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  border: 1px solid rgba(245, 158, 11, 0.4);
  background: rgba(245, 158, 11, 0.12);
  color: var(--ink);
  border-radius: var(--radius-lg);
  padding: 12px 16px;
  margin: 0 0 16px;
  font-size: 0.84rem;
}

.manage-as-banner i {
  color: #f59e0b;
}

.manage-as-return-btn {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 7px 13px;
  font-size: 0.76rem;
  font-weight: 800;
  color: #111827;
  background: #f59e0b;
  text-decoration: none;
  white-space: nowrap;
}

/* Passive "what's on this tab" explainer (Security/Shortener tabs) - reuses .manage-as-banner's
   layout in the brand blue used for informational (not action-needed) messaging, with its own
   dismiss button rather than a CTA link. */
.tab-intro-banner {
  border-color: rgba(14, 165, 233, 0.4);
  background: rgba(14, 165, 233, 0.1);
}

.tab-intro-banner i {
  color: var(--brand-blue);
}

.tab-intro-banner p {
  margin: 0;
  flex: 1;
}

.tab-intro-dismiss {
  margin-left: auto;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--muted);
  padding: 2px 6px;
}

/* Agency invite consent banner (dashboard.php's $pendingOrgInvite branch) - reuses
   .manage-as-banner's layout but in the brand purple used for Concierge/verified accents rather
   than the amber reserved for "you're currently impersonating someone" banners. */
.org-invite-banner {
  border-color: rgba(124, 58, 237, 0.4);
  background: rgba(124, 58, 237, 0.12);
}

.org-invite-banner i {
  color: #7c3aed;
}

.org-invite-banner .manage-as-return-btn {
  background: #7c3aed;
  color: #fff;
  border: none;
  font-family: inherit;
  cursor: pointer;
}

.org-invite-decline-btn {
  margin-left: 0 !important;
  background: transparent !important;
  color: var(--ink) !important;
  border: 1px solid rgba(124, 58, 237, 0.4) !important;
}

/* Admin-view banner's roster switcher (dashboard.php's elseif ($adminView !== null) branch) -
   hops between an org's members without leaving the impersonated session first. */
.manage-as-switcher {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
}

.manage-as-switch-btn {
  border: 1px solid rgba(245, 158, 11, 0.5);
  background: rgba(255, 255, 255, 0.5);
  color: var(--ink);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 0.76rem;
  font-weight: 700;
  cursor: pointer;
}

.manage-as-switch-btn:hover {
  background: rgba(245, 158, 11, 0.2);
}

/* Admin user table's per-row View as / Assets / Delete buttons - .account-actions' own
   margin-top assumes it follows a heading/description block, which isn't the case inside this
   table cell. */
.admin-user-actions .account-actions {
  margin-top: 0;
  flex-wrap: nowrap;
}

.premium-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
}

.premium-toggle input {
  width: 16px;
  height: 16px;
  accent-color: var(--brand-blue);
}

.account-delete-modal {
  width: min(480px, 92vw);
}

.account-delete-copy {
  color: var(--ink-soft);
  font-size: 0.82rem;
  line-height: 1.55;
  margin-bottom: 16px;
}

.account-delete-form {
  display: grid;
  gap: 8px;
}

.account-delete-form label {
  color: var(--ink-soft);
  font-size: 0.76rem;
  font-weight: 700;
  margin-top: 5px;
}

.account-delete-form input {
  width: 100%;
  border: 1px solid var(--panel-border);
  border-radius: 9px;
  background: var(--field-bg);
  color: var(--field-text);
  font: inherit;
  font-size: 0.84rem;
  padding: 10px 11px;
}

.account-form-error {
  color: var(--error-text);
  font-size: 0.78rem;
  min-height: 18px;
}

.account-delete-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

/* Deleting an account is irreversible, so the submit button also requires a ~1.1s press-and-hold
   (in addition to the exact-link + password checks already enforced server-side) rather than
   firing on a single click - one more guard against an accidental or rushed tap. */
.account-danger-btn-hold {
  position: relative;
  overflow: hidden;
  background: rgba(239, 68, 68, 0.12);
  isolation: isolate;
}

.account-danger-btn-hold:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.account-danger-btn-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  background: rgba(239, 68, 68, 0.85);
  z-index: -1;
}

.account-danger-btn-hold.is-holding .account-danger-btn-fill {
  width: 100%;
  transition: width 1.1s linear;
}

.account-danger-btn-hold.is-armed .account-danger-btn-label::after {
  content: " \2014 keep holding\2026";
}

/* Analytics */
.analytics-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

#analyticsContent {
  position: relative;
}

#analyticsContent.is-locked > *:not(.analytics-locked-overlay) {
  filter: blur(6px);
  pointer-events: none;
  user-select: none;
}

.analytics-locked-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  padding: 24px;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
}

.analytics-locked-overlay[hidden] {
  display: none;
}

.analytics-locked-overlay i {
  font-size: 1.8rem;
  color: var(--muted);
}

.analytics-locked-overlay p {
  max-width: 340px;
  font-weight: 600;
  color: var(--ink);
}

.analytics-range-select {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border-radius: 999px;
  border: 1px solid var(--panel-border);
  background: var(--panel-bg);
  box-shadow: var(--shadow);
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-soft);
}

.analytics-range-select select {
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--field-bg);
  color: var(--field-text);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 10px;
}

.analytics-state {
  color: var(--muted);
  font-size: 0.85rem;
  padding: 30px 0;
  text-align: center;
}

/* auto-fit (not a hardcoded 5) so the occasional 6th card - the Global rank card, only shown once
   rank data loads - reflows the whole row evenly instead of spilling onto its own mostly-empty
   second row. */
.analytics-kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}

.analytics-kpi-card {
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  padding: 16px;
  box-shadow: var(--shadow);
}

.analytics-kpi-label {
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 8px;
}

.analytics-kpi-value {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.5rem;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* Used to be a standalone pill sitting below the KPI row on its own, which left a big empty gap
   beside it and gave no hint what the rank was even measuring. Now it's shaped like one more
   .analytics-kpi-card and lives inside .analytics-kpi-grid itself (see dashboard.php), so it sits
   flush with the other stats instead of floating in dead space. */
.analytics-rank-card {
  display: flex;
  flex-direction: column;
}

.analytics-rank-label-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.analytics-rank-icon {
  font-size: 1rem;
  line-height: 1;
}

.analytics-kpi-sublabel {
  margin-top: 6px;
  color: var(--muted);
  font-size: 0.72rem;
  line-height: 1.4;
}

.analytics-rank-card.is-plain .analytics-kpi-value {
  color: var(--ink);
}

.analytics-rank-card.is-gold {
  border-color: rgba(234, 179, 8, 0.45);
  background: rgba(234, 179, 8, 0.12);
}

.analytics-rank-card.is-silver {
  border-color: rgba(148, 163, 184, 0.5);
  background: rgba(148, 163, 184, 0.14);
}

.analytics-rank-card.is-bronze {
  border-color: rgba(217, 119, 6, 0.45);
  background: rgba(217, 119, 6, 0.12);
}

.analytics-chart-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-bottom: 16px;
}

.analytics-chart-card h2 {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1rem;
  letter-spacing: -0.01em;
  margin-bottom: 14px;
}

.analytics-chart-wide {
  grid-column: 1 / -1;
}

.analytics-chart-canvas-wrap {
  position: relative;
  height: 240px;
}

.analytics-chart-wide .analytics-chart-canvas-wrap {
  height: 220px;
}

/* Needs to out-specificity .analytics-chart-wide .analytics-chart-canvas-wrap above (which this
   element also matches, being both a .analytics-chart-canvas-wrap and inside a .analytics-chart-wide
   card) - otherwise that rule's 220px silently wins and the map renders far smaller than intended. */
.analytics-chart-wide .analytics-geo-map-wrap {
  height: 640px;
}

.analytics-empty-state {
  color: var(--muted);
  font-size: 0.82rem;
  padding: 16px 4px 4px;
  text-align: center;
}

/* Image reposition modal */
.image-position-modal {
  width: min(460px, 92vw);
}

.image-position-copy {
  color: var(--ink-soft);
  font-size: 0.82rem;
  line-height: 1.55;
  margin-bottom: 14px;
}

.image-position-stage-wrap {
  display: flex;
  justify-content: center;
  margin-bottom: 16px;
}

.image-position-stage {
  position: relative;
  width: min(320px, 100%);
  overflow: hidden;
  border-radius: 14px;
  border: 1px solid var(--panel-border);
  cursor: grab;
  touch-action: none;
  box-shadow: var(--shadow);
}

.image-position-stage.is-circle {
  border-radius: 50%;
}

.image-position-stage.has-border-ring::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-sizing: border-box;
  border-style: solid;
  border-color: var(--reposition-border-color, #ffffff);
  border-width: var(--reposition-border-width, 4px);
}

.image-position-stage.is-dragging {
  cursor: grabbing;
}

.image-position-stage-img {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* Banner-only: mirrors .creator-banner-blur-fill in creator.css - a zoomed-out photo whose
   aspect ratio doesn't match the strip leaves bare background-size gaps on the sides, so this
   always-cover, blurred copy of the same photo sits behind .image-position-stage-img to show a
   soft continuation of the photo there instead, live while dragging the zoom slider. */
.image-position-stage-blur {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-repeat: no-repeat;
  filter: blur(28px) brightness(0.75);
  transform: scale(1.15);
  pointer-events: none;
}

.image-position-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.image-position-stage.is-dragging .image-position-grid {
  opacity: 1;
}

.image-position-grid span {
  position: absolute;
  background: rgba(255, 255, 255, 0.55);
}

.image-position-grid span:nth-child(1) { left: 33.33%; top: 0; bottom: 0; width: 1px; }
.image-position-grid span:nth-child(2) { left: 66.66%; top: 0; bottom: 0; width: 1px; }
.image-position-grid span:nth-child(3) { top: 33.33%; left: 0; right: 0; height: 1px; }
.image-position-grid span:nth-child(4) { top: 66.66%; left: 0; right: 0; height: 1px; }

.image-position-zoom-row {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--ink-soft);
  font-size: 0.8rem;
  margin: 14px 0;
}

.image-position-zoom-row input[type="range"] {
  flex: 1 1 auto;
}

/* Same [hidden]-before-bare-class ordering as .block-checkbox-row[hidden] above - avatar-only
   Shape and Rotate/Flip controls, otherwise left visible in non-avatar purposes. */
.image-position-shape-row[hidden],
.image-position-rotate-row[hidden] {
  display: none;
}

.image-position-shape-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.image-position-rotate-row {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.image-position-rotate-row .block-secondary-btn {
  flex: 1 1 auto;
}

.image-position-shape-label {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-soft);
}

/* Same [hidden]-before-bare-class reasoning as .block-checkbox-row[hidden] above - the border
   color/width fields and the multi-frame swatch previews are both toggled via element.hidden in
   image-position.js, and their own display:flex would otherwise win over the browser's default
   [hidden] rule by source order. */
.image-position-swatches[hidden],
.image-position-border-fields[hidden] {
  display: none;
}

.image-position-swatches {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 18px;
  margin-bottom: 18px;
}

.image-position-border-fields {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 10px 0 14px;
}

.image-position-swatch-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.image-position-swatch {
  width: 120px;
  background-size: cover;
  background-repeat: no-repeat;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
}

.image-position-swatch.is-circle {
  width: 64px;
  border-radius: 50%;
}

.image-position-swatch-label {
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.image-position-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.image-position-save-btn {
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #111827, #33335c);
  color: #fff;
  font-family: "Sora", sans-serif;
  font-weight: 700;
  font-size: 0.84rem;
  padding: 10px 16px;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.2s ease;
}

.image-position-save-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(17, 24, 39, 0.24);
}

/* Modal */
.modal[hidden] {
  display: none;
}

.modal {
  position: fixed;
  inset: 0;
  z-index: 1300;
  display: grid;
  place-items: center;
}

/* Blocks all interaction (pointer-events included, not just visual) until the upload finishes -
   sits above every other modal (image-position crop modal included) since an upload triggered
   from inside one of those still needs to lock the whole page, not just its own dialog.
   [hidden] must be listed before the bare class (same as .modal above it) - both have equal
   specificity otherwise, and the class rule's own `display: grid` would win by source order,
   permanently overriding the browser's default `[hidden] { display: none }` and leaving the
   overlay visibly stuck up even after JS sets hidden = true. */
.upload-progress-overlay[hidden] {
  display: none;
}

.upload-progress-overlay {
  position: fixed;
  inset: 0;
  z-index: 1400;
  display: grid;
  place-items: center;
  background: rgba(8, 12, 22, 0.65);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.upload-progress-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  /* Was a hardcoded #fff while the text used the theme-aware --ink/--muted variables, which flip
     to light colors in dark mode - light text on a permanently-white panel, nearly unreadable.
     var(--panel-bg) matches .modal-panel's own pattern so both stay in sync with the theme. */
  background: var(--panel-bg);
  border-radius: 16px;
  padding: 28px 34px;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
}

.upload-progress-spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(14, 165, 233, 0.2);
  border-top-color: #0ea5e9;
  animation: upload-progress-spin 0.8s linear infinite;
}

@keyframes upload-progress-spin {
  to { transform: rotate(360deg); }
}

.upload-progress-label {
  font-weight: 700;
  color: var(--ink);
  margin: 0;
}

.upload-progress-hint {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0;
}

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 12, 22, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

/* Forced first-link onboarding walkthrough (assets/js/dashboard.js) - four dim panels frame a
   real hole around the live target element (no overlay sits on top of it, so the element receives
   genuine clicks) rather than faking click-through with pointer-events tricks. Kept below .modal's
   z-index (1300) since opening the real Pro/Concierge modal from the "skip by upgrading" choice
   should always render on top of this. */
.onboarding-mask-panel {
  position: fixed;
  background: rgba(8, 12, 22, 0.62);
  z-index: 1200;
}

.onboarding-mask-panel.is-full {
  inset: 0;
}

.onboarding-ring {
  position: fixed;
  border: 2px solid var(--brand-coral);
  border-radius: 14px;
  box-shadow: 0 0 0 4px rgba(255, 95, 109, 0.25), 0 0 26px rgba(255, 95, 109, 0.35);
  pointer-events: none;
  z-index: 1201;
}

.onboarding-tooltip,
.onboarding-decision-panel {
  position: fixed;
  z-index: 1202;
  width: min(320px, 88vw);
  background: var(--card);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 16px 18px;
  color: var(--ink);
}

.onboarding-decision-panel {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
}

.onboarding-tooltip p,
.onboarding-decision-panel p {
  margin: 0 0 12px;
  font-size: 0.92rem;
  line-height: 1.45;
}

.onboarding-tooltip p:last-child,
.onboarding-decision-panel p:last-child {
  margin-bottom: 0;
}

.onboarding-skip-row {
  margin-top: 2px;
}

.onboarding-skip-link {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  font-size: 0.8rem;
  text-decoration: underline;
}

.onboarding-choice-row,
.onboarding-decision-actions {
  display: flex;
  gap: 10px;
  margin: 10px 0;
}

.onboarding-choice-row .btn,
.onboarding-decision-actions button {
  flex: 1;
}

.modal-panel {
  position: relative;
  width: min(520px, 92vw);
  max-height: 80vh;
  overflow-y: auto;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  padding: 18px;
  box-shadow: var(--shadow);
}

/* The default 520px cap (above) fits a confirmation dialog fine but cramps a File/Type/Uploaded
   by/Used in table down to an unreadable, horizontally-scrolling sliver - widened specifically
   for the admin asset browser so admins can actually scan a user's uploads at a glance. */
#adminAssetsModal .modal-panel {
  width: min(960px, 94vw);
}

/* Wide enough to give the click chart room to breathe without the modal cramping into an
   unreadable sliver, same reasoning as the admin asset browser's own width override above. */
#shortlinkStatsModal .modal-panel {
  width: min(600px, 94vw);
}

.shortlink-stats-modal {
  position: relative;
}

.shortlink-stats-body {
  position: relative;
}

/* Same blur-the-real-layout-behind-a-CTA pattern as #analyticsContent.is-locked - a free account
   never had click data collected for its shortlinks in the first place, so this shows a
   believable demo chart, blurred, instead of a discouraging wall of zeros. */
.shortlink-stats-body.is-locked > *:not(.analytics-locked-overlay) {
  filter: blur(6px);
  pointer-events: none;
  user-select: none;
}

.shortlink-stats-kpi-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.shortlink-stats-chart-wrap {
  position: relative;
  height: 220px;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.modal-header h3 {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.1rem;
}

.modal-close {
  border: 0;
  background: transparent;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}

.plans-modal-backdrop[hidden] {
  display: none;
}

.plans-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1300;
  display: grid;
  place-items: center;
  background: rgba(8, 12, 22, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  padding: 20px;
}

.plans-modal {
  position: relative;
  width: min(480px, 92vw);
  max-height: 86vh;
  overflow-y: auto;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  padding: 28px 24px 24px;
  box-shadow: var(--shadow);
}

.plans-modal h2 {
  font-family: "Bricolage Grotesque", sans-serif;
  font-size: 1.3rem;
  color: var(--ink);
  margin: 4px 0 16px;
}

.plans-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  border: 0;
  background: transparent;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}

.plans-modal-features {
  list-style: none;
  display: grid;
  gap: 10px;
  margin: 0 0 20px;
  padding: 0;
}

.plans-modal-features li {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 600;
}

.plans-modal-features li i {
  color: #22c55e;
  flex: 0 0 auto;
}

.plans-modal-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* The Concierge modal only ever has one option button (unlike the Pro modal's Monthly/Lifetime
   pair), so the default 2-column grid left it stranded at half width - stretch it full width
   and swap it to the same compact inline pill used elsewhere for a lone billing action instead
   of the bulkier two-line gradient pricing card. */
.plans-modal-options-single {
  grid-template-columns: 1fr;
  margin-bottom: 18px;
}

/* This is the only place .billing-concierge-inline-btn's plain neutral-pill look (see its base
   rule) is wrong: everywhere else it's a quiet secondary control sitting next to other UI, but
   here it's the entire point of the modal - the one thing meant to convert someone into Concierge
   - so it gets its own bolder, gradient-filled treatment instead of blending into the page. */
.plans-modal-options-single .billing-concierge-inline-btn {
  justify-content: center;
  width: 100%;
  padding: 13px 16px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
  color: #fff;
  font-size: 0.88rem;
  box-shadow: 0 10px 24px rgba(124, 58, 237, 0.35);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.plans-modal-options-single .billing-concierge-inline-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 28px rgba(124, 58, 237, 0.45);
}

.concierge-badge-swatches {
  display: flex;
  gap: 10px;
  margin: 4px 0 8px;
}

.concierge-badge-swatch {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  transition: transform 0.16s ease, border-color 0.16s ease;
}

.concierge-badge-swatch:hover {
  transform: scale(1.08);
}

.concierge-badge-swatch.is-selected {
  border-color: var(--ink, #0f172a);
}

.share-preview-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 3px;
  padding: 22px 18px;
  border-radius: 14px;
  margin-bottom: 18px;
  background: linear-gradient(150deg, #ff5f6d, #0ea5e9);
}

.share-preview-avatar {
  width: 68px;
  height: 68px;
  border-radius: 50%;
  margin-bottom: 8px;
  background: #fff;
  border: 2px solid rgba(255, 255, 255, 0.6);
  overflow: hidden;
  display: grid;
  place-items: center;
  color: #fff;
  font-weight: 800;
  font-size: 1.3rem;
}

.share-preview-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.share-preview-name {
  font-family: "Bricolage Grotesque", sans-serif;
  font-weight: 800;
  font-size: 1.1rem;
  color: #fff;
  margin: 0;
}

.share-preview-slug {
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
}

.qr-modal-preview {
  display: flex;
  justify-content: center;
  padding: 20px;
  border-radius: 14px;
  margin-bottom: 18px;
  background: #fff;
}

.qr-modal-preview canvas {
  width: 100%;
  max-width: 220px;
  height: auto;
  display: block;
}

.share-target-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

/* Only two QR formats (PNG/PDF) - a dedicated 2-column layout instead of leaving an empty gap
   in the shared 3-column .share-target-grid built for the (6-item) page-share modal. */
.qr-format-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.share-target-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 4px;
  border-radius: 10px;
  border: 1px solid var(--panel-border);
  background: var(--panel-bg);
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.share-target-btn:hover {
  border-color: var(--muted);
}

.share-target-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: transparent;
  overflow: hidden;
}

.share-target-icon img,
.share-target-icon svg {
  width: 20px;
  height: 20px;
  object-fit: contain;
}

.share-target-icon-copy {
  color: var(--ink);
  font-size: 0.9rem;
}

.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: 10px;
}

.icon-picker-grid .icon-tile {
  cursor: pointer;
}

.icon-picker-upload-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

.icon-picker-upload-row .block-secondary-btn {
  flex: 1 1 auto;
}

@media (max-width: 1180px) {
  .dash-grid {
    grid-template-columns: 260px minmax(0, 1fr) 280px;
  }

  .dash-grid-builder {
    grid-template-columns: 64px var(--builder-links-col, 260px) minmax(0, 1fr) var(--builder-design-col, 280px);
  }

  .dash-grid-security {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

@media (max-width: 980px) {
  .dash-grid {
    grid-template-columns: 1fr;
  }

  .dash-grid-builder {
    grid-template-columns: 1fr;
  }

  /* The desktop layout pins each panel to an explicit column (grid-column: 1/2/3/4, further up
     this file) so hiding one of Links/Design doesn't collapse the others out of place. Once the
     grid above drops to a single column, those explicit placements no longer have a column 2-4
     to land in - Grid was auto-generating empty implicit columns to satisfy them instead of
     stacking the panels, which is what actually blew the Builder tab out sideways on mobile. */
  .dash-grid-builder #builderRail,
  .dash-grid-builder #builderPanelLinks,
  .dash-grid-builder .dash-col-preview,
  .dash-grid-builder #builderPanelDesign {
    grid-column: auto;
  }

  .dash-grid-security {
    grid-template-columns: 1fr;
  }

  .builder-rail {
    flex-direction: row;
  }

  .account-grid {
    grid-template-columns: 1fr;
  }

  .analytics-kpi-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .analytics-chart-grid {
    /* Must stay minmax(0, 1fr), not plain 1fr - a bare 1fr track sizes to its grid item's
       min-width: auto (the item's own content min-content size) by default, same as a flex item
       without min-width: 0. The desktop rule above (repeat(2, minmax(0, 1fr))) already gets this
       right; this single-column mobile override didn't, so the analytics-chart-card leaderboards
       (and their .admin-table-wrap tables) resisted shrinking to the viewport, quietly widening
       the whole page and forcing pinch-zoom-out to see anything on a phone. */
    grid-template-columns: minmax(0, 1fr);
  }

  .preview-card {
    position: static;
  }

  .icon-catalog {
    max-height: 320px;
  }
}

@media (max-width: 640px) {
  /* .dashboard-tabs below was already set to order:3 + width:100%, meant to drop the tab bar
     onto its own full-width row - but .nav (flex, no wrap) and .nav-controls (its sole flex
     item, also no explicit width) never actually let it wrap there, so all 5 tabs were forced
     into one unbreakable row and blew out the whole page's width instead of just resizing.
     This isn't unique to the Shortener tab - it hits every tab - but Shortener's sparse content
     makes the page-wide horizontal scroll it causes the most obvious. */
  .nav {
    flex-wrap: wrap;
    row-gap: 12px;
  }

  .nav-controls {
    width: 100%;
  }

  .dashboard-tabs {
    order: 3;
    width: 100%;
    min-width: 0;
  }

  /* .nav-controls .nav-btn (main.css) sets width:auto at this same breakpoint - matching its
     two-class specificity (rather than the single .dashboard-tab class) so this actually wins
     over it instead of silently losing and leaving every tab at its own icon's intrinsic width. */
  .nav-controls .dashboard-tab {
    /* Six tabs' worth of icon+label text no longer fit one row at mobile widths without
       overlapping/spilling out of their own button - the label was the part not fitting, so it's
       hidden here and each tab collapses to a fixed-size icon-only square instead of flexing to
       fill unequal leftover space per label length. */
    flex: 0 0 auto;
    width: 40px;
    min-width: 0;
    padding: 9px 0;
  }

  .dashboard-tab-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .account-head,
  .account-danger-card {
    align-items: stretch;
    flex-direction: column;
  }

  .account-detail-grid {
    grid-template-columns: 1fr;
  }

  .account-danger-btn {
    width: 100%;
  }

  .analytics-kpi-grid {
    grid-template-columns: 1fr;
  }

  .analytics-range-select {
    width: 100%;
    justify-content: space-between;
  }

  .dash-head {
    flex-direction: column;
    align-items: flex-start;
  }

  .quick-add-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .icon-picker-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* /you.php guest preview builder */
.you-static-slot {
  cursor: default;
  opacity: 0.85;
}

.claim-card {
  border: 1px solid rgba(255, 204, 63, 0.4);
  background: linear-gradient(115deg, rgba(255, 95, 109, 0.1), rgba(255, 204, 63, 0.14));
}

.claim-card-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.claim-card-actions .btn,
.claim-card-actions .block-secondary-btn {
  width: 100%;
}

