/* Free.ai — WCAG 2.2 AA fixes that belong to a CLASS of control, not one page.
 *
 * Loaded after styles.css in base.html, so an equal-specificity rule here
 * wins. Every block below cites the success criterion it satisfies and the
 * measurement that produced it (scripts/audit_a11y.py, baseline 2026-09-17).
 *
 * Two standing traps this file is written around, both from
 * templates/CLAUDE.md:
 *   - an inline style="color:…" beats any non-!important rule at any
 *     specificity, so an override of one has to state a value PER THEME or it
 *     fixes light and breaks dark;
 *   - `[style*="…"]` matching is case-sensitive, which is why nothing here
 *     depends on the spelling of a hex in a template.
 */

/* --- Brand ink ------------------------------------------------------------
 * The brand green #16A34A is 3.29:1 on white: it fails as TEXT in light mode
 * and passes comfortably on the dark theme's near-black. One token, two
 * values, so a template can write color:var(--brand-ink) and be correct in
 * both themes. The FILL colour is untouched — white-on-green is a brand
 * decision and is reported, not changed, by the auditor.
 */
/* #166534, not the #15803D fill: measured on production, green ink at
 * #15803D on the grey #e2e3e5 chip behind an outline button is 3.90:1. On
 * white the darker ink is 7.0:1 and the two are hard to tell apart. Ink and
 * fill are allowed to differ -- they are answering different questions. */
:root { --brand-ink: #166534; }        /* 7.0:1 on #ffffff, 5.5:1 on #e2e3e5 */
/* The focus ring is the brand green, not the browser blue: a blue ring on a
 * green-bordered input reads as an error state. Light theme uses the fill
 * green (#15803D, 5.02:1 on white); dark uses a brighter green so the ring
 * stays visible on near-black (#22C55E, about 9:1 on #0a0a0a). SC 2.4.11
 * wants 3:1 against what is adjacent, and outline-offset keeps the ring on
 * the page background rather than on the control it surrounds. */
:root { --focus-ring: #15803D; }
html.dark { --focus-ring: #22C55E; }
html.dark { --brand-ink: #16A34A; }    /* 5.9:1 on #0a0a0a */
/* The footer is dark in BOTH themes, so the ink inside it is the dark-surface
 * value in both. Redefining the token on the element is why this is one line:
 * everything inside inherits it, including the brand mark in the footer logo.
 *
 * Measured on production 2026-09-17 and worth keeping as the cautionary note:
 * swapping the light theme's green ink to #166534 site-wide took that logo
 * from 5.34:1 to 2.48:1 -- a rule that fixed ~340 places broke the one place
 * that was already right, because "light theme" is not the same question as
 * "light surface". It showed up on all ten journey pages at once. */
footer { --brand-ink: #16A34A; }
/* ...and the same is true of /coder/, which is an app surface painted dark in
 * BOTH themes. Measured on production 2026-09-17, light theme: the session
 * rail, the wordmark, the account link and the token counter were all taking
 * the light theme's #166534 on the coder's near-black panel -- **2.43:1**, on
 * ten separate controls. The focus ring goes with it for the same reason: the
 * light theme's #15803D is 3.83:1 on that surface where #22C55E is ~9:1.
 * (css/coder.css re-declares both so the page is correct even if this file is
 * cached stale; the pair is pinned by tests/test_coder_palette.py.) */
#coder-app, #coder-diff-overlay { --brand-ink: #16A34A; --focus-ring: #22C55E; }

/* --- SC 2.4.1 Bypass Blocks ----------------------------------------------
 * The nav is ~40 links before the first word of content. Keyboard and screen
 * reader users get one Tab stop past all of it.
 */
.skip-link {
    position: absolute;
    left: .5rem;
    top: -4rem;
    z-index: 2000;
    padding: .55rem 1rem;
    background: #111827;
    /* !important because this file's own `html:not(.dark) a:not(.btn)` rule is
     * (0,2,2) and outranks a bare `.skip-link` at (0,1,0): the link was
     * painting the light theme's green ink on its own dark chip, 2.49:1, on
     * every page of the site. A rule fixing 340 links reached this one too. */
    color: #fff !important;
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 0 .4rem .4rem;
    transition: top .15s ease-in-out;
}
.skip-link:focus,
.skip-link:focus-visible { top: 0; color: #fff !important; outline: 3px solid var(--focus-ring, #22C55E); outline-offset: 2px; }
/* The target takes focus programmatically; it must not then paint a ring
 * around the whole page. */
main:focus { outline: none; }

/* --- SC 2.4.7 Focus Visible / 2.4.11 Focus Not Obscured -------------------
 * Bootstrap rings .btn and .form-control. It leaves plain links, icon-only
 * .btn-link controls and anything with .border-0 with the UA default, which
 * several of this site's surfaces then remove.
 */
:focus-visible {
    outline: 3px solid var(--focus-ring, #0b57d0);
    outline-offset: 2px;
}
html.dark :focus-visible { outline-color: var(--focus-ring, #22C55E); }
/* 🔴 A bare `:focus-visible` rule is (0,1,0) and loses to Bootstrap's
 * `.btn:focus-visible { outline: 0 }` at (0,2,0). Measured 2026-09-17 on the
 * signup page: every .btn, a.btn and .form-control on the site computed
 * `outline: none 0px` with `box-shadow: rgba(0,0,0,0) 0 0 0 0` -- i.e. the
 * ring above was written, shipped, and applied to nothing that matters.
 *
 * What fixes it is MATCHING Bootstrap's specificity; this file loads after
 * bootstrap.min.css, so the tie goes to us. (Verified by mutation: dropping
 * the !important from this block alone still paints the ring.) The
 * !important is kept because a page-level <style> block renders AFTER the
 * <head> links -- chat.html's own `#chat-form #chat-input:focus{outline:none}`
 * is exactly that -- and there it is the only thing that wins.
 *
 * outline-offset is load-bearing for SC 1.4.11: a blue ring drawn ON the
 * brand-green button would be 1.94:1 against it. Offset by 2px the ring sits
 * on the page background instead -- 6.4:1 on white, higher on the dark
 * theme's near-black. */
.btn:focus-visible,
a.btn:focus-visible,
.btn-close:focus-visible,
.accordion-button:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible,
.page-link:focus-visible,
.nav-link:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
    outline: 3px solid var(--focus-ring, #0b57d0) !important;
    outline-offset: 2px !important;
}
html.dark .btn:focus-visible,
html.dark a.btn:focus-visible,
html.dark .btn-close:focus-visible,
html.dark .accordion-button:focus-visible,
html.dark .form-control:focus-visible,
html.dark .form-select:focus-visible,
html.dark .form-check-input:focus-visible,
html.dark .page-link:focus-visible,
html.dark .nav-link:focus-visible,
html.dark summary:focus-visible,
html.dark [tabindex]:focus-visible { outline-color: var(--focus-ring, #22C55E) !important; }
/* Four places delete the ring on purpose, each with enough specificity or
 * !important to beat everything above. They were written to stop a ring
 * appearing on a MOUSE click, which :focus-visible already handles, and the
 * cost was that these controls -- the theme toggle, the homepage hero input,
 * the chat composer, the coder textarea -- had no keyboard focus indicator at
 * all. Restored at matching specificity, for the keyboard case only. */
/* The theme toggle is a BUTTON and keeps the ring. It was left dangling at
 * the head of the text-input rule below on 2026-09-17, which handed it a
 * border change and no outline: no focus indicator at all. Caught by
 * tests/test_a11y_live_regions_browser.py. */
#dark-mode-toggle:focus-visible {
    outline: 3px solid var(--focus-ring, #15803D) !important;
    outline-offset: 2px !important;
}
/* TEXT INPUTS DO NOT TAKE THE RING (2026-09-17, owner). A 3px ring around a
 * rounded composer reads as an alert, so these use the Google pattern instead:
 * a grey line at rest and a solid near-black line (near-white on dark) when
 * focused, carried by the pill wrapper via :focus-within. That is still a
 * visible focus indicator, and a bigger contrast change than the ring was:
 * #dadce0 to #202124 is about 13:1 against each other. Buttons and links keep
 * the ring, because they have no border to thicken. */
/* ⚠ `.input-pill` is DEAD CSS: it is styled in styles.css and a11y.css and
 * rendered by no template. The first version of this rule hung the indicator
 * off it, so the chat composer lost its ring and gained nothing, i.e. no focus
 * indicator at all. The indicator is applied to the CONTROL, which is a plain
 * .form-control in both places. Verify a selector renders before you rely on
 * it: `grep -rn "input-pill" templates/ static/js/` returns nothing. */
.home-bar .form-control:focus,
#chat-input:focus {
    border-color: #202124 !important;
    border-width: 2px !important;
    box-shadow: none !important;
}
html.dark .home-bar .form-control:focus,
html.dark #chat-input:focus {
    border-color: #e8eaed !important;
}
.home-bar .form-control:focus-visible,
.home-bar input:focus-visible,
#chat-form #chat-input:focus-visible,
.coder-input-row textarea:focus-visible {
    outline: none !important;
    outline-offset: 0 !important;
}
html.dark #dark-mode-toggle:focus-visible,
html.dark #chat-form #chat-input:focus-visible,
html.dark .coder-input-row textarea:focus-visible { outline-color: var(--focus-ring, #22C55E) !important; }

/* A focused control inside a scrolling panel must not hide under the sticky
 * navbar. */
:focus-visible { scroll-margin-top: 72px; }

/* --- SC 2.5.8 Target Size (Minimum), 24x24 --------------------------------
 * Every footer link measured 14px tall with 4px between rows: 32 failures on
 * every page of the site, in both themes and both widths, i.e. the single
 * largest finding in the baseline. Padding the anchor rather than the <li>
 * makes the TARGET grow, which is what the criterion measures.
 */
footer ul li { margin-bottom: 0 !important; }
footer ul li > a {
    display: inline-block;
    padding-top: 6px;
    padding-bottom: 6px;
    line-height: 1.2;
}

/* --- SC 1.4.3 Contrast (Minimum) -----------------------------------------
 * Measured pairs, each one a family rather than an instance.
 */
/* Footer is always dark. Bootstrap's .text-secondary (#6c757d) on it is
 * 4.27:1 — under the bar on every page. */
footer .text-secondary,
footer .text-secondary a,
footer a.text-secondary { color: #9ca3af !important; }
/* .text-secondary is a Bootstrap utility the dark theme never overrode. */
html.dark .text-secondary { color: #b0b0b0 !important; }
/* The -subtle utilities only flip under [data-bs-theme=dark]; this site
 * themes with html.dark, so they keep light-mode ink on a light-mode chip in
 * both themes (3.65:1 on the account history "Expired" badge).
 *
 * The html.dark form is not redundant: the generic `html.dark .text-secondary`
 * rule below is (0,2,0) and appears LATER, so without an equally specific
 * dark rule it repainted this chip #b0b0b0 on #e2e3e5 -- 1.68:1, measured on
 * production, WORSE than the 3.65:1 this block was added to fix. An override
 * of an override has to be scoped per theme as well. */
.bg-secondary-subtle.text-secondary,
.badge.bg-secondary-subtle { color: #495057 !important; }
html.dark .bg-secondary-subtle.text-secondary,
html.dark .badge.bg-secondary-subtle { color: #495057 !important; }
/* Bootstrap's danger red as ink on the dark theme's near-black is 4.37:1.
 * Anchors only, for the same reason as .btn-outline-success above: the same
 * class on a control sitting inside a light card would go the wrong way. */
html.dark a.btn-outline-danger { color: #ff8a94 !important; border-color: #ff8a94; }
/* Brand green used as ink on a light surface. Every one of these is a rule
 * in styles.css that paints TEXT from --primary; the fill rules are left
 * alone. The !important matches the rule being overridden (styles.css:65,73)
 * -- without it the override is a no-op and reads as applied.
 *
 * Scoped to light only: on the dark theme the same green is 5.9:1 and
 * darkening it there would BREAK a pair that passes. That is the
 * "state a value per theme" rule from templates/CLAUDE.md.
 */
html:not(.dark) .text-primary { color: var(--brand-ink) !important; }
html:not(.dark) .btn-outline-primary { color: var(--brand-ink) !important; border-color: var(--brand-ink) !important; }
/* The navbar is deliberately NOT in this rule. Its links are neutral grey
 * (.nav-link, #374151, about 10.3:1 on white), which never had a contrast
 * problem. This selector is (0,3,2) and .nav-link is (0,1,0), so without
 * the exclusion every header link turned green on 2026-09-17: a rule about
 * green ink repainted navigation that was not green to begin with. */
html:not(.dark) a:not(.btn):not(.navbar-brand):not(.nav-link) { color: var(--brand-ink); }
html:not(.dark) .navbar .nav-link { color: #374151; }
html:not(.dark) .breadcrumb a { color: var(--brand-ink); }
html:not(.dark) .cat-card i, /* see the icon exemption below */
html:not(.dark) .quick-tools,
html:not(.dark) .accordion-button:not(.collapsed) { color: var(--brand-ink); }
/* ...and the ~340 places a template writes the hex inline. An inline style
 * beats any non-!important rule, so this family has to shout. The ` i` flag
 * is load-bearing: 162 of those inline styles spell the hex in UPPERCASE and
 * attribute matching is case-sensitive by default. */
html:not(.dark) [style*="color:#16a34a" i] { color: var(--brand-ink) !important; }
/* ...except the wordmark. A logotype is exempt from SC 1.4.3 (and this one
 * is 2.2rem), so the brand mark keeps the bright brand green in both
 * themes. The rule above rewrites any inline #16a34a, which is how the
 * logo got darkened as a side effect of a rule about green body text. */
/* Icons are non-text UI: the bar is 3:1, not 4.5:1, and the bright brand
 * green is 3.29:1 on white. Darkening them was not required and it dulled
 * the category grid. Text labels beside them keep the darker ink. */
.cat-card i, .quick-tools i, .home-logo span { color: #16A34A !important; }
/* A logotype anywhere keeps the brand green. */
.brand-mark { color: #16A34A !important; }
html:not(.dark) .btn-link { color: var(--brand-ink); }
/* The mirror of the above, and it only became necessary when the FILL green
 * was darkened to #15803D on 2026-09-17: every rule that paints TEXT from
 * --primary now reads the darker green, which is 3.83:1 on the dark theme's
 * near-black. These point the ink back at --brand-ink, which is #16A34A there
 * (5.9:1). Fills keep --primary in both themes. */
html.dark .text-primary { color: var(--brand-ink) !important; }
html.dark a:not(.btn):not(.navbar-brand) { color: var(--brand-ink); }
html.dark .btn-link,
html.dark .btn-outline-primary,
html.dark .breadcrumb a,
html.dark .cat-card i,
html.dark .quick-tools,
html.dark .nav-link.active,
html.dark .nav-tabs .nav-link.active,
html.dark .accordion-button:not(.collapsed) { color: var(--brand-ink) !important; }
/* Amber on the dark theme's near-black: #b45309 is 3.94:1. Same shape as
 * above, opposite theme. */
html.dark [style*="color:#b45309" i] { color: #f59e0b !important; }
/* Bootstrap's own green (#198754, NOT the brand green) as ink on the dark
 * theme: 4.06 to 4.36:1 depending on which dark surface it lands on. */
/* ...and ONLY on an <a>. Measured 2026-09-17: the same class on a <label>
 * is the music page's mode toggle, which sits on a LIGHT card that the dark
 * theme does not repaint, so lightening its ink took it from 4.53:1 to
 * 2.60:1. A theme override has to know which surface the element is on, and
 * the anchors are the ones on the page background. */
html.dark a.btn-outline-success, html.dark .text-success { color: #4ade80 !important; }
html.dark a.btn-outline-success { border-color: #4ade80; }
/* Bootstrap's muted grey on the near-white card surface: 4.48:1, which is
 * under the bar by two hundredths and fails exactly as hard as 1.19:1 does. */
html:not(.dark) .btn-outline-secondary { color: #565e64; }
/* The home mode pills keep a pale green pill in BOTH themes, so the ink has
 * to suit THAT surface and not the page behind it. */
.home-tabs .nav-link.active,
.home-tabs .nav-link.active span { color: #15803D !important; }
/* The Chat|Work pill's unselected tab: #6b7280 on #f1f3f5 was 4.34:1.
 *
 * Per theme, and it has to be: the pill's TRACK is `background:#f1f3f5`, which
 * the inline-pastel family above now repaints to var(--bg-card) (#141414) in
 * dark mode. A fixed #4b5563 was right while the track was light and became
 * 2.43:1 the moment the track moved -- caught by the production sweep in the
 * same run that shipped the family. Ink and surface travel together. */
html:not(.dark) .chat-mode-btn:not(.active) { color: #4b5563; }
/* :not(.active) is load-bearing. The SELECTED pill keeps a white chip in both
 * themes and its own #111827 ink; `html.dark .chat-mode-btn` is (0,2,1) and
 * outranks `.chat-mode-btn.active` at (0,2,0), so the unselected-tab fix
 * repainted the selected tab #b0b0b0 on white -- 2.16:1, a fresh finding in
 * the very next sweep. Two tabs, two surfaces, two inks. */
html.dark .chat-mode-btn:not(.active) { color: #b0b0b0; }


/* --- The inline-pastel family, continued ---------------------------------
 * styles.css carries 142 of these selectors: a light chip written inline
 * stays light under the dark theme, so the theme repaints it. Measured
 * 2026-09-17 across a 30-page sitemap sample: 70 inline light backgrounds in
 * templates/ are spelled with a hex the family does not list, and the worst
 * of them (/voices/<slug>/, a whole SEO surface) rendered the dark theme's
 * #f0f0f0 ink on #fce7f3 at 1.03:1.
 *
 * Same shape as the existing blocks, same tint targets, every selector
 * carrying the ` i` flag because 162 inline styles in templates/ spell their
 * hex uppercase and attribute matching is case-sensitive without it.
 *
 * #ffc439 is deliberately absent: it is PayPal's brand yellow on their own
 * button, and repainting a payment brand's button is not a contrast fix. */
/* neutral card */
html.dark [style*="background:#f1f5f9" i],
html.dark [style*="background: #f1f5f9" i],
html.dark [style*="background-color:#f1f5f9" i],
html.dark [style*="background-color: #f1f5f9" i],
html.dark [style*="background:#f5f5f5" i],
html.dark [style*="background: #f5f5f5" i],
html.dark [style*="background-color:#f5f5f5" i],
html.dark [style*="background-color: #f5f5f5" i],
html.dark [style*="background:#f6f8fa" i],
html.dark [style*="background: #f6f8fa" i],
html.dark [style*="background-color:#f6f8fa" i],
html.dark [style*="background-color: #f6f8fa" i],
html.dark [style*="background:#e8e8ea" i],
html.dark [style*="background: #e8e8ea" i],
html.dark [style*="background-color:#e8e8ea" i],
html.dark [style*="background-color: #e8e8ea" i],
html.dark [style*="background:#f1f3f5" i],
html.dark [style*="background: #f1f3f5" i],
html.dark [style*="background-color:#f1f3f5" i],
html.dark [style*="background-color: #f1f3f5" i],
html.dark [style*="background:#fafafa" i],
html.dark [style*="background: #fafafa" i],
html.dark [style*="background-color:#fafafa" i],
html.dark [style*="background-color: #fafafa" i] {
    background-color: var(--bg-card) !important;
}
/* green tint */
html.dark [style*="background:#d1e7dd" i],
html.dark [style*="background: #d1e7dd" i],
html.dark [style*="background-color:#d1e7dd" i],
html.dark [style*="background-color: #d1e7dd" i] {
    background-color: #0f2417 !important;
}
/* rose / pink tint */
html.dark [style*="background:#fce7f3" i],
html.dark [style*="background: #fce7f3" i],
html.dark [style*="background-color:#fce7f3" i],
html.dark [style*="background-color: #fce7f3" i],
html.dark [style*="background:#fdf2f8" i],
html.dark [style*="background: #fdf2f8" i],
html.dark [style*="background-color:#fdf2f8" i],
html.dark [style*="background-color: #fdf2f8" i],
html.dark [style*="background:#fff1f2" i],
html.dark [style*="background: #fff1f2" i],
html.dark [style*="background-color:#fff1f2" i],
html.dark [style*="background-color: #fff1f2" i],
html.dark [style*="background:#ffe4e1" i],
html.dark [style*="background: #ffe4e1" i],
html.dark [style*="background-color:#ffe4e1" i],
html.dark [style*="background-color: #ffe4e1" i],
html.dark [style*="background:#f8d7da" i],
html.dark [style*="background: #f8d7da" i],
html.dark [style*="background-color:#f8d7da" i],
html.dark [style*="background-color: #f8d7da" i] {
    background-color: #2a1420 !important;
}
/* amber / orange tint */
html.dark [style*="background:#fff7ed" i],
html.dark [style*="background: #fff7ed" i],
html.dark [style*="background-color:#fff7ed" i],
html.dark [style*="background-color: #fff7ed" i],
html.dark [style*="background:#fff4a3" i],
html.dark [style*="background: #fff4a3" i],
html.dark [style*="background-color:#fff4a3" i],
html.dark [style*="background-color: #fff4a3" i],
html.dark [style*="background:#fefce8" i],
html.dark [style*="background: #fefce8" i],
html.dark [style*="background-color:#fefce8" i],
html.dark [style*="background-color: #fefce8" i],
html.dark [style*="background:#ffedd5" i],
html.dark [style*="background: #ffedd5" i],
html.dark [style*="background-color:#ffedd5" i],
html.dark [style*="background-color: #ffedd5" i] {
    background-color: #2b2210 !important;
}
/* indigo / purple tint */
html.dark [style*="background:#eef2ff" i],
html.dark [style*="background: #eef2ff" i],
html.dark [style*="background-color:#eef2ff" i],
html.dark [style*="background-color: #eef2ff" i],
html.dark [style*="background:#e0e7ff" i],
html.dark [style*="background: #e0e7ff" i],
html.dark [style*="background-color:#e0e7ff" i],
html.dark [style*="background-color: #e0e7ff" i],
html.dark [style*="background:#faf5ff" i],
html.dark [style*="background: #faf5ff" i],
html.dark [style*="background-color:#faf5ff" i],
html.dark [style*="background-color: #faf5ff" i],
html.dark [style*="background:#f5f3ff" i],
html.dark [style*="background: #f5f3ff" i],
html.dark [style*="background-color:#f5f3ff" i],
html.dark [style*="background-color: #f5f3ff" i],
html.dark [style*="background:#f0f7ff" i],
html.dark [style*="background: #f0f7ff" i],
html.dark [style*="background-color:#f0f7ff" i],
html.dark [style*="background-color: #f0f7ff" i] {
    background-color: #171a2e !important;
}

/* --- SC 2.3.3 Animation from Interactions --------------------------------
 * Nothing on the site honoured prefers-reduced-motion before this. Motion is
 * reduced to an instant rather than removed, so anything whose completion
 * depends on a transitionend/animationend event still fires.
 */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
    html { scroll-behavior: auto !important; }
    .tool-card:hover { transform: none !important; }
}

/* --- Screen-reader-only text ---------------------------------------------
 * Bootstrap ships .visually-hidden; this is the same contract under a name
 * the templates already reach for, so a label added by hand cannot land as
 * dead markup if a page loads without the Bootstrap bundle.
 */
.sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
