:root {
  --brand: #a78bfa;
  --brand-dark: #7c3aed;
  --bg: #150e1f;
  --surface: #1f1630;
  --surface-2: #2a1f42;
  --border: #3a2b55;
  --text: #ece6f7;
  --muted: #a493c2;
  --danger: #e5675e;
  --danger-bg: #3a2226;
  --ok: #4bc07c;
  --ok-bg: #1e3327;
  --warn: #e0b64a;
  --warn-bg: #3a3020;
  /* A completed question block's own fields (see .item-question-block.is-complete /
     .question-answer-block.is-complete below) switch to this light green rather than staying the
     app's usual dark input fill - --ok-field-text is a deep purple, dark enough for strong contrast
     against it while still reading as "on brand" rather than plain black. */
  --ok-field-bg: #dcf5e1;
  --ok-field-text: #2e1065;
}

* { box-sizing: border-box; }

/* Reserve space for the scrollbar so pages whose content height changes (e.g. after adding or
   removing a row) don't nudge the whole layout left/right when the scrollbar appears or disappears. */
html {
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

a { color: var(--brand); }

.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, #2a1a40 0%, #0f0a18 100%);
}

.login-box {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 36px 40px;
  border-radius: 8px;
  width: 340px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}

.login-box h1 {
  font-size: 20px;
  margin: 0 0 4px;
  color: var(--text);
}

.login-box .subtitle {
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 20px;
}

/* The survey-taking interstitial (see views/survey/take.ejs) - same standalone centered-gradient
   shape as .login-page/.login-box (no sidebar/topbar reachable while a required survey is pending),
   just wider to comfortably fit several questions instead of a 2-field login form. */
.survey-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, #2a1a40 0%, #0f0a18 100%);
  padding: 40px 20px;
}

.survey-box {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 36px 40px;
  border-radius: 8px;
  width: 100%;
  max-width: 640px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; margin-bottom: 4px; color: var(--muted); }

/* Every text-style input/select/textarea gets the same dark background, whether it's a full
   stacked-label ".field" or a compact inline "quick add" form / in-table field (search boxes,
   rating dropdowns, add-item inputs). Previously only .field-wrapped fields picked this up, so
   anything outside one rendered with the browser's default white background instead. */
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="date"], input:not([type]), textarea, select, .searchable-select-trigger {
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 14px;
  background: var(--surface-2);
  color: var(--text);
}
input[type="text"]::placeholder, input:not([type])::placeholder, textarea::placeholder { color: var(--muted); }
input:disabled, select:disabled, textarea:disabled { opacity: 0.6; }
/* Chrome/Safari render the built-in calendar-picker icon in black by default, which is invisible
   against this dark theme's inputs - invert it to white so it's actually visible. */
input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); }
/* The calendar POPUP itself (opened by clicking the field or that icon) is OS/browser chrome that
   normal CSS can't reach - color-scheme is the one property browsers respect for it, rendering the
   whole popup in dark colors instead of its default white. accent-color then tints the
   selected/focused day inside it (and the picker icon above) purple to match the app's brand color
   instead of a generic dark gray. */
input[type="date"] { color-scheme: dark; accent-color: var(--brand); }
/* Despite sharing the exact same padding/border/font-size as text inputs above, a native <select>
   still renders taller in most browsers - its built-in dropdown-arrow chrome carries its own
   internal sizing that isn't governed by CSS padding. Turning that native appearance off (and
   drawing our own small arrow instead, since removing it also removes the arrow) makes a select's
   rendered height match a text input's exactly, pixel for pixel, everywhere it's used (Databases'
   Type/Engine dropdowns, Profile's contact-type dropdown, etc). */
select, .searchable-select-trigger {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23a493c2' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  /* The SVG above only declares a viewBox, not width/height attributes, so it has no intrinsic size
     for the browser to fall back on - without an explicit background-size, browsers render it at
     their own oversized default (as wide as the whole select in some browsers), which is the "way
     too wide" triangle. Pinning it to a small, fixed size fixes that regardless of browser default. */
  background-size: 10px 6px;
  padding-right: 28px;
}

.field input, .field select, .field textarea, .field .searchable-select-trigger { width: 100%; }

/* A searchable, checkbox-backed "dropdown" that LOOKS exactly like the native <select> above (same
   shared input/select rule, same custom arrow) but supports multiple selections and lets the user
   filter a long list by typing - used for the Tags survey form's "Specific Audiences" picker, styled
   to match the single-select "Group" dropdown on the Manage Campaigns page. Selection state still
   lives entirely in plain <input type="checkbox"> elements with their original name/value, so no
   server-side code needs to know this widget exists - see public/js/searchable-select.js. */
.searchable-select { position: relative; z-index: 1; }
/* While open, the widget needs to sit above the full-page backdrop below (see
   .searchable-select-backdrop and open()/close() in searchable-select.js) so the trigger and panel
   stay interactive - everything else on the page is behind the backdrop and gets its clicks
   intercepted (closing the panel) instead of reaching whatever it covers underneath. */
.searchable-select.is-open { z-index: 26; }
.searchable-select-trigger { display: block; width: 100%; text-align: left; font-weight: 400; cursor: pointer; }
.searchable-select-trigger:hover { background: var(--surface-2); }
.searchable-select-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 30;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.4);
  display: flex;
  flex-direction: column;
  max-height: 280px;
  overflow: hidden;
}
/* A plain, non-!important "display" declared on the class above always beats the browser's own
   built-in "[hidden] { display: none }" rule in the cascade, REGARDLESS of specificity - normal-
   priority author styles always outrank normal-priority user-agent styles. Without this override,
   the panel's hidden attribute (which searchable-select.js's open()/close() toggle) would have zero
   visual effect: the panel would stay visibly open all the time, and could never actually collapse
   no matter what state the JS itself thinks it's in. This one extra level of selector specificity
   over the plain ".searchable-select-panel" rule above is what lets [hidden] win instead. */
.searchable-select-panel[hidden] { display: none; }
.searchable-select-search { border: none; border-bottom: 1px solid var(--border); border-radius: 0; width: 100%; }
.searchable-select-options { overflow-y: auto; padding: 4px 0; }
.searchable-select-option { display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-size: 14px; color: var(--text); cursor: pointer; }
.searchable-select-option:hover { background: rgba(167, 139, 250, 0.14); }
.searchable-select-option input { width: auto; }
.searchable-select-option.is-hidden { display: none; }
.searchable-select-empty { padding: 10px 12px; margin: 0; }
/* Transparent full-page overlay shown only while a panel is open (appended to <body> by
   searchable-select.js) - sits above every other button/link on the page but below the open widget
   itself (see .searchable-select.is-open), so a click anywhere else on the page - even directly over
   something the panel happens to be covering - always closes the panel on that first click instead of
   silently doing nothing. */
.searchable-select-backdrop { position: fixed; inset: 0; z-index: 20; background: transparent; }
/* Tag Targeted Campaign's "Add Audience" tag-logic builder (campaigns/show.ejs): its up-to-8 AND
   groups need to stack top to bottom, one full-width group per row - unlike the single-group Group
   picker form above it, which deliberately lays its one field and its "+ Add Audience" button out
   side by side (see .u-29977862 in inline-migrated.css). Reusing that same row-flex class here would
   force every group (and the OR labels and the submit button) into one horizontal row instead, so
   this form gets its own column-flex class instead. */
.tag-target-audience-form { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; }

.field .checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--text); }
.field .checkbox-label input { width: auto; }
/* Checkboxes/radios should never stretch to the field's full width - only the ".field input, ..."
   rule above (or an inline style="width:...") should size those. */
input[type="checkbox"], input[type="radio"] { width: auto; }

button, .btn {
  display: inline-block;
  background: var(--brand);
  color: #1e1030;
  border: none;
  padding: 9px 16px;
  border-radius: 4px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  /* Without this, a longer label (e.g. "Start New Audiences") can wrap onto two lines while a
     shorter sibling button (e.g. "Retire") stays on one - and since a flex row of buttons defaults
     to align-items: stretch, that one wrapped button silently forces every other button in the same
     row up to its taller, two-line height too. Keeping every label on a single line is what actually
     makes "every button in the app is the same size now" (see .btn-small below) hold up in practice. */
  white-space: nowrap;
}
button:hover, .btn:hover { background: var(--brand-dark); }
/* Only the solid purple-fill buttons (the default "button, .btn" above) get white hover text - the
   dark #1e1030 base text color is hard to read against the darker brand-dark hover background.
   btn-secondary (transparent fill) and btn-danger (red fill) are excluded so their own hover text
   colors are untouched - tag-remove-btn (the small "x" on a Profile tag pill, see below) is excluded
   for the same reason: it should always match its pill's own text color, never turn white.
   tree-edit-btn (the Groups tree's "Edit", see below) is excluded on the same grounds: it is styled
   as a link, and this rule's (0,4,1) specificity would otherwise beat anything that rule can say
   about its own hover colour and turn it white. */
button:not(.btn-secondary):not(.btn-danger):not(.tag-remove-btn):not(.tree-edit-btn):hover,
.btn:not(.btn-secondary):not(.btn-danger):not(.tag-remove-btn):not(.tree-edit-btn):hover { color: #fff; }
.btn-secondary { background: transparent; color: var(--brand); border: 1px solid var(--brand); }
.btn-secondary:hover { background: rgba(167, 139, 250, 0.14); }
.btn-danger { background: var(--danger); color: #1a0d0c; }
.btn-danger:hover { background: #c9564e; }
/* Every button in the app - toolbar actions, form submits, and in-table row actions alike - is the
   same size now. ".btn-small" is kept as a class (rather than removed from every view) so table
   buttons stay easy to spot in markup, but it no longer changes size; it just inherits the
   standard "button, .btn" padding/font-size above. */
.btn-small { padding: 9px 16px; font-size: 14px; }
/* A structurally-disabled action, e.g. "Start Campaign" before any audience has been added (see
   campaigns/show.ejs) - greyed out and inert rather than clickable-but-rejected, so there's no
   confirm dialog or round trip to the server to discover the action isn't allowed yet. */
button:disabled, .btn:disabled { background: var(--surface-2); color: var(--muted); cursor: not-allowed; }
button:disabled:hover, .btn:disabled:hover { background: var(--surface-2); color: var(--muted); }

.error-banner {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid #5c2f2c;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 13px;
  margin-bottom: 14px;
}

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

.sidebar {
  width: 220px;
  background: #120b1a;
  color: #fff;
  padding: 18px 0;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
}
.sidebar .brand { padding: 0 18px 18px; font-weight: 600; font-size: 16px; border-bottom: 1px solid rgba(255,255,255,0.1); margin-bottom: 12px; color: var(--text); display: flex; align-items: center; gap: 8px; }
.sidebar .brand img { display: block; border-radius: 6px; }
.sidebar nav a {
  display: block;
  padding: 9px 18px;
  color: #c3b6dc;
  text-decoration: none;
  font-size: 14px;
}
.sidebar nav a:hover, .sidebar nav a.active { background: rgba(255,255,255,0.08); color: #fff; }
.sidebar .section-label { padding: 14px 18px 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: #7a6a95; }

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 12px 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* Keeps the company name, logout button, status badge, and profile avatar visible while the
     page content below scrolls, instead of scrolling away with it. */
  position: sticky;
  top: 0;
  z-index: 50;
}
.topbar .who { font-size: 13px; color: var(--muted); }
.topbar form { display: inline; }
.topbar-actions { display: flex; align-items: center; gap: 12px; }
.status-badge { display: inline-flex; cursor: default; line-height: 0; }
.status-badge svg { display: block; }

/* Purple-themed initials avatar next to the status badge in the topbar (see app-shell-open.ejs),
   linking to the self-service Profile page - initials-only since there's no photo upload here. */
.avatar-circle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--brand-dark); color: #fff;
  font-size: 12px; font-weight: 700; text-decoration: none;
  border: 2px solid var(--brand);
}
.avatar-circle:hover { background: var(--brand); }

/* Wraps the store x denomination checkboxes on the Incentives redemption form (see
   views/incentives/index.ejs) so they flow onto multiple lines instead of stretching full-width
   like an ordinary stacked .field. */
.checkbox-label-row { display: flex; flex-wrap: wrap; gap: 8px 20px; }
.checkbox-label-row .checkbox-label { width: auto; }

.content { padding: 24px; flex: 1; }

table { width: 100%; border-collapse: collapse; background: var(--surface); }
table th, table td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border); font-size: 14px; color: var(--text); }
table th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; }
table tr:last-child td { border-bottom: none; }

/* Sortable column headers (see partials/sort-th.ejs) on the Questions, Descriptions, Topics,
   Response Types, Snippets, and Campaigns list pages - a plain-looking link (inherits the muted
   uppercase th styling above, no underline) that only reveals itself as clickable on hover, plus a
   small ▲/▼ arrow on whichever column is the current sort. */
.sort-th-link { color: inherit; text-decoration: none; white-space: nowrap; cursor: pointer; }
.sort-th-link:hover { color: var(--text); text-decoration: underline; }
.sort-th-link.sort-th-active { color: var(--brand); }

/* Shrink a checkbox-only column (e.g. the Tags survey form's Include/Required columns) down to its
   content instead of sharing the table's width evenly - width:1% plus nowrap is the standard trick
   to make a table column take only as much space as its content needs. */
.table-col-checkbox { width: 1%; white-space: nowrap; text-align: center; }

/* Generic "center this column" utility for table headers/cells whose values read better centered
   than left-aligned (short badges, counts, dates) - e.g. Employees' Employee ID/Status, Campaigns'
   Type/Status/Consensus/Iteration/Next Iteration Date, and the various "# of items" counts across
   Topics/Snippets/My Campaigns. Applied directly rather than combined with the app's existing
   right-align utilities (.u-ff778bee/.u-bbee7ca1/.u-08a0ed40) on the same element, since two classes
   both declaring text-align would otherwise depend on brittle stylesheet load order to know which
   one wins. */
.col-center { text-align: center; }

/* Space below the Tags survey form's Questions search row, so it doesn't sit flush against the
   Questions table/card underneath it. Scoped to this one spot rather than added to the shared
   .search-row rule itself, since .search-row is also reused inside .tag-filter-toolbar (which
   already spaces its own children via its own margin/gap). */
.tags-questions-search-row { margin-bottom: 20px; }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 20px; margin-bottom: 20px; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; gap: 12px; }
.toolbar form { display: flex; gap: 8px; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }
.form-actions { margin-top: 20px; display: flex; gap: 10px; }

/* Matches the rendered width of a single .form-grid column (two 1fr columns with a 20px gap, so
   each one is exactly half the row's width minus half the gap) - used by the Profile page's "Add a
   Tag" field, a flex-row field (not a .form-grid one) that should still end up exactly as wide as
   the "Postal Code" field a few cards up, which IS inside a .form-grid. */
.profile-tag-field { width: calc(50% - 10px); flex: 0 0 calc(50% - 10px); }

/* The Profile page's "Additional Emails & Phone Numbers" add-contact row is a flex row (Type,
   Value, Label, then the Add button) - Type (a <select>) and the button should stay their natural
   size, but Value and Label should share and fill all the remaining width up to the Add button
   rather than each shrinking to fit their placeholder text. min-width:0 is required alongside
   flex-grow on a flex item that contains text-input children, or a long placeholder/value can force
   the item wider than the row has room for instead of actually shrinking to share space evenly. */
.contact-field-grow { flex: 1 1 auto; min-width: 0; }

.tree ul { list-style: none; margin: 0; padding-left: 18px; }
.tree > ul { padding-left: 0; }
.tree li { margin: 3px 0; }
.tree .node { padding: 3px 6px; border-radius: 3px; color: var(--text); font-size: 14px; }
.tree .folder .node { font-weight: 600; }
.tree .item .node a { color: var(--text); text-decoration: none; }
.tree .item .node a:hover { color: var(--brand); text-decoration: underline; }
.tree-icon { color: var(--brand); vertical-align: -3px; margin-right: 4px; flex-shrink: 0; }

/* Per-node expand/collapse caret, plus the "Expand All"/"Collapse All" toolbar buttons that toggle
   every caret + child list on the page at once. Default state is expanded (caret points down);
   ".collapsed" is added/removed by JS in groups/index.ejs. */
.tree-toggle {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  margin-right: 2px;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  vertical-align: -3px;
  flex-shrink: 0;
}
.tree-toggle svg { transition: transform 0.12s ease; }
.tree-toggle.collapsed svg { transform: rotate(-90deg); }
.tree-toggle-spacer { display: inline-block; width: 14px; vertical-align: -3px; }
.tree-children.collapsed { display: none; }

/* Each group's "New sub-group"/"+ Item" row (group-node.ejs's <li class="new-group">) holds two
   separate add-forms side by side - wide enough (280px/240px input fields) that they routinely wrap
   onto their own line rather than fitting next to each other, especially several levels deep where
   .tree ul's left padding has already eaten into the available width. As plain inline elements
   (.inline-form's default display:inline) those wrapped lines had no vertical gap between them at
   all, so the two rows looked like they were touching. Stacking them as an explicit flex column with
   a real gap fixes that regardless of how narrow the tree column gets. Scoped to .new-group rather
   than changing .inline-form itself, which is reused (and should stay inline) everywhere else. */
.tree li.new-group { display: flex; flex-direction: column; gap: 10px; margin: 10px 0; }
.tree li.new-group .inline-form { display: flex; align-items: center; gap: 6px; margin-left: 0; }

/* "New root group", "New sub-group" and "New item" are all half the width of the card they sit in,
   and therefore identical to each other. They previously ran 140px / 280px / 240px respectively -
   three arbitrary widths for three fields doing the same job.

   Measured against the CARD, not the immediate parent, which is the whole reason for the container
   query. Each nesting level of the tree adds 18px of left padding, so a plain `width: 50%` resolves
   against a box that shrinks as you go deeper: a sub-group field four levels down would come out
   visibly narrower than the root-group field, which fails the "same width" half of the intent. 50cqi
   is 50% of the card's inline size no matter how deep the field is nested.

   The 50% before it is a fallback for anything without container-query support (pre-2023 browsers),
   where it degrades to half the immediate row rather than to some stray default. The container is
   declared on .card.tree only, not .card, so no other card's layout is affected: inline-size
   containment stops a very wide tree from widening its grid column, which is what we want here but
   is not necessarily right for every card in the app. */
.card.tree { container-type: inline-size; }
.tree-new-input {
  width: 50%;
  width: 50cqi;
}

/* Inline rename for a Groups-tree node (partials/tree-rename.ejs). The static name and the edit
   form are siblings; exactly one is visible at a time, swapped by the delegated handler in
   groups/index.ejs. */
.node-label { display: inline-flex; align-items: center; gap: 4px; }
.node-rename-form { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
/* Both of the above set a display, which beats the UA stylesheet's [hidden] { display: none } on
   specificity - so without these two rules the "hidden" half would stay stubbornly visible and both
   states would render at once. Same trap already documented for .searchable-select-panel[hidden]. */
.node-label[hidden], .node-rename-form[hidden] { display: none; }
.node-rename-form input[type="text"] { width: 200px; }

/* Reads "Edit" and is styled to match the plain <a>Edit</a> in the Response Types list: same brand
   colour, same underline, same font size as the surrounding row. It is a <button> because it opens
   the inline rename form rather than navigating, so the browser's button chrome (background,
   border, centred bold-ish default font) has to be reset for it to read as that link.

   Always visible, again matching Response Types. It was previously a pencil icon revealed on hover,
   which also meant a separate @media (hover: none) rule so it was not an invisible tap target on a
   phone - a text label that is simply always there needs neither. */
.tree-edit-btn {
  padding: 0;
  border: none;
  background: transparent;
  color: var(--brand);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
/* HOVER IS THE HARD PART, and getting it wrong is what made this still read as a button. Two global
   rules further up target `button` and land on this element too:
     button:hover { background: var(--brand-dark); }                     -> (0,1,1)
     button:not(.btn-secondary):not(.btn-danger)...:hover { color: #fff } -> (0,4,1)
   The background one is beaten by this rule's (0,2,0), but ONLY because the background is restated
   here - remove that line and hovering paints a purple pill behind the word. The colour one is not
   beatable this way at all: (0,4,1) outranks (0,2,0), so .tree-edit-btn had to be added to that
   rule's own :not() exclusion list, the same way .tag-remove-btn already is.

   No text-decoration override here on purpose: the plain <a>Edit</a> on Response Types has no
   :hover rule of its own at all (there's no bare `a:hover` anywhere in this file), so it stays
   underlined on hover exactly as it is at rest. An earlier version of this rule set
   `text-decoration: none` on hover, which made the two "identical" Edit controls behave
   differently the moment you actually moved the mouse over them - not a button, but not a faithful
   copy of the link either. Removed so hover is a true no-op, matching the link's own do-nothing
   hover state precisely. */
.tree-edit-btn:hover {
  background: transparent;
  color: var(--brand);
}

/* Billing > Subscription plan picker. The cards were a fixed 220px each in a flex row with the
   Update Plan button sitting beside them, so they left dead space on the right of a wide card and
   the button read as a fourth item in the row.

   grid-auto-flow: column + grid-auto-columns: 1fr gives one equal-width column PER CARD, filling
   the container with an even gap between them - deliberately not `repeat(3, 1fr)`, because the
   plans come from the subscription_plan table rather than being hardcoded, and a fourth plan would
   otherwise wrap onto its own row at a third of the width. This way N plans always share the row
   evenly, whatever N is.

   Update Plan is now a sibling below the grid rather than a flex child inside it, which is what
   puts it underneath the plans. */
.plan-form { margin-top: 12px; }
.plan-picker { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 12px; }
/* min-width: 0 because a grid item's default min-width is auto (its content), which would let a
   long plan name push a column past its 1fr share and make the columns unequal again. */
.plan-option { cursor: pointer; display: block; min-width: 0; margin-bottom: 0; }
.plan-form-actions { margin-top: 12px; }

/* Three equal columns stop being readable long before a phone's width; stack them instead. Sharing
   the 767px breakpoint the rest of the app's small-screen rules use. */
@media (max-width: 767px) {
  .plan-picker { grid-auto-flow: row; grid-auto-columns: auto; }
}

/* Mail Server: dims (not disables - see views/mailserver/index.ejs) the SMTP fields while "Use
   system default mail settings" is checked, so it's visually obvious they aren't currently in
   effect rather than a company admin filling them in and having them silently ignored. */
.smtp-fields-inactive { opacity: 0.5; }

.two-col { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }

.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; background: var(--ok-bg); color: var(--ok); }
.badge.off { background: var(--danger-bg); color: var(--danger); }

/* A Profile page tag pill with its own built-in "x" - a <div> (not the plain <span> other .badge
   uses render as) since it needs to contain a real <form> for the remove action, and <form> isn't
   valid content for a <span>. .badge.tag-badge (rather than plain .tag-badge) beats the base .badge
   rule's display:inline-block on specificity regardless of stylesheet order, so this always renders
   as a flex row (tag text + the little round "x") instead of stacking its children. */
.badge.tag-badge { display: inline-flex; align-items: center; gap: 4px; }
/* .tag-remove-form/.tag-remove-btn are also reused (unchanged) by the Groups tree's group/sub-group/
   item nodes (group-node.ejs, item-node.ejs) - same inline "x" next to the name instead of a separate
   "Delete" button, same interaction pattern as a Profile tag pill above. Folders keep their own
   visible "Delete" button (groups/index.ejs) - deleting a folder is a much bigger, rarer action
   (removes every group/item inside it too) that deserves a harder-to-misclick target. */
.tag-remove-form { display: inline-flex; margin: 0; }
.tag-remove-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.tag-remove-btn:hover { background: rgba(0, 0, 0, 0.15); }
/* Scoped to the tree specifically, rather than changing .tag-remove-btn's own color rule above - a
   Profile page tag pill's "x" should stay the same color as its pill text (color: inherit), but a
   group/sub-group/item's "x" in the Groups tree should read as red/danger, same spirit as the
   btn-danger "Delete" button it replaced. */
.tree .tag-remove-btn { color: var(--danger); }

.inline-form { display: inline; margin-left: 6px; }
.pagination { margin-top: 14px; display: flex; gap: 6px; }
.pagination a, .pagination span { padding: 5px 10px; border: 1px solid var(--border); border-radius: 4px; font-size: 13px; text-decoration: none; color: var(--text); }
.pagination .current { background: var(--brand); color: #1e1030; border-color: var(--brand); }

.muted { color: var(--muted); font-size: 13px; }

.code-block {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px 14px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  color: var(--text);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* Shared search + tag-filter toolbar (Questions, Descriptions, Starting Lists, Response Types) */
.tag-filter-toolbar { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }

.search-row { display: flex; gap: 10px; width: 100%; }
.search-row-input { flex: 1 1 auto; min-width: 0; }
.search-row .btn-secondary { flex: 0 0 auto; white-space: nowrap; }

.tag-toggle-details { border: 1px solid var(--border); border-radius: 6px; padding: 10px 14px; background: var(--surface-2); }
.tag-toggle-details summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text); user-select: none; }
.tag-toggle-details summary .muted { font-weight: normal; margin-left: 4px; }
.tag-toggle-details[open] summary { margin-bottom: 10px; }

/* Questions page: a Snippets template and its generated children are a SINGLE table row (see
   attributes/index.ejs), spanning all four columns. The template's own question text is the
   <summary>, so the row the admin reads and the control that reveals the children are the same
   element rather than two list entries that merely happen to be adjacent.

   Full-width is what buys the alignment: while the group lived inside the Question column, every
   Edit/Delete it contained stopped short of the right edge and read as a separate, unaligned block
   floating mid-row. Spanning the table puts the template's own buttons, and every child's, in the
   same vertical line as the Edit/Delete of the ordinary question rows above and below - the <td>
   here carries the same 12px side padding as every other cell, so right-aligned content inside it
   ends exactly where theirs does. */

/* The click target is the whole summary line. list-style and ::-webkit-details-marker are both
   cleared because engines disagree about which draws the default triangle, and a flex summary
   positions it badly either way - replaced with a ::before chevron that rotates when open. */
.snippet-template-details > summary {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  cursor: pointer;
  user-select: none;
  list-style: none;
}
.snippet-template-details > summary::-webkit-details-marker { display: none; }
.snippet-template-details > summary::before {
  content: '\25B8';
  display: inline-block;
  flex: 0 0 auto;
  color: var(--brand);
  font-size: 11px;
  line-height: 1.5;
  transition: transform 0.12s ease;
}
.snippet-template-details[open] > summary::before { transform: rotate(90deg); }
.snippet-template-details > summary:hover .snippet-template-name { text-decoration: underline; }

/* The template's raw "[SNIPPET:x]" text reads as an ordinary question - same size and weight as
   every other row's - so the page still scans as one flat list of questions. */
.snippet-template-name { flex: 0 1 auto; min-width: 0; }

/* A full-width row has no Answer Type or Tags column of its own, so that information trails the
   question as muted metadata instead of being dropped. It sits on the question's own line, which is
   also where the child count belongs: the number and the question it counts can't be read apart. */
.snippet-template-meta {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}
.snippet-template-sep { color: var(--border); }
.snippet-template-count {
  font-weight: 600;
  color: var(--brand);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 9px;
  white-space: nowrap;
}

/* The controls strip: the set-all Tags buttons on the left, this template's own Edit/Delete pushed
   to the right edge. It stays visible whether or not the group is expanded, because every other
   question on this page can be edited or deleted without being expanded first and the template
   should not be the one exception. */
.snippet-template-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
  font-size: 13px;
}
.snippet-template-controls .inline-form { margin: 0; }
/* A disabled set-all button means every child is already in that state - that is information, not a
   dead control, so it stays legible rather than fading almost out. */
.snippet-template-controls button[disabled] { opacity: 0.55; cursor: default; }
/* Absorbs the slack, which is what actually pins Edit/Delete to the right edge. */
.snippet-template-controls-spacer { flex: 1 1 auto; }
/* Only once the group is open does the strip need a rule under it, separating it from the children
   below; collapsed, a trailing border would just underline a lone row. */
.snippet-template-details[open] ~ .snippet-template-controls {
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

/* The children are a SIBLING of the <details>, not its content - see the comment in
   attributes/index.ejs for why - so this general-sibling rule is what opens and closes them.
   Deliberately plain CSS3: no :has(), no JS, works wherever <details> itself does. */
.snippet-template-children { display: none; }
.snippet-template-details[open] ~ .snippet-template-children {
  display: block;
  /* Indent and a rule dropping from the template, so the children read as belonging to it. Padding
     on the LEFT only - it never moves the right edge, so every child's Edit/Disable/Delete stays in
     the same vertical line as the template's own and as the ordinary rows'. */
  margin-top: 2px;
  padding-left: 14px;
  border-left: 2px solid var(--brand);
}

/* Campaign Questions card (campaigns/show.ejs) still groups a template's attached children in a
   standalone bordered panel - there the template itself is deliberately NOT part of the campaign,
   so the panel has no parent row to merge into and must stand on its own. */
.generated-children-details { margin: 0 0 10px; padding: 10px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); }
.generated-children-details summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text); user-select: none; }
.generated-children-details[open] summary { margin-bottom: 10px; }
.generated-children-table { width: 100%; margin-top: 0; border-collapse: separate; border-spacing: 0; }
.generated-children-table td { border-top: 1px solid var(--border); padding: 14px 0; font-size: 13px; }
.generated-children-table tr:first-child td { border-top: none; }

/* Single-Shot campaigns' Questions card (see campaigns/show.ejs + routes/campaigns.js's
   buildQuestionBlocks + public/js/campaign-questions-dnd.js): drag-and-drop blocks/questions, with
   a Snippets-template block's own nested (admin-dragged) questions and mechanically-generated
   children indented underneath it, and alternating background shading per top-level row so a long
   list of blocks stays easy to visually scan. */
/* Manage Campaigns page's Questions card (campaigns/show.ejs): the plain <h3>Questions</h3> heading
   otherwise only gets the browser's small default heading margin, which reads as cramped once the
   card's own descriptive paragraph(s) and the drag-and-drop question list are right underneath it. */
.campaign-questions-heading { margin: 0 0 28px; }

.question-blocks { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.question-blocks > [data-board-item] { border: 1px solid var(--border); border-radius: 6px; background: var(--surface); }
.question-blocks > [data-board-item]:nth-child(even) { background: var(--surface-2); }
.question-blocks > [data-board-item].is-dragging { opacity: 0.4; }

/* Single-Shot rating page only (see mycampaign/show.ejs): a Snippets block's [data-board-item]
   container holds .field elements directly, with no .question-row/.question-block-header wrapper
   like the admin Questions card below brings its own padding from - .question-answer-block gives it
   that same breathing room, plus the persistent/one-time-flash green states for "every question in
   this block has been answered" (see that view's completion-highlighting script). Kept on its own
   class rather than the shared [data-board-item] selector above so none of this leaks onto the admin
   page's drag-and-drop board items, which already have their own padding/background rules. Placed
   after the nth-child(even) alternating-shading rule above so its equal-specificity green background
   wins the cascade on both odd and even rows. */
.question-answer-block { padding: 14px; transition: background-color 0.4s ease, border-color 0.4s ease; }
.question-blocks > .question-answer-block.is-complete { background: var(--ok-bg); border-color: var(--ok); }
.question-answer-block.is-flash { animation: question-answer-block-flash 0.9s ease; }
/* A completed block's OWN fields default to the app's usual dark input fill, which barely reads as
   different from "not yet answered" against the block's now-green background - swap them to a light
   green with dark purple text instead, so an already-typed answer stays easy to read at a glance
   rather than blending in. Scoped with the same .question-blocks > ancestor as the block's own
   green background above, for the same specificity reason documented there. */
.question-blocks > .question-answer-block.is-complete input[type="text"],
.question-blocks > .question-answer-block.is-complete input[type="number"],
.question-blocks > .question-answer-block.is-complete select,
.question-blocks > .question-answer-block.is-complete textarea {
  background: var(--ok-field-bg);
  color: var(--ok-field-text);
  border-color: var(--brand);
}
.question-blocks > .question-answer-block.is-complete input[type="text"]::placeholder,
.question-blocks > .question-answer-block.is-complete textarea::placeholder {
  color: #5b21b6;
}
/* The select arrow SVG further up this file is drawn in the muted purple used on the app's normal
   dark inputs - too low-contrast against the light green fill above, so a completed block's own
   select gets a darker arrow to match its darker text. */
.question-blocks > .question-answer-block.is-complete select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%232e1065' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
}
/* The question's own label text (and its "1"/"1.A" number prefix, see .question-number further down)
   normally render in a muted purple - fine against the block's usual dark background, but low-contrast
   once it turns green - so both switch to plain white on completion, same as the field-color fix
   above. Scoped with the same .question-blocks > ancestor for the same specificity reason. */
.question-blocks > .question-answer-block.is-complete .field label,
.question-blocks > .question-answer-block.is-complete .question-number {
  color: var(--text);
}
@keyframes question-answer-block-flash {
  0% { background-color: var(--ok-bg); }
  35% { background-color: var(--ok); }
  100% { background-color: var(--ok-bg); }
}
/* The "1" / "1.A" / "1.B" question numbers mycampaign/show.ejs prefixes onto each question's own
   label - a light brand accent so they read as a scanning aid without competing with the question
   text itself. */
.question-number { color: var(--brand); font-weight: 600; }

.question-row { display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
.question-row-name { flex: 1; font-size: 14px; }
/* touch-action: none stops a touchscreen's own scroll gesture from hijacking a press-and-drag on the
   handle before campaign-questions-dnd.js's pointer listeners get a chance to treat it as a drag. */
.drag-handle { cursor: grab; color: var(--muted); font-size: 16px; line-height: 1; user-select: none; flex-shrink: 0; touch-action: none; }
.drag-handle:active { cursor: grabbing; }
.drag-handle-inline { color: var(--muted); }

.question-block-header { display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
.question-block-header strong { flex: 1; font-size: 14px; }
/* Reuses the Questions index page's own collapsible generated-children styling
   (.generated-children-details, defined further up) - just given a bit of side margin here so it
   doesn't sit flush against the block's own edges the way it does inside that page's <td>. */
.question-block-generated-details { margin: 0 14px 10px; }
/* The generated-children rows here are plain .question-row flex divs (not a <table> like the
   Questions index page's .generated-children-table), so that page's own row-spacing fix never
   reached this card - give each row a bit more breathing room and a divider so a long list of
   generated questions doesn't read as one solid block of text. */
.question-block-generated-details .question-row { padding: 14px 0; border-top: 1px solid var(--border); }
.question-block-generated-details .question-row:first-child { border-top: none; }
.question-block-indent { padding: 0 14px 10px 40px; display: flex; flex-direction: column; gap: 6px; }
.question-block-indent .question-row { padding: 6px 10px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); }
/* The hint now renders INSIDE the [data-nest-zone] div itself (see campaigns/show.ejs), not as a
   sibling above it - so it shares that div's own indent padding already and needs none of its own,
   and so the drop zone always has real rendered size to actually receive a pointer/drag over it even
   before anything's ever been nested there. A zero-height, display:none-when-empty zone (the
   previous approach) meant the very first drop into a block had nowhere visible to land. */
.question-block-indent-hint { padding: 0; margin: 0; }
[data-nest-zone] { min-height: 44px; }

/* Consensus rating page only (see mycampaign/show.ejs): wraps the built-in Rating (0-5) field plus
   every attached question for one item in a single completion-tracking container - the whole set
   turns green together once every currently-visible field inside it has an answer, and briefly
   flashes the first time that happens, mirroring Single-Shot's per-generated-child
   .question-answer-block above but scoped to a whole item instead of one Snippets-block instance.
   Kept on its own class (rather than reusing .question-answer-block) since that class's own
   green-background rule is deliberately scoped to sit only inside .question-blocks, a drag-board
   container this page never renders. */
.item-question-block { padding: 14px; margin-top: 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); transition: background-color 0.4s ease, border-color 0.4s ease; }
.item-question-block.is-complete { background: var(--ok-bg); border-color: var(--ok); }
.item-question-block.is-flash { animation: question-answer-block-flash 0.9s ease; }
/* Same reasoning as .question-answer-block.is-complete's own field styling above: a completed
   item's fields default to the app's usual dark fill, which is hard to read against the green
   background and barely distinguishable from an unanswered field - light green + dark purple text
   instead. */
.item-question-block.is-complete input[type="text"],
.item-question-block.is-complete input[type="number"],
.item-question-block.is-complete select,
.item-question-block.is-complete textarea {
  background: var(--ok-field-bg);
  color: var(--ok-field-text);
  border-color: var(--brand);
}
.item-question-block.is-complete input[type="text"]::placeholder,
.item-question-block.is-complete textarea::placeholder {
  color: #5b21b6;
}
.item-question-block.is-complete select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%232e1065' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
}
/* Same label/number color fix as .question-answer-block.is-complete above - the muted purple used for
   question text everywhere else is low-contrast against this block's green completed background. */
.item-question-block.is-complete .field label,
.item-question-block.is-complete .question-number {
  color: var(--text);
}

/* Overall completion bar (see mycampaign/show.ejs and survey/take.ejs): one running "X of Y
   answered" readout summarizing the whole page, kept in sync live by each page's own inline script
   as fields are answered - distinct from the per-item/per-block green highlighting above, which
   only ever reflects one item or Snippets-block instance at a time. Deliberately counts by ordinary
   .field elements rather than anything type-specific, so the identical markup/script works for
   Single-Shot campaigns, Consensus campaigns, and surveys without knowing which layout is in play. */
.completion-bar { margin-top: 14px; }
.completion-bar-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 8px; }
.completion-bar-label { font-size: 14px; font-weight: 600; }
.completion-bar-count { font-size: 13px; color: var(--muted); }
.completion-bar-track { height: 10px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border); overflow: hidden; }
.completion-bar-fill { height: 100%; width: 0%; background: var(--brand); border-radius: 999px; transition: width 0.3s ease, background-color 0.3s ease; }
.completion-bar.is-complete .completion-bar-fill { background: var(--ok); }
.completion-bar.is-complete .completion-bar-count { color: var(--ok); }

/* Audience Results' Question Answers table: same alternating-block visual as the admin/respondent
   Snippets-block nesting above, applied per table row instead of a flex block since results are a
   plain <table> - see campaigns/audience-results.ejs. */
tr.results-block-even { background: var(--surface-2); }
td.results-nested-question { padding-left: 34px; }

.tag-toggle-group { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-toggle { position: relative; display: inline-flex; }
.tag-toggle-input { position: absolute; opacity: 0; width: 0; height: 0; }
.tag-toggle-label {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  background: var(--danger);
  color: #2a0f0d;
  border: 1px solid transparent;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.tag-toggle-label:hover { filter: brightness(1.08); }
.tag-toggle-input:checked + .tag-toggle-label { background: var(--ok); color: #0d2a17; }
.tag-toggle-input:focus-visible + .tag-toggle-label { outline: 2px solid var(--brand); outline-offset: 2px; }

.tag-filter-actions { display: flex; align-items: center; gap: 12px; }

.role-access-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }
.role-employee-card { margin-bottom: 0; }
.role-employee-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.role-employee-name { font-size: 14px; font-weight: 600; color: var(--text); }
.role-all-toggle { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); white-space: nowrap; cursor: pointer; }
.role-chip-group { display: flex; flex-wrap: wrap; gap: 8px; }
.role-chip { position: relative; display: inline-flex; }
.role-chip-input { position: absolute; opacity: 0; width: 0; height: 0; }
.role-chip-label {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.role-chip-label:hover { border-color: var(--brand); }
.role-chip-input:checked + .role-chip-label { background: var(--surface); border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand) inset; }
.role-chip-input:focus-visible + .role-chip-label { outline: 2px solid var(--brand); outline-offset: 2px; }

.due-soon-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 20px;
  background: var(--danger-bg);
  color: var(--danger);
  font-size: 13px;
  border-bottom: 1px solid var(--border);
}
.due-soon-banner a { color: var(--danger); text-decoration: underline; }
.due-soon-dismiss {
  background: none;
  border: none;
  color: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.due-soon-dismiss:hover { opacity: 0.75; }

.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 20px; }
.metric-card { background: var(--surface-2); border-radius: 6px; padding: 14px 16px; text-align: center; }
.metric-label { font-size: 12px; color: var(--muted); text-transform: uppercase; margin-bottom: 6px; }
.metric-value { font-size: 40px; font-weight: 700; color: var(--text); }
.metric-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }

.due-red { color: var(--danger); font-weight: 600; }
.due-orange { color: var(--warn); font-weight: 600; }
.due-green { color: var(--ok); font-weight: 600; }

.tier-badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; text-transform: capitalize; }
.tier-copper { background: #3a2a1a; color: #d99a5b; }
.tier-silver { background: #2c2c32; color: #c7c9d1; }
.tier-gold { background: #3a3020; color: #e0b64a; }
.tier-platinum { background: #24333a; color: #7fd6e0; }
.tier-diamond { background: #23304a; color: #7fb1ff; }

/* Product walkthroughs (see services/walkthroughs.js): Dashboard setup checklist, static Help
   pages, inline per-page guidance callouts, and the guided tour overlay. */
.walkthrough-tracks { display: flex; flex-wrap: wrap; gap: 24px; margin-top: 14px; }
.walkthrough-track { flex: 1 1 280px; min-width: 240px; padding: 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); }
.walkthrough-track-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.walkthrough-steps { list-style: none; margin: 12px 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.walkthrough-step { display: flex; align-items: center; gap: 10px; }
.walkthrough-check {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 999px; border: 1px solid var(--border);
  font-size: 12px; color: var(--muted); background: var(--surface);
}
.walkthrough-step.is-done .walkthrough-check { background: var(--ok); border-color: var(--ok); color: #fff; }

.walkthrough-help-nav { display: flex; gap: 10px; margin-bottom: 18px; }
.walkthrough-help-steps { display: flex; flex-direction: column; gap: 18px; margin-top: 16px; }
.walkthrough-help-step { display: flex; gap: 14px; padding: 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); }
.walkthrough-help-step-number {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 30px; height: 30px; border-radius: 999px; background: var(--accent); color: #fff;
  font-weight: 700; font-size: 14px;
}
.walkthrough-help-step-body h4 { margin: 0 0 6px; font-size: 15px; }
.walkthrough-help-step-body p { margin: 0 0 8px; }

.walkthrough-callout {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 16px; border-radius: 6px;
  border: 1px solid var(--accent); background: var(--surface-2);
}
.walkthrough-callout-text { font-size: 13px; }
.walkthrough-callout-dismiss { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 16px; line-height: 1; padding: 2px 4px; }
.walkthrough-callout-dismiss:hover { color: var(--text); }

/* Guided tour overlay - see public/js/product-tour.js. A fixed, full-viewport dimmed backdrop with a
   transparent "cutout" (box-shadow trick - a huge spread radius on a small, positioned box) around
   the real element being highlighted, plus a small tooltip box anchored near it. Vanilla/CSP-safe:
   no third-party tour library, since the CSP blocks loading one from anywhere but cdnjs. */
.tour-backdrop {
  position: fixed; inset: 0; z-index: 9000; pointer-events: none;
}
.tour-spotlight {
  position: fixed; z-index: 9001; border-radius: 8px; pointer-events: none;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  transition: top 0.2s ease, left 0.2s ease, width 0.2s ease, height 0.2s ease;
}
.tour-tooltip {
  position: fixed; z-index: 9002; max-width: 320px; padding: 14px 16px; border-radius: 8px;
  background: var(--surface); border: 1px solid var(--accent); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  transition: top 0.2s ease, left 0.2s ease;
}
.tour-tooltip-eyebrow { font-size: 11px; text-transform: uppercase; color: var(--accent); font-weight: 700; margin-bottom: 4px; }
.tour-tooltip-title { font-size: 14px; font-weight: 700; margin-bottom: 6px; }
.tour-tooltip-body { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
.tour-tooltip-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.u-list-plain { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }

/* Image/Video-picklist response types: each option (radio for single-select, checkbox for
   allow-multiple) renders as a clickable thumbnail/player card instead of plain text, highlighted
   once picked. */
.option-choice-group { display: flex; flex-wrap: wrap; gap: 12px; }
.option-choice { position: relative; display: inline-flex; }
.option-choice-input { position: absolute; opacity: 0; width: 0; height: 0; }
.option-choice-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 150px;
  padding: 8px;
  border: 2px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
  cursor: pointer;
  user-select: none;
  text-align: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.option-choice-label img, .option-choice-label video { width: 100%; height: 90px; object-fit: cover; border-radius: 4px; background: var(--surface); }
.option-choice-label video { background: #000; }
.option-choice-label span { font-size: 12px; color: var(--text); word-break: break-word; }
.option-choice-label:hover { border-color: var(--brand); }
.option-choice-input:checked + .option-choice-label { border-color: var(--brand); background: var(--surface); box-shadow: 0 0 0 2px var(--brand) inset; }
.option-choice-input:focus-visible + .option-choice-label { outline: 2px solid var(--brand); outline-offset: 2px; }
.option-choice-input:disabled + .option-choice-label { cursor: default; opacity: 0.7; }

/* Admin UI for building an "image"/"video" response type's picklist (Response Types > New/Edit). */
.option-media-manager { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
.option-media-card { width: 150px; text-align: center; }
.option-media-card img, .option-media-card video { width: 100%; height: 90px; object-fit: cover; border-radius: 6px; border: 1px solid var(--border); background: var(--surface); }
.option-media-card video { background: #000; }
.option-media-add-form { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px; border: 1px dashed var(--border); border-radius: 8px; }

/* New Response Type form: initial option rows (name + file) added via "+ Add another option"
   before the response type itself has even been created yet. */
.new-option-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.new-option-row input[type="text"] { flex: 1; min-width: 160px; }

/* Probing logic: a question nested under one specific response value of another question, on the
   rating page - indented and left-bordered to visually show it "belongs" to the question above it. */
.probe-question { margin-left: 22px; margin-top: 10px; padding-left: 14px; border-left: 3px solid var(--border); }

/* Small reusable status/visibility classes - used in place of the equivalent inline style="" so
   style-src doesn't need 'unsafe-inline' for these conditional cases (Test Connection result cards,
   initially-collapsed form sections that JS shows/hides via element.style, which the CSP style-src-attr
   directive does not govern since it only restricts the literal markup style="" attribute). */
.is-hidden { display: none; }
.status-card-ok { border-color: var(--ok); }
.status-card-danger { border-color: var(--danger); }
.status-text-ok { color: var(--ok); }
.status-text-danger { color: var(--danger); }

/* Custom confirmation modal (public/js/confirm-forms.js) for every data-confirm action sitewide
   (deletes, restarts, token regeneration, etc). Deliberately NOT using the browser's native
   window.confirm() - some browsers/embedded contexts silently suppress repeated JS dialogs ("Prevent
   this page from creating additional dialogs"), or block them outright, and once that happens
   window.confirm() just returns false immediately with no dialog ever shown - which looks exactly
   like the button doing nothing at all. This is plain DOM/CSS, so it can't be suppressed that way.
   ".confirm-overlay.is-hidden" (combined selector, so its specificity wins regardless of the two
   classes' order in this file) is what actually hides it - ".is-hidden" alone would otherwise be
   overridden by ".confirm-overlay"'s own "display: flex" below at equal specificity. */
.confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.confirm-overlay.is-hidden { display: none; }
.confirm-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 24px;
  max-width: 420px;
  width: calc(100% - 40px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.confirm-message { margin: 0 0 20px; color: var(--text); font-size: 14px; line-height: 1.5; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; }

/* ==============================================================================================
   Small screens
   ==============================================================================================
   Everything above this point is the desktop layout, unchanged. This block only takes effect
   below 768px, so nothing here can alter how the app looks on a laptop.

   The single most important change is not in this file at all - it is the <meta name="viewport">
   tag now present in every view. Without it a phone renders the page at ~980px and shrinks it to
   fit, which is why the app used to look like a tiny, zoomed-out desktop screenshot. With it, the
   browser reports the real device width and the rules below can do their job.
   ============================================================================================== */

/* The hamburger button. Hidden entirely on desktop, where the sidebar is always visible. */
.nav-toggle { display: none; }
.nav-backdrop { display: none; }

@media (max-width: 767px) {
  /* --- Navigation drawer ---------------------------------------------------------------------
     The 220px sidebar takes 59% of a 375px phone screen, so it slides off-canvas and is opened by
     the hamburger (see public/js/mobile-nav.js). transform is used rather than display/width so
     the drawer animates and keeps its layout while hidden. */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 200;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
  }
  body.nav-open .sidebar { transform: translateX(0); }

  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: 300;
    /* 40px square: at or above the ~44px comfortable tap target once its padding is counted, and
       small enough not to crowd the topbar on a narrow screen. */
    width: 40px;
    height: 40px;
    padding: 8px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
  }
  .nav-toggle-bar { display: block; height: 2px; background: var(--text); border-radius: 2px; }

  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 150;
    background: rgba(0, 0, 0, 0.45);
  }
  .nav-backdrop[hidden] { display: none; }

  /* Leaves room for the fixed hamburger so it never overlaps the company name. */
  .topbar { padding: 12px 12px 12px 56px; }
  .topbar .who { font-size: 12px; }
  .topbar-actions { gap: 8px; }

  /* --- Content ------------------------------------------------------------------------------- */
  .content { padding: 12px; }
  .card { padding: 14px; }

  /* Two-column forms become one column - side-by-side fields are unusable at this width. */
  .form-grid { grid-template-columns: 1fr; }
  .two-col { grid-template-columns: 1fr; }
  .profile-tag-field { width: 100%; flex-basis: 100%; }

  /* Toolbars hold a title and an action button; let them wrap instead of squashing the button. */
  .toolbar { flex-wrap: wrap; }
  .search-row { flex-wrap: wrap; }
  .search-row-input { flex-basis: 100%; }

  .metric-grid { grid-template-columns: 1fr; }
  .form-actions { flex-wrap: wrap; }

  /* --- Tables ---------------------------------------------------------------------------------
     Deliberately a horizontal scroll rather than a re-layout. The app has 25 table pages, some 13
     columns wide, and genuinely reformatting those into stacked cards is a per-page design job -
     it is the next phase of this work, not something a media query can do honestly. What this
     does guarantee is that a wide table scrolls within its own card instead of stretching the
     whole page sideways, which is what makes every other element on the page unreadable.
     -webkit-overflow-scrolling keeps the momentum scroll feel on iOS. */
  .card table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
  }

  /* Standalone (non-shell) pages: login, signup, password reset.
     NOTE: .wizard-box is deliberately NOT listed here even though the wizard is the same shape of
     page. Those pages load app.css first and marketing.css second, and media queries add no
     specificity - so a .wizard-box rule here would silently lose to the plain .wizard-box rule in
     marketing.css and do nothing. Its small-screen override lives in marketing.css instead. */
  .login-box { width: 100%; max-width: 100%; margin: 0; border-radius: 0; }
  .login-page { padding: 16px; }
  .survey-box { padding: 24px 16px; }
}

/* ============================================================================
   Toggle switch - a single on/off setting (Active, Use system default, Allow
   multiple values, etc.) rendered as a pill switch with its ON/OFF state
   spelled out inside it, in the brand purple, instead of a plain checkbox.
   Deliberately opt-in via this class rather than a blanket input[type=checkbox]
   rule: a multi-select list (tag pickers, the Tags survey's Question table,
   audience/skill/response checkboxes) stays a plain, compact checkbox - a row
   of 20+ of these large pills would make those unusable.
   Pure CSS, no extra markup: appearance:none turns the checkbox into a plain
   generated box that still supports ::before/::after, so the knob AND the
   ON/OFF text both come from the same real, native <input type="checkbox">
   already in the page - a screen reader/keyboard user still gets a normal
   checkbox, nothing here is decorative-only or JS-driven. Placed at the very
   end of the file on purpose, after every other input[type=checkbox] rule
   (see .field .checkbox-label input above) so it reliably wins the cascade at
   matching specificity, without resorting to !important. */
input[type="checkbox"].toggle-switch {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: inline-block;
  width: 64px;
  height: 30px;
  min-width: 64px;
  border-radius: 999px;
  background: var(--border);
  border: none;
  margin: 0;
  cursor: pointer;
  vertical-align: middle;
  transition: background-color .15s ease;
}
input[type="checkbox"].toggle-switch::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
  transition: transform .15s ease;
}
input[type="checkbox"].toggle-switch::after {
  content: "OFF";
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--muted);
  pointer-events: none;
}
input[type="checkbox"].toggle-switch:checked { background: var(--brand-dark); }
input[type="checkbox"].toggle-switch:checked::before { transform: translateX(34px); }
input[type="checkbox"].toggle-switch:checked::after { content: "ON"; left: 10px; right: auto; color: #fff; }
input[type="checkbox"].toggle-switch:hover:not(:disabled) { filter: brightness(1.1); }
input[type="checkbox"].toggle-switch:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
input[type="checkbox"].toggle-switch:disabled { opacity: .5; cursor: not-allowed; }

/* --- Participant sign-up / sign-in (Phase 41) ------------------------------------------------
   The second way into this app: external participants, who sign in with a social account rather
   than a password. Shares .login-box so the two entry points look like the same product. */
.participant-auth-box { max-width: 30rem; }

.participant-auth-intro { margin: 0 0 1.25rem; line-height: 1.5; }

.participant-provider-list { display: flex; flex-direction: column; gap: 0.5rem; }

/* Full-width rows rather than a grid: on a phone - where most participants will arrive from a
   social post - a single column of large tap targets is the whole point. */
.participant-provider-btn {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  justify-content: flex-start;
  text-align: left;
  padding: 0.625rem 0.875rem;
}

/* A neutral lettermark stands in for each provider's logo. Deliberately not the real Facebook /
   Google / Apple marks: those are trademarks with their own brand guidelines, and shipping
   hand-drawn copies of them would be both a licensing problem and visibly off-brand. Swap these for
   each provider's officially supplied asset when the apps are registered. */
.participant-provider-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: 0 0 1.5rem;
  border-radius: 50%;
  background: var(--brand, #6b3fa0);
  color: #fff;
  font-weight: 700;
  font-size: 0.8125rem;
  line-height: 1;
}

.participant-auth-note { margin-top: 1rem; font-size: 0.8125rem; line-height: 1.45; }

.participant-auth-divider {
  border: 0;
  border-top: 1px solid var(--border, #e2e2e8);
  margin: 1.25rem 0 1rem;
}

.participant-auth-crossref { font-size: 0.8125rem; line-height: 1.5; margin: 0; }
