/* Site-wide styles for every page that extends base.html.
   Lived inline in base.html until it reached 30 KB, at which point every page
   view re-downloaded and re-parsed the whole thing because a template cannot
   be cached separately from the HTML around it. Out here nginx serves it with
   a long expiry and the browser reads it once per deploy. */

:root {
    /* Twilight, not Halloween. The page used to run brown -> blue-grey
       -> warm brown, which put deep purple-brown next to the orange
       accent — the Halloween palette. It now runs deep indigo to dusk
       blue, keeping a warm glow only at the very bottom, so the accent
       reads as gold against it. The accent itself is unchanged. */
    --brand-bg-start: #131a3e;
    --brand-bg-end: #232a55;
    --brand-bg-warm: #4a4272;
    --brand-accent: #fba92c;
    --brand-accent-soft: #faf2b1;
    --brand-accent-deep: #8a5a12;
    /* Panel fill for cards sitting directly on the page. Was written
       out as rgba(35, 15, 11, x) in eight templates, which is how the
       brown got everywhere in the first place. */
    --brand-panel: 26, 33, 74;
    --brand-surface: #ffffff;
    --brand-surface-alt: #f2f0f8;
    --brand-border: #d8d5e6;
    --brand-text: #1a1716;
    --brand-text-muted: #6b6b6b;
    --brand-shadow: rgba(19, 26, 62, 0.18);
    --istqb-purple: var(--brand-accent-deep);
    --istqb-purple-dark: #6d4709;
    --istqb-purple-light: var(--brand-accent);
    --istqb-blue: var(--brand-bg-end);
    --istqb-gray: var(--brand-text-muted);
    --istqb-light-bg: var(--brand-surface-alt);
}

body {
    font-family: 'Roboto Mono', monospace;
    /* Same shape as before — a warm glow at the top, a three-stop ramp
       down the page — but running through blue. The cool wash at 62%
       keeps the middle of a long page from going muddy where the
       indigo meets the violet. */
    background:
        radial-gradient(circle at top, rgba(251, 169, 44, 0.14), transparent 34%),
        radial-gradient(ellipse 80% 50% at 50% 62%, rgba(86, 190, 205, 0.07), transparent 70%),
        linear-gradient(180deg, var(--brand-bg-start) 0%, var(--brand-bg-end) 46%, var(--brand-bg-warm) 100%);
    color: var(--brand-text);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    position: relative;
}

body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 52vh;
    pointer-events: none;
    z-index: 0;
    opacity: 0.5;
    /* Site-wide starfield. Was 27 hand-placed gradients with a soft
       falloff and background-repeat:no-repeat — each "star" rendered as
       a ~4px blurred blob, and because nothing tiled, the density
       changed with the viewport. Now three tiled layers with an
       EXPLICIT circle size, so a star is genuinely 1px and stays crisp
       and evenly dense at any width. */
    background-image:
        radial-gradient(circle 1px at 18% 22%, rgba(255, 255, 255, 0.9) 0 100%, transparent 100%),
        radial-gradient(circle 1px at 67% 44%, rgba(255, 255, 255, 0.7) 0 100%, transparent 100%),
        radial-gradient(circle 1px at 88% 74%, rgba(255, 255, 255, 0.85) 0 100%, transparent 100%),
        radial-gradient(circle 1px at 36% 81%, rgba(255, 255, 255, 0.6) 0 100%, transparent 100%),
        radial-gradient(circle 1px at 78% 13%, rgba(255, 255, 255, 0.8) 0 100%, transparent 100%),
        radial-gradient(circle 1px at 8% 58%, rgba(255, 255, 255, 0.65) 0 100%, transparent 100%),
        radial-gradient(circle 1.3px at 26% 33%, rgba(255, 255, 255, 0.9) 0 100%, transparent 100%),
        radial-gradient(circle 1.3px at 73% 68%, rgba(255, 255, 255, 0.75) 0 100%, transparent 100%),
        radial-gradient(circle 1.3px at 47% 11%, rgba(255, 255, 255, 0.85) 0 100%, transparent 100%),
        radial-gradient(circle 1.3px at 12% 87%, rgba(255, 255, 255, 0.7) 0 100%, transparent 100%),
        radial-gradient(circle 1.6px at 31% 56%, rgba(255, 246, 216, 0.95) 0 100%, transparent 100%),
        radial-gradient(circle 1.6px at 84% 24%, rgba(255, 246, 216, 0.85) 0 100%, transparent 100%);
    background-size:
        150px 150px, 150px 150px, 150px 150px, 150px 150px, 150px 150px, 150px 150px,
        265px 265px, 265px 265px, 265px 265px, 265px 265px,
        430px 430px, 430px 430px;
    background-repeat: repeat;
    -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.98) 28%, rgba(0, 0, 0, 0.82) 52%, rgba(0, 0, 0, 0.28) 78%, rgba(0, 0, 0, 0) 100%);
    mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.98) 28%, rgba(0, 0, 0, 0.82) 52%, rgba(0, 0, 0, 0.28) 78%, rgba(0, 0, 0, 0) 100%);
}

h1, h2, h3, h4, h5, h6, .navbar-brand, .card-header {
    font-family: 'Press Start 2P', cursive;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.6;
}

h1 { font-size: 1.6rem; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.05rem; }
h4 { font-size: 0.9rem; }
h5 { font-size: 0.78rem; }
h6 { font-size: 0.7rem; }

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

a:hover {
    color: #6d4709;
}

.text-primary {
    color: var(--brand-accent-deep) !important;
}

.text-dark {
    color: var(--brand-text) !important;
}

.content-wrapper > .container > .row:first-child h1,
.content-wrapper > .container > .row:first-child h2,
.content-wrapper > .container > .row:first-child h3,
.content-wrapper > .container > .row:first-child h4,
.content-wrapper > .container > .row:first-child h5,
.content-wrapper > .container-fluid > .row:first-child h1,
.content-wrapper > .container-fluid > .row:first-child h2,
.content-wrapper > .container-fluid > .row:first-child h3,
.content-wrapper > .container-fluid > .row:first-child h4,
.content-wrapper > .container-fluid > .row:first-child h5,
.content-wrapper > .container > div:first-child > h1,
.content-wrapper > .container > div:first-child > h2,
.content-wrapper > .container > div:first-child > h3,
.content-wrapper > .container > div:first-child > h4,
.content-wrapper > .container-fluid > div:first-child > h1,
.content-wrapper > .container-fluid > div:first-child > h2,
.content-wrapper > .container-fluid > div:first-child > h3,
.content-wrapper > .container-fluid > div:first-child > h4,
.content-wrapper > .container-fluid > div:first-child > h5 {
    color: var(--brand-accent-soft);
    text-shadow: 0 2px 18px rgba(0, 0, 0, 0.25);
}

/* "Showing 11 to 12 of 12" under a pagination bar sits on the page
   background, not inside a card, so Bootstrap's dark .text-muted grey is
   almost invisible there. Cards keep their own muted grey further down. */
.pagination-info,
.pagination-info .text-muted,
.pagination-info p {
    color: rgba(232, 236, 255, 0.75) !important;
}

.content-wrapper > .container > .row:first-child > p.text-muted,
.content-wrapper > .container-fluid > .row:first-child > p.text-muted,
.content-wrapper > .container > div:first-child > p.text-muted,
.content-wrapper > .container-fluid > div:first-child > p.text-muted,
.content-wrapper > .container > .d-flex:first-child > p.text-muted,
.content-wrapper > .container-fluid > .d-flex:first-child > p.text-muted {
    color: rgba(250, 242, 177, 0.72) !important;
}

.bg-primary {
    background-color: var(--brand-accent-deep) !important;
    border-color: var(--brand-accent-deep) !important;
}

/* Header/Navigation */
.navbar {
    background:
        linear-gradient(180deg, rgba(20, 16, 31, 0.38) 0%, rgba(20, 16, 31, 0.16) 58%, rgba(20, 16, 31, 0) 100%);
    box-shadow: none;
    padding: 1.05rem 0;
    border-bottom: none;
    backdrop-filter: blur(1.5px);
    -webkit-backdrop-filter: blur(1.5px);
}

.navbar-brand {
    font-family: 'Press Start 2P', cursive;
    font-size: 1rem;
    font-weight: 400;
    color: var(--brand-accent) !important;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.navbar-brand i {
    font-size: 1.8rem;
    color: var(--brand-accent);
}

/* Brand lockup: pixel cup + WBT, with the full name underneath so the
   abbreviation still reads on a first visit. Same mark as the favicon. */
.brand-mark {
    width: 34px;
    height: 34px;
    display: block;
    flex: none;
    image-rendering: pixelated;
}

.brand-text {
    display: flex;
    flex-direction: column;
    gap: 5px;
    line-height: 1;
}

.brand-wbt {
    font-family: 'Press Start 2P', cursive;
    font-size: 1.05rem;
    color: var(--brand-accent);
    letter-spacing: 0.08em;
}

.brand-full {
    font-family: 'Roboto Mono', monospace;
    font-size: 0.52rem;
    font-weight: 500;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(250, 242, 177, 0.55);
}

.footer-brand .brand-mark { width: 28px; height: 28px; }
.footer-brand .brand-wbt { font-size: 0.85rem; }

.nav-link {
    color: rgba(250, 242, 177, 0.88) !important;
    font-weight: 400;
    font-family: 'Roboto Mono', monospace;
    transition: all 0.3s ease;
    padding: 0.55rem 1rem !important;
    border-radius: 8px;
}

.nav-link:hover {
    color: white !important;
    background-color: rgba(250, 242, 177, 0.12);
}

.nav-link.active {
    background-color: rgba(251, 169, 44, 0.2);
    color: white !important;
}

.navbar-text {
    color: rgba(250, 242, 177, 0.88) !important;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.badge-role {
    background-color: rgba(251, 169, 44, 0.18);
    color: var(--brand-accent-soft);
    font-size: 0.65rem;
    padding: 0.25rem 0.75rem;
    border-radius: 8px;
    border: 1px solid rgba(251, 169, 44, 0.18);
    font-family: 'Roboto Mono', monospace;
}

/* Main Content */
main {
    flex: 1;
    padding: 0;
}

/* The header has to outrank every page, because its dropdown opens
   over whatever is below it. `position: relative` here makes the whole
   header one stacking context, so Bootstrap's z-index:1000 on the menu
   only ever competes *inside* it — from the outside the entire header,
   menu included, is worth whatever this line says. At 1 it lost to the
   landing page's layered sky (up to 20) and to the info and sitemap
   shells at 3, which is why the account menu opened underneath them.
   1030 is Bootstrap's own "fixed chrome" level, comfortably above any
   page. Anything a page stacks has to stay below it. */
nav {
    position: relative;
    z-index: 1030;
}

footer {
    position: relative;
    z-index: 1;
}

/* Flash messages are chrome, like the header, and must never be buried
   by a page. The landing hero in particular pulls itself up under the
   transparent header with a negative top margin, so it was drawn over
   the "you have been logged out" line that had just been rendered
   above it — leaving a sliver of blue and a close button.
   Just below the header's 1030, so a dropdown still opens over a
   message rather than the other way round. */
.flash-stack {
    position: relative;
    z-index: 1020;
}

.content-wrapper {
    min-height: calc(100vh - 200px);
}

.content-wrapper > .container {
    padding-top: 1rem;
    padding-bottom: 1rem;
}

/* Alerts */
.alert {
    border-radius: 10px;
    border: 2px solid rgba(10, 10, 10, 0.1);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    margin-bottom: 1.5rem;
    font-family: 'Roboto Mono', monospace;
}

.alert-success {
    background-color: #d1e7dd;
    color: #0f5132;
}

.alert-danger {
    background-color: #f8d7da;
    color: #842029;
}

.alert-warning {
    background-color: #fff3cd;
    color: #664d03;
}

.alert-info {
    background-color: #cff4fc;
    color: #055160;
}

/* Buttons */
.btn-primary {
    background: var(--brand-accent);
    border: 3px solid #0a0a0a;
    padding: 0.6rem 1.5rem;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
    transition: all 0.3s ease;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    color: #0a0a0a;
    border-radius: 10px;
}

.btn-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
    background: #ffc861;
    color: #0a0a0a;
    border-color: #0a0a0a;
}

.btn-istqb {
    background: var(--brand-accent);
    color: #0a0a0a;
    border: 3px solid #0a0a0a;
    padding: 0.75rem 2rem;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
    font-size: 1rem;
    transition: all 0.3s ease;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    border-radius: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.btn-istqb:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
    background: #ffc861;
    color: #0a0a0a;
    border-color: #0a0a0a;
}

.btn-outline-istqb {
    color: var(--brand-accent);
    border: 2px solid var(--brand-accent);
    background: transparent;
    border-radius: 10px;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
}

.btn-outline-istqb:hover {
    color: #0a0a0a;
    background: var(--brand-accent);
    border-color: var(--brand-accent);
}

.btn-outline-primary {
    color: var(--brand-accent);
    border: 2px solid var(--brand-accent);
    background: transparent;
    border-radius: 10px;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
}

.btn-outline-primary:hover {
    color: #0a0a0a;
    background: var(--brand-accent);
    border-color: var(--brand-accent);
}

.btn-secondary {
    background: #3b4270;
    border: 3px solid #0a0a0a;
    color: #f3f2fb;
    border-radius: 10px;
    font-family: 'Roboto Mono', monospace;
}

.btn-secondary:hover {
    background: #47507f;
    border-color: #0a0a0a;
    color: #ffffff;
}

.btn-outline-secondary {
    color: #4a5183;
    border: 2px solid rgba(74, 81, 131, 0.42);
    background: transparent;
    border-radius: 10px;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
}

.btn-outline-secondary:hover {
    color: #ffffff;
    background: #4a5183;
    border-color: #4a5183;
}

.btn-light {
    color: #0a0a0a;
    border-radius: 10px;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
    border: 3px solid #0a0a0a;
}

.btn-outline-light {
    color: var(--brand-accent-soft);
    border: 2px solid rgba(250, 242, 177, 0.42);
    border-radius: 10px;
    font-weight: 500;
    font-family: 'Roboto Mono', monospace;
}

.btn-outline-light:hover {
    color: #0a0a0a;
    background: var(--brand-accent-soft);
    border-color: var(--brand-accent-soft);
}

.btn:disabled,
.btn.disabled,
fieldset:disabled .btn {
    opacity: 0.6;
    color: #83839c;
    border-color: rgba(131, 131, 156, 0.3);
    background: #e8e7f0;
    box-shadow: none;
    transform: none;
}

.bg-istqb {
    background:
        linear-gradient(135deg, rgba(251, 169, 44, 0.18) 0%, rgba(251, 169, 44, 0.02) 28%),
        linear-gradient(135deg, var(--brand-bg-start) 0%, var(--brand-bg-end) 100%) !important;
}

.text-istqb {
    color: var(--brand-accent-deep) !important;
}

.btn-success {
    background: #198754;
    border: 3px solid #0a0a0a;
    font-weight: 500;
    border-radius: 10px;
    font-family: 'Roboto Mono', monospace;
}

/* Cards */
.card {
    border: 3px solid rgba(10, 10, 10, 0.15);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
    background: rgba(255, 255, 255, 0.98);
    overflow: hidden;
}

.card .text-muted,
.table .text-muted,
.modal .text-muted,
.alert .text-muted,
.bg-white .text-muted {
    color: var(--brand-text-muted) !important;
}

/* ============================================================
   TABLES — fit the page, never scroll sideways.
   Bootstrap's .table-responsive is literally `overflow-x: auto`,
   so every table wrapped in it scrolls horizontally as soon as its
   content is wider than the card. Three things are needed to make
   them fit instead: stop the scroll, let long strings wrap, and
   cut the very heavy cell padding.
   ============================================================ */
.table-responsive {
    overflow-x: visible;
}

.table {
    width: 100%;
}

/* Long unbroken strings — emails, usernames, URLs — otherwise set the
   column's minimum width and force the whole table wider than the page.
   Body cells only: breaking short header labels mid-word ("Create/d")
   looks broken and saves almost nothing. */
.table td {
    overflow-wrap: anywhere;
    word-break: break-word;
}

.table th {
    overflow-wrap: normal;
    word-break: normal;
}

/* The px-4 utility puts 1.5rem on each side of every cell; across seven
   or eight columns that is ~350px of padding alone. Bootstrap utilities
   are !important, so overriding them needs !important too. */
@media (max-width: 1400px) {
    .table th.px-4, .table td.px-4 {
        padding-left: 0.85rem !important;
        padding-right: 0.85rem !important;
    }
    .table th.px-3, .table td.px-3 {
        padding-left: 0.6rem !important;
        padding-right: 0.6rem !important;
    }
}

/* Avatar bubbles and other fixed-size circles are flex items inside a
   cell; without this they get squashed into ovals as columns narrow. */
.table .rounded-circle { flex: 0 0 auto; }

/* Row actions.
   These used to sit in a .btn-group, which is the wrong control: a
   button group is one segmented switch, so Bootstrap rounds only the
   outer corners of its *direct* .btn children. Half of these buttons
   are wrapped in a <form> to POST, so those selectors missed them and
   the row ended up with a mix of rounded and square corners at uneven
   spacing. They are separate actions, so they are laid out as separate
   buttons: same size, same radius, same gap, every row. */
.row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    justify-content: flex-end;
}

.row-actions form { display: flex; margin: 0; }

.row-actions .btn {
    /* Fixed box so the icons line up column-wise down the table, no
       matter how many actions a given row happens to offer. */
    width: 2.1rem;
    height: 2.1rem;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    line-height: 1;
}

.row-actions .btn i { font-size: 0.95rem; }

/* Stacked rows put actions on their own line, where left-aligned reads
   better than clinging to the right edge. */
.table-stack .row-actions { justify-content: flex-start; }

/* Narrow viewports: each row becomes a stacked card. The breakpoint has
   to depend on the column count — a 4-column table is fine at 800px
   while a 7-column one is already unreadable — so the class is toggled
   in JS (see the script at the end of this file) rather than by a media
   query. data-label is filled in from the column headers there too, so
   no template has to carry it. */
.table-stack thead { display: none; }

.table-stack,
.table-stack tbody,
.table-stack tr,
.table-stack td {
    display: block;
    width: 100%;
}

.table-stack tr {
    border: 2px solid rgba(10, 10, 10, 0.12);
    border-radius: 10px;
    margin-bottom: 0.75rem;
    padding: 0.25rem 0;
    background: rgba(255, 255, 255, 0.6);
}

.table-stack td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    text-align: right;
    border: none;
    padding: 0.5rem 0.85rem !important;
}

.table-stack td::before {
    content: attr(data-label);
    flex: 0 0 38%;
    text-align: left;
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--brand-text-muted);
}

/* cells with no header (empty-state rows spanning the table) stay full width */
.table-stack td[data-label=""]::before,
.table-stack td:not([data-label])::before { content: none; }
.table-stack td:not([data-label]) { display: block; text-align: center; }

/* Hover effect only for cards that benefit from it (dashboard cards, action cards) */
.card-hoverable {
    transition: all 0.3s ease;
}

.card-hoverable:hover {
    transform: translateY(-4px);
    box-shadow: 0 28px 45px rgba(var(--brand-panel), 0.2);
}

.card-header {
    background: var(--brand-bg-start);
    color: var(--brand-accent-soft);
    border-radius: 12px 12px 0 0 !important;
    padding: 1rem 1.5rem;
    font-weight: 400;
    font-size: 0.7rem;
    border-bottom: 2px solid rgba(250, 242, 177, 0.12);
}

.card-header.bg-white {
    color: var(--brand-accent-deep);
    border-bottom: 2px solid rgba(138, 90, 18, 0.16);
}

.dropdown-menu {
    border-radius: 10px;
    border: 2px solid rgba(10, 10, 10, 0.15);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
    font-family: 'Roboto Mono', monospace;
}

.dropdown-item:active,
.dropdown-item:focus,
.dropdown-item:hover {
    background-color: rgba(251, 169, 44, 0.14);
    color: var(--brand-text);
}

.form-control,
.form-select,
.input-group-text {
    border-radius: 8px;
    border-color: rgba(90, 92, 140, 0.28);
    font-family: 'Roboto Mono', monospace;
}

.input-group-text {
    background: #f7f6fc;
    color: var(--brand-accent-deep);
}

.form-control:focus,
.form-select:focus {
    border-color: rgba(251, 169, 44, 0.65);
    box-shadow: 0 0 0 0.2rem rgba(251, 169, 44, 0.18);
}

.form-check-input:checked {
    background-color: var(--brand-accent-deep);
    border-color: var(--brand-accent-deep);
}

.breadcrumb {
    --bs-breadcrumb-divider-color: rgba(250, 242, 177, 0.42);
    --bs-breadcrumb-item-active-color: rgba(250, 242, 177, 0.78);
    margin-bottom: 0;
}

/* ============================================================
   PAGE HEADER — see partials/page_nav.html.
   One header component for every authenticated page: breadcrumb,
   title, optional actions. Sits directly on the page background,
   replacing the purple #667eea slab that used to top some pages
   and not others.
   ============================================================ */
.page-head {
    margin-bottom: 2rem;
}

.page-head .breadcrumb {
    margin-bottom: 0.85rem;
    font-size: 0.85rem;
}

.page-head-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1.25rem;
    flex-wrap: wrap;   /* keeps the title and buttons off each other on narrow screens */
}

.page-head-text {
    min-width: 0;      /* lets a long title wrap instead of pushing the buttons off-screen */
    flex: 1 1 22rem;
}

.page-title {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 0;
    color: var(--brand-accent-soft);
    text-shadow: 0 2px 18px rgba(0, 0, 0, 0.25);
    overflow-wrap: anywhere;
}

.page-title i {
    color: var(--brand-accent);
    flex: 0 0 auto;
}

.page-subtitle {
    margin: 0.7rem 0 0;
    font-family: 'Roboto Mono', monospace;
    font-size: 0.95rem;
    color: rgba(250, 242, 177, 0.7);
}

.page-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}

@media (max-width: 575.98px) {
    .page-actions { width: 100%; }
    .page-actions > * { flex: 1 1 auto; text-align: center; }
}

.breadcrumb-item,
.breadcrumb-item.active {
    color: rgba(250, 242, 177, 0.78);
}

.breadcrumb-item a {
    color: var(--brand-accent);
    text-decoration: underline;
    text-underline-offset: 0.18em;
}

.breadcrumb-item a:hover {
    color: var(--brand-accent-soft);
}

/* Footer */
footer {
    background:
        linear-gradient(135deg, rgba(251, 169, 44, 0.12) 0%, rgba(251, 169, 44, 0) 36%),
        linear-gradient(180deg, #0c1130 0%, var(--brand-bg-start) 42%, var(--brand-bg-end) 100%);
    color: white;
    padding: 2rem 0 1rem;
    margin-top: 4rem;
    border-top: 1px solid rgba(250, 242, 177, 0.12);
}

footer a {
    color: rgba(250, 242, 177, 0.82);
    text-decoration: none;
    transition: color 0.3s ease;
}

footer a:hover {
    color: var(--brand-accent-soft);
}

.footer-brand {
    font-family: 'Press Start 2P', cursive;
    font-size: 0.85rem;
    font-weight: 400;
    margin-bottom: 1rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--brand-accent);
}

.footer-links {
    list-style: none;
    padding: 0;
    margin: 0;
}

.footer-links li {
    margin-bottom: 0.5rem;
}

/* Responsive */
@media (max-width: 768px) {
    .navbar-brand {
        font-size: 1.2rem;
    }

    .navbar-text {
        font-size: 0.9rem;
    }
}

/* ===========================================================================
   Bug detection panel (judge scoring page)

   This panel answers one question a judge asks repeatedly while scoring:
   which of the planted defects did this team actually find? It used to be
   three bulleted lists of sentences, which meant reading a paragraph to
   extract a fact. It is now a strip of counts you can take in at a glance,
   over three tables you open only when you want the detail.

   Severity and match quality are pills rather than words in a sentence
   because their whole job is to be scanned down a column.
   =========================================================================== */

.bug-eval-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.75rem;
}

@media (max-width: 575.98px) {
    .bug-eval-stats { grid-template-columns: repeat(2, 1fr); }
}

.bug-eval-stat {
    border-radius: 0.6rem;
    padding: 0.6rem 0.75rem;
    text-align: center;
    border: 1px solid transparent;
    /* Tinted rather than saturated: four solid blocks of colour next to each
       other compete with the scores below, which are what the page is for. */
    background: var(--brand-surface-alt);
}

.bug-eval-stat .value {
    font-size: 1.45rem;
    font-weight: 700;
    line-height: 1.1;
}

.bug-eval-stat .label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--brand-text-muted);
}

.bug-eval-stat--found  { background: #e8f6ed; border-color: #a9d9bd; }
.bug-eval-stat--found .value { color: #157347; }
.bug-eval-stat--rate   { background: #fff5e4; border-color: #f2ce8f; }
.bug-eval-stat--rate .value { color: var(--brand-accent-deep); }
.bug-eval-stat--extra  { background: #e7f1fb; border-color: #a8c9ea; }
.bug-eval-stat--extra .value { color: #0a58ca; }
.bug-eval-stat--noise  { background: var(--brand-surface-alt); border-color: var(--brand-border); }
.bug-eval-stat--noise .value { color: var(--brand-text-muted); }

/* The detection rate as a bar as well as a number — 9 of 23 reads faster as
   a length than as a fraction. */
.bug-eval-rate-track {
    height: 6px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.08);
    overflow: hidden;
    margin-top: 0.35rem;
}

.bug-eval-rate-fill {
    height: 100%;
    background: var(--brand-accent);
}

/* --- the three collapsible sections ------------------------------------- */

.bug-eval-section + .bug-eval-section { margin-top: 0.5rem; }

.bug-eval-toggle {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    border: 1px solid var(--brand-border);
    border-radius: 0.5rem;
    background: var(--brand-surface);
    padding: 0.5rem 0.75rem;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--brand-text);
    text-align: left;
}

.bug-eval-toggle:hover { background: var(--brand-surface-alt); }

/* Rotates when the section opens, so the control says which way it is
   without needing a second label. */
.bug-eval-toggle .chev { transition: transform 0.15s ease; margin-left: auto; }
.bug-eval-toggle[aria-expanded="true"] .chev { transform: rotate(90deg); }

.bug-eval-toggle--found  { border-left: 4px solid #198754; }
.bug-eval-toggle--missed { border-left: 4px solid #dc3545; }
.bug-eval-toggle--extra  { border-left: 4px solid #0d6efd; }

.bug-eval-table {
    font-size: 0.82rem;
    margin: 0.4rem 0 0;
}

.bug-eval-table th {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--brand-text-muted);
    font-weight: 600;
    border-bottom: 2px solid var(--brand-border);
    white-space: nowrap;
}

.bug-eval-table td { vertical-align: top; }

.bug-eval-table .bug-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    white-space: nowrap;
}

/* Wide tables scroll inside their own section rather than pushing the
   scoring form sideways. */
.bug-eval-scroll { overflow-x: auto; }

.pill {
    display: inline-block;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}

.pill-blocker  { background: #f8d7da; color: #842029; }
.pill-major    { background: #ffe5d0; color: #8a3d0b; }
.pill-minor    { background: #cfe2ff; color: #084298; }
.pill-trivial  { background: #e2e3e5; color: #41464b; }
.pill-exact    { background: #d1e7dd; color: #0f5132; }
.pill-partial  { background: #fff3cd; color: #664d03; }
.pill-weak     { background: #e2e3e5; color: #41464b; }
.pill-valid    { background: #d1e7dd; color: #0f5132; }
.pill-invalid  { background: #e2e3e5; color: #41464b; }
.pill-technique { background: #ede7f6; color: #4527a0; }

/* ===========================================================================
   AI suggestion panel (judge scoring page)
   ---------------------------------------------------------------------------
   The panel used to be a generic bg-light box: a tiny score badge on top of a
   wall of prose, visually indistinguishable from the judge's own inputs above
   it. Judges could not tell at a glance which number was theirs and which was
   the model's. The panel now carries an unmistakable tint and accent border
   (this is the AI talking), the score is the biggest thing in it, and long
   justifications clamp behind a Show more.
   =========================================================================== */

.ai-suggestion-panel {
    border: 1px solid #c7d2f4;
    border-left: 4px solid #4c6ef5;
    border-radius: 0.5rem;
    background: #f0f4ff;
    padding: 0.6rem 0.75rem;
}

.ai-suggestion-panel.ai-panel-error {
    border-color: #f1c3c8;
    border-left-color: #dc3545;
    background: #fdf2f3;
}

.ai-panel-title { color: #3b5bdb; }

/* The AI's number, sized to be read from across the desk. */
.ai-score-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25rem;
    background: #4c6ef5;
    color: #fff;
    border-radius: 0.5rem;
    padding: 0.15rem 0.6rem;
    font-weight: 700;
    font-size: 1.25rem;
    line-height: 1.4;
}
.ai-score-chip .ai-score-max {
    font-size: 0.75rem;
    font-weight: 600;
    opacity: 0.85;
}
/* While the score is still streaming in it breathes, so a judge never
   mistakes a partial number for a final one. */
.ai-score-chip.ai-score-streaming { animation: ai-pulse 1.2s ease-in-out infinite; }
@keyframes ai-pulse { 50% { opacity: 0.55; } }

/* Compact echo of the AI score on the judge's own score row. */
.ai-ghost-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    border: 1px dashed #4c6ef5;
    background: transparent;
    color: #3b5bdb;
    border-radius: 0.5rem;
    padding: 0.1rem 0.5rem;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
}
.ai-ghost-chip:hover:not(:disabled) { background: #e7edff; }
.ai-ghost-chip:disabled { opacity: 0.5; }

/* Per-category run state: the spinner the page never had. */
.ai-run-state {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: #3b5bdb;
    padding: 0.15rem 0;
    margin-bottom: 0.35rem;
}
.ai-run-state.d-none { display: none; }
.ai-run-state .spinner-border { color: #4c6ef5; }

/* Clamped justification: enough to judge relevance, not a screen and a half.
   Streaming text keeps the clamp so cards grow smoothly, not violently. */
.ai-justification.ai-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.ai-justification.ai-streaming::after {
    content: '▍';
    color: #4c6ef5;
    animation: ai-pulse 1s ease-in-out infinite;
}

/* Unsaved work: the class markDirty always set, now with a rule behind it. */
.btn-unsaved {
    box-shadow: 0 0 0 0.2rem rgba(251, 169, 44, 0.45);
    animation: ai-pulse 1.6s ease-in-out infinite;
}

/* ===========================================================================
   Score provenance and bulk AI preparation (judge pages)
   ---------------------------------------------------------------------------
   Suggestions now PRE-FILL unscored categories (the owner's call: at ~80
   teams a judge must be able to review and save without touching every
   slider), so the sidebar has to say who each number belongs to — that is
   the provenance pill. On the teams page, one button prepares every team of
   a finished event; the chips and the disabled state below track that run.

   Note on the clamp rules further up: "Streaming text keeps the clamp" no
   longer holds — live-streamed justifications render EXPANDED from the
   first token (the judge asked to watch), and only suggestions that arrive
   with the page load start clamped. The .ai-clamp rule itself is unchanged.
   =========================================================================== */

/* Who a score belongs to right now: 'AI' (accepted, untouched) or
   'AI · edited'. Judge-owned scores show nothing — theirs is the default
   state of the world and does not need a label. */
.ai-source-pill {
    display: inline-block;
    background: #e7edff;
    color: #3b5bdb;
    border: 1px solid #c7d2f4;
    border-radius: 0.5rem;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.1rem 0.4rem;
    white-space: nowrap;
}

/* Per-team chips under the bulk progress bar's authority: done / preparing /
   error / pending, colour-matched to the bug-eval pills above. */
.bulk-team-chip { font-weight: 600; }
.bulk-team-chip .spinner-border { width: 0.75rem; height: 0.75rem; border-width: 0.14em; }
.bulk-team-chip--done    { background: #d1e7dd; color: #0f5132; }
.bulk-team-chip--running { background: #cfe2ff; color: #084298; }
.bulk-team-chip--error   { background: #f8d7da; color: #842029; }
.bulk-team-chip--pending { background: #e2e3e5; color: #41464b; }

/* A locked Score Team button keeps pointer events, against Bootstrap's
   .btn.disabled default, so its title tooltip can still explain WHY it is
   locked; the page's click handler swallows the navigation and the scoring
   route redirects any deep link that slips past both. */
.score-team-btn.disabled {
    pointer-events: auto;
    cursor: not-allowed;
}
