

/* ============================================================
   Visual system – components
   Cards, forms, buttons, dialogs, toasts, and the other shared
   component styles. Selectors are the PHP/JS class names.
   ============================================================ */

/* ---------- Cards & surfaces ---------- */

/* Everything that reads as a raised surface, listed by what it IS. .Card is
   the plain standalone one;
   .Form covers every form carrying the shared identity (see FormForm), so its
   subclasses are all included. The rule below it zeroes the trailing margin
   inside one, and has to name the same set - keep the two lists in step. */
:is(
    .Card,
    .Form, .SearchBox, .ThemeSelector,
    .NavDropdownMenu, .ConfirmDialogCard,
    .TestResults, .TestSuitePanel
) {
    background-color: var(--surface);
    background-image: var(--surface-image, none);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
    padding: 1rem;
    box-shadow: var(--shadow-card);
    margin-bottom: 1rem;
    overflow: hidden;
}

.NavDropdownMenu {
    max-height: 90vh;
    overflow-y: auto;
}

:is(
    .Card,
    .Form, .SearchBox, .ThemeSelector,
    .NavDropdownMenu, .ConfirmDialogCard,
    .TestResults, .TestSuitePanel
) > *:last-child {
    margin-bottom: 0;
}

/* ---------- Component layout ---------- */

/* FormForm is the shared identity for the forms that previously composed
   Bootstrap's flex-column/gap-2 utilities. */
.Form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.AISettingsPanel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.RetrievalForm [hidden] {
    display: none;
}

.ArticlePasteLabel {
    align-self: flex-start;
    width: fit-content;
    max-width: 100%;
    box-sizing: border-box;
}

.AISettingsPanel summary {
    cursor: pointer;
    font-weight: 600;
}

.AISettingsBody {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-top: 0.75rem;
}

.AISettingsBody input,
.AISettingsBody select {
    max-width: 40rem;
}

.AISettingsWarning {
    color: var(--warning, var(--muted));
}

.AISettingsControls {
    display: flex;
    gap: 0.5rem;
}

.ProcessingLog {
    display: flex;
    flex-direction: column;
    gap: 0;
    max-height: calc(100vh - 8rem);
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    padding-inline-end: 0.75rem;
    scrollbar-gutter: stable;
}

.InvestigationReportCard {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.ProcessingLogEntry {
    border-bottom: 1px solid var(--hairline);
    margin: 0;
    padding: 0.75rem 0.5rem;
}

.ProcessingLogEntryActive {
    animation: ProcessingLogEntryPulse 2s ease-in-out infinite;
    border-radius: 0.25rem;
}

@keyframes ProcessingLogEntryPulse {
    0%,
    100% {
        background-color: transparent;
    }

    50% {
        background-color: var(--theme-stop-2);
    }
}

@media (prefers-reduced-motion: reduce) {
    .ProcessingLogEntryActive {
        animation: none;
        background-color: var(--theme-stop-2);
    }
}

.ProcessingLogEntry:last-child {
    border-bottom: 0;
}

.ProcessingLogEntry h3 {
    font-size: 1rem;
    margin: 0;
    padding-bottom: 1rem;
}

.ProcessingLogStatus {
    color: var(--muted);
    margin: 0;
    padding-top: 0.25rem;
}

.ProcessingLogEntry dl {
    display: grid;
    gap: 0.25rem 0.75rem;
    grid-template-columns: max-content 1fr;
    margin: 0;
    padding-top: 0.75rem;
}

.ProcessingLogEntry dt {
    color: var(--muted);
    font-weight: 600;
}

.ProcessingLogEntry dd {
    margin: 0;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

.InvestigationWorkspace {
    display: grid;
    gap: 1rem;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    padding-inline-end: calc(var(--investigation-sidebar-width) + 1rem);
}

.InvestigationOutput,
.ProcessingSidebar {
    min-width: 0;
}

.InvestigationOutput {
    min-height: 0;
}

.ProcessingSidebar {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    right: 0;
    z-index: 15;
    width: var(--investigation-sidebar-width);
    height: 100vh;
    max-height: 100vh;
    overflow-y: auto;
    padding: 1rem;
    background-color: var(--surface);
    border-inline-start: 1px solid var(--hairline);
}

.ProcessingSidebar .ProcessingLog {
    flex: 1 1 auto;
    max-height: none;
}

.SidebarAd {
    border: 0;
    display: block;
    height: 600px;
    margin-inline: auto;
    max-width: 100%;
    width: 160px;
    flex-shrink: 0;
}

.AdvertisementSidebar {
    scrollbar-gutter: stable;
}

@media (max-width: 47.99rem) {
    .AdvertisementSidebar.ProcessingSidebar {
        height: 33vh;
        max-height: 33vh;
        overflow-y: auto;
        padding: 1rem;
    }
}

@media (min-width: 48rem) {
    .PageBody {
        height: 100vh;
        overflow: hidden;
    }

    .PageBody > main {
        height: calc(100vh - var(--nav-height) - 1rem);
        overflow: auto;
        overscroll-behavior-y: contain;
        scrollbar-gutter: stable;
        padding-inline-end: 0.75rem;
    }

    .InvestigationWorkspace {
        padding-inline-end: 0;
    }

    .ProcessingSidebar {
        align-self: start;
    }
}

@media (max-width: 47.99rem) {
    .InvestigationWorkspace {
        grid-template-columns: minmax(0, 1fr);
        padding-inline-end: 0;
    }

    .ProcessingSidebar {
        position: static;
        width: auto;
        height: auto;
        max-height: none;
        overflow: visible;
        padding: 0;
        border-inline-start: 0;
    }

    .ProcessingLog {
        height: 33vh;
        max-height: 33vh;
    }
}

.ReportIndexSection,
.RelatedReportsSidebar {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    overflow-wrap: anywhere;
}

.RelatedReportsSidebar {
    padding: 1rem;
    background: var(--surface);
    border: 1px solid var(--hairline);
}

.RelatedReportsSidebar > h2,
.RelatedReportsSidebar > p {
    margin: 0;
}

@media (min-width: 48rem) {
    .RelatedReportsSidebar {
        position: fixed;
        top: 0;
        right: 0;
        width: var(--investigation-sidebar-width);
        height: 100vh;
        height: 100dvh;
        overflow-y: auto;
        scrollbar-gutter: stable;
    }
}

.Notice,
.PageGenerationTime {
    color: var(--muted);
}

.PageGenerationTime {
    font-size: 0.875rem;
}

.VerificationNotice {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.VerificationNoticeActions,
.ConfirmDialogActions {
    display: flex;
    gap: 0.5rem;
}

.SitePolicyLinks {
    display: flex;
    gap: 1rem;
}

.SetupNextSteps,
.ThemeSelector,
.LanguageSelector,
.TestSuitePanel {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.TestSuitePanel {
    align-items: flex-start;
}

.ErrorList {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.UsernameAvailability {
    color: var(--muted);
    font-size: 0.875rem;
}

.HiddenLabel {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* ---------- Animations ---------- */

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

@keyframes mount-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}

.LoadingSpinner {
    display: flex;
    justify-content: center;
    padding: 1rem;
}

.LoadingSpinner::after {
    content: '';
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid var(--hairline);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

.MountIn {
    animation: mount-in 0.25s ease;
}

.SlidingOut {
    overflow: hidden;
    opacity: 0;
    transition: height 0.2s ease, opacity 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
    .SlidingOut { transition: none; }
}

/* ---------- A refused field ---------- */

/* Under the input it is about, in the colour everything that has gone wrong
   wears. Tied to the input by aria-describedby, so it is read out on reaching
   the box rather than found by hunting - which is the whole point of putting
   it here instead of in a toast floating at the edge of the screen. */
.FieldError {
    color: var(--danger);
    font-size: 0.8125rem;
    margin: 0.25rem 0 0;
}

/* The box itself, so the eye lands on which one before reading why. */
.InputField :is(input, textarea)[aria-invalid='true'],
.TextareaField :is(input, textarea)[aria-invalid='true'] {
    border-color: var(--danger);
    outline-color: var(--danger);
}

/* ---------- Skipping the navigation ---------- */

/* Out of the way until somebody tabs to it, then a real control in the corner.
   Moved off-screen rather than hidden: display:none and visibility:hidden both
   take it out of the tab order, which is the one thing it must stay in. */
.SkipLink {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1000;
    padding: 0.75rem 1rem;
    background-color: var(--surface);
    color: var(--ink);
    border: 1px solid var(--hairline);
    border-radius: 0 0 var(--radius-card) 0;
    transform: translateY(-120%);
}

.SkipLink:focus {
    transform: translateY(0);
}

/* The content it lands on is only focusable so the jump lands - it is not a
   control, and should not draw a ring as though it were. */
#PageContent:focus {
    outline: none;
}

/* ---------- Buttons ---------- */

.Button {
    background-color: var(--surface-2);
    background-image: var(--button-image, none);
    color: var(--ink) !important;
    border: none;
    border-radius: var(--radius-pill);
    padding: 0.5rem 1rem;
    font-weight: 550;
    font-size: 0.875rem;
    line-height: 1.4;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease, opacity 0.15s ease;
}

.Button:hover {
    background-color: var(--surface);
    /* Falls back to the resting image, so a theme with a gradient but no hover
       variant simply keeps the one it has rather than losing it on hover. */
    background-image: var(--button-image-hover, var(--button-image, none));
    color: var(--ink) !important;
}

.PrimaryButton,
.PrimaryButton:hover {
    background-color: var(--accent);
    background-image: var(--accent-image);
    color: var(--accent-ink) !important;
}

.Button:active { transform: scale(0.97); }
.Button:disabled { opacity: 0.55; cursor: default; transform: none; }

/* Waiting on the server. Disabling alone only dims a control slightly and
   then nothing happens, which on anything slow reads as a press that did not
   land - so it pulses for as long as it is working. The animation beats the
   :disabled opacity above by being an animation, which is the whole reason
   this is not just another opacity rule. */
@keyframes throb {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.4; }
}

.Working {
    animation: throb 1.1s ease-in-out infinite;
    cursor: progress;
}

/* Somebody who has asked for less movement still needs to know it is working,
   so the pulse becomes a steady dimming rather than nothing at all. */
@media (prefers-reduced-motion: reduce) {
    .Working { animation: none; opacity: 0.5; }
}

/* Forms lay their fields out in a column, and the button that submits one sits
   at the trailing edge rather than stretching across it. */
.SubmitButton { align-self: flex-end; }


/* A checkbox or radio with a label wrapped round it is a control you press, so
   it takes the button's own colours and hover and keeps the box inside. The
   wrapping is what makes the whole of it the target rather than the box alone,
   so this only claims the ones built that way. */
label:has(> input[type='checkbox']),
label:has(> input[type='radio']) {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background-color: var(--surface-2);
    background-image: var(--button-image, none);
    color: var(--ink);
    border-radius: var(--radius-pill);
    padding: 0.5rem 1rem;
    margin-bottom: 0;
    font-weight: 550;
    font-size: 0.875rem;
    line-height: 1.4;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.15s ease, color 0.15s ease;
}

label:has(> input[type='checkbox']):hover,
label:has(> input[type='radio']):hover {
    background-color: var(--surface);
    background-image: var(--button-image-hover, var(--button-image, none));
    color: var(--ink);
}

a.Button {
    display: inline-block;
    text-align: center;
}

a.Button:hover { color: var(--ink) !important; }

.GoogleSignInButton {
    align-items: center;
    background: #fff;
    border: 1px solid #dadce0;
    border-radius: 4px;
    box-shadow: 0 1px 2px rgb(60 64 67 / 30%);
    color: #3c4043 !important;
    display: inline-flex;
    font-family: Arial, sans-serif;
    font-size: 0.875rem;
    font-weight: 500;
    gap: 0.75rem;
    justify-content: center;
    letter-spacing: 0.01em;
    min-height: 44px;
    padding: 0.625rem 1.25rem;
    transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.GoogleSignInButton:hover {
    background: #f8fafd;
    box-shadow: 0 1px 3px rgb(60 64 67 / 40%);
    color: #3c4043 !important;
}

.GoogleSignInButton img {
    height: 18px;
    width: 18px;
}

emoji-picker.Active {
    opacity: 1;
    pointer-events: auto;
    display: flex;
}

.emoji-text {
    font-size: 1.25rem;
    line-height: 1;
    vertical-align: middle;
}

.emoji-only .emoji-text {
    font-size: 2.5rem;
    line-height: 1.3;
}

/* ---------- Forms (component classes) ---------- */

.CheckboxField {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.CheckboxField input { width: auto; margin: 0; }
.CheckboxField label { display: inline; margin-bottom: 0; }

.TextareaField textarea {
    width: 100%;
    resize: vertical;
}

/* Every wording the button can show, stacked in one cell so the widest of
   them sets the width once and toggling never resizes it. */
.ToggleButton {
    display: inline-grid;
    grid-template-areas: 'label';
    place-items: center;
}

.ToggleButtonLabel { grid-area: label; }

.ToggleButtonLabel.Inactive { visibility: hidden; }

.ProgressBar {
    display: none;
    width: 100%;
    height: 0.4rem;
    border-radius: var(--radius-pill);
    overflow: hidden;
    margin-top: 0.5rem;
    accent-color: var(--accent);
}

.ProgressBar.Active { display: block; }

label.visually-hidden { margin-bottom: 0; }

/* ---------- Quill editor ---------- */

.ql-toolbar.ql-snow {
    background-color: var(--surface);
    border-color: var(--hairline);
    border-top-left-radius: var(--radius-card);
    border-top-right-radius: var(--radius-card);
}

.ql-container.ql-snow {
    border-color: var(--hairline);
    border-bottom-left-radius: var(--radius-card);
    border-bottom-right-radius: var(--radius-card);
    font-size: 0.9375rem;
}

.ql-editor {
    background-color: var(--surface);
    color: var(--ink);
    min-height: 4.5rem;
    border-bottom-left-radius: var(--radius-card);
    border-bottom-right-radius: var(--radius-card);
}

.ql-editor.ql-blank::before {
    color: var(--muted);
    opacity: 1;
    font-style: normal;
    font-size: 0.9375rem;
}

.ql-snow .ql-stroke { stroke: var(--ink); }
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill { fill: var(--ink); }
.ql-snow .ql-picker { color: var(--ink); }
.ql-snow .ql-picker-options {
    background-color: var(--surface);
    border-color: var(--hairline);
}

/* ---------- People ---------- */

.User { display: flex; align-items: flex-start; }

.User h2 { margin-bottom: 1rem; }

/* ---------- Search ---------- */

.SearchBox {
    position: relative;
    display: flex;
    align-items: center;
    padding: 0.5rem;
}

.SearchInput {
    width: 100%;
    border: none;
    background-color: transparent;
    font-size: 1rem;
}

.SearchInput:focus { box-shadow: none; }

.SearchClearButton {
    display: none;
    flex: none;
    padding: 0 0.25rem;
    border: none;
    background: none;
    color: var(--danger) !important;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.SearchBox.HasQuery .SearchClearButton { display: block; }

/* The words for the language offer, rendered by the server because they are in
   a language this page is not in. LanguagePrompt.js takes them into a dialog
   and takes this away; it is never something to read in place. */
.LanguagePrompt { display: none; }

/* ---------- Confirm dialog ---------- */

.ConfirmDialogOverlay {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: color-mix(in srgb, var(--shadow-color) 45%, transparent);
    padding: 1rem;
}

.ConfirmDialogCard {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(24rem, 100%);
    box-shadow: var(--shadow-pop);
}

.ConfirmDialogMessage {
    color: var(--ink);
}

.ConfirmDialogInput {
    resize: vertical;
}

.ConfirmDialogActions {
    justify-content: flex-end;
}

/* ---------- Test results ---------- */

.TestResultsBadge {
    display: inline-block;
    align-self: flex-start;
    padding: 0.25rem 0.5rem;
    border-radius: var(--radius-ctl);
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.TestResultsPass {
    background-color: var(--accent-soft);
    color: var(--accent-strong);
}

.TestResultsFail {
    background-color: var(--danger-soft);
    color: var(--danger);
}

.TestResultsOutput {
    margin: 0;
    padding: 0.5rem;
    border-radius: var(--radius-ctl);
    background-color: var(--surface-2);
    color: var(--ink);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-x: auto;
}

/* ---------- Error & error list ---------- */

.Error { color: var(--danger); }

.ErrorList {
    background-color: var(--danger-soft);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
    border-radius: var(--radius-ctl);
    padding: 0.5rem 1rem;
    list-style: none;
    margin: 0;
    color: var(--danger);
}

/* ---------- Settings sections (fold‑outs) ---------- */

.SettingsSection {
    border-top: 1px solid var(--hairline);
}

.SettingsSection:last-of-type {
    border-bottom: 1px solid var(--hairline);
}

.SettingsSection > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    cursor: pointer;
    padding-block: 1rem;
    list-style: none;
    font-family: var(--display-font);
    font-weight: 640;
    font-size: 1.2rem;
    line-height: 1.2;
    letter-spacing: -0.01em;
}

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

.SettingsSection > summary:hover {
    color: var(--accent);
}

.SettingsSection > summary::after {
    content: '';
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--muted);
    border-bottom: 2px solid var(--muted);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.SettingsSection[open] > summary::after {
    transform: rotate(-135deg);
}

.SettingsSection[open] > :last-child {
    margin-bottom: 1.25rem;
}

/* ---------- Toasts ---------- */
.ToastContainer {
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(22rem, calc(100vw - 2rem));
}

.Toast {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    background-color: var(--surface);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-pop);
    padding: 0.5rem 1rem;
    color: var(--ink);
    font-size: 0.875rem;
    opacity: 0;
    transform: translateY(0.5rem);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.Toast.Active {
    opacity: 1;
    transform: translateY(0);
}

.ToastMessage {
    flex: 1 1 auto;
}

.ToastCloseButton {
    background: none;
    border: none;
    padding: 0;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease;
}

.ToastCloseButton:hover {
    color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
    .Toast {
        transition: none;
    }
}
