:root {
  color-scheme: light;
  --page-max-width: 1440px;
  --topbar-height: 76px;
  --panel-sticky-gap: 12px;
  --panel-sticky-top: calc(var(--topbar-height) + var(--panel-sticky-gap));
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-soft: #eef2f5;
  --ink: #17212b;
  --muted: #687482;
  --muted-lighter: #9da7b1;
  --line: #dce3ea;
  --line-strong: #b9c6d2;
  --accent: #226c63;
  --accent-dark: #174f49;
  --accent-soft: #dff2ee;
  --accent-light: #369186;
  --accent-lighter: rgb(109, 219, 206);
  --save: #1673d1;
  --save-dark: #0f5cab;
  --save-soft: #e4f1ff;
  --warning: #b65e19;
  --danger: #b4333a;
  --shadow: 0 12px 36px rgba(28, 39, 49, 0.1);
  --radius: 8px;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

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

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
}

body.is-resizing {
  cursor: ew-resize;
  user-select: none;
}

button,
input,
select {
  font: inherit;
}

button {
  cursor: pointer;
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.35;
}

.app-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.app-loading-overlay {
  position: fixed;
  inset: var(--topbar-height) 0 0;
  z-index: 95;
  display: grid;
  place-items: start center;
  padding: 96px 16px 32px;
  background:
    radial-gradient(circle at 50% 0, rgba(255, 255, 255, 0.9), transparent 360px),
    rgba(246, 247, 249, 0.94);
  backdrop-filter: blur(8px);
}

body:not(.is-booting) .app-loading-overlay {
  display: none;
}

.app-loading-card {
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(360px, 100%);
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 18px 46px rgba(28, 39, 49, 0.16);
}

.app-loading-card strong,
.app-loading-card span {
  display: block;
}

.app-loading-card strong {
  font-size: 15px;
  line-height: 1.25;
}

.app-loading-card span:not(.app-loading-spinner) {
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

.composer-login-overlay {
  position: fixed;
  inset: 0;
  z-index: 220;
  display: grid;
  place-items: center;
  padding: 24px 16px;
  background:
    radial-gradient(circle at 50% 0, rgba(255, 255, 255, 0.9), transparent 360px),
    rgba(246, 247, 249, 0.96);
  backdrop-filter: blur(8px);
}

.composer-login-overlay[hidden] {
  display: none;
}

.composer-login-card {
  display: grid;
  gap: 14px;
  width: min(340px, 100%);
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 18px 46px rgba(28, 39, 49, 0.16);
}

.composer-login-card input[type="password"] {
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
}

.composer-login-error {
  margin: 0;
  color: var(--danger);
  font-size: 12px;
}

.composer-login-actions {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 8px;
}

.admin-signin-button {
  width: 100%;
  margin-bottom: 10px;
}

.app-loading-spinner {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border: 3px solid var(--accent-soft);
  border-top-color: var(--accent);
  border-radius: 999px;
  animation: app-loading-spin 760ms linear infinite;
}

@keyframes app-loading-spin {
  to { transform: rotate(360deg); }
}

.topbar {
  min-height: 76px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 24px;
  border-bottom: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(12px);
  box-shadow: 0 8px 24px rgba(23, 33, 43, 0.06);
  position: sticky;
  top: 0;
  z-index: 100;
}

.topbar h1,
.canvas-toolbar h2,
.panel h2 {
  margin: 0;
  line-height: 1.1;
}

.topbar h1 {
  font-size: 24px;
}

.topbar-title {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

.storage-status-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The message and its Sign in button together are wider than a phone. Keeping this on one
     line pushed the whole topbar sideways instead, so the badge takes a second row and the
     layout around it stays put. */
  flex-wrap: wrap;
  gap: 4px 6px;
  max-width: 100%;
  min-width: 0;
  padding: 4px 10px;
  border: 1px solid #e8d49a;
  border-radius: 999px;
  background: #fdf3d7;
  color: #8a6410;
  font-size: 11px;
  font-weight: 790;
}

/* The circled "!", drawn rather than typed. Mirrors .page-toast::before at badge scale so the
   alert reads the same here as it does in a toast, and inherits currentColor, which the U+26A0
   emoji it replaced could not -- that always rendered in the platform's own yellow whatever the
   badge was doing around it. */
.storage-status-badge-icon {
  display: inline-grid;
  place-items: center;
  width: 15px;
  height: 15px;
  flex: none;
  border: 2px solid currentColor;
  border-radius: 999px;
  font: 900 9px/1 system-ui, sans-serif;
}

.storage-status-badge-icon::before {
  content: "!";
}

/* Signed out is the one state here the author has to act on -- their work is not being saved
   anywhere -- so it takes the alert toast's solid red rather than sharing the amber used for
   "inactive", "portal unavailable" and "save stalled". The data-state attribute was already
   being set for exactly this kind of distinction and had no styling hung off it until now. */
.storage-status-badge[data-state="signed-out"] {
  border-color: #8f2029;
  background: #a92d35;
  color: #fff;
  font-weight: 600;
}

/* Heavier than the 1px currentColor default: on a saturated red the thinner outline disappears
   into the background at this size. */
.storage-status-badge[data-state="signed-out"] .storage-signin-button {
  border: 2px solid #fff;
}

.storage-status-badge[data-state="signed-out"] .storage-signin-button:hover {
  background: rgba(255, 255, 255, 0.18);
}

.storage-status-badge > span {
  min-width: 0;
}

.topbar-title > div {
  min-width: 0;
}

.back-button {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
  min-height: 42px !important;
}

.back-button span:first-child {
  font-size: 15px;
  font-weight: 900;
  line-height: 1;
  margin-top: 6px;
}

.eyebrow {
  margin: 0 0 5px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: uppercase;
}

.topbar-actions,
.button-row,
.toolbar-meta,
.span-label-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.primary-button,
.save-button,
.ghost-button,
.danger-button,
.icon-button,
.mode-button {
  min-height: 38px;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0 14px;
  font-weight: 700;
}

.primary-button {
  background: var(--accent);
  color: #fff;
}

.primary-button:hover {
  background: var(--accent-dark);
}

.save-button {
  background: var(--save);
  color: #fff;
}

.save-button:hover {
  background: var(--save-dark);
}

.ghost-button,
.icon-button {
  background: #fff;
  border-color: var(--line);
  color: var(--ink);
}

.ghost-button:hover,
.icon-button:hover {
  border-color: var(--line-strong);
  background: var(--surface-soft);
}

/* The AI Prompt button carries the warn orange, the same #dd8500 the toasts and the
   personalization banner use. Not because anything is wrong -- because this dialog is where
   somebody is about to go and build a widget, and the prompt is the thing that decides
   whether what comes back can be imported and translated. It should not look like the third
   grey button in a row of grey buttons. */
.ghost-button.field-info-prompt {
  background: #dd8500;
  border-color: #dd8500;
  color: #fff;
}

.ghost-button.field-info-prompt:hover {
  background: #bf7300;
  border-color: #bf7300;
}

.mode-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
}

.mode-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 82px;
  background: transparent;
  color: var(--muted);
}

.mode-button-icon {
  width: 16px;
  height: 16px;
  display: inline-block;
  flex: 0 0 auto;
  background: currentColor;
}

.design-mode-icon {
  -webkit-mask: url("assets/design_services.svg") center / contain no-repeat;
  mask: url("assets/design_services.svg") center / contain no-repeat;
}

.preview-mode-icon {
  -webkit-mask: url("assets/visibility.svg") center / contain no-repeat;
  mask: url("assets/visibility.svg") center / contain no-repeat;
}

.mode-button.is-active {
  background: #fff;
  border-color: var(--line);
  color: var(--ink);
  box-shadow: 0 3px 10px rgba(23, 33, 43, 0.08);
}
#previewModeButton.is-active {
  background-color: #f72015;
  color: #fff
}

.breakpoint-switcher {
  display: grid;
  gap: 4px;
  justify-items: center;
}

.breakpoint-buttons {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
}

.breakpoint-button {
  width: 34px;
  min-height: 32px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
}

.breakpoint-button.is-active {
  background: #fff;
  border-color: var(--line);
  color: var(--ink);
  box-shadow: 0 3px 10px rgba(23, 33, 43, 0.08);
}

.breakpoint-button.has-overrides {
  border-color: rgba(109, 219, 206, 0.7);
}

.breakpoint-button.has-overrides .breakpoint-icon {
  color: var(--accent-lighter);
}

.breakpoint-icon {
  width: 20px;
  height: 16px;
  display: inline-block;
  color: currentColor;
  position: relative;
}

.breakpoint-icon-laptop {
  border: 2px solid currentColor;
  border-bottom-width: 3px;
  border-radius: 3px 3px 2px 2px;
}

.breakpoint-icon-laptop::after {
  content: "";
  position: absolute;
  left: -4px;
  right: -4px;
  bottom: -7px;
  border-bottom: 3px solid currentColor;
  border-radius: 0 0 5px 5px;
}

.breakpoint-icon-tablet {
  width: 15px;
  height: 20px;
  border: 2px solid currentColor;
  border-radius: 4px;
}

.breakpoint-icon-mobile {
  width: 11px;
  height: 20px;
  border: 2px solid currentColor;
  border-radius: 5px;
}

.breakpoint-clear-button {
  min-height: 20px;
  padding: 0 8px;
  border-radius: 999px;
  color: var(--accent-dark);
  font-size: 10px;
}

.language-switcher {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
}

.language-switcher[hidden] {
  display: none;
}

.ui-settings-switcher {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.ui-settings-switcher[hidden] {
  display: none;
}

.ui-settings-button {
  min-height: 38px;
  width: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--accent-dark);
  box-shadow: inset 0 0 0 1px rgba(54, 145, 134, 0.08);
  cursor: pointer;
}

.ui-settings-button:hover,
.ui-settings-button[aria-expanded="true"] {
  background: var(--accent-lighter);
  border-color: rgba(54, 145, 134, 0.38);
}

.ui-settings-icon {
  -webkit-mask: url("assets/settings_account_box.svg") center / contain no-repeat;
  mask: url("assets/settings_account_box.svg") center / contain no-repeat;
}

.ui-settings-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 85;
  width: 260px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.2);
}

.translation-service-settings {
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.ui-settings-heading {
  margin: 2px 4px 6px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
}

/* Two declared tracks, not three, plus column auto-flow so Stop creates an implicit third
   COLUMN when it is shown -- without that it would create an implicit row and drop onto a
   second line. Declaring the third track instead would leave an empty one trailing a column
   gap in the mode where Stop is hidden, pulling Check 6px off the right edge. */
.translation-service-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-auto-flow: column;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

.translation-service-status {
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: 11px;
  font-weight: 800;
}

.translation-service-status[data-status="running"] {
  border-color: rgba(54, 145, 134, 0.3);
  background: var(--accent-lighter);
  color: var(--accent-dark);
}

.translation-service-status[data-status="offline"] {
  border-color: rgba(169, 45, 53, 0.25);
  background: #fff1f2;
  color: #a92d35;
}

.mini-button {
  min-height: 28px;
  padding: 0 8px;
  font-size: 11px;
}

.translation-start-button,
.ui-settings-link-button {
  width: 100%;
  min-height: 30px;
  justify-content: center;
  font-size: 11px;
}

/* An anchor rather than a button, so the portal opens in a new tab with the ordinary
   middle-click and ctrl-click gestures. That costs the few things .ghost-button assumes a
   button already brings: a box to centre in, and no underline. */
.ui-settings-link-button {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  text-decoration: none;
}

.ui-language-options {
  display: grid;
  gap: 2px;
}

.ui-language-option {
  width: 100%;
  min-height: 34px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font-size: 12px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.ui-language-option:hover,
.ui-language-option.is-active {
  background: var(--accent-lighter);
}

.language-button {
  min-height: 30px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  cursor: pointer;
}

.language-button::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-2px) rotate(45deg);
}

.language-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 80;
  width: 210px;
  max-height: min(340px, calc(100vh - 120px));
  overflow: auto;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.18);
}

.language-option {
  width: 100%;
  min-height: 34px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font-size: 12px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.language-option:hover,
.language-option.is-active {
  background: var(--accent-lighter);
}

.language-option:disabled,
.language-option.is-disabled {
  color: var(--muted);
  cursor: not-allowed;
  opacity: 0.42;
}

.language-option:disabled:hover,
.language-option.is-disabled:hover {
  background: transparent;
}

/* Separates the page's own languages from the ones it is not in. Margins on both sides
   rather than padding, so the gap belongs to the rule and not to the options around it. */
.language-menu-divider {
  height: 1px;
  margin: 8px 6px;
  background: var(--line);
}

.language-option-code {
  margin-left: auto;
  color: var(--muted);
  font-size: 10px;
  font-weight: 900;
}

.language-flag {
  width: 20px;
  height: 14px;
  flex: 0 0 auto;
  overflow: hidden;
  border: 1px solid rgba(15, 23, 42, 0.16);
  border-radius: 2px;
  background: #fff;
  box-shadow: 0 1px 1px rgba(15, 23, 42, 0.08);
}

.language-flag svg {
  display: block;
  width: 100%;
  height: 100%;
}

.page-content-shell,
.composer-grid {
  min-height: 100%;
  font-family: var(--page-content-font-family, inherit);
}

/* The page's own padding, so the canvas shows the space that actually publishes. The
   surrounding .canvas-wrap padding is editor chrome and stays out of this: it frames the
   page rather than being part of it, which is exactly why the space an author used to see
   here could not be changed from Page Details. */
.page-content-shell {
  padding:
    var(--page-padding-top, 24px)
    var(--page-padding-right, 24px)
    var(--page-padding-bottom, 24px)
    var(--page-padding-left, 24px);
  /* Unset by default, so the canvas and its grid guides show through exactly as before. */
  background: var(--page-background, transparent);
}

.page-content-shell:lang(zh),
.composer-grid:lang(zh) {
  --page-content-font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans SC", "Source Han Sans SC", system-ui, sans-serif;
}

.page-content-shell:lang(ja),
.composer-grid:lang(ja) {
  --page-content-font-family: "Yu Gothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", system-ui, sans-serif;
}

.page-content-shell:lang(hi),
.composer-grid:lang(hi) {
  --page-content-font-family: "Nirmala UI", "Noto Sans Devanagari", system-ui, sans-serif;
}

.page-content-shell:lang(bn),
.composer-grid:lang(bn) {
  --page-content-font-family: "Nirmala UI", "Noto Sans Bengali", system-ui, sans-serif;
}

.page-content-shell:lang(ar),
.composer-grid:lang(ar) {
  --page-content-font-family: "Segoe UI", Tahoma, Arial, sans-serif;
}

.page-content-shell:lang(zh) .widget-body,
.page-content-shell:lang(zh) .widget-body *,
.page-content-shell:lang(ja) .widget-body,
.page-content-shell:lang(ja) .widget-body *,
.page-content-shell:lang(hi) .widget-body,
.page-content-shell:lang(hi) .widget-body *,
.page-content-shell:lang(bn) .widget-body,
.page-content-shell:lang(bn) .widget-body *,
.page-content-shell:lang(ar) .widget-body,
.page-content-shell:lang(ar) .widget-body * {
  font-family: var(--page-content-font-family);
}

.show-grid-toggle {
  min-height: 38px;
  width: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--muted);
  font-weight: 790;
  white-space: nowrap;
}

.show-grid-toggle input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.show-grid-toggle:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.show-grid-toggle:has(input:disabled) {
  border-color: var(--line);
  background: #f4f7f9;
  color: var(--muted);
  cursor: not-allowed;
  opacity: 0.62;
}

.grid-toggle-icon {
  width: 17px;
  height: 17px;
  display: block;
  background: currentColor;
  -webkit-mask: url("assets/calendar_view_week.svg") center / contain no-repeat;
  mask: url("assets/calendar_view_week.svg") center / contain no-repeat;
}

.export-menu {
  position: relative;
}

.export-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.button-icon {
  /* width: 16px;
  height: 16px; */
  width: 24px;
  height: 24px;
  display: inline-block;
  flex: 0 0 auto;
  background: currentColor;
}
.button-icon.back-button-icon {
  width: 28px;
  height: 28px;
}

.back-button-icon {
  -webkit-mask: url("assets/sheets_rtl.svg") center / contain no-repeat;
  mask: url("assets/sheets_rtl.svg") center / contain no-repeat;
}

.export-button-icon {
  -webkit-mask: url("assets/browser_updated.svg") center / contain no-repeat;
  mask: url("assets/browser_updated.svg") center / contain no-repeat;
}

.clear-button-icon {
  -webkit-mask: url("assets/backspace.svg") center / contain no-repeat;
  mask: url("assets/backspace.svg") center / contain no-repeat;
}

.done-button-icon {
  -webkit-mask: url("assets/close.svg") center / contain no-repeat;
  mask: url("assets/close.svg") center / contain no-repeat;
}

/* The breakpoint clear pill is the one icon button that is not .small, and at 10px text a
   24px icon dwarfs the label it sits next to. */
.breakpoint-clear-button {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.breakpoint-clear-button .button-icon {
  width: 12px;
  height: 12px;
}

/* The Field info dialog's Close is a primary button, so it misses the .ghost-button.small
   rules that give every other icon button its size and its gap. */
.primary-button:has(.button-icon) {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.primary-button .button-icon {
  width: 16px;
  height: 16px;
}

.edit-icon {
  -webkit-mask: url("assets/edit.svg") center / contain no-repeat;
  mask: url("assets/edit.svg") center / contain no-repeat;
}

.delete-icon {
  -webkit-mask: url("assets/delete.svg") center / contain no-repeat;
  mask: url("assets/delete.svg") center / contain no-repeat;
}

/* .button-icon is 24px for the topbar. Scoped to .small so the Lists back-button
   icon, which shares the base class but is not small, keeps its size. */
.ghost-button.small .button-icon {
  width: 15px;
  height: 15px;
}

.ghost-button.small:has(.button-icon) {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.export-options {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 120;
  min-width: 120px;
  display: grid;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 12px 30px rgba(23, 33, 43, 0.16);
  transform-origin: top right;
  animation: exportMenuIn 120ms ease-out;
}

.export-options[hidden] {
  display: none;
}

.export-options button {
  min-height: 38px;
  width: 100%;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font-weight: 790;
  text-align: left;
}

.export-options button:hover,
.export-options button:focus-visible {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

@keyframes exportMenuIn {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.danger-button {
  color: #fff;
  background: var(--danger);
}

.delete-page-button {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.delete-page-button-icon {
  width: 16px;
  height: 16px;
  display: inline-block;
  flex: 0 0 auto;
  background: currentColor;
  -webkit-mask: url("assets/delete.svg") center / contain no-repeat;
  mask: url("assets/delete.svg") center / contain no-repeat;
}

.small {
  min-height: 30px;
  padding: 0 10px;
  font-size: 13px;
}

.workspace {
  --left-panel-column: minmax(280px, 320px);
  --right-panel-width: min(340px, calc(100% - 24px));
  flex: 1;
  display: grid;
  grid-template-columns: var(--left-panel-column) minmax(520px, 1fr);
  align-items: start;
  gap: 16px;
  padding: 16px;
  min-height: 0;
  position: relative;
}

.workspace.left-panel-collapsed {
  --left-panel-column: 44px;
}

.workspace.compact-sidebars {
  grid-template-columns: var(--left-panel-column) minmax(0, 1fr);
}

.workspace.has-breakpoint-preview .canvas-panel {
  width: min(100%, var(--active-breakpoint-width));
  justify-self: center;
}

.pages-view {
  flex: 1;
  width: 100%;
  margin-inline: auto;
  padding: 18px;
}

.pages-header {
  position: sticky;
  top: var(--topbar-height);
  z-index: 80;
  /* Bleed across the pages-view padding so the sticky backdrop covers the
     full viewport width while scrolling. */
  margin-inline: -18px;
  padding: 18px 18px 12px;
  margin-top: -18px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
  background: rgba(246, 247, 249, 0.94);
  backdrop-filter: blur(12px);
}

.pages-header-container {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  width: min(100%, 1144px);
  margin-inline: auto;
}

.pages-header-container > div:first-child {
  display: contents;
}

.list-tabs {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  grid-column: 1;
  grid-row: 1;
  gap: 5px;
  padding: 4px;
  margin-bottom: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-soft);
  box-shadow: 0 8px 22px rgba(23, 33, 43, 0.07);
}

.list-tab-button {
  min-height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* 6px, not 14px. The two tabs share row 1 with the header actions on narrow phones (see
     the max-width: 360px block at the end of this file), and they are the only part of that
     row that grows with translation. Trimming 8px a side gives back 32px across the pair,
     which is what lets the widest label set -- Japanese "ウィジェット" -- clear the buttons
     at 353px instead of running under them. */
  padding: 0 6px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-weight: 820;
}

.list-tab-button.is-active {
  background: #fff;
  border-color: var(--accent);
  border-width: 2px;
  color: var(--ink);
  box-shadow: 0 3px 10px rgba(23, 33, 43, 0.08);
}

.list-tab-icon {
  width: 18px;
  height: 18px;
  display: inline-block;
  flex: 0 0 18px;
  background: currentColor;
}

.list-tab-page-icon {
  -webkit-mask: url("assets/space_dashboard.svg") center / contain no-repeat;
  mask: url("assets/space_dashboard.svg") center / contain no-repeat;
}

.list-tab-widget-icon {
  -webkit-mask: url("assets/widgets.svg") center / contain no-repeat;
  mask: url("assets/widgets.svg") center / contain no-repeat;
}

.pages-header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  justify-self: end;
  grid-column: 2;
  grid-row: 1;
  gap: 8px;
}

.pages-action-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.sort-control-label {
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

[data-sort-label]::after {
  content: ': ';
}

.sort-select {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.sort-select-button {
  min-height: 34px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--ink);
  font-size: 12px;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
}

.sort-select-button:hover {
  background: #fff;
}

.sort-select-button .button-icon,
.sort-option .button-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  background: currentColor;
}

.sort-az-icon {
  -webkit-mask: url("assets/sort-az.svg") center / contain no-repeat;
  mask: url("assets/sort-az.svg") center / contain no-repeat;
}

.sort-newest-icon {
  -webkit-mask: url("assets/sort-newest.svg") center / contain no-repeat;
  mask: url("assets/sort-newest.svg") center / contain no-repeat;
}

.sort-custom-icon {
  -webkit-mask: url("assets/sort-custom.svg") center / contain no-repeat;
  mask: url("assets/sort-custom.svg") center / contain no-repeat;
}

.sort-select-caret {
  width: 7px;
  height: 7px;
  margin-top: -4px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

.sort-select-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 120;
  min-width: 160px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.18);
}

.sort-option {
  width: 100%;
  min-height: 34px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font-size: 12px;
  font-weight: 550;
  text-align: left;
  cursor: pointer;
}

.sort-option:hover {
  background: var(--surface-soft);
}

.sort-option.is-active {
  color: var(--accent);
}

.sort-option-check {
  margin-left: auto;
  visibility: hidden;
  font-weight: 790;
}

.sort-option.is-active .sort-option-check {
  visibility: visible;
}

.sort-select-compact .sort-control-label {
  font-size: 11px;
}

.sort-select-compact .sort-select-button {
  min-height: 30px;
  padding: 0 7px;
  font-size: 11px;
}

/* Upload, not download. Below 1199px this button loses its label and is read as an icon
   alone, and a downward arrow into a tray says "save this to my machine" -- the opposite of
   what it does. Same Material Symbols family and viewBox as the icon it replaces, so nothing
   about the masking changes. */
.import-button-icon {
  -webkit-mask: url("assets/upload.svg") center / contain no-repeat;
  mask: url("assets/upload.svg") center / contain no-repeat;
}

.reset-source-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 8px;
  border-color: var(--accent-light);
  border-width: 2px;;
  background-color: var(--accent-light);
  color: #fff;
}
.reset-source-button:hover {
  background-color: var(--accent-soft);
  color: var(--ink);
  border-color: var(--accent);
}

.reset-settings-icon {
  width: 30px;
  height: 30px;
  -webkit-mask: url("assets/reset-settings.svg") center / contain no-repeat;
  mask: url("assets/reset-settings.svg") center / contain no-repeat;
  margin-left: -12px;
}

.remove-widget-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* Full width on its own grid line, icon and label centred together. */
  grid-column: 1 / -1;
  width: 100%;
}

.remove-widget-button .page-list-delete-icon {
  width: 16px;
  height: 16px;
  background: currentColor;
}

.add-page-button-icon {
  -webkit-mask: url("assets/add_circle.svg") center / contain no-repeat;
  mask: url("assets/add_circle.svg") center / contain no-repeat;
}

.pages-header h2 {
  margin: 0;
  font-size: 22px;
  line-height: 1.1;
}

.list-count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  padding: 4px 8px;
  margin-left: 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 15px;
  font-weight: 650;
  line-height: 1;
  vertical-align: 1px;
}

.list-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps rather than overflows. The title, the How to Import button and the sort control
     together need about 376px of the 357px a 375px phone leaves, so without this the whole
     page gained 18px of sideways scroll -- measured. Wrapping costs nothing at any width
     that fits, and drops the two controls onto their own line at the widths that do not. */
  flex-wrap: wrap;
  gap: 10px 12px;
  grid-column: 1 / -1;
  grid-row: 3;
  width: 100%;
}

/* The how-to button and the sort control travel together at the right-hand end. Grouped
   rather than dropped into .list-title-row directly: that row is space-between, so a third
   child would have parked the button in the middle of the row on its own, reading as a
   heading rather than as a control. */
.list-title-tools {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* .ghost-button.small everywhere else in the app sits inside a dialog footer. Out here it
   stands next to the sort control, so it takes that control's 34px rather than the footer's
   28px -- side by side, the 6px difference read as a mistake.

   Two classes deep on purpose: .small is declared again further down this file with a 28px
   min-height, and a single-class rule up here ties it on specificity and loses on source
   order. Everything else .small sets (12px, 0 9px padding) already matches the sort button
   beside it, so only the height is taken back. */
.list-title-tools .list-howto-button {
  /* Laid out itself, because nothing else does it. The app's buttons get their flex box from
     rules keyed on .button-icon (the masked-span icons); this one carries an inline
     .button-icon-svg instead, so it was falling through as display:block and putting the icon
     and the label on the text baseline -- 1.5px high and 3px low respectively. Same values as
     the sort control it stands next to, which lands both of its children dead on centre. */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-height: 34px;
  white-space: nowrap;
}

.pages-header .eyebrow {
  grid-column: 1 / -1;
  grid-row: 2;
}

.pages-list {
  display: grid;
  gap: 10px;
  width: min(100%, 1144px);
  margin-inline: auto;
}

.widgets-library-list {
  align-content: start;
}

.pages-empty,
.page-list-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: var(--shadow);
}

.pages-empty {
  padding: 22px;
  color: var(--muted);
}

.pages-empty h3 {
  margin: 0 0 6px;
  color: var(--ink);
}

.pages-empty p {
  margin: 0;
}

.page-list-card {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 12px;
}

.list-drag-handle {
  width: 18px;
  height: 24px;
  display: inline-block;
  align-self: center;
  justify-self: center;
  opacity: 0.72;
  cursor: grab;
  background: rgba(56, 72, 89, 0.92);
  -webkit-mask: url("assets/drag_indicator.svg") center / contain no-repeat;
  mask: url("assets/drag_indicator.svg") center / contain no-repeat;
}

.page-list-card:hover .list-drag-handle {
  opacity: 0.9;
}

.page-list-card.is-dragging .list-drag-handle {
  cursor: grabbing;
}

.page-list-card.is-reorder-disabled .list-drag-handle {
  opacity: 0.15;
  cursor: default;
}

.page-list-card[draggable="true"] {
  cursor: grab;
}

.page-list-card.is-dragging {
  cursor: grabbing;
  opacity: 0.55;
}

.page-list-card.is-drop-before,
.page-list-card.is-drop-after {
  position: relative;
}

.page-list-card.is-drop-before::before,
.page-list-card.is-drop-after::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  height: 3px;
  border-radius: 999px;
  background: var(--accent-lighter);
  box-shadow: 0 0 0 3px rgba(109, 219, 206, 0.25);
}

.page-list-card.is-drop-before::before {
  top: -7px;
}

.page-list-card.is-drop-after::after {
  bottom: -7px;
}

.page-list-card.is-reorder-disabled {
  cursor: default;
}

.page-list-copy {
  min-width: 0;

  display: flex;
  justify-content: space-between;
  flex-direction: column;
  height: 100%;
}

.page-list-card p,
.page-list-card h3 {
  margin: 0;
}

.page-list-card p {
  color: var(--muted);
  font-size: 11px;
  font-weight: 790;
  text-transform: uppercase;
}

.page-list-card h3 {
  font-size: 16px;
}

.page-list-title {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 3px;
}

.page-list-title h3 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-list-title-icon {
  width: 24px;
  height: 24px;
  display: inline-block;
  flex: 0 0 24px;
  background: var(--accent-light);
  -webkit-mask: url("assets/space_dashboard.svg") center / contain no-repeat;
  mask: url("assets/space_dashboard.svg") center / contain no-repeat;
}

.page-list-side {
  display: grid;
  justify-items: end;
  gap: 8px;
}

.page-list-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
}

.page-list-meta span {
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 790;
}

.page-list-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.page-list-actions .ghost-button {
  padding: 0px 7px;
}
.page-list-actions .ghost-button span {
  font-size: 11.25px;
}

.composer-open-button,
.page-edit-button,
.page-delete-button,
.page-clone-button,
.widget-delete-button,
.widget-source-button,
.widget-settings-button {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-height: 32px;
}

.page-list-actions .ghost-button.icon-only,
.page-list-actions .widget-source-button {
  width: 34px;
  justify-content: center;
  padding-inline: 6px;
}

.clone-button-icon,
.page-list-delete-icon,
.details-button-icon,
.composer-button-icon,
.widget-delete-icon,
.widget-source-button-icon,
.info-button-icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  flex: 0 0 24px;
  background: var(--accent-light);
}

.clone-button-icon {
  -webkit-mask: url("assets/copy_all.svg") center / contain no-repeat;
  mask: url("assets/copy_all.svg") center / contain no-repeat;
}

.page-list-delete-icon {
  -webkit-mask: url("assets/delete.svg") center / contain no-repeat;
  mask: url("assets/delete.svg") center / contain no-repeat;
}

.widget-delete-icon {
  -webkit-mask: url("assets/delete.svg") center / contain no-repeat;
  mask: url("assets/delete.svg") center / contain no-repeat;
}

.details-button-icon {
  -webkit-mask: url("assets/edit_document.svg") center / contain no-repeat;
  mask: url("assets/edit_document.svg") center / contain no-repeat;
}

.composer-button-icon {
  -webkit-mask: url("assets/dashboard_customize.svg") center / contain no-repeat;
  mask: url("assets/dashboard_customize.svg") center / contain no-repeat;
}

.info-button-icon {
  -webkit-mask: url("assets/info.svg") center / contain no-repeat;
  mask: url("assets/info.svg") center / contain no-repeat;
}

.widget-library-title-icon {
  width: 24px;
  height: 24px;
  display: inline-block;
  flex: 0 0 24px;
  background: var(--accent-light);
  -webkit-mask: url("assets/widgets.svg") center / contain no-repeat;
  mask: url("assets/widgets.svg") center / contain no-repeat;
}

.widget-library-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 12px;
  font-weight: 640;
  text-transform: none;
}

.widget-language-meta-label {
  color: var(--muted);
  font-size: 12px;
  font-weight: 760;
}

.widget-language-list {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}

.widget-language-token {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.widget-language-code {
  color: var(--muted);
  font-size: 11px;
  font-weight: 750;
  text-transform: lowercase;
}

.widget-language-flag {
  width: 16px;
  height: 12px;
  border-radius: 2px;
}

.widget-settings-icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  flex: 0 0 24px;
  background: var(--accent-light);
  -webkit-mask: url("assets/manufacturing.svg") center / contain no-repeat;
  mask: url("assets/manufacturing.svg") center / contain no-repeat;
}

.html-source-icon {
  -webkit-mask: url("assets/code_xml.svg") center / contain no-repeat;
  mask: url("assets/code_xml.svg") center / contain no-repeat;
}

.css-source-icon {
  -webkit-mask: url("assets/css.svg") center / contain no-repeat;
  mask: url("assets/css.svg") center / contain no-repeat;
}

.js-source-icon {
  -webkit-mask: url("assets/javascript.svg") center / contain no-repeat;
  mask: url("assets/javascript.svg") center / contain no-repeat;
}

.css-source-icon,
.js-source-icon {
  width: 28px;
  height: 28px;
  flex-basis: 28px;
}

body.preview-mode .workspace {
  width: min(100%, var(--page-max-width));
  grid-template-columns: minmax(0, 1fr);
  justify-content: center;
  margin-inline: auto;
}

body.preview-mode.page-width-full .workspace {
  width: 100%;
}

body.preview-mode .page-panel,
body.preview-mode .inspector-panel {
  display: none;
}

body.preview-mode .canvas-panel {
  grid-column: 1;
  grid-row: 1;
  width: 100%;
  min-height: calc(100vh - 108px);
}

body.preview-mode .canvas-toolbar {
  background: #fff;
}

body.preview-mode .widget-card {
  cursor: default;
}

body.preview-mode .widget-card:hover {
  border-color: var(--line);
  box-shadow: 0 6px 18px rgba(23, 33, 43, 0.08);
}

body.preview-mode .widget-card.is-selected {
  border-color: var(--line);
  box-shadow: 0 6px 18px rgba(23, 33, 43, 0.08);
  outline-color: transparent;
  outline-width: 0;
}

body.preview-mode .resize-grip {
  display: none;
}

body.preview-mode .widget-options-button {
  display: none;
}

.panel,
.canvas-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  min-width: 0;
}

.panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px;
  overflow: hidden;
  position: sticky;
  top: var(--panel-sticky-top);
  max-height: calc(100dvh - var(--panel-sticky-top) - var(--panel-sticky-gap));
}

.page-panel {
  overflow: hidden;
}

.canvas-panel {
  grid-column: 2;
  grid-row: 1;
}

.inspector-panel {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  z-index: 20;
  width: var(--right-panel-width);
  transition: width 180ms ease, box-shadow 180ms ease, transform 180ms ease;
}

.inspector-panel:not(.is-collapsed) {
  box-shadow: 0 18px 52px rgba(23, 33, 43, 0.5);
}

.inspector-panel.is-collapsed {
  width: 44px;
  box-shadow: 0 10px 34px rgba(23, 36, 43, 0.7);
}

.panel-collapsible-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow: auto;
  padding-right: 2px;
}

.page-summary-section {
  flex: 0 0 auto;
  margin-top: 0px;
  padding-top: 24px;
  border-top: solid 1px #e3e3e3;
}

.widgets-section {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.widgets-section .palette {
  max-height: max(180px, calc(100dvh - var(--panel-sticky-top) - 236px));
  overflow: auto;
  padding-right: 4px;
}

/*
 * Keep Page Details pinned beneath the widget palette while collapsed. When it
 * is expanded, preserve the palette's own fixed-height scrollbar and let the
 * details extend beneath it. The outer column then scrolls only as needed to
 * reach the expanded details.
 */
.page-panel .panel-collapsible-content:has(.composer-page-details[open]) {
  overflow-y: auto;
  scrollbar-gutter: stable;
}

.page-panel .panel-collapsible-content:has(.composer-page-details[open]) .widgets-section {
  flex: 0 0 auto;
  min-height: auto;
  overflow: visible;
}

.panel-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
}

.panel-top-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}

.panel-collapse-button {
  width: 30px;
  min-height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--muted);
  font-size: 18px;
  font-weight: 900;
}

.panel-collapse-button:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.panel-collapse-button strong {
  display: none;
}

.panel.is-collapsed {
  align-items: center;
  gap: 0;
  padding: 8px 6px;
  overflow: hidden;
}

.panel.is-collapsed .panel-top-row {
  width: 100%;
  display: block;
}

.panel.is-collapsed .panel-title-with-icon {
  display: none;
}

.panel.is-collapsed .sort-select {
  display: none;
}

.panel.is-collapsed .panel-collapsible-content {
  display: none;
}

.panel.is-collapsed .panel-collapse-button {
  width: 100%;
  min-height: 76px;
  flex-direction: column;
  gap: 6px;
  padding: 8px 0;
  align-self: stretch;
}

.panel.is-collapsed .panel-collapse-button strong {
  display: block;
}

.collapse-panel-icon {
  width: 24px;
  height: 24px;
  background: var(--accent-light);
}

.left-panel-icon {
  -webkit-mask: url("assets/widgets.svg") center / contain no-repeat;
  mask: url("assets/widgets.svg") center / contain no-repeat;
}

.right-panel-icon {
  -webkit-mask: url("assets/manufacturing.svg") center / contain no-repeat;
  mask: url("assets/manufacturing.svg") center / contain no-repeat;
}

.page-summary-icon {
  -webkit-mask: url("assets/edit_document.svg") center / contain no-repeat;
  mask: url("assets/edit_document.svg") center / contain no-repeat;
}

.page-summary-title {
  margin: 0 0 10px;
}

.composer-page-details {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
}

.composer-page-details > summary {
  min-height: 54px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  list-style: none;
}

.composer-page-details > summary::-webkit-details-marker {
  display: none;
}

.composer-page-details > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  margin-left: auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  color: var(--muted);
  transform: rotate(45deg);
  transition: transform 120ms ease;
}

.composer-page-details[open] > summary::after {
  transform: rotate(225deg);
}

/* Direct child only. The grid is what stacks Page / title / code, since all three are inline
   elements -- but as a descendant selector it also caught any span nested deeper and stacked
   that too, which put the copy button on its own line under the code. */
.composer-page-details > summary > span {
  min-width: 0;
  display: grid;
  gap: 3px;
}

.composer-page-details > summary small,
.composer-page-details > summary em {
  color: var(--muted);
  font-size: 11px;
  font-style: normal;
  font-weight: 790;
  text-transform: uppercase;
}

.composer-page-details > summary strong {
  overflow: hidden;
  color: var(--ink);
  font-size: 15px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.composer-page-fields {
  padding: 4px 12px 12px;
}

/* Consecutive check rows in Page Details sat flush against each other, reading as one box
   with two lines rather than two settings. The inspector's rows get their spacing from a
   container gap; this panel is a plain block, so the gap goes between the pair directly. */
.composer-page-fields .check-row + .check-row {
  margin-top: 8px;
}

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

.panel-heading > div:first-child {
  min-width: 0;
}

.panel-heading h2 {
  overflow-wrap: anywhere;
}

.panel h2 {
  font-size: 15px;
}

.panel-title-with-icon {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.panel-title-with-icon .collapse-panel-icon {
  width: 24px;
  height: 24px;
  color: var(--accent-light);
  flex: 0 0 auto;
}

.active-toggle,
.check-row,
.country-item {
  display: flex;
  align-items: center;
  gap: 9px;
}

.disclosure-field-group {
  gap: 8px;
  margin-top: 12px;
}

/* Page Details stacks its blocks in a container with no gap of its own, so each one brings its
   own spacing. Country and rank targeting get theirs from the .disclosure-field-group wrapper
   above; Page styles and Default Widget Styling sit directly in the container and so had none,
   which left their headings butted against the row above and against each other. The same 12px,
   so all four groups read as one evenly spaced set rather than two pairs. */
.composer-page-fields > .inspector-group,
.page-edit-form > .inspector-group {
  margin-top: 12px;
}

/* Country and rank targeting use the same .inspector-group shell as Widget Styling. They
   were a bare uppercase label with a chevron, which read as a heading over the fields that
   followed rather than as a section that opens -- so the fields underneath looked like they
   belonged to it, and the thing you could actually click was the least obvious part. */
.account-grid {
  display: grid;
  gap: 6px;
  padding: 6px 0 10px;
}

.account-item {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface-soft);
  color: var(--ink);
  font-size: 12px;
}

/* A title this page has selected that the current list does not offer -- renamed, or missing
   because the portal could not be reached. Shown so it can be cleared, dimmed so it does not
   read as a normal choice. */
.account-item.is-unavailable {
  border-style: dashed;
  color: var(--muted);
}

/* Why the list above may be short. Sits inside the same container as the checkboxes. */
.account-notice {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.45;
}

.active-toggle {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}

.field,
.field-group {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.field > span,
.field-group > span,
.span-label-row > span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 790;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* A field label plus a small eye-catching marker beside it (currently just "Required" on
   the data source URL, shown only once Data Source is checked -- it lives inside the same
   container the checkbox already shows/hides, so no extra script is needed here). Flex
   rather than leaving it inline: the label text and the marker need a fixed gap between
   them regardless of how long the label happens to be. */
.field-label-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.field-required-badge {
  color: #c0392b;
}

input[type="text"],
input[type="number"],
input[type="datetime-local"],
.field select {
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
}

/* Collapsible groups in the inspector. The panel had grown to where the controls people
   reach for constantly -- span, visibility, data source -- sat above a long run of styling
   fields and a source editor, so every visit meant scrolling past settings that are set
   once and left alone. Closed by default for that reason; the browser keeps each one's
   open state while the panel stays mounted, so a session spent on styling is not punished. */
/* Deliberately identical to .widget-source-editor above: these two are peers in the panel
   and the eye should read them as one kind of thing. Anything that changes there should
   change here. */
.inspector-group {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
}

.inspector-group > summary {
  display: grid;
  gap: 2px;
  padding: 11px 12px;
  cursor: pointer;
  font-weight: 800;
}

.inspector-group > summary small {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
}

/* Shared by every collapsible group heading, in the inspector and in both Page Details
   surfaces, so they stay one size wherever they appear. Inheriting the panel's 1rem made
   these read as page headings rather than as section titles. */
.inspector-group-heading,
.widget-source-heading {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.9rem;
}

.inspector-group-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  background: var(--accent-light);
  -webkit-mask: url("assets/palette.svg") center / contain no-repeat;
  mask: url("assets/palette.svg") center / contain no-repeat;
}

/* Icon per group. The mask is swapped rather than the element, so every heading keeps one
   size, colour and alignment however many groups get added. */
.inspector-group-icon.is-languages {
  -webkit-mask-image: url("assets/globe.svg");
  mask-image: url("assets/globe.svg");
}

.inspector-group-icon.is-countries {
  -webkit-mask-image: url("assets/globe.svg");
  mask-image: url("assets/globe.svg");
}

.inspector-group-icon.is-ranks {
  -webkit-mask-image: url("assets/rank-badge.svg");
  mask-image: url("assets/rank-badge.svg");
}

/* Distinct from the palette the widget styling group uses, because the two sit next to each
   other and the difference between styling the PAGE and styling the widgets on it is exactly
   what a reader is trying to tell apart at that moment. */
.inspector-group-icon.is-page-styles {
  -webkit-mask-image: url("assets/design_services.svg");
  mask-image: url("assets/design_services.svg");
}

/* The group in Page Details wraps its own fields rather than a single rendered block, so
   it needs the padding and stacking the inspector's markup already carries. */
.inspector-group-body {
  display: grid;
  gap: 12px;
  padding: 0 12px 12px;
}

/* The targeting grids are their group's body in their own right, so they take the inset
   without the field stacking: they set their own tighter gap for a list of checkboxes. */
.inspector-group > .country-grid,
.inspector-group > .account-grid {
  padding: 0 12px 12px;
}

/* How many countries or ranks are selected, read at a glance without opening the group. */
.inspector-group-count {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  min-height: 18px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--save);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

/* Add to library sits in the same row as Move Up / Move Down / Remove, which wraps, so it
   takes the full width rather than squeezing in beside them. Accent rather than ghost: it is
   the one action there that creates something. */
/* Full width on its own grid line, like Remove directly beneath it: the inspector's button
   row is a two-column grid, so spanning it is what "full width" means here. Accent rather
   than ghost, because it is the one action in that row that creates something. */
.add-to-library-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  grid-column: 1 / -1;
  width: 100%;
}

.add-to-library-categories {
  display: grid;
  gap: 4px;
  max-height: 190px;
  overflow: auto;
}

.add-to-library-empty {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
}

.add-to-library-new-category {
  margin-top: 8px;
}

/* Copy the page code. Always visible, never hover-gated: an affordance nobody can see is not
   an affordance, and the whole reason this exists is that people could not tell how to get the
   code out. Muted rather than hidden is how it stays quiet without becoming a secret. */
.copy-code-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 120ms ease, background 120ms ease;
}

.copy-code-button:hover {
  background: var(--surface-soft);
  color: var(--ink);
}

/* Confirmation lands on the button itself: the pointer is already there, and a toast for
   something this small would be louder than the action. */
.copy-code-button.is-copied {
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.page-list-code {
  display: flex;
  align-items: center;
  gap: 4px;
}

.code-with-copy {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.input-with-copy {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.input-with-copy input {
  min-width: 0;
  flex: 1 1 auto;
}

/* Page background: a swatch, the value in full, and a way back to nothing. The hex field is
   there because a design hands you a colour as text, and typing it beats hunting for it in
   an eyedropper. */
.page-background-controls {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
}

.page-background-swatch {
  width: 38px;
  height: 32px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
}

.page-background-hex {
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Reads as the selected state when the page is transparent, matching Full/Contained above
   it. It stays clickable there: a control that shows an answer is worth being able to press
   even when pressing it changes nothing. */
.page-background-clear.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

/* The language grid is three columns because it was written for a dialog. In either side
   panel that leaves each name clipped to about five characters ("Engli", "Span") and pushes
   the panel itself into sideways scrolling, which is how the title-translations list came to
   look broken. Both panels get one column and whole words.

   The @media rules on the base grid do not help here: they measure the VIEWPORT, and these
   panels are narrow on a wide screen. */
.page-panel .language-checkbox-grid,
.inspector .language-checkbox-grid {
  grid-template-columns: minmax(0, 1fr);
}

/* Page padding: four sides, one control. Laid out as a row so the four read as a set and
   the whole thing costs one line of the panel rather than four. */
.page-padding-controls {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}

.page-padding-controls label {
  display: grid;
  gap: 3px;
  min-width: 0;
}

/* Spans the four columns and sits right, so it reads as an action ON the row above rather than a
   fifth field in it. */
.page-padding-controls .padding-clear {
  grid-column: 1 / -1;
  justify-self: end;
  margin-top: 2px;
}

.page-padding-controls label span {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
}

.page-padding-controls input {
  width: 100%;
  min-width: 0;
  text-align: center;
}

/* One chevron for both groups, drawn rather than inherited from the platform: the default
   marker cannot sit at the right-hand edge, and both summaries lay their two lines out as
   a grid, which drops the marker entirely in some engines. */
.inspector-group > summary,
.widget-source-editor > summary {
  position: relative;
  padding-right: 30px;
  list-style: none;
}

.inspector-group > summary::-webkit-details-marker,
.widget-source-editor > summary::-webkit-details-marker {
  display: none;
}

.inspector-group > summary::after,
.widget-source-editor > summary::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 12px;
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 120ms ease;
}

.inspector-group[open] > summary::after,
.widget-source-editor[open] > summary::after {
  transform: translateY(-30%) rotate(-135deg);
}

/* Whose data an api-mode preview shows. Sits directly under the resolved URL because the
   two answer one question together: what is fetched, and on behalf of whom. */
.data-source-account {
  display: grid;
  gap: 8px;
  margin-top: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.02);
}

.data-source-account-status {
  color: var(--muted);
  line-height: 1.45;
}

.data-source-preview-as > span {
  font-size: 12px;
  color: var(--muted);
}

/* Nothing to preview as until a focus account sets the ceiling, and an enabled field
   there would invite an ID that can only ever be refused. */
.data-source-preview-as input:disabled {
  background: rgba(0, 0, 0, 0.04);
  cursor: not-allowed;
}

/* A placeholder that reads as a value is worse than no placeholder at all. The data source
   field placeholder is a real endpoint name, so an author scanning the panel sees
   "GetRegionPipeline" and reasonably concludes the source is configured -- then the widget
   renders "Data unavailable" and nothing in the UI suggests the field was simply empty.
   Italic is the part that carries the distinction; the colour alone was not enough. */
input::placeholder,
textarea::placeholder {
  color: var(--muted);
  font-style: italic;
  opacity: 0.75;
}

.page-width-controls {
  display: grid;
  grid-template-columns: minmax(76px, auto) minmax(0, 1fr);
  gap: 8px;
}

.contained-width-control {
  display: grid;
  grid-template-columns: 42px minmax(0, 1fr);
  gap: 6px;
}

.width-mode-button {
  width: 42px;
  min-height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--muted);
  font-size: 13px;
  font-weight: 790;
}

.width-mode-button strong {
  display: none;
}

#fullWidthButton strong,
#pageEditFullWidthButton strong,
#widgetFullWidthButton strong {
  display: inline;
  white-space: nowrap;
}

#fullWidthButton,
#pageEditFullWidthButton,
#widgetFullWidthButton {
  width: auto;
  padding-inline: 10px;
}

.width-mode-button.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.width-icon {
  width: 18px;
  height: 14px;
  display: inline-block;
  border: 2px solid currentColor;
  border-radius: 3px;
  position: relative;
  flex: 0 0 auto;
}

.full-width-icon::before,
.full-width-icon::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 5px;
  border-top: 2px solid currentColor;
}

.full-width-icon::before {
  left: -7px;
}

.full-width-icon::after {
  right: -7px;
}

.contained-width-icon::before,
.contained-width-icon::after {
  content: "";
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 2px;
  background: currentColor;
}

.contained-width-icon::before {
  left: -5px;
}

.contained-width-icon::after {
  right: -5px;
}

.contained-width-input {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px;
}

.contained-width-input input {
  min-height: 42px;
  padding-right: 4px;
}

.contained-width-input span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 790;
}

.contained-width-input input:disabled {
  background: var(--surface-soft);
  color: #8b98a4;
  cursor: not-allowed;
}

input:focus,
textarea:focus,
button:focus-visible {
  outline: 3px solid rgba(34, 108, 99, 0.2);
  outline-offset: 2px;
}

.widget-source-editor {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
}

.widget-source-editor summary {
  display: grid;
  gap: 2px;
  padding: 11px 12px;
  cursor: pointer;
  font-weight: 800;
}

.widget-source-editor summary small {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
}

/* Sizing and layout live with .inspector-group-heading above; these two are peers and
   must not drift apart. */

.widget-source-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  background: var(--accent-light);
  -webkit-mask: url("assets/code_xml.svg") center / contain no-repeat;
  mask: url("assets/code_xml.svg") center / contain no-repeat;
}

.widget-source-fields {
  display: grid;
  gap: 10px;
  padding: 0 10px 10px;
}

.widget-source-fields textarea {
  width: 100%;
  min-height: 120px;
  resize: vertical;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
  font: 12px/1.5 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
  tab-size: 2;
}

body.has-page-modal { overflow: hidden; }

.page-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 48px);
}

.page-modal__backdrop {
  position: absolute;
  inset: 0;
  border: 0;
  background: rgba(10, 18, 25, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.page-modal__window {
  position: relative;
  width: min(100%, 960px);
  max-height: min(88vh, 900px);
  overflow: auto;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.36);
}

.page-modal__close {
  display: grid;
  place-items: center;
  position: sticky;
  z-index: 2;
  top: 12px;
  float: right;
  width: 40px;
  height: 40px;
  margin: 12px 12px -52px 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(23, 33, 43, 0.9);
  color: #fff;
  appearance: none;
  cursor: pointer;
}

.page-modal__close::before,
.page-modal__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 2px;
  border-radius: 999px;
  background: currentColor;
  transform-origin: center;
}

.page-modal__close::before { transform: translate(-50%, -50%) rotate(45deg); }
.page-modal__close::after { transform: translate(-50%, -50%) rotate(-45deg); }

.page-modal__content { padding: 24px; }
.page-modal__content.is-widget-runtime-modal { padding: 16px; }
.page-modal__content:has(.page-modal__media) { padding: 0; }
.page-modal__media { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; background: #000; }

.page-toast-region {
  position: fixed;
  z-index: 310;
  inset-inline: 16px;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.page-toast-region[data-position="top"] { top: 16px; }
.page-toast-region[data-position="bottom"] { bottom: 16px; }

/* Centred, not top-aligned. The circle is 22px and a line of the message is 1.45 x 13px =
   18.85px, so aligning their TOPS leaves the glyph hanging 1.6px low -- measured on the
   portal, and just visible once you know to look. Centring lands them on the same axis with
   no arithmetic to keep in step with the font size.
   The trade for a very long toast is that the icon floats mid-message rather than sitting by
   the first line. These run to one or two lines; if that stops being true the fix is
   align-items: start plus a half-difference margin on the ::before, not a magic number. */
.page-toast {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  align-items: center;
  column-gap: 9px;
  max-width: min(560px, 100%);
  padding: 13px 16px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 9px;
  background: #17212b;
  color: #fff;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 180ms ease, transform 180ms ease;
  pointer-events: auto;
}

.page-toast::before {
  content: "i";
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 2px solid currentColor;
  border-radius: 999px;
  font: 900 13px/1 system-ui, sans-serif;
  grid-column: 1;
  grid-row: 1;
}

.page-toast__content {
  grid-column: 2;
  min-width: 0;
  line-height: 1.45;
}

.page-toast[data-toast-type="alert"] { background: #a92d35; color: #fff; }
.page-toast[data-toast-type="alert"]::before { content: "!"; }
.page-toast[data-toast-type="warn"] { background: #dd8500; color: #fff; }
.page-toast[data-toast-type="warn"]::before { content: "!"; }
.page-toast[data-toast-type="notify"] { border-color: #e5ca6a; background: #fff3bf; color: #513f08; }
.page-toast[data-toast-type="notify"]::before { content: "i"; }
.page-toast[data-toast-type="success"] { background: #15803d; color: #fff; }
.page-toast[data-toast-type="success"]::before { content: "\2713"; }
.page-toast.is-loading::before {
  content: "";
  border: 2px solid currentColor;
  border-right-color: transparent;
  background: transparent;
  animation: toast-spin 760ms linear infinite;
}

.page-toast-region[data-position="top"] .page-toast { transform: translateY(-8px); }
.page-toast.is-visible { opacity: 1; transform: translateY(0); }

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

/* Named .two-fields but it was a single column, so the publish dates stacked and
   ate vertical space. auto-fit pairs them up wherever there is room for two
   columns and drops back to one when there is not, so the wide page-edit dialog
   gets a side-by-side pair while the narrow composer sidebar stays stacked -
   no media query needed. */
.two-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
  margin-top: 12px;
}

.country-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  padding: 6px 0 10px;
}

.country-item {
  min-height: 34px;
  padding: 7px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font-size: 13px;
}

.country-item:has(input:disabled) {
  background: var(--surface-soft);
  color: var(--muted);
  opacity: 0.65;
}

.palette {
  display: grid;
  gap: 10px;
}

.palette-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 34px;
  gap: 6px;
  align-items: stretch;
}

.palette-item {
  display: grid;
  grid-template-columns: 34px 1fr;
  gap: 10px;
  align-items: center;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  text-align: left;
  touch-action: none;
}

.palette-edit-button {
  min-width: 32px;
  min-height: 100%;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--muted);
  font-size: 15px;
  font-weight: 900;
}

.widget-default-edit-icon {
  width: 18px;
  height: 18px;
  display: inline-block;
  background: var(--accent-light);
  -webkit-mask: url("assets/manufacturing.svg") center / contain no-repeat;
  mask: url("assets/manufacturing.svg") center / contain no-repeat;
}

.palette-edit-button:hover,
.palette-edit-button.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.palette-item:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.palette-item.is-touch-dragging {
  border-color: var(--accent);
  background: var(--accent-soft);
  transform: scale(0.98);
}

.summary-icon {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--accent-dark);
  font-weight: 900;
}

.palette-icon {
  width: 34px;
  height: 34px;
  display: inline-block;
  flex: 0 0 34px;
  background: var(--accent-light);
  -webkit-mask: url("assets/widgets.svg") center / contain no-repeat;
  mask: url("assets/widgets.svg") center / contain no-repeat;
}

.palette-item strong,
.palette-item small {
  display: block;
}

.palette-item small {
  margin-top: 2px;
  color: var(--muted);
}

.palette-item small.palette-language-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}

.palette-language-meta .widget-language-meta-label {
  font-size: 11px;
}

.palette-language-meta .widget-language-list {
  gap: 4px;
}

.palette-language-meta .widget-language-flag {
  width: 16px;
  height: 11px;
}

.palette-language-meta .widget-language-code {
  font-size: 10px;
  display: none;
}

.widget-default-editor,
.widget-source-dialog,
.widget-import-language-dialog {
  position: fixed;
  inset: 0;
  z-index: 180;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 24px;
  border: 0;
  border-radius: 0;
  background: rgba(17, 24, 32, 0.42);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.page-edit-dialog {
  position: fixed;
  inset: 0;
  z-index: 175;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 24px;
  background: rgba(17, 24, 32, 0.42);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/* 600px is not arbitrary: it is what fits three language columns without them crushing.
   The grid below steps down to two and then one as the space goes. */
.widget-default-modal,
.widget-source-modal,
.widget-import-language-modal,
.page-edit-modal {
  width: min(100%, 600px);
  max-height: min(720px, calc(100vh - 48px));
  overflow: auto;
  /* No top padding: it belongs to the sticky heading below, which spans the full width and
     has to start at the very top edge. Left here, sticky content would scroll through it. */
  padding: 0 14px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: var(--shadow);
}

.widget-source-modal {
  width: min(100%, 780px);
}

/* Same shell as the source dialog, but wide enough to hold a 12-column widget at the width
   previewStageWidth asks for (1180px) plus the modal's own padding. */
.widget-preview-dialog {
  position: fixed;
  inset: 0;
  z-index: 180;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 24px;
  border: 0;
  border-radius: 0;
  background: rgba(17, 24, 32, 0.42);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.widget-preview-modal {
  width: min(100%, 1240px);
  max-height: min(860px, calc(100vh - 48px));
  overflow: auto;
  /* No top padding, for the same reason as the other modals: the sticky heading below owns
     that space and content would otherwise scroll through it. */
  padding: 0 14px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: var(--shadow);
}

.widget-preview-modal > .panel-heading {
  position: sticky;
  top: 0;
  z-index: 2;
  margin: 0 -14px 14px;
  padding: 14px;
  background: #fff;
  border-bottom: 1px solid var(--line);
}

/* The stage stands in for the canvas: it centres the card and holds it at the span width the
   widget was authored against, rather than letting it fill the dialog. The checkered ground
   is deliberate -- it makes a widget with a transparent background readable as transparent
   instead of looking like it failed to paint. */
.widget-preview-stage {
  display: flex;
  justify-content: center;
  padding: 20px;
  border-radius: 8px;
  background-color: var(--surface-muted, #f4f6f8);
  background-image:
    linear-gradient(45deg, rgba(17, 24, 32, 0.04) 25%, transparent 25%, transparent 75%, rgba(17, 24, 32, 0.04) 75%),
    linear-gradient(45deg, rgba(17, 24, 32, 0.04) 25%, transparent 25%, transparent 75%, rgba(17, 24, 32, 0.04) 75%);
  background-position: 0 0, 8px 8px;
  background-size: 16px 16px;
}

.widget-preview-stage > .widget-card {
  width: 100%;
  /* Set per widget by openWidgetPreviewDialog from its defaultSpan. The fallback matters only
     if the property is missing, which would mean the card had been mounted some other way. */
  max-width: var(--preview-stage-width, 590px);
}

.widget-preview-icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  flex: 0 0 24px;
  background: var(--accent-light);
  -webkit-mask: url("assets/visibility.svg") center / contain no-repeat;
  mask: url("assets/visibility.svg") center / contain no-repeat;
  /* Measured with getBBox, not eyeballed. In Material Symbols' "0 -960 960 960" box the eye's
     ink spans -800..-200, centring on -500 against the box's own -480 -- so it sits 20/960
     high, where delete, code_xml, css and javascript all centre exactly. At a 20px icon that
     is 0.42px, which is enough to read as riding high beside them. Nudged down by exactly
     that, so the shift is derived from the glyph rather than picked until it looked right.
     The gear (manufacturing) is 0.42px LOW by the same measure: pre-existing, left alone. */
  -webkit-mask-position: center calc(50% + 0.42px);
  mask-position: center calc(50% + 0.42px);
}
/* Keep a modal's actions reachable. These modals scroll (max-height + overflow), and a long
   form -- the page details one runs well past a screen -- used to carry Save and Done off the
   top, leaving no way to commit without scrolling back up.

   The negative margin cancels the modal's own 14px padding so the bar spans edge to edge,
   then puts it back as its own padding; without that, content scrolls visibly through the gap
   either side of it. Scoped to these four rather than .panel-heading generally, because that
   class is shared with static panels that neither scroll nor want a border. */
.widget-default-modal > .panel-heading,
.widget-source-modal > .panel-heading,
.widget-import-language-modal > .panel-heading,
.page-edit-modal > .panel-heading {
  position: sticky;
  top: 0;
  z-index: 2;
  /* Negative side margins only, so the bar spans edge to edge. It previously pulled upward
     too, which was the bug: in a scroll container sticky top:0 is the PADDING edge, so the
     bar dropped 14px on sticking and content scrolled visibly through the gap above it. The
     modal now carries no top padding and this owns that space instead. */
  margin: 0 -14px 14px;
  padding: 14px;
  background: #fff;
  border-bottom: 1px solid var(--line);
}


.widget-import-language-modal {
  width: min(100%, 560px);
}

.modal-helper-text {
  margin: 10px 0 14px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}

/* Three across at full width, two below 600 and one below 480. Keyed to the modal's own
   width rather than the viewport: these grids sit inside it, so the viewport is the wrong
   thing to ask -- but container queries are not available here, so the breakpoints mirror
   the modal's max width instead. */
.language-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

/* The import dialog's Page Languages accordion is .inspector-group, shared with the
   inspector's, so it needs no styling of its own. These two rules are the only difference:
   spacing from the field above it, and insetting the body from the group's own border, which
   the inspector does not need because its grid is the whole body. */
.widget-import-language-modal > .inspector-group.page-languages {
  margin-top: 14px;
}

.widget-import-language-modal > .inspector-group.page-languages > :not(summary) {
  margin-left: 12px;
  margin-right: 12px;
}

.widget-import-language-modal > .inspector-group.page-languages > .language-checkbox-grid {
  margin-bottom: 12px;
}

/* The badges are decoration on a summary, not controls: without this the disclosure's pointer
   and the badge's own cursor disagree as you cross the row. */
.inspector-group > summary .widget-translation-badges .translation-badge {
  cursor: inherit;
}

/* Same technique and the same reason as .field-info-modal's footer: this modal scrolls as one
   box, so the actions are inside the scroll region rather than a fixed sibling outside it.
   Negative side margins cancel the modal's 14px padding so the bar spans edge to edge, and
   the shadow reads upward onto whatever scrolls past beneath. Without this the buttons sat
   below sixteen languages and were reachable only by scrolling to the very bottom. */
.widget-import-language-modal > .modal-footer-actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin: 14px -14px 0;
  padding: 12px 14px 14px;
  background: #fff;
  border-top: 1px solid var(--line);
  box-shadow: 0 -10px 14px -10px rgba(23, 33, 43, 0.28);
}

/* Dead space under the sticky heading, in three parts, all of which were stacking: the
   heading's own padding-bottom: 14px, then its margin-bottom: 14px, then
   .modal-helper-text's margin-top: 10px -- 38px of nothing between the title and the first
   line of content. The margins do not collapse because the heading is a sticky flex item.
   Zeroing the top of whatever follows and halving the heading's own two gives 15px.
   Scoped to this modal, which is both the page and the widget import dialog, rather than the
   shared sticky-heading rule above: the wider modals it also serves have room for the space
   and use it to separate a heading that carries Save and Cancel from the form below. */
.widget-import-language-modal > .panel-heading {
  padding-bottom: 7px;
  margin-bottom: 7px;
}

/* The last and largest piece of it: nothing had ever overridden the h2's user-agent margin,
   which at this font size is 0.83em -- 19.92px above the title and 19.92px below, inside the
   heading. The eyebrow's own 5px is enough to separate it from the title, and the heading's
   padding is enough below. */
.widget-import-language-modal > .panel-heading h2 {
  margin: 0;
}

.widget-import-language-modal > .panel-heading + * {
  margin-top: 0;
}

/* Separates the languages this page IS in from the rest of the list. Spans every column so it
   still divides the row in the three-column dialog, and carries its own margin because the
   grid gap alone reads as an accident rather than a break. */
.language-check-divider {
  grid-column: 1 / -1;
  height: 1px;
  margin: 4px 0;
  background: var(--line);
}

@media (max-width: 640px) {
  .language-checkbox-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px) {
  .language-checkbox-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The page's languages, under its title in the Pages list. Wraps rather than pushing the
   row wider, since a page in eight languages should not reflow the card. */
.page-list-languages {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

.page-list-languages:empty {
  display: none;
}

/* Inside the accordion summary now, so it needs its own breathing room rather than the gap
   it used to get from being a sibling of the grid. */
.page-languages > summary .widget-translation-badges {
  margin-top: 8px;
}

/* Translate carries the primary colour exactly when pressing it would do something, so the
   page's own Save changes stays the last step rather than competing with it. */
.ghost-button.is-pending {
  /* --save, not --accent: this is the blue of Save changes, and it means the same thing here
     -- there is unsaved intent sitting in front of you. The greens are for Publish and
     Export, which are the steps AFTER this one. */
  background: var(--save);
  border-color: var(--save);
  color: #fff;
}

.ghost-button.is-pending:hover {
  filter: brightness(1.06);
}

/* The margin follows the button. It used to close the gap above a trailing button; now it
   opens the one below a leading one, between Translate and the language list it heads. */
/* Sits in the accordion summary under the badges, so the margin separates it from those
   rather than from the panel above. Full width so it reads as the panel's own action and not
   as a third badge. */
.page-translate-button {
  width: 100%;
  margin-top: 10px;
}

/* Explicit, because the button's own display would otherwise beat the hidden attribute. */
.page-translate-button[hidden] {
  display: none;
}

/* Raised while a translation pass runs. Blurred rather than dimmed so it reads as "busy, wait"
   rather than "disabled, something is wrong", and it swallows every click underneath. */
.app-busy-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  backdrop-filter: blur(2px);
  background: rgba(23, 33, 43, 0.12);
  cursor: progress;
}

.app-busy-overlay[hidden] {
  display: none;
}

body.is-translating {
  cursor: progress;
}

/* A declared language the page has not finished translating. Still selectable -- previewing
   it is how the author sees what is outstanding -- but it should not look finished. */
.language-option.is-incomplete strong::after {
  content: "*";
  margin-left: 2px;
  color: var(--danger);
  font-weight: 900;
}

.language-check-item {
  min-width: 0;
  min-height: 36px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 7px;
  padding: 8px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  font-size: 12px;
  font-weight: 720;
}

.language-check-item strong {
  color: var(--muted);
  font-size: 10px;
  font-weight: 850;
}

.language-check-name {
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

/* The name text, clipped rather than left to spill. The item's middle column is already
   minmax(0, 1fr) so the column itself shrinks correctly, but nothing stopped the text inside
   it overflowing -- at two columns on a 353px screen it ran straight over the language code
   to its right, reading "EnglishEN". Ellipsis instead, so this cannot come back the next time
   the list is narrow or a locale carries longer names. */
.language-check-name > span:not(.language-flag) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.language-check-name .language-flag {
  width: 18px;
  height: 12px;
  flex: 0 0 auto;
}

/* Mirror of the header Save changes button, so edits made in the inspector
   have the save action in view. Same handler and visibility state. */
.inspector-save-button {
  width: 100%;
  margin: 4px 0 10px;
}

/* Two-button footer for the import mismatch confirm. */
.import-mismatch-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.widget-data-source {
  margin-top: 12px;
}

/* The section heading is the toggle: unchecked collapses the whole control so
   the inspector stays short. Styled to match a .field-group > span label. */
.data-source-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.data-source-toggle > span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 790;
  letter-spacing: 0;
  text-transform: uppercase;
}

.data-source-fields {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

.data-source-fields[hidden] {
  display: none;
}

/* Two mode boxes stacked, not side by side: this sits in a fixed-width sidebar panel
   (both the per-instance and the library-default data source editors), never a wide
   canvas area, so there is no width worth reflowing at. Reuses .check-row so the
   radios match the existing toggle boxes. */
.data-source-modes {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.data-source-modes .check-row {
  display: flex;
  gap: 10px;
  margin: 0;
  cursor: pointer;
}

.data-source-modes .check-row:has(input:checked) {
  border-color: var(--accent, #2f6fed);
  background: var(--accent-soft, #f2f6ff);
}

.data-source-modes input[type="radio"] {
  margin-top: 2px;
  flex: 0 0 auto;
}

.widget-data-source .data-source-url {
  margin-top: 10px;
  margin-bottom: 0;
}

.widget-data-source .data-source-hint {
  margin-top: 6px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

/* A saved page's code is read-only. Muted rather than greyed-out-disabled, because it stays
   selectable on purpose: it is what an author copies into a sitemap entry. */
input[type="text"].is-locked {
  background: var(--surface-muted, #f4f4f5);
  color: var(--muted);
  cursor: default;
}

/* The resolved offset under the publish window. Same treatment as the data-source hint:
   it reports what the fields above actually mean, rather than labelling them. */
.publish-window-hint {
  margin: -8px 0 14px;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
}

.publish-window-hint:empty {
  display: none;
}

.data-source-resolved {
  display: block;
  margin-top: 6px;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
  /* The expansion is a long URL and must not stretch the panel. */
  overflow-wrap: anywhere;
}

.data-source-inheritance {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.data-source-inheritance .ghost-button {
  flex-shrink: 0;
}

.widget-translation-settings {
  margin-top: 12px;
}

.widget-translation-header {
  min-height: 32px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.widget-translation-header > span {
  color: var(--muted);
  font-size: 11px;
  font-weight: 850;
  text-transform: uppercase;
}

/* A header that is itself the disclosure control, so the label and the chevron are one target
   rather than a button sitting next to inert text. */
.disclosure-header {
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}

/* Categories read as an accordion, matching Page Styles and Allowed Countries. The wrapper
   borrows .inspector-group's box; this gives its BUTTON header the padding and weight a
   <summary> would have, so the toggle underneath stays a button and keeps its aria-expanded
   rather than being rebuilt as a <details>. */
.inspector-group > .disclosure-header {
  padding: 11px 12px;
  font-size: 0.9rem;
  font-weight: 800;
}

/* The body sits inside the same box now, so it needs the inset the wrapper used to give it. */
.inspector-group > .widget-translation-badges,
.inspector-group > .widget-category-grid,
.inspector-group > .widget-category-add {
  padding: 0 12px 12px;
}

.inspector-group > .widget-category-grid[hidden],
.inspector-group > .widget-category-add[hidden] {
  display: none;
}

/* A category heading that can be dragged says so, and shows where it will land. Matches the
   widget rows, which already carry a grab cursor in Custom sort. */
.widget-group > summary.is-reorderable {
  cursor: grab;
}

.widget-group.is-dragging {
  opacity: 0.5;
}

.widget-group.is-drop-target > summary {
  box-shadow: inset 0 2px 0 var(--accent);
}

.disclosure-chevron {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  background: var(--muted);
  transition: transform 0.12s ease;
  -webkit-mask: url("assets/chevron.svg") center / contain no-repeat;
  mask: url("assets/chevron.svg") center / contain no-repeat;
}

.disclosure-header[aria-expanded="true"] .disclosure-chevron {
  transform: rotate(180deg);
}

.disclosure-header:hover > span {
  color: var(--ink);
}

/* The chevron is a mask, so its colour is the background it shows through. */
.disclosure-header:hover .disclosure-chevron {
  background-color: var(--ink);
}

/* The Page Details block puts two buttons in the header. It is a <span>, so the label
   styling above would otherwise make them small, uppercase and muted. */
.widget-translation-header > .page-translation-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
  text-transform: none;
}

.widget-translation-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.translation-badge {
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  border: 1px solid rgba(71, 186, 235, 0.45);
  border-radius: 999px;
  background: rgba(71, 186, 235, 0.16);
  color: var(--accent-dark);
}

.translation-badge .language-flag {
  width: 18px;
  height: 12px;
  flex: 0 0 auto;
}

.translation-badge strong {
  font-size: 11px;
  font-weight: 850;
}

.translation-empty {
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
}

/* A language badge doubles as the button that opens its wording below. Pending ones are
   outlined rather than filled, so "translated" and "queued for translation" read apart at a
   glance instead of only in the tooltip. */
.translation-badge-button {
  cursor: pointer;
  font: inherit;
}

.translation-badge-button.is-pending,
.translation-badge.is-pending {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
}

.translation-badge-button.is-active {
  border-color: var(--accent-dark);
  background: var(--accent-dark);
  color: #fff;
}

.widget-translation-editor {
  margin-top: 10px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-muted, #f7f8fa);
}

.widget-translation-editor[hidden] {
  display: none;
}

.widget-translation-editor-head {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: 8px;
}

.widget-translation-editor-head strong {
  font-size: 12px;
  font-weight: 850;
}

.widget-translation-editor-head small {
  color: var(--muted);
  font-size: 11px;
  font-weight: 650;
}

/* Capped and scrolled: a page-sized widget can carry fifty strings, and an uncapped list
   pushes Save off the bottom of a dialog that is already scrolling. */
.widget-translation-rows {
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.widget-translation-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  align-items: start;
  gap: 8px;
}

.widget-translation-source {
  min-width: 0;
  padding-top: 7px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.widget-translation-value {
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 650;
  line-height: 1.35;
  resize: vertical;
}

/* Locked rows read as a value, not a disabled field: a greyed-out box invites clicking it to
   find out why it is greyed out. */
/* Marks the title and name rows in the widget translation editor, so an authored field is
   not mistaken for one of the markup strings that happens to read the same. */
.widget-translation-authored {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--line);
  color: var(--muted);
  font-size: 9px;
  font-style: normal;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}

.widget-translation-locked {
  min-width: 0;
  padding: 6px 8px;
  border: 1px dashed var(--line);
  border-radius: 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.widget-translation-value:focus-visible {
  outline: 2px solid var(--accent-dark);
  outline-offset: 1px;
}

@media (max-width: 620px) {
  .widget-translation-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .widget-translation-source {
    padding-top: 0;
  }
}

.widget-translation-language-grid {
  margin-top: 10px;
}

.widget-translation-language-grid[hidden] {
  display: none;
}

.modal-footer-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 14px;
}

.modal-footer-actions button,
.modal-footer-actions .field-info-download {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

/* The download is an anchor so it stays a real, right-clickable link, which means it
   arrives with none of the button reset the sibling buttons get from the element itself. */
.field-info-download {
  text-decoration: none;
  cursor: pointer;
  font-size: inherit;
}

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

.modal-footer-actions .button-icon-svg {
  flex: 0 0 auto;
}

#widgetSourceSubtitle {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
}

.widget-source-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 7px;
}

#widgetSourceCode {
  width: 100%;
  min-height: min(58vh, 520px);
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #101820;
  color: #f5fbff;
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
  font-size: 12px;
  line-height: 1.55;
  resize: vertical;
}

#widgetSourceCode[readonly] {
  cursor: default;
}

#widgetSourceCode.is-editing {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(71, 186, 235, 0.2);
}

.page-edit-form {
  display: grid;
}

.page-edit-active {
  min-height: 34px;
  justify-content: flex-start;
  margin-top: 2px;
}

.page-edit-danger-actions {
  display: flex;
  justify-content: flex-start;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

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

.modal-actions button {
  white-space: nowrap;
}

.modal-save-menu {
  flex: 0 0 auto;
  position: relative;
}

.widget-default-save-options {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 190;
  width: min(270px, calc(100vw - 48px));
  display: grid;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 12px 30px rgba(23, 33, 43, 0.16);
  transform-origin: top right;
  animation: exportMenuIn 120ms ease-out;
}

.widget-default-save-options[hidden] {
  display: none;
}

.widget-default-save-notice {
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: 12px;
  font-weight: 760;
  line-height: 1.35;
}

.widget-default-save-notice[hidden] {
  display: none;
}

/* white-space: normal because a button does NOT wrap by default: the UA style computes to
   nowrap, so "Apply to all Trend Chart widgets on this page" -- the widget's name is spliced
   in, so the length is whatever the widget is called -- ran 108px past this 265px menu, out
   through the modal's right edge, and gave the modal a horizontal scrollbar. Padding moved
   onto both axes for the second line. */
.widget-default-save-options button {
  min-height: 38px;
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font-weight: 790;
  text-align: left;
  white-space: normal;
  line-height: 1.3;
}

.widget-default-save-options button:hover,
.widget-default-save-options button:focus-visible {
  border-color: var(--save);
  background: var(--save-soft);
  color: var(--save-dark);
}

.pixel-input-row {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px;
}

.pixel-input-row span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 790;
}

.canvas-panel {
  display: flex;
  flex-direction: column;
  min-height: 680px;
  overflow: hidden;
}

.canvas-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px;
  border-bottom: 1px solid var(--line);
}

.canvas-toolbar h2 {
  font-size: 22px;
}

.toolbar-meta {
  flex-wrap: wrap;
  justify-content: flex-end;
  color: var(--muted);
  font-size: 13px;
}

.toolbar-meta span {
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
}

/*
 * No padding, deliberately.
 *
 * This element is the editor's viewport onto the page, not part of the page: it exists only in
 * index.html and is never emitted into an export or a BP page, both of which build their own
 * container and apply pagePaddingCss() to it. So any padding here is a gap the author sees in
 * Design mode, cannot remove from Page Details, and will not get in the published result -- the
 * canvas quietly disagreeing with the output it is meant to be previewing.
 *
 * The page's own spacing lives on .page-content-shell as --page-padding-*, which IS editable from
 * Page Details and does publish. That is the padding an author should be reaching for.
 *
 * Two rules further down used to override this: 14px at top level, which applied always and made
 * the value here dead, and 12px under 560px. Both removed.
 */
.canvas-wrap {
  position: relative;
  flex: 1;
  overflow: auto;
  /*
   * Top only, and editor-only -- see below. The language flags and the auto-adjusted notice are
   * positioned at top: -12px, so they deliberately straddle their card's top edge. This element
   * is the scroll container, so on the FIRST row there is nothing above the card to overhang
   * into and both badges were clipped in half. It went unnoticed while the canvas carried 14px
   * of padding on every side and only showed up once that came off.
   *
   * 14px rather than 12px: the badges have a border and a shadow that sit outside the -12px.
   */
  padding: 14px 0 0;
  background:
    linear-gradient(90deg, rgba(71, 186, 235, 0.5) 1px, transparent 1px) 0 0 /
      calc(100% / 12) 100%,
    #fbfcfd;
}

body.grid-hidden .canvas-wrap,
body.preview-mode .canvas-wrap {
  background: #fbfcfd;
}

/* Preview shows the published geometry, nothing else. Both badges the padding above exists for
   are hidden here, so the space would be a gap the author sees and the advisor never gets --
   which is the whole reason the canvas padding came off in the first place. */
body.preview-mode .canvas-wrap {
  padding-top: 0;
}

.drop-hint {
  position: absolute;
  inset: 18px;
  display: none;
  place-items: center;
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius);
  color: var(--muted);
  font-weight: 790;
  pointer-events: none;
}

.canvas-wrap.is-empty .drop-hint {
  display: grid;
}

.composer-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-flow: dense;
  grid-auto-rows: minmax(28px, auto);
  /* Page-level "Widget gap" setting; adaptive fit reads the resolved columnGap
     back off this element, so span maths follows automatically. */
  gap: var(--widget-gap, 14px);
  align-items: stretch;
  align-content: start;
  min-height: 100%;
}

.composer-grid.drag-over {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
}

.widget-card {
  --span: 6;
  /* Imported pages set this from the source grid, so a dashboard's full-height left column
     stays full height instead of being flattened into the first row and stretching every
     card beside it to match. Everything else stays on one row and never notices. */
  --row-span: 1;
  grid-column: span var(--span);
  grid-row: span var(--row-span);
  position: relative;
  display: flex;
  min-width: 0;
  min-height: var(--widget-min-height, 150px);
  border: 1px solid var(--widget-border-color, var(--line));
  border-radius: var(--widget-radius, var(--radius));
  background: var(--widget-background-color, #fff);
  box-shadow: 0 6px 18px rgba(23, 33, 43, 0.08);
  transition:
    border-color 160ms ease,
    box-shadow 160ms ease,
    transform 160ms ease;
  touch-action: none;
}

body.preview-mode .widget-card {
  touch-action: pan-y;
}

.widget-card:hover {
  border-color: var(--muted-lighter);
  box-shadow: 0 10px 28px rgba(34, 108, 99, 0.16);
  outline: 2px dashed var(--muted-lighter);
  outline-offset: -2px;
}

body.preview-mode .widget-card:hover {
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
  outline: 0;
  outline-offset: 0;
}

.widget-card.is-selected {
  border-color: var(--accent-lighter);
  box-shadow: 0 10px 28px rgba(34, 108, 99, 0.16);
  outline: 4px solid var(--accent-lighter);
  outline-offset: -4px;
}
.widget-card.is-selected:hover {
  outline-style: solid;
}

body.preview-mode .widget-card.is-selected,
body.preview-mode .widget-card.is-selected:hover {
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
  outline: 0;
  outline-offset: 0;
}

/* The rules above exist to strip authoring highlights in Preview, but they hard-code
   the theme line colour, which would repaint a widget's own border on hover or
   selection. Fall back to the widget's colour instead. Extra class = higher
   specificity, so this wins regardless of source order. */
body.preview-mode .composer-grid .widget-card:hover,
body.preview-mode .composer-grid .widget-card.is-selected,
body.preview-mode .composer-grid .widget-card.is-selected:hover {
  border-color: var(--widget-border-color, var(--line));
}

/* Shadowless must stay shadowless whatever the pointer is doing. The hover and
   selected rules above re-declare box-shadow at higher specificity, so a plain
   .is-shadowless rule would lose the moment you moused over the card - the same
   trap the border fix hit. Match their specificity to win. */
.widget-card.is-shadowless:hover,
body.preview-mode .composer-grid .widget-card.is-shadowless,
body.preview-mode .composer-grid .widget-card.is-shadowless:hover,
body.preview-mode .composer-grid .widget-card.is-shadowless.is-selected {
  box-shadow: none;
}

.widget-card.is-dragging {
  opacity: 0.44;
  transform: scale(0.98);
}

.widget-card.is-drop-before,
.widget-card.is-drop-after {
  box-shadow: 0 10px 28px rgba(34, 108, 99, 0.16);
}

.widget-card.is-drop-before::after,
.widget-card.is-drop-after::after {
  content: "";
  position: absolute;
  top: -4px;
  bottom: -4px;
  z-index: 6;
  width: 5px;
  border-radius: 999px;
  background: var(--accent-lighter);
  box-shadow: 0 0 0 4px rgba(91, 221, 211, 0.18);
  pointer-events: none;
}

.widget-card.is-drop-before::after {
  left: -10px;
}

.widget-card.is-drop-after::after {
  right: -10px;
}

body.is-pointer-dragging {
  cursor: grabbing;
  user-select: none;
}

.widget-card.is-breakpoint-hidden {
  border-style: dashed;
  background: repeating-linear-gradient(
      -45deg,
      rgba(104, 116, 130, 0.06),
      rgba(104, 116, 130, 0.06) 8px,
      rgba(255, 255, 255, 0.86) 8px,
      rgba(255, 255, 255, 0.86) 16px
    ),
    #fff;
  opacity: 0.72;
}

.widget-card.is-breakpoint-hidden .widget-container {
  filter: grayscale(0.25);
}

.widget-card.is-breakpoint-hidden::before {
  content: "Hidden at breakpoint";
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  padding: 4px 7px;
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
}

.composer-grid.is-measuring-scroll .widget-card.is-scrollable,
.widget-card.is-scrollable.has-row-height {
  height: var(--row-height, 150px);
  /* Deliberately NOT overflow: hidden. It used to be, and it clipped the language flags
     and the autofit badge, which straddle the top edge at top:-12px -- so a scrollable
     widget was the one card in the grid whose flags sat inside it, over its own title.
     Nothing needs the card to clip: content is contained by .widget-body's own
     overflow: auto, measured as staying 1px inside the card even at a capped height. */
}

.widget-container {
  container-type: inline-size;
  container-name: widget;
  width: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  padding: 14px;
}

.composer-grid.is-measuring-scroll .widget-card.is-scrollable .widget-container,
.widget-card.is-scrollable.has-row-height .widget-container {
  height: 100%;
  min-height: 0;
}

.widget-card.is-widget-contained .widget-container {
  max-width: min(100%, var(--widget-max-width));
  margin-inline: auto;
}

.widget-body {
  flex: 1;
  position: relative;
}

/* Mirrors the element floor that exported pages apply inside .pc-root, so what
   you compose here matches what the export renders. Values match the user-agent
   defaults except for the margin/padding reset. See STYLE_ISOLATION_NOTES.md. */
.widget-body :where(h1,h2,h3,h4,h5,h6,p,ul,ol,li,dl,dt,dd,figure,blockquote,pre,hr,table,fieldset,legend) {
  margin: 0;
  padding: 0;
}

/* Sectioning elements. Mirrored here for the same reason as the rest of the floor: the
   composer has no hostile host of its own, but if it does not apply the same neutralisation
   then a widget that looks right while being composed can still break once the Business
   Portal styles its bare `section` elements -- which is exactly what happened. */
.widget-body :where(section,article,aside,nav,header,footer,main,div,address,figcaption) {
  display: block;
  width: auto;
  max-width: none;
  min-width: auto;
  margin: 0;
  padding: 0;
  float: none;
}

.widget-body :where(h1,h2,h3,h4,h5,h6) {
  font-weight: 700;
  line-height: normal;
}

.widget-body :where(ul,ol) {
  list-style: none;
}

.widget-body > .widget-action-blocker {
  display: grid !important;
  position: absolute !important;
  inset: 0 !important;
  z-index: 3 !important;
  place-items: center !important;
  min-width: 0 !important;
  min-height: 0 !important;
  margin: 0 !important;
  padding: 12px !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  color: var(--ink) !important;
  cursor: not-allowed !important;
  text-align: center !important;
  font: inherit !important;
}

body.preview-mode .widget-body > .widget-action-blocker {
  display: none !important;
}

.composer-grid.is-measuring-scroll .widget-card.is-scrollable .widget-body,
.widget-card.is-scrollable.has-row-height .widget-body {
  min-height: 0;
  overflow: auto;
  padding-right: 4px;
}

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

.widget-language-overlay {
  position: absolute;
  top: -12px;
  left: 10px;
  z-index: 9;
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  /* Leave room for the auto-adjusted notice on the right. */
  max-width: calc(100% - 130px);
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 6px 14px rgba(23, 33, 43, 0.12);
  opacity: 0;
  pointer-events: none;
  transition: opacity 140ms ease;
}

.widget-card:hover .widget-language-overlay {
  opacity: 1;
}

/* The rule that used to tuck these two badges to top: 6px on a scrollable card is gone.
   It was working around the card's own overflow: hidden, and the cure showed as the
   disease: the badges were visible but sat inside the card over its title, unlike every
   other card in the grid. The clip is what was wrong, and it has been removed above. */

body.preview-mode .widget-language-overlay {
  display: none;
}

/* One row for every right-hand card badge. They used to be positioned individually, which
   was fine while there was only one: a second badge landed on top of the first. */
.widget-card-badges {
  position: absolute;
  top: -12px;
  right: 10px;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

body.preview-mode .widget-card-badges {
  display: none;
}

/* Data source on, no focus account chosen in the portal. Worth shouting about: the widget
   looks like it is simply showing sample data, and the network tab shows no failed request
   to explain why, because resolveUrl throws before anything is sent. */
.widget-focus-warning {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border: 1px solid var(--danger);
  border-radius: 999px;
  background: #fdecec;
  color: var(--danger);
  font-size: 10px;
  font-weight: 790;
  white-space: nowrap;
  pointer-events: auto;
  cursor: pointer;
  text-decoration: none;
}

/* Filled on hover rather than underlined: the badge is a pill, and an underline inside one
   reads as a rendering fault. */
a.widget-focus-warning:hover,
a.widget-focus-warning:focus-visible {
  background: var(--danger);
  color: #fff;
}

/* Deliberately quieter than .widget-autofit-notice: that one flags something the author
   should fix, this one is a standing statement of fact about a widget with no data source.
   Paired copy in shared/widget-presentation.css, which is what the BP and exports load --
   the composer does not use that file, so these two blocks travel together. */
.widget-mock-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: auto;
}

/* The replacement for a widget whose configured data source produced nothing. Sized to hold
   the card's shape rather than collapse it, so a dashboard does not reflow into a jumble the
   moment one endpoint is down. */
.widget-no-data {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  padding: 20px 16px;
  border: 1px dashed var(--line-strong);
  border-radius: 10px;
  background: var(--surface-soft);
  text-align: center;
}

.widget-no-data__message {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.widget-autofit-notice {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border: 1px solid #d97706;
  border-radius: 999px;
  background: #fff7ed;
  color: #b45309;
  font-size: 10px;
  font-weight: 790;
  white-space: nowrap;
  pointer-events: auto;
}

body.preview-mode .widget-autofit-notice {
  display: none;
}

.widget-options-button {
  width: 30px;
  height: 30px;
  display: inline-grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--save);
  color: #fff;
  box-shadow: 0 6px 14px rgba(22, 115, 209, 0.24);
  opacity: 0.92;
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 8;
}

.widget-options-button:hover,
.widget-options-button:focus-visible {
  background: var(--save-dark);
  opacity: 1;
}

.widget-options-button:focus-visible {
  outline: 3px solid var(--save-soft);
  outline-offset: 2px;
}

.widget-options-button-icon {
  width: 18px;
  height: 18px;
  display: block;
  background: currentColor;
  -webkit-mask: url("assets/settings_b_roll.svg") center / contain no-repeat;
  mask: url("assets/settings_b_roll.svg") center / contain no-repeat;
}

.widget-header h3 {
  margin: 3px 0 0;
  font-size: 18px;
  line-height: 1.2;
}

/* "Show title" off. Design mode keeps the text on screen but marks it, so an
   author can still tell widgets apart while composing; Preview and exports hide
   it for real. Struck through at half opacity rather than tinted, so it reads
   the same whatever the theme. Same treatment for the page title in the toolbar. */
.widget-card.is-title-hidden .widget-header h3,
body.hide-page-title .canvas-toolbar #canvasTitle {
  opacity: 0.5;
  text-decoration: line-through;
}

/* Preview must match the export: visually hidden, not display:none, so the
   heading stays in the accessibility tree. Out of flow, so the flex header
   collapses by itself and only its own margin needs clearing. */
body.preview-mode .widget-card.is-title-hidden .widget-header {
  margin-bottom: 0;
}

body.preview-mode .widget-card.is-title-hidden .widget-header h3 {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  opacity: 1;
  text-decoration: none;
}

.widget-kicker {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 790;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* Same glyph as the widget library rows, scaled down to sit with the kicker.
   A pseudo-element on purpose: renderCanvas assigns .widget-kicker.textContent,
   which would wipe any real child node. */
.widget-kicker::before {
  content: "";
  width: 13px;
  height: 13px;
  flex: 0 0 13px;
  background: var(--accent-light);
  -webkit-mask: url("assets/widgets.svg") center / contain no-repeat;
  mask: url("assets/widgets.svg") center / contain no-repeat;
}

/* The widget type is authoring context: useful in the composer, noise in
   Preview. Exports omit the element entirely (see buildHtmlExport). */
body.preview-mode .widget-kicker {
  display: none;
}

/* Same reasoning for the canvas chrome: the column count, widget tally and
   layout label describe the editor, not the page. Exports never render them. */
body.preview-mode .canvas-toolbar .eyebrow,
body.preview-mode .canvas-toolbar .toolbar-meta {
  display: none;
}

/* "Show page title" off: in Preview that leaves the toolbar with nothing to
   say, so drop the bar entirely. Design mode keeps it so you can still see and
   edit which page you are on. */
body.preview-mode.hide-page-title .canvas-toolbar {
  display: none;
}

/* Per-widget drop shadow. Replaces the old "show border" toggle, which became
   redundant once the border colour could be set to transparent - and the shadow
   had no control at all before this. */
.widget-card.is-shadowless {
  box-shadow: none;
}

/* Border / Background / Radius / Drop shadow. Collapsed to a single row each
   until switched on, with the value in effect shown beside the label so the
   sections stay readable without opening them. */
.appearance-controls {
  display: grid;
  /* Border and Background pair up side by side wherever there is room for two columns --
     the Page Details dialog is wide enough, the composer sidebar is not, and auto-fit
     decides per container rather than needing a separate rule for each. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 6px;
  margin-top: 12px;
}

/* Radius carries four corner inputs and Drop shadow is a single toggle; neither belongs in
   a half-width column. Keyed off the section name rather than nth-child, so reordering
   APPEARANCE_SECTIONS cannot silently rearrange the layout. */
.appearance-section[data-appearance-section="radius"],
.appearance-section[data-appearance-section="shadow"] {
  grid-column: 1 / -1;
}

.appearance-section {
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}

.appearance-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  cursor: pointer;
}

.appearance-head strong {
  font-size: 13px;
  font-weight: 700;
}

/* The value in effect. Grey and small so it reads as status, not as a control. */
.appearance-value {
  margin-left: auto;
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  white-space: nowrap;
}

/* Inherited from the page rather than set on this widget. */
.appearance-value.is-inherited {
  font-style: italic;
  opacity: 0.8;
}

.appearance-body {
  display: flex;
  align-items: end;
  gap: 8px;
  padding: 0 12px 10px;
}

.appearance-body[hidden] {
  display: none;
}

.appearance-field {
  display: grid;
  gap: 4px;
  font-size: 11px;
  color: var(--muted);
}

.appearance-field input[type="color"] {
  width: 44px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff;
  cursor: pointer;
}

/* The four corner inputs shared a row with Clear all and Reset, which squeezed each input to
   about 30px -- not enough to read a two-digit radius. The grid takes the whole row now and the
   two buttons wrap beneath it, side by side, so this costs one extra row rather than two.
   Scoped by :has so the colour sections, which fit their row fine, are left alone. */
.appearance-body:has(.appearance-radius) {
  flex-wrap: wrap;
}

.appearance-radius {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 5px;
  flex: 1 0 100%;
}

.appearance-radius label {
  display: grid;
  gap: 3px;
  justify-items: center;
  font-size: 10px;
  color: var(--muted);
}

.appearance-radius input {
  width: 100%;
  min-height: 30px;
  text-align: center;
  font-size: 12px;
}

.appearance-body .ghost-button {
  flex: none;
}

.color-input-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 6px;
}

/* A checkerboard button rather than a "Transparent" text label: it reads as
   "no colour" instantly and costs ~28px instead of ~85px, which matters in a
   266px sidebar. Distinct from Reset, which restores the theme default. */
.transparent-swatch {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #c9ced6 25%, transparent 25%, transparent 75%, #c9ced6 75%),
    linear-gradient(45deg, #c9ced6 25%, transparent 25%, transparent 75%, #c9ced6 75%);
  background-size: 10px 10px;
  background-position: 0 0, 5px 5px;
}

.transparent-swatch:hover:not(:disabled) {
  border-color: var(--accent);
}

.transparent-swatch[aria-pressed="true"] {
  border-color: var(--accent);
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.transparent-swatch:disabled {
  opacity: 0.4;
  cursor: default;
}

.color-input-row input[type="color"] {
  width: 40px;
  height: 34px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
}

.color-input-row input[type="text"] {
  min-height: 34px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

.resize-grip {
  position: absolute;
  right: 5px;
  bottom: 5px;
  /* Above .widget-action-blocker, which covers the whole body at z-index 3. Without this the
     blocker swallows the grab area below, so the only grabbable part was the visible 18x18. */
  z-index: 4;
  width: 18px;
  height: 18px;
  border-right: 3px solid var(--line-strong);
  border-bottom: 3px solid var(--line-strong);
  border-radius: 0 0 5px;
  cursor: ew-resize;
  touch-action: none;
  user-select: none;
}

/* An invisible grab area, because 18x18 is a small target for a drag. It grows UP and LEFT
   only, into the card. Growing on all four sides put it 6px past the card's bottom-right
   corner (the grip sits 5px in, plus the 1px border), and on a page whose own padding is 0
   that lands outside .page-content-shell, where it becomes real scrollable overflow in
   .canvas-wrap: the bottom-right card's halo alone gave the canvas a horizontal AND a
   vertical scrollbar for content that fit, each one appearing because the other stole 15px.
   Up and left keeps a 30x30 target entirely inside the card. */
.resize-grip::before {
  content: "";
  position: absolute;
  inset: -12px 0 0 -12px;
}

.kpi-value {
  margin: 0;
  font-size: 38px;
  line-height: 1;
  font-weight: 900;
}

.kpi-row,
.mini-stat-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: center;
}

.kpi-delta {
  padding: 5px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-dark);
  font-size: 12px;
  font-weight: 900;
}

.kpi-copy {
  margin: 12px 0 0;
  color: var(--muted);
  line-height: 1.45;
}

.chart {
  display: grid;
  align-items: end;
  grid-template-columns: repeat(8, 1fr);
  gap: 7px;
  height: 142px;
  padding: 10px;
  border-radius: 8px;
  background: var(--surface-soft);
}

.bar {
  min-height: 20px;
  border-radius: 5px 5px 2px 2px;
  background: linear-gradient(180deg, #2d8d82, #174f49);
}

.mini-stat-row {
  margin-top: 12px;
  color: var(--muted);
  font-size: 13px;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.data-table th,
.data-table td {
  padding: 9px 8px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  white-space: nowrap;
}

.data-table th {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
}

.content-copy {
  margin: 0;
  color: #31404e;
  line-height: 1.55;
}

.content-list {
  margin: 14px 0 0;
  padding-left: 18px;
  color: #31404e;
  line-height: 1.55;
}

@container widget (max-width: 310px) {
  .widget-header {
    display: grid;
  }

  .kpi-value {
    font-size: 30px;
  }

  .chart {
    height: 116px;
    gap: 4px;
  }

  .data-table th:nth-child(3),
  .data-table td:nth-child(3) {
    display: none;
  }
}

.empty-inspector {
  padding: 14px;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  color: var(--muted);
  line-height: 1.45;
}

.inspector {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
  gap: 0;
  overflow: hidden;
}

.inspector-panel .panel-collapsible-content {
  overflow: hidden;
}

.inspector-scroll {
  display: grid;
  gap: 14px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
}

.selected-summary {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 10px;
  align-items: center;
}

.selected-summary p,
.selected-summary h3 {
  margin: 0;
}

.selected-summary p {
  color: var(--muted);
  font-size: 12px;
  font-weight: 790;
  text-transform: uppercase;
}

.selected-summary h3 {
  margin-top: 3px;
  font-size: 18px;
}

.span-label-row {
  justify-content: space-between;
}

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

.span-option {
  aspect-ratio: 1;
  min-width: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: #fff;
  color: var(--muted);
  font-size: 11px;
  font-weight: 900;
}

.span-option.is-allowed {
  color: var(--accent-dark);
  background: var(--accent-soft);
  border-color: rgba(34, 108, 99, 0.32);
}

.span-option.is-selected {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}

.span-option.is-auto-fit {
  color: #b45309;
  border: 1px dashed #d97706;
  background: #fff7ed;
}

.span-option.is-too-narrow {
  border-style: dashed;
  opacity: 0.48;
}

.allowed-picker .span-option {
  background: #f5f7f9;
}

.allowed-picker .span-option.is-allowed {
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.allowed-picker .span-option.is-selected {
  box-shadow: inset 0 0 0 2px var(--accent);
}

.span-option:disabled {
  color: #bac4cd;
  background: #f5f7f9;
  cursor: not-allowed;
}

.check-row {
  align-items: flex-start;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.check-row span {
  display: grid;
  gap: 3px;
}

.check-row small {
  color: var(--muted);
  line-height: 1.35;
  font-size: 12px;
}

.check-row strong {
  font-size: 13px;
  font-weight: 700;
}

/* Long help text was eating the sidebar, so each setting keeps a compact info
   button and the prose moves into a shared dialog. The default .check-row span is
   a grid, which would stack the button under the label - flip it to a row only
   where a button is present, leaving description-less rows untouched. */
.check-row span:has(> .info-button) {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Masked rather than an <img> so the glyph inherits a theme colour, matching
   how .widget-kicker::before draws its icon. */
.info-button {
  width: 15px;
  height: 15px;
  flex: none;
  padding: 0;
  border: 0;
  background: var(--muted);
  -webkit-mask: url("assets/info.svg") center / contain no-repeat;
  mask: url("assets/info.svg") center / contain no-repeat;
  opacity: 0.7;
  cursor: pointer;
  transition: opacity 0.12s ease;
}

.info-button:hover,
.info-button:focus-visible {
  opacity: 1;
}

.info-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The info dialog can be opened from INSIDE other overlays (the import dialogs carry a
   rules info button), so it must sit above their shared 180 rather than tie on DOM order. */
.field-info-layer {
  z-index: 200;
}

.field-info-modal {
  max-width: 460px;
  /* Bottom padding moves onto the footer below, which claims it via its own padding instead --
     the same fix as the sticky heading above, mirrored for the bottom edge. Help text here (the
     import rules) can run well past a screen, and AI Prompt / Sample Project / Close matter
     enough now to be found scrolling, not chased to the bottom. */
  padding-bottom: 0;
}

/* The one thing in this dialog that is not a rule, so it does not look like one. Tinted with
   the same #dd8500 the AI Prompt button carries, so the note and the button it sends you to
   read as one thing rather than as two unrelated pieces of furniture. */
.field-info-preferred {
  margin: 0 0 14px;
  padding: 10px 12px;
  border-left: 3px solid #dd8500;
  border-radius: 0 6px 6px 0;
  background: rgba(221, 133, 0, 0.09);
  color: var(--ink);
  font-size: 13px;
  line-height: 1.5;
}

/* Darker than the border so the label still carries against the tint behind it. */
.field-info-preferred strong {
  color: #8a5200;
}

/* Sticky rather than .inspector-footer's plain position:relative: THIS modal scrolls as one
   box (overflow:auto on .field-info-modal itself), so the footer is inside the same scroll
   region as the text above it, not a non-scrolling sibling outside it. Negative side margins
   cancel the modal's own 14px side padding so the bar spans edge to edge, matching the sticky
   heading's technique; the drop shadow reads upward onto whatever scrolls past beneath it. */
.field-info-modal > .modal-footer-actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin: 14px -14px 0;
  padding: 12px 14px 14px;
  background: #fff;
  border-top: 1px solid var(--line);
  box-shadow: 0 -10px 14px -10px rgba(23, 33, 43, 0.28);
}

.field-info-text {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

/* One rule per element, so a gap can sit between them. The import rules run to thirty-odd
   lines and read as a single block without it -- nobody finds the one rule they came for.
   A span rather than a p because .field-info-text is itself a p, and a p cannot nest. */
.field-info-line {
  display: block;
}

.field-info-line + .field-info-line {
  margin-top: 10px;
}

/* The binding example. A real code block: it keeps its own whitespace and does not wrap,
   because the indentation is the part that shows which bindings sit inside the row template
   and a reflowed line would read as a different nesting level. Sideways scrolling is the
   lesser evil here. */
.field-info-example {
  /* Undoing the bare pre rules further down, which exist for the widget source viewer: a
     dark box with a fixed 250px ceiling. Left alone they clip this example at roughly half
     its length, which is the one thing it cannot afford to be. */
  min-height: 0;
  max-height: none;
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #f6f8fa;
  color: #17212b;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.55;
  white-space: pre;
  overflow-x: auto;
}

/* Widened only for the dialog that carries an example, so every other info popup keeps its
   reading width. The example runs to 70 columns, and the import rules beside it are the
   longest help text in the app.

   The How to Import view is the same dialog with the example hidden, so it matches on the
   how-to button instead. Keyed on the dialog rather than on its contents on purpose: keyed
   on the example alone, toggling to the steps resized the box from 680 to 460 under the
   reader's cursor and wrapped all four footer buttons onto two lines. */
.field-info-modal:has(.field-info-example:not([hidden])),
.field-info-modal:has(.field-info-howto:not([hidden])) {
  /* Both, because .widget-import-language-modal sets an explicit width: a max-width alone
     raises the ceiling on a box that is not asking to grow. */
  width: min(100%, 680px);
  max-width: 680px;
}

/* The rules info button leads the footer row; push the action buttons to the far edge. */
/* Beside the actions rather than banished to the far edge. It explains what those buttons
   are about to do, and an auto margin put it a whole dialog away from them -- at this width
   it read as unrelated furniture. Larger here than inline beside a label, too: next to 32px
   buttons carrying 18px icons, a 15px glyph reads as a speck. */
/* The rules-info button now carries a label (Details), so it needs the ordinary ghost-button
   box rather than being masked whole into a bare glyph -- that pattern stays for the check-row
   info buttons, which have no room for a label. The icon moves to a child .info-button-icon
   span; .ghost-button.info-button beats the plain .info-button rule on specificity, so every
   property that rule sets for the icon-only case is overridden back to normal button chrome.
*/
.modal-footer-actions .ghost-button.info-button {
  width: auto;
  height: auto;
  align-self: center;
  /* Put back, not inherited: .info-button sets `border: 0` for the bare-glyph case, and the
     shorthand takes the width and the style down with the colour, so .ghost-button's
     border-color alone had nothing to paint. It read as a bare link beside two boxed
     buttons. */
  border: 1px solid var(--line);
  background: #fff;
  opacity: 1;
  -webkit-mask: none;
  mask: none;
}




.button-row {
  flex-wrap: wrap;
  justify-content: space-between;
}

/* The pinned actions, below the scrolling settings. The shadow reads upward onto whatever is
   passing beneath it, so a panel scrolled to its end no longer leaves the last field flush
   against the buttons and looking like one of them. Opaque, or the content would show
   through rather than pass under. */
.inspector-footer {
  flex: 0 0 auto;
  position: relative;
  z-index: 1;
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  background: var(--surface);
  box-shadow: 0 -10px 14px -10px rgba(23, 33, 43, 0.28);
}

/* Grid rather than flex so Move Up / Move Down share the first line and Remove
   spans the full width beneath them. */
.inspector-footer > .button-row {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: var(--surface);
}

.icon-button {
  width: 42px;
  padding: 0;
  font-size: 20px;
}

.move-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.move-button span:first-child {
  font-size: 15px;
  font-weight: 800;
  line-height: 1;
}

.json-section {
  min-height: 0;
}

pre {
  max-height: 280px;
  min-height: 170px;
  overflow: auto;
  margin: 0;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #111820;
  color: #d8e6ee;
  font-size: 12px;
  line-height: 1.45;
}

/* Composer chrome density. Widget internals above keep their own scale. */
.topbar {
  min-height: 64px;
  padding: 12px 18px;
}

.topbar h1 {
  font-size: 21px;
}

.eyebrow {
  font-size: 11px;
}

.primary-button,
.save-button,
.ghost-button,
.danger-button,
.icon-button,
.mode-button {
  min-height: 32px;
  
  font-size: 13px;
}
.mode-toggle {
  gap: 2px;
  padding: 2px;
}

.mode-button {
  min-width: 72px;
}

.show-grid-toggle {
  min-height: 32px;
  width: 32px;
  font-size: 13px;
}

.breakpoint-button {
  width: 30px;
  min-height: 28px;
}

.breakpoint-icon {
  transform: scale(0.9);
}

.grid-toggle-icon {
  width: 15px;
  height: 15px;
}

.export-options button {
  min-height: 32px;
  font-size: 12px;
}

.workspace {
  gap: 12px;
  padding: 12px;
}

.panel {
  gap: 18px;
  padding: 12px;
}

.panel-heading {
  margin-bottom: 10px;
}

.panel h2 {
  font-size: 14px;
}

.active-toggle,
.country-item,
.palette-item,
.toolbar-meta,
.selected-summary p,
.check-row small {
  font-size: 12px;
}

.field,
.field-group {
  gap: 6px;
  margin-bottom: 11px;
}

.field > span,
.field-group > span,
.span-label-row > span {
  font-size: 11px;
}

input[type="text"],
input[type="number"],
input[type="datetime-local"] {
  min-height: 34px;
  padding: 6px 9px;
  font-size: 13px;
}

.page-width-controls {
  gap: 6px;
  grid-template-columns: minmax(70px, auto) minmax(0, 1fr);
}

.contained-width-control {
  grid-template-columns: 36px minmax(0, 1fr);
}

.width-mode-button {
  width: 36px;
  min-height: 36px;
}

.contained-width-input input {
  min-height: 36px;
}

.width-icon {
  width: 16px;
  height: 12px;
}

.country-grid,
.palette,
.inspector-scroll {
  gap: 7px;
}

.country-item {
  min-height: 30px;
  padding: 5px 7px;
}

.palette-row {
  grid-template-columns: minmax(0, 1fr) 30px;
  gap: 6px;
}

.palette-item {
  grid-template-columns: 30px 1fr;
  gap: 8px;
  padding: 8px;
}

.palette-icon,
.summary-icon {
  width: 30px;
  height: 30px;
  border-radius: 7px;
}

.palette-item small {
  font-size: 11px;
}

.canvas-toolbar {
  padding: 14px;
}

.canvas-toolbar h2 {
  font-size: 20px;
}

.toolbar-meta span {
  padding: 5px 8px;
}

.selected-summary {
  grid-template-columns: 34px 1fr;
  gap: 8px;
}

.selected-summary h3 {
  font-size: 16px;
}

.span-picker {
  gap: 3px;
}

.span-option {
  border-radius: 4px;
  font-size: 10px;
}

.check-row {
  padding: 10px;
}

.button-row {
  gap: 8px;
}

.icon-button {
  width: 36px;
  font-size: 17px;
}

.small {
  min-height: 28px;
  padding: 0 9px;
  font-size: 12px;
}

pre {
  min-height: 150px;
  max-height: 250px;
  padding: 10px;
  font-size: 11px;
}

@media (max-width: 1880px) {
  .workspace {
    grid-template-columns: var(--left-panel-column) minmax(0, 1fr);
  }

  .workspace.left-panel-collapsed {
    --left-panel-column: 44px;
  }
}

@media (max-width: 1199px) {
  .pages-header-actions .pages-action-button:not(.list-sort-button) {
    width: 38px;
    padding-inline: 0;
  }

  .pages-header-actions .pages-action-button:not(.list-sort-button) > span:not(.button-icon) {
    display: none;
  }

  .topbar .back-button > span:not(.button-icon),
  .topbar .mode-button > span:not(.mode-button-icon),
  .topbar .export-button > span:not(.button-icon) {
    display: none;
  }

  .topbar .back-button,
  .topbar .mode-button,
  .topbar .export-button {
    justify-content: center;
    padding-inline: 10px;
  }
}

@media (max-width: 480px) {
  .page-list-actions .page-delete-button,
  .page-list-actions .page-clone-button,
  .page-list-actions .page-edit-button,
  .page-list-actions .composer-open-button {
    width: 34px;
    justify-content: center;
    padding-inline: 6px;
  }

  .page-list-actions .page-delete-button > span:not(:first-child),
  .page-list-actions .page-clone-button > span:not(:first-child),
  .page-list-actions .page-edit-button > span:not(:first-child),
  .page-list-actions .composer-open-button > span:not(:first-child) {
    display: none;
  }
}

@media (max-width: 820px) {
  .topbar,
  .canvas-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }

  /* Stacking the topbar would carry the settings button down into the wrapped row of actions,
     somewhere different on every screen. It is the control you reach for when something is
     wrong, so it is pinned to the corner instead and stays put. */
  .ui-settings-switcher {
    position: absolute;
    top: 10px;
    right: 14px;
    z-index: 2;
  }

  .topbar-title {
    width: 100%;
    flex-wrap: wrap;
    /* Only this row runs alongside the pinned button; the rows below it keep the full width. */
    padding-right: 52px;
  }

  /* Smaller, and allowed to take a second line: a long product name should push the header
     taller rather than push the page sideways. */
  .topbar h1 {
    font-size: 18px;
    overflow-wrap: anywhere;
  }

  .topbar-actions {
    width: 100%;
    flex-wrap: wrap;
  }

  .topbar-actions > button,
  .save-button,
  .breakpoint-switcher,
  .show-grid-toggle,
  .export-menu,
  .mode-toggle {
    flex: 1;
  }

  .export-menu > .primary-button {
    width: 100%;
  }

  .mode-toggle {
    min-width: 210px;
  }

  .workspace {
    padding: 10px;
  }

  .canvas-panel {
    min-height: 560px;
  }

  .page-list-card {
    grid-template-columns: 14px minmax(0, 1fr);
    align-items: start;
  }

  .page-list-card .page-list-side {
    grid-column: 2;
  }

  .page-list-side {
    justify-items: start;
  }

  /* Two columns, not one: Import and New Page stay on the top row beside the tabs rather
     than taking a row of their own. Their labels are already hidden below 1199px, so the
     pair is two icon buttons -- at 390px that is far narrower than the space left over
     beside the two tabs, and it buys back a whole row of vertical space above the list. */
  .pages-header-container {
    align-items: flex-start;
    grid-template-columns: minmax(0, 1fr) auto;
    /* Halved from the base 16px purely to buy room for the shared row. Measured: the widest
       locale (Spanish "Páginas") needs 330px of container at a 16px gap, which does not fit
       a 375px phone, and 322px at 8px, which does. Row gap is left alone. */
    column-gap: 8px;
  }

  .pages-header-actions {
    grid-column: 2;
    grid-row: 1;
    width: auto;
    justify-content: flex-end;
    /* nowrap so the two icons stay side by side; wrapping them would re-create the extra
       row this rule exists to remove. */
    flex-wrap: nowrap;
  }

  /* Both span the full width now that row 1 is shared. Without the span they would be
     placed in column 1 and squeezed against the actions column, and the title row carries
     How to Import plus the Sort control, which needs the width. */
  .pages-header .eyebrow {
    grid-row: 2;
    grid-column: 1 / -1;
  }

  .list-title-row {
    grid-row: 3;
    grid-column: 1 / -1;
  }

  .page-list-meta {
    justify-content: flex-start;
  }

  .page-list-actions {
    justify-content: flex-start;
  }

}

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

  .widget-card {
    grid-column: 1 / -1;
  }
}

@media (max-width: 560px) {
  .topbar {
    padding: 14px;
  }

  .widget-default-editor,
  .widget-source-dialog {
    align-items: end;
    padding: 12px;
  }

  .page-edit-dialog {
    align-items: end;
    padding: 12px;
  }

  .widget-default-modal,
  .widget-source-modal,
  .page-edit-modal {
    max-height: calc(100vh - 24px);
  }
}

/* The sign-in affordance inside the storage badge. Sits with the message rather than in a
   settings menu, because the badge is where an author is looking when they wonder why their
   pages are missing. */
.storage-signin-button {
  margin-left: 8px;
  padding: 2px 10px;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font-size: 12px;
  font-weight: 700;
}

.storage-signin-button:hover {
  background: rgba(0, 0, 0, 0.06);
}

/* Publish sits beside Save because they are two steps of one thing: Save is private to the
   author, Publish is the act that changes what advisors see. */
.publish-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.publish-state {
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

/* Never published and behind are both "advisors are not seeing your work", so they share a
   colour; current is deliberately quiet, because nothing needs doing. */
.publish-state[data-state="never"],
.publish-state[data-state="behind"] {
  border-color: #b45309;
  color: #b45309;
}

.publish-button {
  padding: 8px 14px;
  border: 0;
  border-radius: 8px;
  background: #0f766e;
  color: #fff;
  font-weight: 700;
}

.publish-button:disabled {
  background: var(--line);
  color: var(--muted);
}

.page-history-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
}

.page-history-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.page-history-row[data-published="true"] {
  border-color: #0f766e;
  background: rgba(15, 118, 110, 0.06);
}

.page-history-label {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.page-history-label span {
  color: var(--muted);
  font-size: 11px;
}

.page-history-live {
  color: #0f766e;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

/* Two answers, neither of which is a cancellation: both save the page change. Spaced apart
   so the quieter one does not read as a dismissal of the dialog. */
.appearance-apply-actions {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

/* Under the topbar rather than a toast: a toast is missed if you stepped away, which is
   exactly the case this exists for. */
.page-changed-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  border-bottom: 1px solid #f59e0b;
  background: #fffbeb;
  color: #92400e;
  font-size: 13px;
}

.page-changed-text {
  flex: 1 1 auto;
}

/* Categories, collapsed to their current choices like Translations: the full list only
   matters while you are changing it. */
.widget-category-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-top: 8px;
}

@media (max-width: 640px) {
  .widget-category-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px) {
  .widget-category-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Sized to match the language items directly below them -- they are the same kind of choice
   and were inheriting body text, which made them shout next to everything else. */
.widget-category-grid .check-row {
  min-height: 36px;
  align-items: center;
  gap: 7px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
  font-weight: 720;
}

.widget-category-grid .check-row .category-row-name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.category-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
}

/* Only while the list is open for editing -- the same click that reveals the checkboxes is the
   one that means "I am changing the categories themselves".

   Scoped through .check-row to outrank `.check-row span`, which is a grid and was stacking the
   two buttons one above the other -- the same trap the info-button rule above works around. */
.widget-category-grid .check-row .category-row-actions {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.category-row-action {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.category-row-action:hover {
  background: var(--surface-soft);
  color: var(--ink);
}

.category-row-icon {
  width: 13px;
  height: 13px;
  display: inline-block;
  flex: 0 0 auto;
  background: currentColor;
}

.category-row-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 24px;
  padding: 0 6px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  font: inherit;
  color: var(--ink);
}

/* Data source, Categories and Translations are stacked directly on each other, and without
   rules between them the three read as one long list. Ruled on both sides rather than only
   below, so Categories is bounded the same way at the top as at the bottom. */
#widgetCategorySettings {
  padding-top: 12px;
  padding-bottom: 12px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* The category capsules carry names rather than two-letter codes, so they need the same
   weight as the language badges to read as the same kind of thing. */
#widgetCategoryBadges .translation-badge {
  font-size: 12px;
  font-weight: 720;
}

.widget-category-add {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  /* Breathing room before the next section starts. The disclosure closes flush to whatever
     follows it otherwise, and the New category row reads as belonging to Translations. */
  margin-bottom: 10px;
}

.widget-category-add input[type="text"] {
  flex: 1 1 auto;
}


/* Locked while a dialog is open. The padding-right that accompanies this is set in script,
   because the scrollbar width is only knowable at runtime and differs per platform. */
body.modal-open {
  overflow: hidden;
}

/* Accordion groups in both widget lists. The same shell serves the main library page and the
   composer's sidebar, so a category behaves identically wherever it is met. */
.widget-group {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  margin-bottom: 8px;
}

/* The groups carry their own margin. Leaving the list's gap on as well spaced the headers
   nearly twice as far apart as the rows inside them. */
.pages-list:has(> .widget-group),
.palette:has(> .widget-group) {
  gap: 0;
}

.widget-group > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 720;
  color: var(--ink);
  list-style: none;
}

.widget-group > summary::-webkit-details-marker {
  display: none;
}

/* A caret drawn here rather than the native marker, which cannot be styled consistently
   across browsers and sits in the wrong place in Safari. */
.widget-group > summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.12s ease;
}

.widget-group[open] > summary::before {
  transform: rotate(90deg);
}

.widget-group-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The count is why the group can stay shut: it says whether opening is worth it. */
.widget-group-count {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: 11px;
}

.widget-group-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 8px 8px;
}

.palette-group > .widget-group-body {
  padding: 0 6px 6px;
}

/* URL parameter failure notice. Shown only when a page was opened with pc.* parameters AND
   a widget rejected them, so a page that simply has no data never blames a link. When every
   data source failed it takes the canvas over (.is-takeover hides the grid from JS); when
   only some did it sits above them and the working widgets stay. */
.pc-url-param-notice-host { margin-bottom: 14px; }
.pc-url-param-notice {
  text-align: center;
  border: 1px solid #f0c9a8;
  background: #fdf3ea;
  color: #7a4a12;
  border-radius: 10px;
  padding: 14px 16px;
  font-size: 13px;
  line-height: 1.45;
}
.pc-url-param-notice__message { margin: 0; }
/* The takeover carries the whole page, so it reads as a page state rather than a strip. */
.pc-url-param-notice.is-takeover { padding: 28px 24px; font-size: 14px; }

/* Narrowest phones: give the header actions their own row back.
   Kept last in the file on purpose -- it has to override the max-width: 820px block above,
   and media queries carry equal specificity, so source order is what decides.

   Measured rather than guessed. Sharing row 1 costs tabs + actions + gap, which at an 8px
   gap is 322px in the widest locale (Spanish "Paginas"; en/fr need 310px). The container is
   the viewport less 36px of header padding, so one row needs about 358px of viewport and
   comfortably fits the 390px phone this was asked for. Below 360px it does not fit, and the
   grid does not wrap: the tab group is inline-flex and simply overflows its column, so the
   upload button lands on top of the Widgets tab. A second row is worse than nothing; two
   controls overlapping is worse than both. */
@media (max-width: 360px) {
  .pages-header-container {
    grid-template-columns: 1fr;
  }

  /* Carlton's layout, 2026-09-01. Rather than a second column, the actions share row 1 with
     the tabs in the SAME full-width cell and right-align inside it. Grid items in one cell
     are allowed to overlap, and that is what makes this fit at 353px where a real second
     column could not: the tabs sit left at their intrinsic width, the two icon buttons sit
     hard right, and the only thing that has to fit is tabs + buttons, with no column track
     to round up. margin-top drops the buttons onto the tab pills' optical line. */
  .pages-header-actions {
    grid-column: 1;
    grid-row: 1;
    /* NOT width: 100%. Sharing a cell means this box sits ON TOP of the tabs, and a
       full-width one swallows every click across the row while painting nothing -- which
       left the Pages/Widgets tabs looking normal and completely unclickable. Sized to its
       content and pushed right by the base rule's justify-self: end, it only covers the two
       buttons it actually draws. */
    width: auto;
    flex-wrap: wrap;
    margin-top: 4px;
  }

  .pages-header .eyebrow {
    grid-row: 3;
  }

  .list-title-row {
    grid-row: 4;
  }
}

/* Narrow phones: stack the modal footer buttons.
   Three buttons at their natural width (How to Import, Choose folder, Choose export file)
   overflow a 353px dialog and the last one is pushed off the right edge. Full-width and
   stacked, they stay reachable, and the sticky bar above keeps them on screen.
   Order is deliberate: the two that DO something come first, and the informational
   How to Import goes last, so the primary action sits nearest the thumb. */
@media (max-width: 480px) {
  .modal-footer-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .modal-footer-actions button,
  .modal-footer-actions .field-info-download {
    justify-content: center;
    width: 100%;
  }

  /* Full width like the other two. The base rule sets width: auto and align-self: center for
     the horizontal case, and both out-specify .modal-footer-actions button above, so they
     have to be undone at the same specificity -- align-self included, since centring a flex
     item in a column container shrinks it to content whatever its width says. */
  .modal-footer-actions .ghost-button.info-button {
    order: 1;
    width: 100%;
    align-self: stretch;
  }

  /* One per row. Two columns leave roughly 130px per item at 353px, which is not enough for
     a flag, a name and a two-letter code side by side -- the names collided with the codes.
     The list being collapsed by default is what makes the extra length affordable. */
  .language-checkbox-grid {
    grid-template-columns: 1fr;
  }
}

/* The source-language select, brought down to the dialog's own scale. It was picking up the
   generic .field select box -- min-height 40px and `font: inherit`, so ~16px text -- inside a
   modal whose body copy is 13px and whose controls are 34px, which is why it read as a
   different size of thing to everything around it. 34px matches .list-tab-button, the
   shortest standard control in the app. Both import dialogs share this class. */
.widget-import-language-modal .source-language-field select {
  min-height: 34px;
  padding: 6px 9px;
  font-size: 13px;
}

/* The label and hint either side of it, to the same scale as the rest of the modal. */
.widget-import-language-modal .source-language-field > span {
  font-size: 11px;
}

.widget-import-language-modal .source-language-field small {
  font-size: 11px;
}
