/**
 * 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);
}

/* FC demo sandbox (Part A) - the CTA close, rendered into .fi-submit-section
   in place of the real submit button; hidden until the question cap is
   reached (see interview-form.js showInterviewComplete()). */
.fi-demo-cta-text {
    font-size: 1.05rem;
    margin-bottom: 1rem;
}

/* FC demo sandbox Part B - lead-gen pitch-capture funnel. .fi-lead-q mirrors
   .fi-question's box visually WITHOUT the .fi-question class itself - that
   class drives interview-form.js's global question-count/progress/index
   bookkeeping, which these two pre-rendered (hidden-until-revealed) fields
   must never join (see default.php's comment at the funnel markup). */
.fi-lead-q {
    margin-bottom: 1.5rem;
    padding: 1.5rem;
    background: white;
    border: 1px solid var(--fi-gray-200);
    border-radius: var(--fi-radius);
    box-shadow: var(--fi-shadow);
}

.fi-lead-invitation .fi-btn-small {
    margin-left: 0.75rem;
}

.fi-lead-funnel-error {
    color: var(--fi-danger);
    margin: 0.75rem 0;
    font-size: 0.9rem;
}

.fi-lead-nudge .fi-form-group {
    max-width: 400px;
}

.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;
    }
}
