/*
 * accessibility_widget.css — presentation for the in-house accessibility widget.
 *
 * Organisation:
 *   §A  Widget chrome + self-exemption
 *   §B  Content adjusters   (font scale, spacing, alignment, emphasis, focus)
 *   §C  Colour adjusters    (saturation filters, high-contrast sweep)
 *   §D  Orientation adjusters (hide images, stop animations, cursors)
 *   §E  Wrapper-effect fallout (position:fixed neutralisation)
 *   §F  Print + reduced motion
 *
 * TWO ATTRIBUTE HOOKS, so one stylesheet serves both the public site and the admin
 * portal (which has no single wrapper element of its own):
 *
 *   [data-a11y-scope]  the content region(s) the adjusters apply INSIDE
 *   [data-a11y-fx]     the region(s) wrapper effects (filter/zoom) apply TO
 *
 * Public site: #wrap carries both.
 * Admin:       <header>, #adminShell and the admin footer each carry both, because
 *              they are separate <body> children.
 *
 * WHERE WRAPPER EFFECTS GO (§C/§E). `filter` and `zoom` also target
 * `.reveal-modal` / #f5-reveal-shim-overlay explicitly, because
 * app/assets/javascripts/ui/public/foundation5_reveal_shim.js MOVES each modal to
 * <body> on first open (`$el.appendTo('body')`) and appends its own overlay there
 * too. Filtering the wrapper alone would leave modals full-colour over a grayscale
 * page. The widget itself is never inside a [data-a11y-fx] region, which is what
 * makes it structurally exempt from the effects it applies.
 *
 * Contrast/dark/light modes are NOT here: they are applied by re-declaring the
 * site's own theme custom properties from JS (see THEME_MODES in
 * accessibility_widget.js). col_theme.css has 525 var() references and 1 hardcoded
 * hex; admin.css was migrated onto an --admin-* palette for the same reason. So
 * that recolours both with no selector knowledge and no !important. §C only
 * carries what variables cannot reach.
 */

/* ================================================== §0  READABLE FONT (self-hosted)
 *
 * Atkinson Hyperlegible (Braille Institute, SIL Open Font License 1.1) — designed
 * for low vision, with deliberately differentiated character shapes.
 *
 * Self-hosted rather than fetched from Google Fonts: no third-party request (the
 * layout already loads six CDNs), it keeps working behind the network filtering
 * common on school-district connections, and it needs no rework if a
 * Content-Security-Policy is ever enabled (config/initializers/content_security_policy.rb
 * is currently entirely commented out).
 *
 * Both `latin` and `latin-ext` subsets are shipped — latin-ext is what carries the
 * Polish diacritics (ą ę ł ń ś ź ż) this site serves. 34 KB total, and only
 * downloaded when a visitor actually turns the readable font on.
 *
 * Propshaft resolves these same-directory relative URLs: the stylesheet's logical
 * path is `ui/public/accessibility_widget.css` and the fonts (in app/assets/fonts/ui/public)
 * resolve to the same `ui/public` logical directory.
 */

@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/ui/public/atkinson-hyperlegible-400-latin-b8c1bd4f.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/ui/public/atkinson-hyperlegible-400-latin-ext-fcfd636d.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/ui/public/atkinson-hyperlegible-700-latin-00c0f0a8.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/ui/public/atkinson-hyperlegible-700-latin-ext-b6e53251.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================ §A  WIDGET CHROME */

.a11y-widget {
  position: fixed;
  bottom: 16px;
  left: 16px; /* bottom-RIGHT is taken by the locale switcher */
  z-index: 2147483000;
  font: 15px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: #1a1a1a;
}

/*
 * Admin portal placement: bottom-LEFT, stacked directly ABOVE .admin-tour-trigger
 * ("Take a tour"), which sits at bottom: 20px. Both were in the bottom-right
 * corner until they were found sitting on top of page content and the footer's
 * partner logos at the widest layout; the left corner is the quiet one in the
 * admin, where the right-hand side is where the content actually reaches.
 *
 * The base left: 16px above already puts it where it belongs, so only the stack
 * offset changes here. Below 1024px the rail is off-canvas and the corner is
 * empty; at 1024px and up this lands inside the rail's own column, below the
 * menu — the trigger is narrower than even the collapsed rail, so it never
 * reaches the content area. The space under the menu is reserved by
 * .sidebar-inner's padding-bottom in ui/admin/admin.css, which is sized for this
 * control and the tour button together. The panel keeps the base left: 0 and
 * opens rightwards, over the content rather than off-screen.
 */
.a11y-widget[data-a11y-context="admin"] {
  bottom: 68px;
}

/*
 * Self-exemption, defence in depth. The widget is already outside #wrap, so
 * filters and zoom cannot reach it; these rules cover the INHERITABLE properties
 * that still cascade down from <html> (root font-size, letter-spacing, cursor).
 * Colours here are hardcoded literals on purpose: a contrast mode must never be
 * able to make the accessibility controls themselves unreadable.
 */
.a11y-widget,
.a11y-widget * {
  filter: none !important;
  letter-spacing: normal !important;
  text-align: start !important;
  text-decoration: none !important;
  text-shadow: none !important;
  visibility: visible !important;
  font-family: inherit !important;
}

.a11y-widget { font-size: 15px !important; } /* immune to html{font-size:N%} */

.a11y-widget button {
  font: inherit;
  cursor: pointer !important;
  color: inherit;
  background: none;
  border: 0;
  margin: 0;
}

.a11y-widget :focus-visible {
  outline: 3px solid #ffbf00 !important;
  outline-offset: 2px !important;
}

/* ---- trigger ---- */

.a11y-widget__trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px; /* 44px minimum hit area */
  padding: 8px 14px;
  background: #1f3d7a !important;
  color: #ffffff !important;
  border: 2px solid #ffffff !important;
  border-radius: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  font-weight: 600;
}

.a11y-widget__trigger:hover { background: #16305f !important; }

.a11y-widget__trigger svg {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  fill: currentColor;
}

/* Hidden interface: the full trigger goes, a small icon takes its place.
   "Hide" has to leave a way back that a MOUSE user can find — an sr-only control is
   reachable by Tab and invisible to everyone else, which is how hiding became a
   one-way door. This is the compromise: no label, no panel, just a quiet 44px disc,
   about a quarter the width of the trigger it replaces. */
html.a11y-hidden-ui .a11y-widget__trigger,
html.a11y-hidden-ui .a11y-widget__panel { display: none !important; }

/* Only present while hidden — the trigger itself is the way in the rest of the time. */
.a11y-widget__restore { display: none; }

html.a11y-hidden-ui .a11y-widget__restore {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;                 /* 2.5.8: comfortably past the 24px minimum */
  height: 44px;
  padding: 0;
  border: 2px solid #ffffff;
  border-radius: 50%;
  background: #1f3d7a;
  color: #ffffff;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
  /* Subdued until wanted, but never invisible: 0.65 keeps the white-on-#1f3d7a
     glyph well past 4.5:1 against a light page, which a lower value would not. */
  opacity: .65;
  transition: opacity .15s ease, transform .15s ease;
}

html.a11y-hidden-ui .a11y-widget__restore:hover { opacity: 1; transform: scale(1.05); }
html.a11y-hidden-ui .a11y-widget__restore:focus-visible {
  opacity: 1;
  outline: 3px solid #ffbf00;
  outline-offset: 2px;
}

html.a11y-hidden-ui .a11y-widget__restore svg {
  width: 24px;
  height: 24px;
  fill: currentColor;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  html.a11y-hidden-ui .a11y-widget__restore { transition: none; }
  html.a11y-hidden-ui .a11y-widget__restore:hover { transform: none; }
}

/* ---- panel ---- */

.a11y-widget__panel {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 0;
  width: 360px;
  max-width: calc(100vw - 32px);
  max-height: min(80vh, 620px);
  overflow-y: auto;
  background: #ffffff !important;
  color: #1a1a1a !important;
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);
}

.a11y-widget__panel[hidden] { display: none !important; }

.a11y-widget__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
  background: #f4f6fa !important;
  border-radius: 12px 12px 0 0;
}

.a11y-widget__title {
  margin: 0 !important;
  font-size: 16px !important;
  font-weight: 700;
  color: #1a1a1a !important;
}

.a11y-widget__close {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  font-size: 22px !important;
  line-height: 1;
  border-radius: 8px;
}

.a11y-widget__close:hover { background: rgba(0, 0, 0, 0.07) !important; }

.a11y-widget__body { padding: 6px 16px 12px; }

.a11y-widget__group { border: 0; margin: 0; padding: 12px 0 4px; }
.a11y-widget__group + .a11y-widget__group { border-top: 1px solid rgba(0, 0, 0, 0.1); }

.a11y-widget__group-title {
  margin: 0 0 10px !important;
  font-size: 12px !important;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em !important;
  color: #55606e !important;
}

/* ---- switch rows ---- */

.a11y-widget__switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 6px 8px;
  border-radius: 8px;
  text-align: left !important;
}

.a11y-widget__switch:hover { background: #eef2f8 !important; }

.a11y-widget__switch-track {
  position: relative;
  flex: 0 0 auto;
  width: 42px;
  height: 24px;
  background: #b9c0cc !important;
  border-radius: 12px;
  transition: background 0.15s ease-in-out;
}

.a11y-widget__switch-track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  background: #ffffff;
  border-radius: 50%;
  transition: transform 0.15s ease-in-out;
}

.a11y-widget__switch[aria-checked="true"] .a11y-widget__switch-track {
  background: #1f7a3d !important;
}

.a11y-widget__switch[aria-checked="true"] .a11y-widget__switch-track::after {
  transform: translateX(18px);
}

.a11y-widget__switch[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed !important;
}

/* ---- profiles ---- */

.a11y-widget__note {
  margin: 0 8px 10px !important;
  font-size: 12px !important;
  line-height: 1.45;
  color: #55606e !important;
}

.a11y-widget__note--limits {
  margin-top: 10px !important;
  padding-top: 10px;
  border-top: 1px dashed rgba(0, 0, 0, 0.16);
}

.a11y-widget__profile {
  align-items: flex-start;
  padding: 10px 8px;
}

.a11y-widget__profile-text { display: block; }

.a11y-widget__profile-name {
  display: block;
  font-weight: 600;
  font-size: 14px !important;
}

.a11y-widget__profile-desc {
  display: block;
  margin-top: 2px;
  font-size: 12px !important;
  line-height: 1.4;
  color: #55606e !important;
  font-weight: 400;
}

/*
 * "(customised)" is rendered but hidden, and revealed only when the JS marks the
 * ACTIVE profile as tuned. The profile stays on — the user adjusted it, they did not
 * switch it off — so the label has to say both things at once.
 */
.a11y-widget__profile-custom { display: none; }

.a11y-widget__profile[data-a11y-customised] .a11y-widget__profile-custom {
  display: inline;
  font-weight: 400;
  font-style: italic;
  color: #6a5300 !important;
}

/* ---- radio groups ---- */

.a11y-widget__radios {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 4px 0 10px;
}

.a11y-widget__radio {
  min-height: 40px;
  padding: 8px 12px;
  background: #eef2f8 !important;
  color: #1a1a1a !important;
  border: 2px solid transparent !important;
  border-radius: 8px;
  font-size: 14px !important;
}

.a11y-widget__radio:hover { background: #e0e7f2 !important; }

.a11y-widget__radio[aria-checked="true"] {
  background: #1f3d7a !important;
  color: #ffffff !important;
  font-weight: 600;
}

.a11y-widget__radio[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed !important;
}

/* ---- ranges ---- */

.a11y-widget__range-row { padding: 6px 8px 12px; }

.a11y-widget__range-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 14px !important;
}

.a11y-widget__range-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #1f3d7a !important;
}

.a11y-widget__range-row input[type="range"] {
  display: block;
  width: 100%;
  height: 28px;
  margin: 0;
  accent-color: #1f3d7a;
  cursor: pointer !important;
}

/* ---- footer ---- */

.a11y-widget__footer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid rgba(0, 0, 0, 0.12);
  background: #f4f6fa !important;
  border-radius: 0 0 12px 12px;
}

.a11y-widget__footer-btn {
  min-height: 40px;
  padding: 8px 12px;
  background: #ffffff !important;
  color: #1a1a1a !important;
  border: 1px solid rgba(0, 0, 0, 0.2) !important;
  border-radius: 8px;
  font-size: 13px !important;
}

.a11y-widget__footer-btn:hover { background: #e8edf5 !important; }

.a11y-widget__statement {
  margin-left: auto;
  align-self: center;
  font-size: 13px !important;
  color: #1f3d7a !important;
  text-decoration: underline !important;
}

@media (max-width: 420px) {
  .a11y-widget__panel { width: calc(100vw - 32px); }
}

/* ======================================================= §B  CONTENT ADJUSTERS */

/*
 * Font scaling is applied inline as html{font-size:N%} by the JS: 63% of the
 * public CSS's font-sizes are em-based and nothing declares an html/body
 * font-size, so one root write scales almost everything.
 *
 * These patch rules cover the handful of TEXT rules in col_theme.css that use px
 * and so would not scale. Icon-only px rules (.topic-icons, .bookmark .fa,
 * .notifications-counter and the menu glyph) are deliberately NOT patched:
 * scaling an icon inside a fixed-size badge just overflows it.
 */
html.a11y-on .notifications-header { font-size: calc(16px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on .notifications-header small,
html.a11y-on .notifications-when { font-size: calc(13px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on .notification-overlay-item p { font-size: calc(16px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on .notification-message { font-size: calc(13px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on .notification-timestamp { font-size: calc(12px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on .top-bar-section ul,
html.a11y-on .top-bar-section ul li > a { font-size: calc(22px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on #profile-menu-small li a { font-size: calc(18px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on nav.main ul li a,
html.a11y-on .main nav ul li a { font-size: calc(16px * var(--a11y-font-scale, 1)) !important; }
html.a11y-on #top_login_buttons a { font-size: calc(16px * var(--a11y-font-scale, 1)) !important; }

/* Enlarged text must not be clipped by the legacy fixed px heights. */
html.a11y-on [data-a11y-scope] [class*="tour-desc"],
html.a11y-on [data-a11y-scope] .items .type p {
  height: auto !important;
  max-height: none !important;
  overflow: visible !important;
  white-space: normal !important;
}

/* ---- line height / letter spacing ---- */

/*
 * Scoped to text-bearing elements rather than set on the root, so authored
 * values are left alone when the user has not asked for a change (the custom
 * property is simply absent and `inherit` applies).
 */
html.a11y-on [data-a11y-scope] p,
html.a11y-on [data-a11y-scope] li,
html.a11y-on [data-a11y-scope] dd,
html.a11y-on [data-a11y-scope] dt,
html.a11y-on [data-a11y-scope] td,
html.a11y-on [data-a11y-scope] th,
html.a11y-on [data-a11y-scope] blockquote,
html.a11y-on [data-a11y-scope] label,
html.a11y-on [data-a11y-scope] h1,
html.a11y-on [data-a11y-scope] h2,
html.a11y-on [data-a11y-scope] h3,
html.a11y-on [data-a11y-scope] h4,
html.a11y-on [data-a11y-scope] h5,
html.a11y-on [data-a11y-scope] h6,
html.a11y-on [data-a11y-scope] a {
  line-height: var(--a11y-line-height, inherit);
  letter-spacing: var(--a11y-letter-spacing, inherit);
}

/* ---- readable font ---- */

/*
 * One three-line override reaches every text element, because col_theme.css:5732
 * routes body/p/h1-h6/a through these theme variables.
 */
html.a11y-readable-font {
  --main-font: "Atkinson Hyperlegible", Verdana, Tahoma, "DejaVu Sans", sans-serif;
  --second-font: "Atkinson Hyperlegible", Verdana, Tahoma, "DejaVu Sans", sans-serif;
  --signup-page-font: "Atkinson Hyperlegible", Verdana, Tahoma, "DejaVu Sans", sans-serif;
}

html.a11y-readable-font [data-a11y-scope],
html.a11y-readable-font [data-a11y-scope] p,
html.a11y-readable-font [data-a11y-scope] li,
html.a11y-readable-font [data-a11y-scope] h1,
html.a11y-readable-font [data-a11y-scope] h2,
html.a11y-readable-font [data-a11y-scope] h3,
html.a11y-readable-font [data-a11y-scope] h4,
html.a11y-readable-font [data-a11y-scope] h5,
html.a11y-readable-font [data-a11y-scope] h6,
html.a11y-readable-font [data-a11y-scope] a,
html.a11y-readable-font [data-a11y-scope] button,
html.a11y-readable-font [data-a11y-scope] input,
html.a11y-readable-font [data-a11y-scope] select,
html.a11y-readable-font [data-a11y-scope] textarea,
html.a11y-readable-font [data-a11y-scope] label,
html.a11y-readable-font [data-a11y-scope] td,
html.a11y-readable-font [data-a11y-scope] th {
  font-family: "Atkinson Hyperlegible", Verdana, Tahoma, "DejaVu Sans", sans-serif !important;
}

/* Never restyle icon fonts — it turns glyphs into tofu. */
html.a11y-readable-font .fa,
html.a11y-readable-font .fas,
html.a11y-readable-font .far,
html.a11y-readable-font .fab,
html.a11y-readable-font [class^="fa-"],
html.a11y-readable-font [class*=" fa-"] {
  font-family: "Font Awesome 5 Free", "Font Awesome 5 Brands", "FontAwesome" !important;
}

/* ---- alignment ---- */

/* Inputs, buttons and numeric cells are excluded on purpose. */
html[data-a11y-align="left"] [data-a11y-scope] :is(p, li, dd, dt, blockquote, h1, h2, h3, h4, h5, h6) {
  text-align: left !important;
}
html[data-a11y-align="center"] [data-a11y-scope] :is(p, li, dd, dt, blockquote, h1, h2, h3, h4, h5, h6) {
  text-align: center !important;
}
html[data-a11y-align="right"] [data-a11y-scope] :is(p, li, dd, dt, blockquote, h1, h2, h3, h4, h5, h6) {
  text-align: right !important;
}

/* ---- emphasis ---- */

/*
 * 11 rules in the loaded CSS set `text-decoration: none` on links, the strongest
 * at specificity (0,2,1), so !important is genuinely required here.
 */
html.a11y-emph-links [data-a11y-scope] a[href] {
  text-decoration: underline !important;
  text-underline-offset: 2px !important;
  text-decoration-thickness: 2px !important;
}

html.a11y-emph-links [data-a11y-scope] a[href]:hover,
html.a11y-emph-links [data-a11y-scope] a[href]:focus {
  outline: 2px dotted currentColor !important;
  outline-offset: 2px !important;
}

html.a11y-emph-titles [data-a11y-scope] :is(h1, h2, h3, h4, h5, h6) {
  font-weight: 800 !important;
  text-decoration: underline !important;
  text-underline-offset: 3px !important;
  outline: 1px dashed currentColor;
  outline-offset: 4px;
}

/*
 * `* { outline: none }` at col_theme.css:8 and explore.css:44 is universal-selector,
 * i.e. specificity zero, so these win without !important on that count. The
 * !important is for the vendor class-level rules (tui-calendar, jQuery UI).
 * Uses `outline`, not `box-shadow`: col_theme.css:707 sets `box-shadow: none` on
 * the Explore search input at (1,1,1) and would win that fight.
 */
html.a11y-emph-focus [data-a11y-scope] :is(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus,
html.a11y-emph-focus [data-a11y-scope] :is(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus-visible {
  outline: 4px solid #ffbf00 !important;
  outline-offset: 2px !important;
}

html.a11y-emph-hover [data-a11y-scope] :is(a[href], button, [role="button"], input[type="submit"], input[type="button"]):hover {
  outline: 3px dashed currentColor !important;
  outline-offset: 2px !important;
  text-decoration: underline !important;
}

/* ========================================================= §C  COLOUR ADJUSTERS */

/*
 * Saturation modes use a filter. It is applied to #wrap AND the body-level modal
 * nodes the reveal shim relocates — see the file header.
 */
html[data-a11y-saturation="mono"] { --a11y-filter: grayscale(100%); }
html[data-a11y-saturation="low"]  { --a11y-filter: saturate(45%); }
html[data-a11y-saturation="high"] { --a11y-filter: saturate(190%); }

html[data-a11y-saturation] [data-a11y-fx],
html[data-a11y-saturation] .reveal-modal,
html[data-a11y-saturation] #f5-reveal-shim-overlay,
html[data-a11y-saturation] #campaign_widgets {
  filter: var(--a11y-filter, none);
}

/*
 * Content scaling uses the `zoom` property, not `transform: scale`. zoom is
 * layout-affecting, so content REFLOWS — no horizontal scrollbar, no clipped text,
 * and hit-testing stays correct. transform: scale does none of that and would also
 * desynchronise the Leaflet map's coordinate maths.
 *
 * The JS hides the control entirely where CSS.supports('zoom') is false.
 */
html.a11y-on [data-a11y-fx],
html.a11y-on .reveal-modal,
html.a11y-on #f5-reveal-shim-overlay {
  zoom: var(--a11y-zoom, normal);
}

/*
 * Link colour under any contrast mode.
 *
 * Both Foundation builds are loaded from a CDN and hardcode `a { color: #1779ba }`,
 * which no amount of variable re-declaration can reach — measured on the admin
 * portal, where content links stayed mid-blue against a dark background. This routes
 * anchors through the theme's own link variable instead, and the engine DOES recolour
 * that one (see LINK_VARS), so links follow every mode on both surfaces.
 */
html[data-a11y-contrast] [data-a11y-scope] a[href],
html[data-a11y-contrast] .reveal-modal a[href],
html[data-a11y-contrast] .reveal a[href] {
  color: var(--h1-and-links-color) !important;
}

/*
 * Vendor-styled surfaces under a contrast mode.
 *
 * Foundation (CDN) and select2 set BOTH background and colour on form controls from
 * their own hardcoded values. Re-declaring theme variables recolours the inherited
 * text but cannot touch those backgrounds, which produced a genuine contrast failure
 * in dark mode: select2's white background with our near-white inherited text.
 *
 * So these surfaces are pinned to the theme's own (recoloured) variables. Values come
 * from variables rather than literals so every mode — dark, light, high — stays
 * consistent. The fallback chains cover both surfaces: the public theme declares
 * --main-bg-color, the admin palette declares --admin-white3-bg-color, and each is
 * recoloured by role.
 */
html[data-a11y-contrast] [data-a11y-scope] :is(input, select, textarea,
  .select2-selection, .select2-selection__rendered, .select2-search__field,
  .callout, .dropdown-pane) {
  background-color: var(--main-bg-color, var(--admin-white3-bg-color, #fff)) !important;
  color: var(--content-color, #111) !important;
  border-color: var(--content-color, #111) !important;
}

/*
 * Buttons get the same treatment, and this DOES flatten branded button colours while
 * a contrast mode is on. That is the intended trade: a bare <button> takes Foundation's
 * CDN background while inheriting our recoloured text, which measured 1.45:1 on
 * Explore's "Load More". Someone who has explicitly asked for a contrast mode wants
 * legibility over brand, and the flattening disappears the moment they turn it off.
 */
html[data-a11y-contrast] [data-a11y-scope] :is(button, .button, .btn,
  input[type="submit"], input[type="button"], [role="button"]) {
  background-color: var(--main-bg-color, var(--admin-white3-bg-color, #fff)) !important;
  color: var(--content-color, #111) !important;
  border: 2px solid var(--content-color, #111) !important;
}

/*
 * NOTE on admin sidebar labels: three residual near-misses live there (1.08–2.95:1 on
 * the selected menu item). Pinning a fixed foreground was tried and REVERTED — the
 * submenu anchors sit on Foundation's blue only when active, and on recoloured light
 * surfaces otherwise, so any fixed colour fails one of the two states. Fixing it
 * properly means giving those items a background we own rather than Foundation's.
 *
 * Tables. Foundation (CDN) paints `table`/`tbody` #fefefe and stripes rows #f1f1f1
 * from its own literals, so in dark mode the admin listings kept a white table under
 * our light text — measured 1.58:1 on the Organisations action links, the single
 * biggest cluster of contrast failures.
 *
 * Made TRANSPARENT rather than repainted, so the already-recoloured page ground shows
 * through and no new surface colour is invented. Zebra striping is lost while a
 * contrast mode is active; legibility wins over that affordance.
 */
html[data-a11y-contrast] [data-a11y-scope] :is(table, thead, tbody, tfoot, tr, th, td) {
  background-color: transparent !important;
  /* Foundation also hardcodes the table TEXT colour (#0a0a0a), which left
     near-black cells on the recoloured dark surface — measured 1.16:1 on the
     settings and organisations listings. `inherit` chains up to the recoloured
     page colour instead of inventing another one, and cells with their own
     colour (status ticks, danger text) still win on specificity. */
  color: inherit !important;
}

/*
 * Decorative background IMAGES on text-bearing banners are dropped under a contrast
 * mode, so the recoloured background colour actually shows through. Without this the
 * hero text is recoloured while the photo behind it is not — measured 1.11:1 on
 * Explore's "Find. Do. Achieve." band in light mode.
 */
html[data-a11y-contrast] [data-a11y-scope] :is(header, .splash, [class*="-splash"],
  [class*="hero"], [class*="banner"]) {
  background-image: none !important;
}

/* Placeholders must stay distinguishable from real input, but still legible. */
html[data-a11y-contrast] [data-a11y-scope] ::placeholder {
  color: var(--second-text-color, var(--admin-midgrey-color, #767676)) !important;
  opacity: 1;
}

/*
 * Tier B: the bounded !important sweep, for `high` contrast only.
 *
 * Needed because explore.css (86 hex), theme6.css (19 hex) and the CDN Foundation
 * stylesheet do not paint through theme variables, so the variable re-declaration
 * cannot reach them. High contrast is a REPLACEMENT of the design rather than a
 * tint of it, which is why a blanket sweep is the right answer here and nowhere
 * else. `:where()` keeps the inner list at specificity zero; the outer
 * `html[...] #wrap` carries the weight.
 */
html[data-a11y-contrast="high"] [data-a11y-scope],
html[data-a11y-contrast="high"] .reveal-modal {
  background-color: #000000 !important;
  color: #ffffff !important;
}

html[data-a11y-contrast="high"] [data-a11y-scope] :where(*:not(svg):not(svg *)),
html[data-a11y-contrast="high"] .reveal-modal :where(*:not(svg):not(svg *)) {
  background-color: transparent !important;
  background-image: none !important;
  color: #ffffff !important;
  border-color: #ffffff !important;
  box-shadow: none !important;
  text-shadow: none !important;
}

html[data-a11y-contrast="high"] [data-a11y-scope] :where(a[href], a[href] *),
html[data-a11y-contrast="high"] .reveal-modal :where(a[href], a[href] *) {
  color: #ffff00 !important;
}

html[data-a11y-contrast="high"] [data-a11y-scope] :where(button, [role="button"], input[type="submit"], .button) {
  background-color: #000000 !important;
  color: #ffff00 !important;
  border: 2px solid #ffff00 !important;
}

html[data-a11y-contrast="high"] [data-a11y-scope] :where(input, select, textarea) {
  background-color: #000000 !important;
  color: #ffffff !important;
  border: 2px solid #ffffff !important;
}

/* Keep images visible and legible against the forced black ground. */
html[data-a11y-contrast="high"] [data-a11y-scope] :where(img, video) {
  background: transparent !important;
  outline: 1px solid #ffffff;
}

/* ==================================================== §D  ORIENTATION ADJUSTERS */

/*
 * hideImages is two-tier: 44 CSS background-image declarations survive
 * `img { display: none }`. Content images are hidden with `visibility` so layout
 * does not collapse; only the decorative hero/splash backgrounds are cleared, and
 * the ~30 icon sprites are left alone because removing them removes affordances.
 *
 * Leaflet tiles are <img class="leaflet-tile"> — exempting them is what keeps the
 * Explore map from vanishing entirely.
 */
html.a11y-hide-images [data-a11y-scope] img:not(.leaflet-tile):not([class*="leaflet"]),
html.a11y-hide-images [data-a11y-scope] picture,
html.a11y-hide-images [data-a11y-scope] video,
html.a11y-hide-images [data-a11y-scope] svg:not(.a11y-widget svg) {
  visibility: hidden !important;
}

html.a11y-hide-images [data-a11y-scope] .leaflet-container img,
html.a11y-hide-images [data-a11y-scope] .leaflet-container svg {
  visibility: visible !important;
}

html.a11y-hide-images [data-a11y-scope] header,
html.a11y-hide-images [data-a11y-scope] .splash,
html.a11y-hide-images [data-a11y-scope] [class*="-splash"],
html.a11y-hide-images [data-a11y-scope] [class*="hero"] {
  background-image: none !important;
}

/*
 * stopAnimations. The loaded CSS has 0 @keyframes and 22 transitions, none with
 * !important — but the universal selector needs !important to outrank the
 * class/id-level rules it is overriding.
 *
 * The JS half (slick autoplay, Leaflet zoom animation, animated GIFs) lives in
 * accessibility_overlays.js: CSS cannot reach any of them.
 */
html.a11y-stop-animations [data-a11y-scope],
html.a11y-stop-animations [data-a11y-scope] *,
html.a11y-stop-animations [data-a11y-scope] *::before,
html.a11y-stop-animations [data-a11y-scope] *::after {
  animation: none !important;
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition: none !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}

/*
 * Large cursors. The universal selector plus !important is required to beat the
 * many `cursor: pointer` rules. Cursors are SVG data URIs so there is no extra
 * request and no image asset to keep in sync.
 */
html[data-a11y-cursor="bigBlack"] [data-a11y-scope],
html[data-a11y-cursor="bigBlack"] [data-a11y-scope] * {
  cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M6 2 L6 38 L15 29 L21 44 L28 41 L22 26 L34 26 Z" fill="%23000000" stroke="%23ffffff" stroke-width="2.5"/></svg>') 6 2, auto !important;
}

html[data-a11y-cursor="bigWhite"] [data-a11y-scope],
html[data-a11y-cursor="bigWhite"] [data-a11y-scope] * {
  cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M6 2 L6 38 L15 29 L21 44 L28 41 L22 26 L34 26 Z" fill="%23ffffff" stroke="%23000000" stroke-width="2.5"/></svg>') 6 2, auto !important;
}

/* ================================================== §E  WRAPPER-EFFECT FALLOUT */

/*
 * A `filter` (or `zoom`) on an ancestor makes that ancestor the containing block
 * for its fixed-position descendants, so they stop tracking the viewport and
 * scroll with the document instead.
 *
 * The offenders are enumerable, so they are neutralised only while a wrapper
 * effect is actually active (html.a11y-wrapper-fx, set by the JS). Everything
 * outside #wrap — the widget itself and the locale switcher — is unaffected.
 */
html.a11y-wrapper-fx #explore-search-container.sticky,
html.a11y-wrapper-fx #notification-overlay,
html.a11y-wrapper-fx [data-a11y-scope] .fixed,
html.a11y-wrapper-fx .expanded .top-bar-section ul {
  position: static !important;
  box-shadow: none !important;
}

/*
 * Admin equivalents. The sidebar is position:fixed when collapsed and sticky when
 * expanded; both resolve against #adminShell once it carries a filter, so the menu
 * would scroll away from the viewport. Made static only while an effect is active.
 */
html.a11y-wrapper-fx .admin-shell #menu-area,
html.a11y-wrapper-fx .admin-shell .sidebar-inner,
html.a11y-wrapper-fx .dcallout {
  position: static !important;
  max-height: none !important;
}

html.a11y-wrapper-fx .reveal-modal {
  position: fixed !important; /* re-assert: the modal is outside #wrap, so it is
                                 filtered independently and keeps its own viewport
                                 containing block */
}

/* ==================================================== §E2  OVERLAY CONTAINER */

/*
 * The overlay container is appended to <body> by accessibility_overlays.js — a
 * sibling of #wrap, so the zoom/filter effects never distort the reading guide,
 * mask or magnifier. Sizes and positions are set inline by the JS; only the
 * exemptions live here.
 */
#a11y-overlays,
#a11y-overlays * {
  filter: none !important;
  zoom: normal !important;
  pointer-events: none !important;
}

/* The magnifier renders text, so it must resist the site's own text adjusters. */
#a11y-magnifier {
  letter-spacing: normal !important;
  text-align: left !important;
  text-decoration: none !important;
  text-transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
  /* The guide/mask follow the pointer by design; only decorative easing goes. */
  #a11y-reading-guide,
  #a11y-reading-mask,
  #a11y-magnifier { transition: none !important; }
}

/* ---- read mode ---- */

/*
 * Opened with dialog.showModal(), so it renders in the browser's top layer and is
 * immune to the zoom/filter on #wrap. ::backdrop dims the page behind it.
 */
#a11y-read-mode::backdrop {
  background: rgba(0, 0, 0, 0.72);
}

#a11y-read-mode,
#a11y-read-mode * {
  filter: none !important;
  zoom: normal !important;
  letter-spacing: normal !important;
  text-align: left !important;
}

#a11y-read-mode :is(h1, h2, h3, h4, h5, h6) {
  font-family: inherit;
  line-height: 1.3;
  margin: 1.4em 0 0.5em;
}

#a11y-read-mode a { color: #0b4f9c; text-decoration: underline; }
#a11y-read-mode img { margin: 1em 0; }

/* ---- useful links (jump list) ---- */

/* Only shown once the "Page links and headings" switch is on — an empty list with a
   Refresh button next to its own switch is just clutter. */
.a11y-widget__links-panel { display: none; padding: 0 8px 10px; }

html.a11y-useful-links-on .a11y-widget__links-panel { display: block; }

.a11y-widget__links-head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 13px !important;
}

.a11y-widget__links {
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid rgba(0, 0, 0, 0.14);
  border-radius: 8px;
}

.a11y-widget__links:empty { display: none; }

.a11y-widget__useful-link {
  display: block;
  width: 100%;
  min-height: 36px;
  padding: 8px 10px;
  text-align: left !important;
  font-size: 13px !important;
  border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important;
}

.a11y-widget__useful-link:hover { background: #eef2f8 !important; }
.a11y-widget__useful-link.is-heading { font-weight: 700; background: #f7f9fc !important; }

/*
 * Transient highlight on a jumped-to target, so a sighted keyboard user can see
 * where they landed. Scoped to #wrap content, not the widget.
 */
.a11y-target-flash {
  outline: 4px solid #ffbf00 !important;
  outline-offset: 3px !important;
}

/* ---- colour pickers in the panel ---- */

.a11y-widget__color-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 44px;
  padding: 6px 8px;
}

.a11y-widget__color-row input[type="color"] {
  width: 44px;
  height: 32px;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, 0.25);
  border-radius: 6px;
  background: none;
  cursor: pointer !important;
}

.a11y-widget__color-row input[type="color"]:disabled {
  opacity: 0.4;
  cursor: not-allowed !important;
}

.a11y-widget__color-clear {
  min-height: 32px;
  padding: 4px 10px;
  background: #ffffff !important;
  border: 1px solid rgba(0, 0, 0, 0.2) !important;
  border-radius: 6px;
  font-size: 12px !important;
}

/* Monochrome neutralises any picked colour, so say so rather than looking broken. */
html[data-a11y-saturation="mono"] .a11y-widget__color-row {
  opacity: 0.55;
}

/* ============================================================ §F  PRINT + MOTION */

@media print {
  /* Filters and scaling print unreliably; the widget itself must never print. */
  [data-a11y-scope],
  .reveal-modal,
  #f5-reveal-shim-overlay {
    filter: none !important;
    zoom: normal !important;
  }

  .a11y-widget,
  .a11y-skip-link,
  #a11y-overlays { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .a11y-widget__switch-track,
  .a11y-widget__switch-track::after { transition: none !important; }
}
