/**
 * FlowContent - Interview Form Styles
 * Clean, professional design for the public interview form.
 */

:root {
    --fi-primary: #2563eb;
    --fi-primary-dark: #1d4ed8;
    --fi-success: #16a34a;
    --fi-warning: #d97706;
    --fi-danger: #dc2626;
    --fi-gray-50: #f9fafb;
    --fi-gray-100: #f3f4f6;
    --fi-gray-200: #e5e7eb;
    --fi-gray-300: #d1d5db;
    --fi-gray-500: #6b7280;
    --fi-gray-700: #374151;
    --fi-gray-900: #111827;
    --fi-radius: 8px;
    --fi-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
}

.fi-interview {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    color: var(--fi-gray-900);
    line-height: 1.6;
}

.fi-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem 1rem;
}

/* Header */
.fi-header {
    text-align: center;
    margin-bottom: 2rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid var(--fi-gray-200);
}

.fi-header h1 {
    font-size: 1.75rem;
    font-weight: 700;
    margin-bottom: 0.5rem;
}

.fi-subtitle {
    font-size: 1.1rem;
    color: var(--fi-gray-500);
    max-width: 600px;
    margin: 0 auto;
}

.fi-time {
    display: inline-block;
    margin-top: 1rem;
    padding: 0.5rem 1rem;
    background: var(--fi-gray-50);
    border-radius: var(--fi-radius);
    font-size: 0.9rem;
    color: var(--fi-gray-500);
}

/* 0.220.0 (open-call friction fix, Task 2) - the up-front "what a good answer looks
   like" statement (interview_standard_statement). Sits with .fi-subtitle/.fi-time in
   the welcome banner, which scrolls away like the old progress container used to -
   expected here, since the persistent bar + each question's own
   .fi-question-objective are what stay in view for the rest of the session. */
.fi-standard-statement {
    max-width: 600px;
    margin: 0.75rem auto 0;
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--fi-gray-700);
}

/* 0.167.0 - submission step map: text-forward, shows what the steps ARE and their
   live status. Not a graphical component by design (James, 2026-07-30). */
.fi-steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1.5rem;
    list-style: none;
    counter-reset: fistep;
    margin: 0 0 0.75rem;
    padding: 0;
    font-size: 0.85rem;
    color: var(--fi-gray-500);
}

.fi-step::before {
    counter-increment: fistep;
    content: counter(fistep) ". ";
    font-weight: 600;
}

.fi-step-label {
    font-weight: 600;
    color: var(--fi-gray-700);
}

.fi-step-status {
    margin-left: 0.35rem;
}

.fi-step-status::before {
    content: "- ";
}

.fi-step-active .fi-step-label {
    color: var(--fi-primary);
}

.fi-step-done {
    color: var(--fi-gray-400, #9ca3af);
}

.fi-step-done .fi-step-label {
    color: var(--fi-gray-400, #9ca3af);
}

.fi-step-done .fi-step-label::after {
    content: " \2713";
    color: var(--fi-success, #16a34a);
    font-weight: 700;
}

/* Persistent bottom bar (0.220.0, open-call friction fix). Replaces the progress
   readout's old home here in normal document flow: .fi-progress-container used to
   render near the top of the page and worked correctly ("Question 8 of ~8"), but a
   respondent deep into a dynamic interview had scrolled hundreds of pixels below it
   by the final question and never saw it ("I never saw any numbers but that would
   have helped" - the contributor who abandoned one answer short of finishing). The
   SAME #fiProgressBar/#fiProgressText pair (ids unchanged - no interview-form.js
   changes needed for this half of the fix) now lives inside .fi-bar-progress, fixed
   to the viewport bottom via .fi-persistent-bar below, alongside an End Now control
   and an optional per-brand guidance line - see default.php's fi-persistent-bar
   markup. Colors reuse the SAME tokens as the rest of this file (:root above) - no
   new palette - with var(--fi-gray-900) applied as a background for once instead of
   text, so the bar reads as a distinct, persistent surface against the light card
   stack it floats over. */
.fi-persistent-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    background: var(--fi-gray-900);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
}

.fi-persistent-bar-inner {
    max-width: 800px;
    margin: 0 auto;
    padding: 0.65rem 1rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
}

.fi-bar-progress {
    position: relative;
    flex: 1 1 160px;
    min-width: 140px;
    height: 22px;
    background: var(--fi-gray-700);
    border-radius: 11px;
    overflow: hidden;
}

.fi-progress-bar {
    height: 100%;
    background: var(--fi-primary);
    border-radius: 11px;
    transition: width 0.3s ease;
}

.fi-progress-text {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
    white-space: nowrap;
}

/* Per-brand standing guidance (interview_bar_guidance) - only ever in the DOM when
   the operator set one (see default.php's empty-string skip), so no extra
   display:none default is needed here. */
.fi-bar-guidance {
    flex: 2 1 220px;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--fi-gray-300, #d1d5db);
}

.fi-bar-end-now {
    flex: 0 0 auto;
    margin-left: auto;
}

.fi-end-now-confirm {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
}

.fi-end-now-confirm-text {
    font-size: 0.8rem;
    max-width: 220px;
    color: var(--fi-gray-100, #f3f4f6);
}

.fi-end-now-error {
    flex-basis: 100%;
    text-align: right;
    font-size: 0.78rem;
    /* var(--fi-danger) is tuned for a light background - this is a light-on-dark
       tint of the same red, not a new hue. */
    color: #fca5a5;
}

/* ----------------------------------------------------------------
 * Question-header bar mode (0.221.0, open-call friction fix, variant B)
 *
 * The alternative to .fi-persistent-bar above: instead of pinning progress +
 * guidance + the step map to the viewport edge (outside the respondent's natural
 * sightline while they're answering - the owner's canary verdict on 0.220.1),
 * this strip attaches directly above the ACTIVE question's own text and stays
 * sticky within that question's own card - see interview-form.js's
 * placeQuestionBar(), which moves the single #fiQuestionBar element (never
 * cloned) here as the current question changes. #fiProgressBar/#fiProgressText
 * inside it are the exact same elements updateProgress() already updates - no JS
 * changes needed there. #fiSteps is the exact same step map markup/ids as the
 * fixed_bottom mode's copy in default.php, just rendered in a different spot.
 *
 * Light theme throughout (--fi-gray-50 background), unlike .fi-persistent-bar's
 * deliberate dark-strip exception noted above - this has to read as part of the
 * white question card, not a foreign dark band dropped into it. Negative margins
 * bleed it flush to the card's own edges (undoing .fi-question's 1.5rem padding
 * below), with smaller padding of its own inside, like a card header.
 * ---------------------------------------------------------------- */
.fi-question-bar {
    position: sticky;
    /* Constrained to the .fi-question it's currently inside: sticky elements stop
       sticking once their containing block's bottom edge scrolls past this
       offset, so this pins while the respondent is on THIS question and releases
       - scrolling away with the rest of that question's card - the moment they
       scroll past it entirely, exactly as the brief requires. No JS involved in
       the un-pin; it's the standard "sticky header confined to its parent card"
       behavior, and .fi-question never has overflow/position set in a way that
       would break it. */
    top: 0;
    z-index: 5;
    margin: -1.5rem -1.5rem 1rem;
    padding: 0.6rem 1.5rem;
    background: var(--fi-gray-50);
    border-bottom: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius) var(--fi-radius) 0 0;
}

/* Mobile-first base: progress and guidance stack (their own row each) rather
   than compete for width on a narrow screen - condensing happens via the
   min-width bump below, not by shrinking text past legibility. */
.fi-question-bar-progress-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.fi-question-bar .fi-bar-progress {
    height: 20px;
    background: var(--fi-gray-200);
    border-radius: 10px;
}

/* Row 1 reuses .fi-progress-text/.fi-bar-guidance verbatim (same classes/ids as
   .fi-persistent-bar above) but those were tuned for a DARK bar - override the
   two color-bearing properties for this light one. Everything else (layout,
   sizing, the progress fill itself) is shared and untouched. */
.fi-question-bar .fi-progress-text {
    color: var(--fi-gray-900);
}

.fi-question-bar .fi-bar-guidance {
    color: var(--fi-gray-500);
    font-size: 0.78rem;
}

/* Row 2 - the step map. Same .fi-steps rule set as everywhere else in this file
   (already light-theme-correct - no color overrides needed), just tightened up
   for a compact strip instead of open page flow. */
.fi-question-bar .fi-steps {
    margin: 0.5rem 0 0;
    gap: 0.25rem 1.25rem;
    font-size: 0.75rem;
}

@media (min-width: 481px) {
    /* Enough width for progress and guidance to share a row - matches
       .fi-persistent-bar-inner's desktop layout above. */
    .fi-question-bar-progress-row {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.4rem 1rem;
    }

    .fi-question-bar .fi-bar-progress {
        flex: 1 1 160px;
        min-width: 140px;
    }

    .fi-question-bar .fi-bar-guidance {
        flex: 2 1 200px;
    }
}

/* Narrow-width priority ladder (mobile-first requirement): progress is never
   sacrificed, guidance gives way first, the step checklist second. 480px/360px
   roughly track "typical phone" and "smallest supported phone" rather than
   matching this file's single 640px breakpoint elsewhere - .fi-question-bar
   needs its own finer ladder because it has three things competing for a much
   narrower column (inside one question card) than the full-width persistent bar
   ever did. */
@media (max-width: 480px) {
    .fi-question-bar .fi-bar-guidance {
        display: none;
    }
}

@media (max-width: 360px) {
    .fi-question-bar .fi-steps {
        display: none;
    }
}

/* End Now relocated beside Continue (interview-form.js's addContinueButton()),
   not up in row 1 with progress - the brief: it is the alternative to Continue,
   so it reads as a sibling action, not a status readout. .fi-continue-wrap's own
   opacity/transform reveal transition is untouched; this just gives the pair a
   shared row. */
.fi-question-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-top: 1rem;
}

.fi-question-actions .fi-continue-wrap {
    margin-top: 0;
}

/* .fi-bar-end-now/.fi-end-now-confirm-text/.fi-end-now-error were tuned for
   .fi-persistent-bar's dark strip and right-aligned layout (margin-left: auto,
   text-align: right, light-on-dark tint colors) - override for a centered
   row on a light card. */
.fi-question-actions .fi-bar-end-now {
    flex: 0 0 auto;
    margin-left: 0;
}

.fi-question-actions .fi-end-now-confirm {
    justify-content: center;
}

.fi-question-actions .fi-end-now-confirm-text {
    color: var(--fi-gray-700);
}

.fi-question-actions .fi-end-now-error {
    text-align: center;
    color: var(--fi-danger);
}

/* A collapsed (already-answered) question shows only a one-line preview - see
   .fi-question.fi-question-collapsed below for the textarea/meta/pass-button
   hides this joins. Without this, #fiQuestionBar/.fi-question-actions would
   sit orphaned and visible inside a collapsed card for the brief window between
   collapseQuestion() (which doesn't move them - see placeQuestionBar()'s own
   docblock) and the next placeQuestionBar()/addContinueButton() call moving them
   into the new current question, most noticeable during a dynamic/adaptive
   "Thinking..." pause between questions. */
.fi-question.fi-question-collapsed .fi-question-bar,
.fi-question.fi-question-collapsed .fi-question-actions {
    display: none;
}

/* Keep the last question's textarea and the Continue/Submit button scrollable
   clear of the fixed bar above - paired with each scroll target's
   scroll-margin-bottom below (native scrollIntoView() calls in interview-form.js
   already target .fi-question elements and would otherwise land partly behind it).

   0.221.0 (open-call friction fix, variant B) - scoped to .fi-bar-mode-fixed_bottom
   (see default.php's fi-bar-mode-* class on #fiInterview). Unscoped, this padding
   would still apply in question_header/off modes even though neither has a fixed
   bar to clear - dead space at the bottom of every page for no reason. The
   fixed_bottom values themselves are byte-for-byte unchanged from 0.220.1. */
.fi-bar-mode-fixed_bottom .fi-container {
    padding-bottom: 6rem;
}

.fi-bar-mode-fixed_bottom .fi-question,
.fi-bar-mode-fixed_bottom .fi-submit-section {
    scroll-margin-bottom: 6rem;
}

/* Autosave indicator */
.fi-autosave {
    text-align: right;
    font-size: 0.8rem;
    color: var(--fi-gray-500);
    margin-bottom: 1.5rem;
}

.fi-autosave.fi-saving .fi-autosave-text::before {
    content: "Saving... ";
    color: var(--fi-warning);
}

/* Questions */
.fi-question {
    margin-bottom: 2rem;
    padding: 1.5rem;
    background: white;
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
    box-shadow: var(--fi-shadow);
}

.fi-question-error {
    border-color: var(--fi-danger);
}

.fi-question-declined {
    opacity: 0.5;
}

.fi-question-declined .fi-textarea {
    background: var(--fi-gray-100);
    min-height: 3rem;
    height: 3rem;
}

.fi-btn-pass {
    background: none;
    border: 1px solid var(--fi-gray-300);
    color: var(--fi-gray-500);
    padding: 0.2rem 0.75rem;
    border-radius: var(--fi-radius);
    cursor: pointer;
    font-size: 0.8rem;
    margin-left: auto;
}

.fi-btn-pass:hover {
    border-color: var(--fi-gray-500);
    color: var(--fi-gray-700);
}

.fi-question-declined .fi-btn-pass {
    border-color: var(--fi-primary);
    color: var(--fi-primary);
}

.fi-question-header {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.fi-question-number {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    min-width: 28px;
    background: var(--fi-primary);
    color: white;
    border-radius: 50%;
    font-size: 0.85rem;
    font-weight: 600;
}

.fi-question-text {
    font-size: 1.05rem;
    font-weight: 500;
    line-height: 1.5;
}

.fi-required {
    color: var(--fi-danger);
}

.fi-textarea {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    font-size: 1rem;
    line-height: 1.6;
    resize: vertical;
    font-family: inherit;
    transition: border-color 0.2s;
}

.fi-textarea:focus {
    outline: none;
    border-color: var(--fi-primary);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}

.fi-question-meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
    font-size: 0.8rem;
}

/* 0.220.0 (open-call friction fix) - fi-question-objective renders each question's
   server-side `objective` (AIService::generateNextQuestion()'s 'objective' key -
   previously consumed only for scoring) as respondent-facing guidance, placed under
   the question text and above the textarea. It replaces the word-count meter removed
   below: a meter with no real minimum (every dynamically generated question shipped
   with data-min-words="0" hardcoded) told a respondent that any length was fine,
   while the objective now tells them what a good answer actually contains. Styled as
   help text - muted, left-accent bar, smaller type - so it reads as guidance ABOUT the
   question, not as part of the question itself. Both the page-load path
   (tmpl/interview/default.php) and the AJAX path (appendDynamicQuestion() in
   interview-form.js) render this element only when an objective string is present -
   never as an empty box. */
.fi-question-objective {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: baseline;
    background: var(--fi-gray-50);
    border-left: 3px solid var(--fi-gray-300);
    border-radius: 0 var(--fi-radius) var(--fi-radius) 0;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.75rem;
    font-size: 0.85rem;
    line-height: 1.5;
}

.fi-question-objective-label {
    font-weight: 600;
    color: var(--fi-gray-500);
    white-space: nowrap;
}

.fi-question-objective-text {
    color: var(--fi-gray-700);
}

/* 0.220.0 - fi-word-count / fi-word-count-ok / fi-word-count-low / fi-min-words
   removed here. The meter they styled had no real target on the AJAX-generated path
   (data-min-words="0" was hardcoded - see interview-form.js's appendDynamicQuestion())
   so it always read "0 words" and climbed with nothing to compare against, quietly
   telling a respondent that short answers were fine. fi-question-objective above now
   carries that guidance signal instead. */

.fi-save-status {
    margin-left: auto;
    font-size: 0.8rem;
}

.fi-saved {
    color: var(--fi-success);
}

/* Item 7 (WP-4): a failed/unreachable autosave used to render as tiny grey
   text indistinguishable from the idle state. Give it a visible, persistent
   pill so a rate-limit/expiry/network failure doesn't slip past the
   respondent while they keep answering into the void. */
.fi-save-status-error {
    color: #fff;
    background: var(--fi-danger);
    font-weight: 600;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
}

/* Images */
.fi-section {
    margin-top: 3rem;
    padding-top: 2rem;
    border-top: 1px solid var(--fi-gray-200);
}

.fi-section h2 {
    font-size: 1.25rem;
    margin-bottom: 0.5rem;
}

.fi-dropzone {
    border: 2px dashed var(--fi-gray-300);
    border-radius: var(--fi-radius);
    padding: 2rem;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s;
}

.fi-dropzone:hover,
.fi-dropzone-active {
    border-color: var(--fi-primary);
    background: rgba(37, 99, 235, 0.02);
}

.fi-dropzone-icon {
    font-size: 2rem;
    display: block;
    margin-bottom: 0.5rem;
}

.fi-image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
    margin-top: 1.5rem;
}

.fi-image-card {
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
    overflow: hidden;
    position: relative;
}

.fi-image-remove {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 28px;
    height: 28px;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}

.fi-image-remove:hover {
    background: rgba(220, 38, 38, 0.9);
}

.fi-image-card img {
    width: 100%;
    height: 150px;
    object-fit: cover;
}

.fi-image-card .fi-input {
    border: none;
    border-top: 1px solid var(--fi-gray-200);
    border-radius: 0;
    font-size: 0.85rem;
    padding: 0.5rem;
}

/* Input group for URL */
.fi-input-group {
    display: flex;
    gap: 0.5rem;
}

.fi-input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    font-size: 0.95rem;
}

.fi-input:focus {
    outline: none;
    border-color: var(--fi-primary);
}

/* Buttons */
.fi-btn {
    display: inline-block;
    padding: 0.5rem 1.25rem;
    border: none;
    border-radius: var(--fi-radius);
    font-size: 0.95rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
    text-decoration: none;
}

.fi-btn-primary {
    background: var(--fi-primary);
    color: white;
}

.fi-btn-primary:hover {
    background: var(--fi-primary-dark);
}

.fi-btn-secondary {
    background: var(--fi-gray-100);
    color: var(--fi-gray-700);
    border: 1px solid var(--fi-gray-300);
}

.fi-btn-secondary:hover {
    background: var(--fi-gray-200);
}

.fi-btn-large {
    padding: 0.75rem 2rem;
    font-size: 1.1rem;
}

/* Submit section */
.fi-submit-section {
    margin-top: 3rem;
    text-align: center;
    padding: 2rem;
    background: var(--fi-gray-50);
    border-radius: var(--fi-radius);
}

.fi-muted {
    color: var(--fi-gray-500);
    font-size: 0.85rem;
    margin-top: 0.75rem;
}

/* Completion card */
.fi-interview-complete {
    min-height: 50vh;
    display: flex;
    align-items: center;
}

.fi-card {
    padding: 3rem;
    text-align: center;
    border-radius: var(--fi-radius);
    box-shadow: var(--fi-shadow);
}

.fi-card-success {
    background: white;
    border: 2px solid var(--fi-success);
}

.fi-card-success h1 {
    color: var(--fi-success);
}

/* Landing Page */
.fi-landing-form {
    margin: 2rem 0;
}

.fi-landing-form h2,
.fi-landing-request h2 {
    font-size: 1.25rem;
    margin-bottom: 0.75rem;
}

.fi-token-form .fi-input-group {
    max-width: 500px;
    margin: 0 auto;
}

.fi-input-large {
    font-size: 1.1rem;
    padding: 0.75rem 1rem;
}

.fi-divider {
    margin: 2rem 0;
    border: none;
    border-top: 1px solid var(--fi-gray-200);
}

.fi-landing-request {
    margin-top: 1.5rem;
}

.fi-alert {
    padding: 1rem;
    border-radius: var(--fi-radius);
    margin-bottom: 1.5rem;
}

.fi-alert-error {
    background: #fef2f2;
    color: var(--fi-danger);
    border: 1px solid #fecaca;
}

.fi-alert-success {
    background: #f0fdf4;
    color: var(--fi-success);
    border: 1px solid #bbf7d0;
}

/* Request Form */
.fi-request-form {
    text-align: left;
}

.fi-form-group {
    margin-bottom: 1rem;
}

.fi-form-group label {
    display: block;
    font-weight: 600;
    font-size: 0.9rem;
    margin-bottom: 0.35rem;
    color: var(--fi-gray-700);
}

.fi-field-desc {
    font-size: 0.85rem;
    color: var(--fi-gray-500);
    margin: 0 0 0.35rem;
}

.fi-form-row {
    display: flex;
    gap: 1rem;
}

.fi-form-half {
    flex: 1;
}

.fi-select {
    appearance: auto;
    cursor: pointer;
}

.fi-request-form .fi-textarea {
    min-height: auto;
}

/* Follow-up Questions */
.fi-follow-up {
    border-top: 2px dashed var(--fi-gray-200);
    padding-top: 1rem;
}

.fi-follow-up-prompt {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    background: linear-gradient(135deg, rgba(37, 99, 235, 0.06), rgba(22, 163, 74, 0.06));
    border-left: 3px solid var(--fi-primary);
    border-radius: 0 var(--fi-radius) var(--fi-radius) 0;
    margin-bottom: 0.75rem;
}

.fi-follow-up-icon {
    font-size: 1.25rem;
    line-height: 1.5;
    flex-shrink: 0;
}

.fi-follow-up-text {
    font-style: italic;
    color: var(--fi-gray-700);
    line-height: 1.5;
    font-size: 0.95rem;
}

.fi-follow-up-textarea {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
    font-size: 0.95rem;
    line-height: 1.6;
    resize: vertical;
    font-family: inherit;
    background: var(--fi-gray-50);
    transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s;
}

.fi-follow-up-textarea:focus {
    outline: none;
    border-color: var(--fi-primary);
    background: white;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}

/* Profile Section */
.fi-profile-section {
    border-top: 2px solid var(--fi-gray-200);
    padding-top: 2rem;
}

.fi-profile-grid {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 2rem;
    align-items: start;
}

.fi-profile-headshot {
    text-align: center;
}

.fi-headshot-img {
    width: 140px;
    height: 140px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid var(--fi-gray-200);
    margin-bottom: 0.75rem;
    display: block;
    margin-left: auto;
    margin-right: auto;
}

.fi-headshot-placeholder {
    width: 140px;
    height: 140px;
    border-radius: 50%;
    background: var(--fi-primary);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    font-weight: 700;
    margin: 0 auto 0.75rem;
}

.fi-btn-small {
    font-size: 0.8rem;
    padding: 0.3rem 0.75rem;
    background: var(--fi-gray-100);
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    cursor: pointer;
    color: var(--fi-gray-700);
}

.fi-btn-small:hover {
    background: var(--fi-gray-200);
}

.fi-profile-fields .fi-textarea {
    min-height: auto;
}

.fi-profile-save-status {
    font-size: 0.85rem;
    color: var(--fi-success);
    min-height: 1.2rem;
}

/* Progressive Reveal */
.fi-question.fi-question-hidden {
    display: none;
}

.fi-question.fi-question-collapsed {
    cursor: pointer;
    padding: 1rem 1.5rem;
    opacity: 0.85;
    transition: opacity 0.2s;
}

.fi-question.fi-question-collapsed:hover {
    opacity: 1;
}

.fi-question.fi-question-collapsed .fi-textarea,
.fi-question.fi-question-collapsed .fi-question-meta,
.fi-question.fi-question-collapsed .fi-follow-up,
.fi-question.fi-question-collapsed .fi-btn-pass,
/* 0.222.0 (open-call friction fix, Phase 2) - the D9 question-swap widget
   (addQuestionSwapAffordance() in interview-form.js) is meaningless on an
   already-answered, collapsed question - hidden the same CSS-only way as the
   other current-question-only controls in this list, rather than removed from
   the DOM by collapseQuestion() itself. */
.fi-question.fi-question-collapsed .fi-question-swap {
    display: none;
}

.fi-collapsed-preview {
    color: var(--fi-gray-500);
    font-size: 0.9rem;
    font-style: italic;
    margin-top: 0.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fi-expand-btn {
    font-size: 0.8rem;
    color: var(--fi-primary);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    margin-left: 0.5rem;
}

.fi-expand-btn:hover {
    text-decoration: underline;
}

/* Thinking Indicator */
.fi-thinking {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    margin-top: 1rem;
    color: var(--fi-gray-500);
    font-size: 0.9rem;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.fi-thinking.fi-thinking-visible {
    opacity: 1;
}

.fi-thinking-dots {
    display: inline-flex;
    gap: 3px;
}

.fi-thinking-dots span {
    width: 6px;
    height: 6px;
    background: var(--fi-primary);
    border-radius: 50%;
    animation: fi-pulse 1.4s ease-in-out infinite;
}

.fi-thinking-dots span:nth-child(2) {
    animation-delay: 0.2s;
}

.fi-thinking-dots span:nth-child(3) {
    animation-delay: 0.4s;
}

@keyframes fi-pulse {
    0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
    40% { opacity: 1; transform: scale(1); }
}

/* Continue Button */
.fi-continue-wrap {
    text-align: center;
    margin-top: 1rem;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.fi-continue-wrap.fi-continue-visible {
    opacity: 1;
    transform: translateY(0);
}

.fi-btn-continue {
    display: inline-block;
    padding: 0.6rem 1.5rem;
    background: var(--fi-primary);
    color: white;
    border: none;
    border-radius: var(--fi-radius);
    font-size: 0.95rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.2s;
}

.fi-btn-continue:hover {
    background: var(--fi-primary-dark);
}

/* Bonus Round Consent Prompt (TASK 3, 0.159.0) - offered inline in place of the next question
   box when a dynamic interview hits its ceiling with unfinished business. Same "AI speaks,
   respondent decides right here, no dialog" language as .fi-follow-up-prompt, closed out with
   the same primary/secondary button pair already used elsewhere on this page (.fi-btn-continue /
   .fi-btn.fi-btn-secondary). */
.fi-bonus-prompt {
    text-align: center;
    padding: 1.5rem;
    margin-top: 1.5rem;
    background: linear-gradient(135deg, rgba(37, 99, 235, 0.06), rgba(22, 163, 74, 0.06));
    border: 1px solid rgba(37, 99, 235, 0.2);
    border-radius: var(--fi-radius);
}

.fi-bonus-prompt-text {
    color: var(--fi-gray-700);
    font-size: 0.95rem;
    margin: 0 0 1rem;
}

.fi-bonus-prompt-actions {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* Angle Chooser (0.220.0, open-call friction fix) - THE INTERACTION: offered once,
   inline, in place of the next question box, right after question 1 is answered and
   before question 2 is generated. Same "AI speaks, respondent decides right here, no
   dialog" language and insertion point as .fi-bonus-prompt/.fi-follow-up-prompt above -
   see interview-form.js's showAngleChooser(). Light theme throughout, using the SAME
   tokens as the rest of this file (:root above) - no new palette; --fi-gray-900 is used
   only for option text color, matching this file's existing occasional-text-color-only
   use of that token (the persistent bar's dark strip is the one deliberate exception,
   not the base). Options are full-width buttons, not radio inputs, per the brief's
   mobile/keyboard requirement - a sentence-length label needs a real tap target, not a
   tiny circle next to one. */
.fi-angle-chooser {
    text-align: center;
    padding: 1.5rem;
    margin-top: 1.5rem;
    background: linear-gradient(135deg, rgba(37, 99, 235, 0.06), rgba(22, 163, 74, 0.06));
    border: 1px solid rgba(37, 99, 235, 0.2);
    border-radius: var(--fi-radius);
}

.fi-angle-chooser-lead-in {
    color: var(--fi-gray-700);
    font-size: 0.95rem;
    margin: 0 0 1rem;
}

.fi-angle-options {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    text-align: left;
}

.fi-angle-option {
    display: block;
    width: 100%;
    padding: 0.85rem 1rem;
    background: white;
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    font-family: inherit;
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--fi-gray-900);
    text-align: left;
    cursor: pointer;
    transition: border-color 0.2s, background-color 0.2s;
}

.fi-angle-option:hover,
.fi-angle-option:focus-visible {
    border-color: var(--fi-primary);
    background: rgba(37, 99, 235, 0.04);
    outline: none;
}

.fi-angle-option:disabled {
    cursor: default;
    opacity: 0.6;
}

.fi-angle-option-selected {
    border-color: var(--fi-primary);
    background: rgba(37, 99, 235, 0.08);
    font-weight: 500;
}

.fi-angle-chooser-error {
    margin: 0.75rem 0 0;
    font-size: 0.85rem;
    color: var(--fi-danger);
}

/* ----------------------------------------------------------------
 * Content Moderation Flag Feedback (0.222.0, open-call friction fix, Phase 1)
 *
 * Rendered inline in place of the question's continue button when a committed
 * answer is flagged (interview-form.js's showModerationNotice()), TASK 1-4 of
 * D1-D4 - docs/plans/open-call-moderation-and-question-swap-2026-08-27.md. Two
 * tone variants, chosen by is_strike (never by the raw category string):
 * -strike is an actual misconduct category and keeps the fi-alert-error red the
 * respondent already associates with "that was rejected" elsewhere on this page;
 * -boundary is out_of_scope_topic ALONE (is_strike false) and uses the same calm
 * blue/green "AI speaks, respondent decides" gradient as .fi-follow-up-prompt/
 * .fi-bonus-prompt/.fi-angle-chooser above - deliberately NOT red, because a
 * topic boundary is not misconduct and must not look punished the same way.
 * ---------------------------------------------------------------- */
.fi-moderation-notice {
    margin-top: 1rem;
    padding: 1rem 1.25rem;
    border-radius: var(--fi-radius);
    font-size: 0.9rem;
    line-height: 1.5;
}

.fi-moderation-notice-strike {
    background: #fef2f2;
    color: var(--fi-danger);
    border: 1px solid #fecaca;
}

.fi-moderation-notice-boundary {
    background: linear-gradient(135deg, rgba(37, 99, 235, 0.06), rgba(22, 163, 74, 0.06));
    color: var(--fi-gray-700);
    border: 1px solid rgba(37, 99, 235, 0.2);
}

/* TASK 2 - the cleared answer, read back clearly marked as not submitted. A
   muted, dashed-border card with italic quoted text so it can never be mistaken
   for the live (now empty) textarea below it. border/text color is
   currentColor - it inherits from .fi-moderation-notice-strike/-boundary above,
   so this needs no color rules of its own for either tone. */
.fi-moderation-cached {
    margin-bottom: 0.75rem;
    padding: 0.6rem 0.75rem;
    background: rgba(255, 255, 255, 0.6);
    border: 1px dashed currentColor;
    border-radius: var(--fi-radius);
    opacity: 0.9;
}

.fi-moderation-cached-label {
    display: block;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.3rem;
    opacity: 0.75;
}

.fi-moderation-cached-text {
    margin: 0;
    font-style: italic;
    white-space: pre-wrap;
    word-break: break-word;
}

/* TASK 3 - the message, verbatim server copy. Not italic (unlike the cached
   answer above) - this is the system's own text, not a quote. */
.fi-moderation-message {
    margin: 0;
    font-weight: 500;
}

/* TASK 4 - the strike-progression line. Plain and small - this is information,
   not a second warning - and only ever present in the -strike tone (see
   showModerationNotice()'s hasStrikeCounts guard; -boundary never renders it). */
.fi-moderation-progress {
    margin: 0.5rem 0 0;
    font-size: 0.82rem;
    opacity: 0.85;
}

/* TASK 3 - the two exits, same button-pair layout as .fi-bonus-prompt-actions/
   .fi-angle-options above. .fi-btn-secondary's existing light-gray fill reads
   fine over either tone's tinted background, so no new button colors here. */
.fi-moderation-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 0.85rem;
}

.fi-moderation-actions .fi-btn {
    font-size: 0.85rem;
    padding: 0.45rem 1rem;
}

/* "Prefer a new question" is LIVE (0.222.0, open-call friction fix, Phase 2 -
   D5-D9). :disabled now only ever applies transiently, while a re-ask request is
   in flight (requestReask() in interview-form.js) or on the final strike just
   before the page reloads into the appeal screen. Same dimmed/no-cursor posture
   as .fi-angle-option:disabled above, reused rather than inventing a second
   disabled treatment. */
.fi-moderation-new-question-btn:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

/* ----------------------------------------------------------------
 * Question Swap - Re-ask & Skip (0.222.0, open-call friction fix, Phase 2 -
 * D5-D9, docs/plans/open-call-moderation-and-question-swap-2026-08-27.md)
 *
 * Two surfaces share these rules: the standalone .fi-question-swap block (TASK 2/
 * D9 - rendered on EVERY current/unanswered question, flagged or not - see
 * addQuestionSwapAffordance() in interview-form.js) and the "Prefer a new
 * question"/skip pair inside .fi-moderation-actions above (TASK 1 - that pair
 * reuses .fi-btn/.fi-moderation-new-question-btn styling already defined there;
 * only the confirm-row/error pieces below are shared with it).
 *
 * .fi-question-swap-link is deliberately quiet by default (TASK 2's brief: "a
 * text link near the question, not a button competing with Continue") - muted
 * grey, no background/border, reserving --fi-primary for hover/focus so the
 * affordance reads as present-but-optional until someone actually reaches for
 * it. Modeled on this file's only other inline-text-button precedent,
 * .fi-expand-btn (Progressive Reveal, above).
 * ---------------------------------------------------------------- */
/* 0.222.6 - THE UTILITY ROW. Ask, Skip and End as real buttons, on one line,
 * underneath Next.
 *
 * History worth keeping, because it took four passes to get here. These started as
 * grey-500 text at 0.82rem with `text-decoration-color: transparent`, so the underline
 * only appeared ON HOVER and the two sat inline with no gap - rendering as the single
 * grey run "Ask me a different questionSkip this question". The owner could not find
 * them on a live interview even after being told they were there. My brief had said
 * "quiet, not competing with Continue", and quiet got implemented as invisible.
 *
 * His ruling: "they should be buttons not just links, that looks sloppy and unfinished."
 * Right - a bare text link next to a solid Continue button reads as unfinished markup,
 * not as a deliberate hierarchy.
 *
 * ⚠ WHY THIS IS ITS OWN ROW, and it is a constraint rather than a taste call: Next is
 * CONDITIONAL, appearing only once an answer reaches length, while all three of these
 * are permanent. Sharing a row would mean Next's arrival reflows them out from under a
 * respondent who is mid-answer and already reaching for one.
 */
.fi-question-swap {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0.85rem 0 0.25rem;
}

.fi-question-swap-link,
.fi-question-swap .fi-bar-end-now button,
.fi-question-swap .fi-bar-end-now .fi-btn {
    background: white;
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    padding: 0.45rem 0.8rem;
    color: var(--fi-gray-700);
    cursor: pointer;
    font-size: 0.85rem;
    font-family: inherit;
    line-height: 1.3;
    text-decoration: none;
    white-space: normal;
    transition: border-color 0.15s, color 0.15s, background-color 0.15s;
}

.fi-question-swap-link:hover,
.fi-question-swap-link:focus-visible {
    border-color: var(--fi-primary);
    color: var(--fi-primary);
    background: var(--fi-gray-100);
    outline: none;
}

.fi-question-swap-link:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    text-decoration: none;
}

/* TASK 4 - the inline skip confirm, same "never confirm()/alert()" language as
   .fi-end-now-confirm above, scaled down to sit next to a quiet text link
   instead of the persistent bar. Both variants (standalone quiet link,
   moderation-notice button) render this identically - see renderSkipControl()
   in interview-form.js. */
.fi-question-swap-skip-confirm {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.fi-question-swap-skip-confirm-text {
    color: var(--fi-gray-500);
}

/* Shared by requestReask()'s brief inline failure (showSwapError()) and
   requestSkip()'s confirm-row failure - same small, red, "something went wrong,
   nothing else changed" treatment used nowhere else on this page because
   nothing else on this page fails this quietly. */
.fi-question-swap-error {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.8rem;
    color: var(--fi-danger);
}

/* The notice variant's wrapping span sits as a flex item inside
   .fi-moderation-actions - display:block above already puts this on its own
   line within that span; this only trims the extra top margin the standalone
   variant needs (that one hangs directly off a lone link with nothing above it
   in its own container) down to something that reads as "part of this row"
   instead. */
.fi-question-swap-error-notice {
    margin-top: 0.4rem;
}

/* ----------------------------------------------------------------
 * Question Swap Picker (0.222.0, open-call friction fix, Phase 2 reconciliation -
 * D12/D12a/D12b, docs/plans/open-call-moderation-and-question-swap-2026-08-27.md)
 *
 * Renders "1 + 3 alts" once requestReask() fetches a variant set - see
 * renderReaskPicker() in interview-form.js. Deliberately styled to MATCH
 * .fi-angle-chooser/.fi-angle-option above rather than invent a second visual
 * pattern (the brief: "same interaction language as the existing angle chooser") -
 * full-width plain option buttons, not radio inputs, same reasoning (a
 * sentence-length label needs a real tap target, comfortable on mobile with no
 * extra media-query work, exactly like .fi-angle-option). Scaled down and given a
 * plain (non-gradient) surface rather than copied wholesale, since this renders
 * INLINE inside the compact swap widget / moderation-actions row (per-question,
 * possibly several times down a long interview) rather than as the once-per-
 * session, form-level panel the angle chooser is.
 * ---------------------------------------------------------------- */
.fi-reask-picker {
    display: block;
    width: 100%;
    margin-top: 0.5rem;
    padding: 0.85rem 1rem;
    background: var(--fi-gray-50);
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
}

.fi-reask-picker-lead-in {
    margin: 0 0 0.6rem;
    font-size: 0.85rem;
    color: var(--fi-gray-700);
}

.fi-reask-options {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.fi-reask-option {
    display: block;
    width: 100%;
    padding: 0.7rem 0.85rem;
    background: white;
    border: 1px solid var(--fi-gray-300);
    border-radius: var(--fi-radius);
    font-family: inherit;
    font-size: 0.88rem;
    line-height: 1.45;
    color: var(--fi-gray-900);
    text-align: left;
    cursor: pointer;
    transition: border-color 0.2s, background-color 0.2s;
}

.fi-reask-option:hover,
.fi-reask-option:focus-visible {
    border-color: var(--fi-primary);
    background: rgba(37, 99, 235, 0.04);
    outline: none;
}

.fi-reask-option:disabled {
    cursor: default;
    opacity: 0.6;
}

/* TASK 1 - "keeping the original must stay possible... do not force a choice."
   Quiet by design (same reasoning as .fi-question-swap-link above) so the three
   real alternatives read as the primary choice and this reads as the plain,
   always-available way out, not a fourth option competing with them. */
.fi-reask-picker-dismiss {
    display: block;
    width: 100%;
    margin-top: 0.6rem;
    padding: 0.55rem 0.85rem;
    background: none;
    border: none;
    color: var(--fi-gray-500);
    font-size: 0.82rem;
    text-align: center;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: transparent;
    transition: color 0.15s, text-decoration-color 0.15s;
}

.fi-reask-picker-dismiss:hover,
.fi-reask-picker-dismiss:focus-visible {
    color: var(--fi-primary);
    text-decoration-color: currentColor;
    outline: none;
}

.fi-reask-picker-dismiss:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    text-decoration: none;
}

/* chooseReask()'s "no dead end" failure - same small, red, "something went wrong,
   nothing else changed" treatment as .fi-question-swap-error above. */
.fi-reask-picker-error {
    margin: 0.6rem 0 0;
    font-size: 0.8rem;
    color: var(--fi-danger);
}

/* ----------------------------------------------------------------
 * Public Pitch Refusal - Intake Gate (0.222.0, open-call friction fix, Phase 4,
 * docs/plans/open-call-moderation-and-question-swap-2026-08-27.md)
 *
 * Renders inline on the public "want to be featured?" pitch form
 * (#fiRequestForm) when a submission is refused - see interview-form.js's
 * showPitchRefusal(). Deliberately reuses .fi-moderation-notice/
 * .fi-moderation-notice-boundary/.fi-moderation-message/.fi-moderation-actions
 * from Phase 1 above rather than a second visual pattern - the brief: this is
 * the SAME "not your fault" boundary register, never a red alarm style, since
 * an intake refusal is a topic boundary, not misconduct. Only the angle-list
 * markup below is new; everything else is the shared moderation-notice
 * treatment as-is, including .fi-alert-error for showPitchSubmitError()'s
 * unrelated "something actually went wrong" fallback (a real technical
 * failure, correctly NOT this calm treatment).
 * ---------------------------------------------------------------- */
.fi-pitch-refusal-angles-lead-in {
    margin: 0.6rem 0 0.4rem;
    font-weight: 500;
}

.fi-pitch-refusal-angles {
    margin: 0 0 0.25rem;
    padding-left: 1.25rem;
}

.fi-pitch-refusal-angles li {
    margin-bottom: 0.35rem;
    line-height: 1.5;
}

/* Interview Complete Banner */
.fi-interview-done {
    text-align: center;
    padding: 1.5rem;
    margin-top: 1.5rem;
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.08), rgba(37, 99, 235, 0.08));
    border: 1px solid rgba(22, 163, 74, 0.2);
    border-radius: var(--fi-radius);
    color: var(--fi-gray-700);
    font-size: 0.95rem;
}

/* Question entrance animation */
.fi-question.fi-question-entering {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.4s ease, transform 0.4s ease;
}

.fi-question.fi-question-entered {
    opacity: 1;
    transform: translateY(0);
}

/* Responsive */
@media (max-width: 640px) {
    .fi-container {
        padding: 1rem;
    }

    .fi-question {
        padding: 1rem;
    }

    .fi-header h1 {
        font-size: 1.35rem;
    }

    .fi-image-grid {
        grid-template-columns: 1fr;
    }

    .fi-profile-grid {
        grid-template-columns: 1fr;
    }

    .fi-form-row {
        flex-direction: column;
        gap: 0;
    }

    /* 0.220.0 (open-call friction fix) - the persistent bar at narrow widths: progress
       and End Now are load-bearing (they're the whole point of this arc), so they get
       their own full-width row each; the standing guidance line is the first thing
       that gives way - it drops to a clamped, truncating third row rather than
       pushing the bar's height (and therefore the container's padding-bottom below)
       up indefinitely on a long per-brand string. */
    .fi-persistent-bar-inner {
        padding: 0.5rem 0.75rem;
        gap: 0.4rem 0.6rem;
    }

    .fi-bar-progress {
        order: 1;
        flex: 1 1 100%;
    }

    .fi-bar-end-now {
        order: 2;
        margin-left: 0;
        flex: 1 1 100%;
        display: flex;
        justify-content: flex-end;
    }

    .fi-bar-guidance {
        order: 3;
        flex: 1 1 100%;
        max-height: 2.6em;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .fi-end-now-confirm-text {
        max-width: 100%;
    }

    /* The bar can grow to three stacked rows here - give the scrollable content
       proportionally more clearance than the desktop single-row default above.
       0.221.0 - scoped to .fi-bar-mode-fixed_bottom, same reasoning as the
       unscoped-padding comment above this media query. */
    .fi-bar-mode-fixed_bottom .fi-container {
        padding-bottom: 9rem;
    }

    .fi-bar-mode-fixed_bottom .fi-question,
    .fi-bar-mode-fixed_bottom .fi-submit-section {
        scroll-margin-bottom: 9rem;
    }
}

/* ----------------------------------------------------------------
 * Campaign Listings (Landing Page) — v0.4.0
 * ---------------------------------------------------------------- */

.fi-campaign-listings {
    margin-top: 2rem;
}

.fi-campaign-listings h2 {
    margin-bottom: 0.25rem;
}

.fi-campaign-section {
    margin-top: 1.5rem;
}

.fi-campaign-section h3 {
    font-size: 1.1rem;
    margin-bottom: 0.75rem;
    color: var(--fi-text-muted, #6c757d);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.fi-campaign-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}

.fi-campaign-card {
    border: 1px solid var(--fi-border, #dee2e6);
    border-radius: 0.5rem;
    padding: 1.25rem;
    background: var(--fi-card-bg, #fff);
    transition: box-shadow 0.2s;
}

.fi-campaign-card:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.fi-campaign-card-link {
    display: block;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}

.fi-campaign-card-link:hover {
    border-color: var(--fi-link, #0d6efd);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    transform: translateY(-2px);
}

.fi-campaign-card-link:hover .fi-campaign-card-title {
    color: var(--fi-link, #0d6efd);
}

.fi-campaign-card-closed {
    opacity: 0.7;
}

.fi-campaign-card-cta {
    margin-top: 0.75rem;
    text-align: right;
}

.fi-btn-sm {
    display: inline-block;
    padding: 0.35rem 0.75rem;
    font-size: 0.85rem;
}

.fi-campaign-card-title {
    font-size: 1rem;
    margin: 0 0 0.5rem;
}

.fi-campaign-card-desc {
    font-size: 0.9rem;
    color: var(--fi-text-muted, #6c757d);
    margin: 0 0 0.75rem;
}

.fi-campaign-card-meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 0.85rem;
}

.fi-campaign-card-deadline {
    color: var(--fi-text-muted, #6c757d);
}

.fi-campaign-card-status {
    display: inline-block;
    padding: 0.2em 0.6em;
    border-radius: 0.25rem;
    font-size: 0.8rem;
    font-weight: 600;
}

.fi-status-accepting {
    background: #d4edda;
    color: #155724;
}

.fi-status-published {
    background: #cce5ff;
    color: #004085;
}

/* Listing Type Options (Pitch Form) */

.fi-listing-options {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.fi-radio-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--fi-border, #dee2e6);
    border-radius: 0.375rem;
    cursor: pointer;
    transition: background 0.15s;
}

.fi-radio-label:hover {
    background: var(--fi-hover-bg, #f8f9fa);
}

.fi-listing-price {
    margin-left: auto;
    font-weight: 600;
    color: var(--fi-text-muted, #6c757d);
}

/* Policies Box */

.fi-policies-box {
    margin: 1.5rem 0;
    padding: 1rem 1.25rem;
    background: var(--fi-policies-bg, #e7f3ff);
    border: 1px solid var(--fi-policies-border, #b6d4fe);
    border-radius: 0.375rem;
}

.fi-policies-box h4 {
    margin: 0 0 0.5rem;
    font-size: 0.95rem;
}

.fi-policies-text {
    font-size: 0.9rem;
    line-height: 1.6;
}

/* Checkbox Label */

.fi-checkbox-label {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    cursor: pointer;
}

.fi-checkbox-label .fi-checkbox {
    margin-top: 0.2rem;
}

/* Campaign Showcase (Landing Page Redesign) */

.fi-campaign-showcase {
    margin-bottom: 2rem;
}

.fi-campaign-showcase h2 {
    font-size: 1.15rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--fi-gray-500);
    margin-bottom: 1rem;
}

.fi-campaign-published-section {
    margin-top: 2rem;
}

.fi-campaign-type-badge {
    display: inline-block;
    padding: 0.15em 0.5em;
    border-radius: 0.25rem;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: #d4edda;
    color: #155724;
    margin-bottom: 0.5rem;
}

.fi-campaign-type-badge.fi-badge-published {
    background: #cce5ff;
    color: #004085;
}

.fi-campaign-card-topic {
    font-size: 0.85rem;
    color: var(--fi-gray-500);
    margin: 0 0 0.35rem;
}

.fi-campaign-card-pricing {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin: 0.5rem 0;
}

.fi-price-badge {
    display: inline-block;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.15rem 0.5rem;
    border-radius: 3px;
}

.fi-price-free {
    background: #d1fae5;
    color: #065f46;
}

.fi-price-featured {
    background: #fef3c7;
    color: #92400e;
}

.fi-price-highlighted {
    background: #e0e7ff;
    color: #3730a3;
}

/* Global Policies Block (Landing Page Bottom) */

.fi-policies-block {
    margin-top: 2rem;
    padding: 1.5rem 2rem;
    background: var(--fi-gray-50);
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
}

.fi-policies-block h3 {
    font-size: 1rem;
    margin: 0 0 0.5rem;
    color: var(--fi-gray-700);
}

.fi-policies-block-section + .fi-policies-block-section {
    margin-top: 1.25rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--fi-gray-200);
}

.fi-policies-block-content {
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--fi-gray-700);
}

/* Contact Found Banner */

.fi-contact-found {
    font-size: 0.9rem;
}

.fi-contact-found .fi-btn-sm {
    padding: 0.2rem 0.6rem;
    font-size: 0.85rem;
    margin-left: 0.25rem;
}

/* Generation error banner (0.222.1).
 *
 * ⚠ THIS BLOCK DID NOT EXIST. showGenerationError() has been building a banner with a
 * "Try Again" button since 0.158.0 and NOTHING styled it, so the recovery affordance
 * rendered as unstyled inline content the respondent could not see as a button. The
 * owner hit a real generation failure on the canary, reported "I'm not even seeing a
 * retry button", and was clicking the banner blind - landing on the invisible button by
 * accident, which is why the interview appeared to lurch forward on a stray click.
 *
 * A retry control nobody can see is worse than no retry control: the respondent believes
 * they are stuck, and the only way out is an accident. Same family as the progress bar
 * that rendered off screen and the step checklist nobody had ever seen - built, correct,
 * and invisible.
 *
 * The detail line is deliberately quiet and monospace: it is diagnostic text, shown only
 * when the caller passes a message, and it must never compete with the human sentence
 * above it.
 */
.fi-generation-error {
    padding: 1rem;
    border-radius: var(--fi-radius);
    margin-bottom: 1.5rem;
    background: #fef2f2;
    color: var(--fi-danger);
    border: 1px solid #fecaca;
}

.fi-generation-error p {
    margin: 0 0 0.75rem;
}

.fi-generation-error .fi-btn-continue {
    display: inline-block;
    width: auto;
}

.fi-generation-error-detail {
    margin: 0.75rem 0 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    line-height: 1.4;
    opacity: 0.75;
    word-break: break-word;
}

/* Option-button text wrapping (0.222.2).
 *
 * ⚠ FOUND ON THE LIVE CANARY, NOT IN THE STYLESHEET. Every option list this component
 * renders as <button> elements - the angle chooser, the re-ask picker, the moderation
 * exits - was clipping its own text off the right edge of the panel. Each option is a
 * whole question or a whole proposed direction, so a clipped option is an unreadable
 * option, and the respondent is asked to choose between three sentences they cannot
 * finish reading.
 *
 * Our own rules already said display:block, width:100%, text-align:left - all correct.
 * The override is coming from OUTSIDE this stylesheet: the host Joomla template styles
 * bare `button`, and a flex item additionally refuses to shrink below its content's
 * intrinsic width because min-width defaults to auto. Neither is visible from inside
 * this file, which is exactly why nothing caught it until somebody looked at the page.
 *
 * Declared here as a defensive block rather than patched into each rule so a future
 * option list inherits the fix instead of rediscovering the bug.
 */
.fi-angle-option,
.fi-reask-option,
.fi-moderation-actions button,
.fi-question-swap-link {
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: normal;
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
    height: auto;
}

.fi-angle-options,
.fi-reask-options,
.fi-angle-chooser,
.fi-reask-picker {
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
}

/* =========================================================================
   Pitch portal landing - access bar, intro, tabs and card media (0.233.0)
   Pitch portal tabs arc, James 2026-09-08.
   ========================================================================= */

/* --- Access-code bar: persistent, above the tabs, never inside one. ------ */
.fi-access-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.85rem 1.15rem;
    margin-bottom: 1.5rem;
    border: 1px solid var(--fi-border, #dee2e6);
    border-radius: 0.5rem;
    background: var(--fi-card-bg, #fff);
}

.fi-access-bar-copy {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 12rem;
}

.fi-access-bar-title {
    font-size: 0.95rem;
}

.fi-access-bar-desc {
    font-size: 0.85rem;
    color: var(--fi-text-muted, #6c757d);
}

.fi-access-bar .fi-token-form {
    flex: 1 1 18rem;
    margin: 0;
}

/* --- Intro ---------------------------------------------------------------- */
.fi-landing-intro {
    margin-bottom: 1.5rem;
}

.fi-landing-intro-title {
    font-size: 1.6rem;
    margin: 0 0 0.5rem;
}

.fi-landing-intro-lead {
    font-size: 1rem;
    color: var(--fi-text-muted, #6c757d);
    margin: 0 0 1rem;
    max-width: 60ch;
}

/* Ratio box rather than a fixed height so the welcome video is not letterboxed
   on a phone. */
.fi-landing-video {
    position: relative;
    width: 100%;
    max-width: 46rem;
    aspect-ratio: 16 / 9;
    border-radius: 0.5rem;
    overflow: hidden;
    background: #000;
}

.fi-landing-video iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* --- Tabs ----------------------------------------------------------------
   ⚠ Everything here is gated behind .fi-tabs-ready, which interview-form.js
   adds. Without JS the tab strip stays hidden (it would do nothing) and every
   panel stays visible, so the page degrades to the stack it used to be rather
   than to a page with three quarters of its content unreachable. Do NOT move
   these rules out from under that class. */
.fi-tablist {
    display: none;
}

.fi-tabs-ready .fi-tablist {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 1.25rem;
    border-bottom: 2px solid var(--fi-border, #dee2e6);
    overflow-x: auto;
    /* 0.234.0 - overflow-y MUST be stated. Per CSS overflow, setting one axis to a
       non-visible value forces the OTHER axis from visible to auto, so overflow-x:auto
       alone silently gave the strip a vertical scrollbar - which is exactly the stray
       scrollbar James saw beside the tabs on desktop. The tab buttons also sit 2px
       below the strip via margin-bottom:-2px to overlap the border, so there is real
       overflow for that phantom axis to find. */
    overflow-y: hidden;
    scrollbar-width: thin;
}

.fi-tab {
    flex: 0 0 auto;
    padding: 0.65rem 1.1rem;
    border: 0;
    border-bottom: 3px solid transparent;
    margin-bottom: -2px;
    background: none;
    font: inherit;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--fi-text-muted, #6c757d);
    cursor: pointer;
    white-space: nowrap;
}

.fi-tab:hover {
    color: var(--fi-link, #0d6efd);
}

.fi-tab[aria-selected="true"] {
    color: var(--fi-link, #0d6efd);
    border-bottom-color: var(--fi-link, #0d6efd);
}

.fi-tab:focus-visible {
    outline: 2px solid var(--fi-link, #0d6efd);
    outline-offset: -2px;
}

.fi-tabs-ready .fi-tabpanel {
    display: none;
}

.fi-tabs-ready .fi-tabpanel.is-active {
    display: block;
}

/* --- Card media strip ------------------------------------------------------ */
.fi-campaign-card-media-on {
    padding: 0;
    overflow: hidden;
}

.fi-campaign-card-body {
    padding: 1.25rem;
}

.fi-campaign-card-media {
    position: relative;
    aspect-ratio: 16 / 9;
    /* The type gradient is the BASE layer on every card, imaged or not, and the
       image sits on top of it. Found by a browser walk 2026-09-08: with a neutral
       grey base, a card showed a blank grey rectangle for the whole window between
       first paint and the lazy image resolving - and a lazy image inside a tab
       panel that started hidden may never be fetched at all, making the blank
       permanent. Painting the placeholder underneath means there is no state in
       which a card looks broken; the JS fallback below is then a refinement that
       drops the torn-image element, not the thing standing between a reader and a
       grey box. */
    background: linear-gradient(135deg, #64748b 0%, #334155 100%);
    overflow: hidden;
}

.fi-campaign-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The type badge rides on the media strip instead of sitting above the title,
   which buys back the vertical space the image costs. */
.fi-campaign-card-media .fi-campaign-type-badge {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    margin: 0;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

/* Empty state, per campaign type. Not decoration: campaign_image is unset on
   every open roundup today, so without this the most important cards on the
   page would render as blank grey boxes. */
.fi-campaign-card-media-empty {
    background: linear-gradient(135deg, #64748b 0%, #334155 100%);
}

.fi-campaign-card-media.fi-media-product_roundup {
    background: linear-gradient(135deg, #f59e0b 0%, #b45309 100%);
}

.fi-campaign-card-media.fi-media-destination_roundup {
    background: linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%);
}

.fi-campaign-card-media.fi-media-expert_roundup {
    background: linear-gradient(135deg, #8b5cf6 0%, #5b21b6 100%);
}

.fi-campaign-card-media.fi-media-gift_guide {
    background: linear-gradient(135deg, #ec4899 0%, #9d174d 100%);
}

.fi-campaign-card-media.fi-media-interview {
    background: linear-gradient(135deg, #10b981 0%, #065f46 100%);
}

@media (max-width: 600px) {
    .fi-access-bar {
        align-items: stretch;
    }

    .fi-landing-intro-title {
        font-size: 1.35rem;
    }
}

/* Scoping handshake preamble (0.235.0) - editorial housekeeping, not a question.
   Visually distinct from a numbered question ON PURPOSE: the whole feature exists so a
   respondent does not mistake this for something that will be published. */
.fi-question-preamble {
    border-left: 3px solid var(--fi-link, #0d6efd);
    background: var(--fi-gray-050, #f8f9fa);
}

.fi-question-preamble .fi-question-header {
    /* No number span is rendered for a preamble, so nothing needs reserving for it. */
    padding-left: 0;
}

.fi-question-preamble .fi-question-text {
    font-style: italic;
    color: var(--fi-text-muted, #6c757d);
}

/* Editorial alignment bands (0.237.0). The pause is deliberately calm rather than an
   error colour: nothing went wrong, the piece is simply waiting on a person. */
.fi-alignment-pause {
    margin: 1.5rem 0;
    border-left: 3px solid var(--fi-link, #0d6efd);
    font-size: 0.95rem;
    line-height: 1.6;
}

.fi-alignment-notice {
    margin: 0 0 1.25rem;
    font-size: 0.9rem;
}

/* =========================================================================
   Preparing overlay (0.247.0)

   The bridge across requestInterview()'s silence. That task makes up to two
   paid model calls - moderatePitch, then either angles or a label - before it
   can hand back a redirect, so on the auto-approve path the submitter sits on
   a dead-looking button for several seconds with the page unchanged. James hit
   it on the 0.245.0 representing walk and read his own click as a stall.

   Deliberately NOT a spinner. A spinner says "wait"; this says what is being
   prepared and for whom, which is the part that makes a five-second wait feel
   like work happening rather than a failure. The bar fills to 92% and stops -
   it never reaches 100%, because a bar that completes while the page has not
   moved reads as a second, worse stall.
   ========================================================================= */
.fi-preparing-overlay {
    position: fixed;
    inset: 0;
    z-index: 2147483000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    background: rgba(17, 24, 39, 0.55);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    opacity: 0;
    transition: opacity 180ms ease;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.fi-preparing-overlay.is-visible {
    opacity: 1;
}

.fi-preparing-card {
    width: 100%;
    max-width: 30rem;
    background: #fff;
    color: var(--fi-gray-900, #111827);
    border-radius: calc(var(--fi-radius, 8px) * 1.5);
    box-shadow: 0 20px 45px rgba(0, 0, 0, 0.25);
    padding: 2rem 1.75rem 1.5rem;
    text-align: center;
    transform: translateY(8px) scale(0.985);
    transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.fi-preparing-overlay.is-visible .fi-preparing-card {
    transform: none;
}

/* The plane tracks left to right on the same clock as the bar - one motion,
   read two ways, so the icon is progress rather than decoration. */
.fi-preparing-plane {
    display: block;
    width: 2.75rem;
    height: 2.75rem;
    margin: 0 auto 1rem;
    color: var(--fi-primary, #2563eb);
    animation: fi-preparing-fly 2.6s ease-in-out infinite;
}

@keyframes fi-preparing-fly {
    0%, 100% { transform: translate(-6px, 3px) rotate(-4deg); }
    50%      { transform: translate(6px, -3px) rotate(4deg); }
}

.fi-preparing-title {
    margin: 0 0 0.4rem;
    font-size: 1.2rem;
    font-weight: 700;
    line-height: 1.35;
}

.fi-preparing-sub {
    margin: 0 0 1.5rem;
    font-size: 0.95rem;
    color: var(--fi-gray-500, #6b7280);
}

.fi-preparing-track {
    height: 6px;
    border-radius: 999px;
    background: var(--fi-gray-200, #e5e7eb);
    overflow: hidden;
}

.fi-preparing-fill {
    height: 100%;
    width: 4%;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--fi-primary, #2563eb), var(--fi-primary-dark, #1d4ed8));
    transition: width 7000ms cubic-bezier(0.16, 0.9, 0.3, 1);
}

.fi-preparing-tip {
    margin: 1.25rem 0 0;
    min-height: 2.8rem;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--fi-gray-700, #374151);
    opacity: 1;
    transition: opacity 320ms ease;
}

.fi-preparing-tip.is-fading {
    opacity: 0;
}

.fi-preparing-tip-label {
    display: block;
    margin-bottom: 0.2rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--fi-gray-500, #6b7280);
}

/* Motion is the whole mechanism here, so the reduced-motion case keeps the
   information and drops only the movement - the bar still advances, in one
   step rather than a sweep, and the plane simply sits still. */
@media (prefers-reduced-motion: reduce) {
    .fi-preparing-overlay,
    .fi-preparing-card,
    .fi-preparing-tip {
        transition: none;
    }
    .fi-preparing-plane {
        animation: none;
    }
    .fi-preparing-fill {
        transition: width 400ms linear;
    }
}

@media (max-width: 480px) {
    .fi-preparing-card {
        padding: 1.5rem 1.25rem 1.25rem;
    }
    .fi-preparing-title {
        font-size: 1.05rem;
    }
}

/* 0.256.0 - Previously Published layout (Prism, PR #85): top 6 as cards, the rest as a
   year-grouped link list, one date + read row per card. Scoped under .fi-pub-v2. */
.fi-pub-v2 { display: grid; gap: 14px; }
.fi-pub-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 4px 0 0; }
.fi-pub-head h3 { font-size: 15px; font-weight: 600; margin: 0; letter-spacing: .01em; }
.fi-pub-head-more { margin-top: 14px; padding-top: 18px; border-top: 1px solid #e5e7eb; }
.fi-pub-count { font-size: 13px; color: #6b7280; }
/* Item 9: the date and the read link share ONE row; the button row is gone. */
.fi-pub-card .fi-campaign-card-body { display: grid; gap: 10px; }
.fi-pub-card .fi-campaign-card-title { margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.fi-pub-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13.5px; margin-top: auto; }
.fi-pub-date { color: #6b7280; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 0.256.0 build: the date never wraps ("Sep / 22, / 2026" broke the one-row footer), and the
   no-link status pill stays compact beside it. */
.fi-pub-meta .fi-campaign-card-status { white-space: nowrap; }
/* A grid item may shrink below its content's min width - without this a card whose date and
   pill cannot wrap grew wider than its column and was clipped (titles and pills cut off). */
.fi-pub-grid > * { min-width: 0; }
.fi-pub-read { color: #1d6fb8; font-weight: 600; white-space: nowrap; }
.fi-pub-card:hover .fi-pub-read, .fi-pub-card:focus-visible .fi-pub-read { text-decoration: underline; }
/* Item 8: everything after the top 6 is a plain, scannable link list, grouped by year. */
.fi-pub-year { border: 1px solid #e5e7eb; border-radius: 8px; background: #fff; }
.fi-pub-year > summary { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; cursor: pointer; font-weight: 600; font-size: 14px; list-style: none; }
.fi-pub-year > summary::-webkit-details-marker { display: none; }
.fi-pub-year > summary::after { content: "+"; margin-left: 10px; color: #6b7280; font-weight: 400; }
.fi-pub-year[open] > summary::after { content: "\2212"; }
.fi-pub-year > summary > span:first-child { flex: 1; }
.fi-pub-year-count { font-weight: 400; color: #6b7280; font-size: 13px; }
.fi-pub-list { list-style: none; margin: 0; padding: 0 0 6px; border-top: 1px solid #f0f1f3; }
.fi-pub-row { display: grid; grid-template-columns: 1fr auto 96px; gap: 14px; align-items: baseline; padding: 11px 16px; text-decoration: none; color: inherit; font-size: 14px; }
.fi-pub-row:hover .fi-pub-row-title { color: #1d6fb8; text-decoration: underline; }
.fi-pub-list li + li .fi-pub-row { border-top: 1px solid #f3f4f6; }
.fi-pub-row-title { color: #1f2937; font-weight: 500; }
.fi-pub-row-type { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: #6b7280; white-space: nowrap; }
.fi-pub-row-date { text-align: right; color: #6b7280; font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fi-pub-illustrative > summary { color: #6b7280; }
@media (max-width: 560px) {
  /* Mobile: the top 6 become a 2-up grid of compact cards instead of six full-width ones. */
  .fi-pub-grid { grid-template-columns: 1fr 1fr !important; gap: 10px !important; }
  .fi-pub-card .fi-campaign-card-body { padding: 10px 10px 12px !important; gap: 8px; }
  .fi-pub-card .fi-campaign-card-title { font-size: 13.5px; -webkit-line-clamp: 3; }
  .fi-pub-card .fi-campaign-type-badge { font-size: 9.5px; }
  .fi-pub-meta { font-size: 12px; gap: 6px; }
  /* 2-up cards have no room for date + pill; the pill is redundant here (the tab says
     Published, and the absent "Read" already marks a card with no article link). */
  .fi-pub-meta .fi-campaign-card-status { display: none; }
  .fi-pub-read-long { display: none; }
  .fi-pub-row { grid-template-columns: 1fr auto; grid-template-areas: "t t" "k d"; gap: 4px 10px; padding: 11px 14px; }
  .fi-pub-row-title { grid-area: t; }
  .fi-pub-row-type { grid-area: k; }
  .fi-pub-row-date { grid-area: d; }
}

/* ==========================================================================
   0.268.0 - Coordinated interview (PR-assisted). Only rendered for entries with
   coordinated = 1; nothing here touches a non-coordinated page.
   ========================================================================== */
.fi-coord-header { margin: 0 0 1rem; }
.fi-coord-header .fi-coord-by { margin: 0 0 .25rem; }
.fi-coord-header .fi-coord-role { margin: 0 0 .5rem; }
.fi-coord-badge { display: inline-block; padding: .15rem .6rem; border-radius: 999px; background: #eef2ff; color: #3730a3; font-size: .8rem; font-weight: 600; }
.fi-coord-header .fi-coord-interviewee { margin: 0 0 .5rem; font-size: 1.05rem; }
.fi-coord-header .fi-coord-intro { margin: 0; color: #64748b; }
.fi-arc { margin: 1rem 0; }
.fi-arc-list { margin: .75rem 0 0; padding-left: 1.4rem; }
.fi-arc-item { margin: 0 0 .4rem; }
.fi-arc-category { margin-left: .4rem; color: #64748b; font-size: .85rem; }
.fi-coord-save-row { display: flex; align-items: center; gap: .75rem; margin-top: .5rem; }
.fi-coord-save-bad { color: #b91c1c; }
.fi-review-list { margin: 1rem 0 0; padding-left: 1.4rem; }
.fi-review-item { margin: 0 0 1.25rem; }
.fi-review-q { margin: 0 0 .35rem; }
.fi-review-fu { margin-top: .6rem; }
.fi-review-a { margin: 0; padding: .6rem .8rem; background: #f8fafc; border-left: 3px solid #cbd5e1; border-radius: 0 4px 4px 0; }
.fi-coord-actions { display: flex; flex-direction: column; gap: 1.5rem; }
.fi-coord-changes-form textarea { display: block; width: 100%; margin: .4rem 0 .6rem; }

/* ==========================================================================
   0.269.0 - the tabbed interview shell (coordinated + direct variants).
   One block, scoped to .fi-tabs-shell / .fi-utility, so the pitch-page tabs and every other
   page are untouched. Without JS the tablist stays hidden and every panel stacks under its
   own visible heading (the .fi-tabs-ready class is what reveals the strip and hides panels).
   Status text colours are darker than the legacy --fi-success/-warning/-danger on purpose:
   #16a34a on white is 3.3:1 and fails AA for text. No dark mode (James, 2026-10-01).
   ========================================================================== */
.fi-tabs-shell, .fi-utility {
    --fi-ok-text: #15803d;
    --fi-warn-text: #b45309;
    --fi-bad-text: #b91c1c;
    --fi-focus: #1d4ed8;
}
.fi-sr-only {
    position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* utility strip */
.fi-utility { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem; margin: 0 0 1rem; }
.fi-utility-share { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.fi-utility-share form { margin: 0; }
.fi-utility .fi-autosave { margin: 0; }
.fi-utility .fi-autosave-text, .fi-autosave-mirror { font-size: .9rem; color: var(--fi-gray-700); }
.fi-copy-msg { font-size: .88rem; color: var(--fi-gray-700); }
.fi-submit-row { display: flex; justify-content: center; margin-top: 2rem; }
.fi-submit-row + .fi-submit-section { margin-top: .5rem; }

/* every control in the shell is a 44px target with a visible 3px focus ring */
.fi-tabs-shell .fi-btn, .fi-utility .fi-btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.fi-tabs-shell .fi-btn-small { min-height: 44px; }
.fi-tabs-shell .fi-input, .fi-tabs-shell input[type="text"], .fi-tabs-shell input[type="url"], .fi-tabs-shell input[type="file"] { min-height: 44px; }
.fi-tabs-shell a:focus-visible, .fi-tabs-shell button:focus-visible, .fi-tabs-shell input:focus-visible,
.fi-tabs-shell textarea:focus-visible, .fi-tabs-shell [tabindex]:focus-visible, .fi-utility button:focus-visible {
    outline: 3px solid var(--fi-focus); outline-offset: 2px;
}
.fi-tabs-shell .fi-tabpanel:focus { outline: none; }
.fi-tabs-shell .fi-textarea:focus, .fi-tabs-shell .fi-input:focus { border-color: var(--fi-focus); }
.fi-tabs-shell a:not(.fi-btn):not(.fi-tab) { color: #1d4ed8; }

/* the tab strip (reuses .fi-tablist / .fi-tab / .fi-tabs-ready from the pitch page) */
.fi-tabs-shell .fi-tabwrap { position: relative; }
.fi-tabs-shell.fi-tabs-ready .fi-tablist { flex-wrap: nowrap; gap: .15rem; margin-bottom: 1rem; overflow-x: auto; scrollbar-width: none; }
.fi-tabs-shell.fi-tabs-ready .fi-tablist::-webkit-scrollbar { display: none; }
.fi-tabs-shell .fi-tab {
    min-height: 48px; padding: .4rem .9rem; display: inline-flex; align-items: center; gap: .55rem;
    text-align: left; color: var(--fi-gray-700); border-radius: 6px 6px 0 0; line-height: 1.25;
}
.fi-tabs-shell .fi-tab[aria-selected="true"] { color: #1d4ed8; border-bottom-color: #1d4ed8; background: #eff6ff; }
.fi-tabs-shell .fi-tab:hover { color: #1d4ed8; }
.fi-tabs-shell .fi-tab:focus-visible { outline: 3px solid var(--fi-focus); outline-offset: -3px; }
.fi-tabs-shell .fi-tab[aria-disabled="true"] { color: #4b5563; cursor: not-allowed; font-weight: 500; }
.fi-tabs-shell .fi-tab[aria-disabled="true"]:hover { color: #4b5563; }
.fi-tab-n {
    display: inline-flex; align-items: center; justify-content: center; width: 1.5rem; height: 1.5rem; border-radius: 50%;
    background: var(--fi-gray-200); color: var(--fi-gray-900); font-size: .8rem; font-weight: 700; flex: 0 0 auto;
}
.fi-tabs-shell .fi-tab[aria-selected="true"] .fi-tab-n { background: #1d4ed8; color: #fff; }
.fi-tab-text { display: inline-flex; flex-direction: column; }
.fi-tab-label { font-weight: 600; font-size: .95rem; }
.fi-tab-short { display: none; }
.fi-tab-status { display: inline-flex; align-items: center; gap: .25rem; font-size: .8rem; font-weight: 600; }
.fi-st-icon { flex: 0 0 auto; }
.fi-tab-status.fi-st-done { color: var(--fi-ok-text); }
.fi-tab-status.fi-st-warn { color: var(--fi-warn-text); }
.fi-tab-status.fi-st-todo { color: var(--fi-gray-700); }
.fi-tab-status.fi-st-lock { color: var(--fi-gray-700); }

/* panels */
.fi-tabs-shell .fi-tabpanel { margin: 0 0 2rem; }
.fi-tabs-shell:not(.fi-tabs-ready) .fi-tabpanel { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--fi-gray-200); }
.fi-tabs-shell:not(.fi-tabs-ready) .fi-tabpanel:first-of-type { border-top: 0; margin-top: 0; }
.fi-panel-title { font-size: 1.4rem; margin: 0 0 1rem; }
.fi-tabs-shell.fi-tabs-ready .fi-panel-title { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); }
.fi-card-block { background: #fff; border: 1px solid var(--fi-gray-200); border-radius: var(--fi-radius); padding: 1.25rem; margin: 0 0 1rem; box-shadow: var(--fi-shadow); }
.fi-card-block h3 { margin: 0 0 .5rem; font-size: 1.15rem; }
.fi-tabs-shell .fi-section { margin-top: 1rem; padding: 1.25rem; border: 1px solid var(--fi-gray-200); border-radius: var(--fi-radius); background: #fff; box-shadow: var(--fi-shadow); }
.fi-tabs-shell .fi-section h2 { font-size: 1.15rem; }
.fi-tabs-shell .fi-header { margin: 0; padding: 0; border: 0; }
.fi-tabs-shell .fi-muted, .fi-tabs-shell .fi-header .fi-time { color: var(--fi-gray-700); }
.fi-cta { text-align: center; border-color: #bfdbfe; background: #eff6ff; }
.fi-cta .fi-btn { margin-top: .5rem; }
.fi-tabs-shell .fi-btn-primary { background: #1d4ed8; }
.fi-tabs-shell .fi-btn-primary:hover { background: #1e40af; }
.fi-tabs-shell .fi-btn-secondary { color: #1f2937; }
.fi-tabs-shell .fi-alert-success { color: var(--fi-ok-text); background: #f0fdf4; border-color: #86efac; }
.fi-tabs-shell .fi-alert-warn { background: #fffbeb; color: #78350f; border: 1px solid #fcd34d; }
.fi-tabs-shell .fi-alert-info { background: #eff6ff; color: #1e3a8a; border: 1px solid #bfdbfe; }
.fi-tabs-shell .fi-alert-error { color: var(--fi-bad-text); }
.fi-chip { display: inline-block; padding: .15rem .6rem; border-radius: 999px; background: #e5e7eb; color: #1f2937; font-size: .8rem; font-weight: 600; }
.fi-chip.fi-chip-ok { background: #dcfce7; color: var(--fi-ok-text); }
.fi-photos-head { display: flex; align-items: center; gap: .75rem; margin: 0 0 .5rem; }
.fi-hero-chip { display: inline-block; margin-left: .35rem; padding: .05rem .5rem; border-radius: 999px; background: #dcfce7; color: var(--fi-ok-text); font-size: .75rem; font-weight: 700; }
.fi-image-card { position: relative; }
.fi-tabs-shell .fi-image-remove { min-width: 44px; min-height: 44px; }
.fi-tabs-shell .fi-image-card .fi-input { min-height: 44px; }
.fi-back-line { margin: 0 0 1rem; color: var(--fi-gray-700); }

/* Pre-question 1: dashed + blue outlined so it is never mistaken for one of the six questions */
.fi-prequestion { border: 2px dashed #2563eb; border-radius: var(--fi-radius); background: #f8fbff; padding: 1.1rem; margin: 0 0 1.5rem; }
.fi-pre-header { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin: 0 0 .5rem; }
.fi-pre-label { font-weight: 700; color: #1e3a8a; }
.fi-pre-text { display: block; font-weight: 600; font-size: 1.05rem; margin: 0 0 .25rem; }
.fi-prequestion .fi-textarea { width: 100%; }
.fi-pre-foot { display: flex; justify-content: space-between; gap: 1rem; margin-top: .4rem; font-size: .88rem; }
.fi-lock-line { margin: .75rem 0; padding: .6rem .8rem; background: #f3f4f6; border-radius: 6px; color: #1f2937; font-weight: 600; }
.fi-lock-label { font-weight: 800; }

/* inline accessible confirm (never window.confirm) */
.fi-confirm { margin-top: .75rem; padding: .9rem 1rem; border: 2px solid #1d4ed8; border-radius: var(--fi-radius); background: #eff6ff; display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; }
.fi-confirm[hidden] { display: none; }
.fi-confirm p { margin: 0; flex: 1 1 100%; }
.fi-more-form { margin: 0 0 1.25rem; }
.fi-more-form label { display: block; font-weight: 600; margin: 0 0 .25rem; }
.fi-more-form textarea { display: block; width: 100%; margin: 0 0 .6rem; }
.fi-more-form input[type="file"] { display: block; margin: 0 0 .6rem; max-width: 100%; }

/* mobile: one row, number badges hidden under 540px, Review may sit off-screen behind an edge fade */
@media (max-width: 539px) {
    .fi-tab-n { display: none; }
    .fi-tabs-shell .fi-tab { padding: .3rem .2rem; gap: 0; flex: 0 1 auto; min-width: 0; }
    .fi-tabs-shell .fi-tablist { gap: 0; }
    .fi-tab-text { min-width: 0; }
    .fi-tabs-shell .fi-tab-label { font-size: .74rem; }
    .fi-tabs-shell .fi-tab-status { font-size: .64rem; gap: .1rem; white-space: nowrap; }
    .fi-tabs-shell .fi-tab-status .fi-st-icon { width: 11px; height: 11px; }
    .fi-tab-long { display: none; }
    .fi-tab-short { display: inline; }
}
.fi-tabs-shell .fi-tabwrap.fi-fade-r::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 2px; width: 28px; pointer-events: none;
    background: linear-gradient(to right, rgba(255,255,255,0), #f9fafb);
}
@media (prefers-reduced-motion: reduce) {
    .fi-tabs-shell *, .fi-utility * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}
.fi-cta h2 { margin: 0 0 .5rem; font-size: 1.15rem; }
.fi-tabs-shell a:not(.fi-btn):not(.fi-tab) { text-decoration: underline; }

/* --- 0.269.0 phase 2: the direct variant of the shell ------------------------------------------------ */
.fi-welcome { margin: 0 0 .5rem; padding: 0; border: 0; }
.fi-welcome h1 { margin: 0 0 .25rem; font-size: 1.6rem; }
.fi-intro-block { margin: 0 0 1rem; }
.fi-intro-block .fi-subtitle, .fi-intro-block .fi-time, .fi-intro-block .fi-standard-statement { color: var(--fi-gray-700); }
.fi-next { margin: 1.5rem 0 0; text-align: right; }
.fi-check-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.fi-check-list li { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .25rem .75rem; padding: .6rem 0; border-top: 1px solid var(--fi-gray-200); min-height: 44px; }
.fi-check-list li:first-child { border-top: 0; }
.fi-chk-what { font-weight: 700; }
.fi-chk-state { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .25rem .6rem; font-weight: 600; }
.fi-chk-state.fi-st-done .fi-chk-text { color: var(--fi-ok-text); }
.fi-chk-state.fi-st-done .fi-chk-text::before { content: "\2713\00a0"; }
.fi-chk-state.fi-st-todo .fi-chk-text { color: var(--fi-warn-text); }
.fi-chk-state.fi-st-todo .fi-chk-text::before { content: "\25CB\00a0"; }
.fi-chk-state a { display: inline-flex; align-items: center; min-height: 44px; }
.fi-direct-submit .fi-submit-section { margin-top: 1rem; }
.fi-direct-submit .fi-confirm { margin-top: 1rem; }
.fi-review-list .fi-review-a { white-space: pre-wrap; }

/* the interview engine's own small controls get the same 44px target inside the shell (measured by shell-targets.mjs) */
.fi-tabs-shell .fi-expand-btn, .fi-tabs-shell .fi-btn-pass, .fi-tabs-shell .fi-question-swap-link { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* ------------------------------------------------------------------
   0.276.0 - open-call menu arcs: the interview cards on the request form, and the shared credit step.
   Equal weight on purpose: every card has the SAME five rows (title, card text, looking for, not for,
   time) so a row of cards lines up. Where subgrid is supported the rows align ACROSS cards; elsewhere
   the flex fallback keeps each card the same height with the time line pinned to the bottom.
   The radio stays visible and focusable inside its label (it is the control, not decoration).
   ------------------------------------------------------------------ */
.fi-arc-menu { border: 0; margin: 0 0 1.5rem; padding: 0; min-width: 0; }
.fi-arc-menu > legend { font-size: 1.25rem; font-weight: 700; padding: 0; margin: 0 0 .5rem; }
.fi-arc-intro { margin: 0 0 1rem; color: var(--fi-gray-700); }
.fi-arc-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; align-items: stretch; }
.fi-arc-card { position: relative; display: flex; flex-direction: column; box-sizing: border-box; padding: 1rem 1rem .9rem 2.6rem; border: 2px solid var(--fi-gray-200); border-radius: var(--fi-radius); background: #fff; cursor: pointer; transition: border-color .15s, background .15s, box-shadow .15s; }
.fi-arc-card:hover { border-color: var(--fi-gray-300); background: var(--fi-gray-50); }
.fi-arc-card .fi-arc-radio { position: absolute; left: .9rem; top: 1.15rem; width: 1.15rem; height: 1.15rem; margin: 0; }
.fi-arc-card:focus-within { outline: 3px solid var(--fi-primary); outline-offset: 2px; }
.fi-arc-card.is-chosen { border-color: var(--fi-primary); background: #eff6ff; box-shadow: 0 0 0 1px var(--fi-primary); }
.fi-arc-title { display: block; font-size: 1.05rem; font-weight: 700; line-height: 1.3; margin: 0 0 .5rem; }
.fi-arc-text { display: block; margin: 0 0 .75rem; line-height: 1.5; }
.fi-arc-meta { display: block; margin: 0 0 .5rem; font-size: .9rem; line-height: 1.45; color: var(--fi-gray-700); }
.fi-arc-meta-label { display: block; font-weight: 700; color: var(--fi-gray-900); }
.fi-arc-time { display: block; margin-top: auto; padding-top: .6rem; font-size: .85rem; color: var(--fi-gray-500); }
@supports (grid-template-rows: subgrid) {
    .fi-arc-card { display: grid; grid-row: span 5; grid-template-rows: subgrid; row-gap: 0; }
    .fi-arc-card > .fi-arc-title, .fi-arc-card > .fi-arc-text, .fi-arc-card > .fi-arc-meta, .fi-arc-card > .fi-arc-time { min-width: 0; }
    .fi-arc-card > .fi-arc-time { margin-top: 0; align-self: end; }
}
.fi-arc-note { margin: 1rem 0 0; color: var(--fi-gray-700); }
.fi-arc-none { margin: .75rem 0 0; }
.fi-arc-none a { font-weight: 600; }
.fi-credit-step { border: 1px solid var(--fi-gray-200); border-radius: var(--fi-radius); background: var(--fi-gray-50); margin: 0 0 1.5rem; padding: 1rem 1.25rem; min-width: 0; }
.fi-credit-step[hidden] { display: none; }
.fi-credit-step > legend { font-size: 1.1rem; font-weight: 700; padding: 0 .4rem; }
.fi-credit-step .fi-radio-label { background: #fff; }
.fi-credit-note { margin: .75rem 0 0; color: var(--fi-gray-700); font-size: .92rem; line-height: 1.5; }
@media (prefers-reduced-motion: reduce) { .fi-arc-card { transition: none; } }
.fi-arc-error { margin: 1rem 0 0; }
.fi-arc-error[hidden] { display: none; }
