/* =============================================================================
   controls.css — the form controls, drawn by us rather than by the browser.

   Why this file exists
   --------------------
   Every checkbox, radio button and dropdown in this app was, until now, whatever
   the operating system felt like drawing. On a dark page that means a Windows
   checkbox rendering as a white box with a blue tick, a native select with a
   light-grey chrome dropdown, and a scrollbar from 2009 — three different design
   languages sitting inside a surface that had been carefully tokenised. It is
   the single loudest remaining source of "this looks like a web form from
   fifteen years ago".

   How this is done, and what it costs
   -----------------------------------
   Everything here styles the **native element**. There is no wrapper div, no
   `<span class="checkmark">`, no JavaScript, and no template was changed to make
   any of it work. That constraint is not laziness — it is what keeps the
   controls correct:

     * the label/`for` association, and clicking a label to toggle, still work;
     * keyboard interaction (Space, arrow keys, type-ahead in a select) is the
       browser's, and is therefore right in every locale and on every platform;
     * screen readers see a checkbox, because it *is* a checkbox;
     * form submission, `required`, validation and autofill are untouched.

   The usual "custom checkbox" recipe — hide the input, draw a div, wire it up —
   throws all of that away and then reimplements about a third of it. This does
   not.

   The one real constraint it imposes: `::before` and `::after` are not reliably
   rendered on `<input>` elements across engines (Firefox in particular). So the
   marks here — the tick, the radio dot, the select chevron — are drawn with
   `background-image` and animated with `background-size`, which works
   everywhere. That is why you will not find a stroke-draw animation below; a
   tick that renders in Chrome and vanishes in Firefox is not a nicer tick.

   Load order
   ----------
   base.html loads this *after* each page's own stylesheet, so at equal
   specificity it has the last word. Page-specific rules that are deliberately
   more specific (a select squeezed into a table row, say) still win, which is
   what you want.
   ============================================================================= */


/* =============================================================================
   1. SHARED
   ============================================================================= */

/* The mark colours are declared once here rather than inline in each data URI,
   because an SVG in a `url()` cannot read a custom property. Changing the tick
   colour means changing it in one place — these two — not in six data URIs. */
:root {
  /* A tick, as a standalone SVG. White, because it is only ever drawn on the
     filled accent box. */
  --mf-mark-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.8'/%3E%3C/svg%3E");
  /* The bar for the indeterminate state — a third state that `:indeterminate`
     can reach and that a tick would misrepresent as "yes". */
  --mf-mark-dash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E");
  /* The dropdown chevron and the search field's cancel cross. Grey rather than
     currentColor — see above. Each needs a per-palette twin, because an SVG behind
     url() cannot read a custom property and cannot inherit a colour either. */
  --mf-mark-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2398a1b3' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5L8 10.5L12 6.5'/%3E%3C/svg%3E");
  --mf-mark-clear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2398a1b3' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l8 8M12 4l-8 8'/%3E%3C/svg%3E");
}

/* The light twins. Two selectors for the same reason tokens.css needs two — an
   explicit choice and "whatever the OS says" cannot share a rule, because a media
   query is not part of a selector. Getting this wrong is not subtle: it is a
   mid-grey glyph on a white field, which is legible, so it survives review and
   then looks slightly wrong forever. */
[data-theme="light"] {
  --mf-mark-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234a5265' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5L8 10.5L12 6.5'/%3E%3C/svg%3E");
  --mf-mark-clear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234a5265' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l8 8M12 4l-8 8'/%3E%3C/svg%3E");
}

@media (prefers-color-scheme: light) {
  [data-theme="system"] {
    --mf-mark-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234a5265' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5L8 10.5L12 6.5'/%3E%3C/svg%3E");
    --mf-mark-clear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234a5265' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l8 8M12 4l-8 8'/%3E%3C/svg%3E");
  }
}


/* =============================================================================
   2. CHECKBOX

   An 18px square that fills with the accent and pops a tick in. The "pop" is a
   background-size transition on an overshoot curve — the tick scales past its
   final size and settles back, which is what makes a toggle feel mechanical
   rather than like a repaint. It is 160ms; anything longer and ticking six
   boxes in a row feels like waiting.
   ============================================================================= */

input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin: 0;
  /* Sits on the text baseline of the label next to it rather than on the line
     box, which is what stops a checkbox from riding high beside its own text. */
  vertical-align: -3px;
  background-color: var(--mf-bg-sunken);
  background-image: var(--mf-mark-check);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0 0;
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-xs);
  cursor: pointer;
  transition: background-color var(--mf-dur-fast) var(--mf-ease),
              border-color var(--mf-dur-fast) var(--mf-ease),
              background-size 160ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

input[type="checkbox"]:hover:not(:disabled) {
  border-color: var(--mf-accent);
  background-color: var(--mf-accent-wash);
}

input[type="checkbox"]:checked {
  background-color: var(--mf-accent);
  border-color: var(--mf-accent);
  background-size: 100% 100%;
}

input[type="checkbox"]:checked:hover:not(:disabled) {
  background-color: var(--mf-accent-hover);
  border-color: var(--mf-accent-hover);
}

/* `:indeterminate` is only ever set from script, but it is a state the platform
   has and styling it costs one rule. Unstyled it renders as an empty box, which
   reads as "no" — the one thing indeterminate does not mean. */
input[type="checkbox"]:indeterminate {
  background-color: var(--mf-accent);
  border-color: var(--mf-accent);
  background-image: var(--mf-mark-dash);
  background-size: 100% 100%;
}

/* A press cue. Native checkboxes have one and losing it makes a custom control
   feel like it did not register the click. */
input[type="checkbox"]:active:not(:disabled) {
  transform: scale(0.92);
  transition-duration: 60ms;
}

input[type="checkbox"]:focus-visible {
  outline: none;
  box-shadow: var(--mf-focus-ring);
}

input[type="checkbox"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The toggle switch in dashboard.css deliberately hides its own input and draws
   a track and a thumb instead. That rule is `.toggle-switch input` — specificity
   (0,1,1), exactly the same as `input[type="checkbox"]` above — so with this
   file loading later, the block above would win the tie and put an 18px checkbox
   back on screen next to every switch in the settings pages.
   Re-hidden explicitly, at a specificity nothing here can beat. */
.toggle-switch input[type="checkbox"] {
  position: absolute;
  width: 0;
  height: 0;
  margin: 0;
  opacity: 0;
  border: 0;
  background: none;
  transform: none;
}


/* =============================================================================
   3. RADIO

   Same construction, round, and the dot is a radial-gradient rather than an SVG
   so there is no second asset to keep in sync with the palette.
   ============================================================================= */

input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin: 0;
  vertical-align: -3px;
  background-color: var(--mf-bg-sunken);
  background-image: radial-gradient(circle, var(--mf-fg-on-brand) 100%, transparent 0);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0 0;
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-full);
  cursor: pointer;
  transition: background-color var(--mf-dur-fast) var(--mf-ease),
              border-color var(--mf-dur-fast) var(--mf-ease),
              background-size 160ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

input[type="radio"]:hover:not(:disabled) {
  border-color: var(--mf-accent);
  background-color: var(--mf-accent-wash);
}

input[type="radio"]:checked {
  background-color: var(--mf-accent);
  border-color: var(--mf-accent);
  background-size: 7px 7px;
}

input[type="radio"]:active:not(:disabled) {
  transform: scale(0.92);
  transition-duration: 60ms;
}

input[type="radio"]:focus-visible {
  outline: none;
  box-shadow: var(--mf-focus-ring);
}

input[type="radio"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}


/* =============================================================================
   4. SELECT

   `appearance: none` removes the platform chrome — including, importantly, the
   native arrow, which on Windows is a grey wedge in a grey well that no amount
   of surrounding styling can make look deliberate.

   What this does NOT restyle is the open dropdown list itself. That popup is
   drawn by the OS, outside the page, and CSS cannot reach it. Anyone claiming
   otherwise is building a div that impersonates a select and losing type-ahead,
   Escape, and mobile's native picker in the process. The closed control is ours;
   the open list stays the platform's, and that is the right trade.

   `:not([multiple])` keeps this off list boxes, which have no arrow to draw and
   would end up with a chevron floating in the top-right corner. It also raises
   the specificity to (0,1,1), matching `.form-group select` — and because this
   file loads later, the padding below wins the tie, which is what stops label
   text running underneath the chevron.
   ============================================================================= */

/* --- The shell, at type specificity on purpose ---------------------------
   (0,0,1), which loses to every page rule that styles a select. That is the
   point: `.channel-select` and `.role-table select` deliberately run at
   `--mf-control-height-sm` because they sit inside table rows, and a generic
   rule that outranked them would inflate every row in the app.

   So this only reaches the selects nobody styled — and there were more of those
   than anyone would guess. The audit-findings and telemetry filter bars style
   `input[type="text"]` and stop, so their four dropdowns were still rendering as
   native OS controls: a pale box with a system chevron, sitting next to a search
   field that had been carefully themed. That is the mismatch this fixes.

   Height comes from vertical padding, and there is deliberately no `min-height`
   here. `min-height` beats `height` no matter which rule set it, so a floor of
   --mf-control-height would have quietly overruled the two compact selects above
   and inflated every row in the roles and channels tables — the one thing this
   rule's low specificity exists to prevent. Padding loses to their `padding: 0`
   the way it should, and on a select nobody styled it adds up to the same 38px:
   ~20px line box + 2x8px + 2x1px border. */
select {
  padding: var(--mf-space-2) var(--mf-space-3);
  background-color: var(--mf-bg-sunken);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-sm);
  color: var(--mf-fg);
  font-family: inherit;
  font-size: var(--mf-text-md);
}

/* --- The chevron and the chrome removal, at (0,1,1) -----------------------
   These have to win everywhere, including over the page rules the block above
   deliberately loses to, because they are what removes the native arrow — and a
   select with the OS chrome stripped and no arrow drawn back on is worse than an
   untouched one.

   `padding-right` is in this rule and not the one above for exactly that reason:
   the page rules set `padding: 0 var(--mf-space-2)` as a shorthand, and without
   overriding the right side the longest option would run underneath the chevron.

   `:not([multiple])` keeps all of this off list boxes, which have no arrow to
   draw and would get one floating in the corner. It also raises the specificity
   to (0,1,1) — matching `.form-group select` and `.catalog-bar select` — and
   since this file loads last, the tie falls this way. */
select:not([multiple]) {
  appearance: none;
  -webkit-appearance: none;
  background-image: var(--mf-mark-chevron);
  background-repeat: no-repeat;
  background-position: right var(--mf-space-3) center;
  background-size: 16px 16px;
  /* 32px: the chevron occupies 12-28px in from the right edge, so this leaves a
     4px gap. --mf-space-7 (48px) would be comfortable on a full-width field and
     eat a third of the 160px selects in the roles table. */
  padding-right: var(--mf-space-6);
  cursor: pointer;
  transition: border-color var(--mf-dur-fast) var(--mf-ease),
              box-shadow var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
}

select:not([multiple]):disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* The options in the popup. Firefox and Windows Chrome honour these; macOS does
   not. Set anyway: where it lands, a light-grey list stops flashing out of a
   dark page, and where it does not, nothing is worse than before. */
select option {
  background-color: var(--mf-surface);
  color: var(--mf-fg);
}

select optgroup {
  background-color: var(--mf-surface);
  color: var(--mf-fg-subtle);
  font-style: normal;
  font-weight: var(--mf-weight-semibold);
}


/* =============================================================================
   5. TEXT FIELDS

   A baseline for every text-ish input in the app, including the ones sitting in
   templates that never got a `.form-group` around them — which is most of the
   telemetry filter bars. Element-level specificity, so anything more specific
   still overrides it.
   ============================================================================= */

/* --- Size, at zero specificity ---------------------------------------------
   `:where()` contributes nothing to specificity, so this whole rule sits at
   (0,0,0) and loses to literally any page rule. That is the point.

   The problem it fixes: a field only got a height if some page rule gave it one.
   `.form-group input` does, `.catalog-search input` does — but the account page
   writes `<label>Current password <input type="password"></label>` with no
   wrapper class at all, so its three password fields fell through every rule in
   the app and rendered as bare ~20px slivers. Same for any other field written
   that way.

   Height comes from padding rather than `height` or `min-height`, and that is
   not a stylistic choice. `min-height` beats `height` no matter which rule set
   it or how specific that rule was, so a floor here would silently overrule
   every compact field in the app — the same trap the select shell above spells
   out. Padding loses to `padding: 0 …` the way it should, and where nothing
   overrides it, 19px of line box plus 2x8px plus the border lands on the same
   38px as --mf-control-height. */
:where(input[type="text"],
       input[type="password"],
       input[type="email"],
       input[type="url"],
       input[type="search"],
       input[type="number"],
       input[type="date"],
       input[type="datetime-local"],
       input[type="time"]) {
  padding: var(--mf-space-2) var(--mf-space-3);
  line-height: var(--mf-leading-snug);
}

/* A field written as `<label>Text <input></label>` is an inline flow: the label's
   own text sits on a line and the input follows it. Making the label a column is
   what turns that into a stacked field-with-caption, and it is additive — a label
   that already had layout from a page rule keeps it. */
:where(.form-grid, .settings-form, .card) label:has(> input:not([type="checkbox"]):not([type="radio"])),
:where(.form-grid, .settings-form, .card) label:has(> textarea),
:where(.form-grid, .settings-form, .card) label:has(> select) {
  display: flex;
  flex-direction: column;
  gap: var(--mf-space-2);
}

input[type="text"],
input[type="password"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
textarea {
  background-color: var(--mf-bg-sunken);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-sm);
  color: var(--mf-fg);
  font-family: inherit;
  font-size: var(--mf-text-md);
  transition: border-color var(--mf-dur-fast) var(--mf-ease),
              box-shadow var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
}

input[type="text"]::placeholder,
input[type="password"]::placeholder,
input[type="email"]::placeholder,
input[type="url"]::placeholder,
input[type="search"]::placeholder,
input[type="number"]::placeholder,
textarea::placeholder {
  color: var(--mf-fg-subtle);
}

input[type="text"]:hover:not(:disabled),
input[type="password"]:hover:not(:disabled),
input[type="email"]:hover:not(:disabled),
input[type="url"]:hover:not(:disabled),
input[type="search"]:hover:not(:disabled),
input[type="number"]:hover:not(:disabled),
select:not([multiple]):hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--mf-border-strong);
}

/* This block used to be written with `:focus` and a comment explaining that a text field
   is the one control where a mouse click and a Tab mean the same thing — the caret is in
   it either way, and the ring is what tells you where your typing is going.

   That reasoning was right and the selector was still wrong, because :focus-visible
   already agrees with it. The spec says an element that expects keyboard input always
   matches :focus-visible when focused, regardless of how it was focused, so a clicked text
   field keeps its ring here exactly as before. Nothing renders differently; what changes
   is that this file no longer carries the one bare :focus that docs/DESIGN.md forbids and
   tests/test_responsive.py now fails on — and the next person does not read it as
   permission to use :focus on a button. */
input[type="text"]:focus-visible,
input[type="password"]:focus-visible,
input[type="email"]:focus-visible,
input[type="url"]:focus-visible,
input[type="search"]:focus-visible,
input[type="number"]:focus-visible,
input[type="date"]:focus-visible,
select:not([multiple]):focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--mf-accent);
  box-shadow: 0 0 0 3px var(--mf-accent-wash);
  background-color: var(--mf-bg-sunken);
}

textarea {
  /* Only vertically. A textarea dragged wider than its container is a layout
     bug the user performed on themselves. */
  resize: vertical;
  min-height: 84px;
  padding: var(--mf-space-3);
  line-height: var(--mf-leading-normal);
}

/* Chrome's autofill paints its own near-white background and there is no
   property to change it — the only lever is the inset shadow trick, which
   repaints the field from the inside. Without this, saving a password turns the
   login form into two white bars on a black card. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus-visible {
  /* The three selectors carry identical declarations; the :hover and :focus-visible ones
     exist only to keep the specificity above whatever else may style a focused field, so
     swapping :focus for :focus-visible here changes nothing that renders. An autofilled
     field is always a text field, and a text field always matches :focus-visible. */
  -webkit-text-fill-color: var(--mf-fg);
  -webkit-box-shadow: 0 0 0 1000px var(--mf-bg-sunken) inset;
  caret-color: var(--mf-fg);
  transition: background-color 9999s ease-out;
}

/* The spinner on a number field is a pair of arrows in OS chrome. Kept, but
   only shown on hover/focus so it stops shouting on a page full of fields. */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  opacity: 0;
  transition: opacity var(--mf-dur-fast) var(--mf-ease);
}

input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:focus::-webkit-inner-spin-button {
  opacity: 1;
}

/* Safari draws a rounded "search field" with its own cancel button. Normalise
   it so a search box is the same shape as every other field. */
input[type="search"] {
  -webkit-appearance: textfield;
}

input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  cursor: pointer;
  background-image: var(--mf-mark-clear);
  background-size: contain;
  opacity: 0.6;
}

input[type="search"]::-webkit-search-cancel-button:hover {
  opacity: 1;
}

/* The calendar picker icon is near-black on a dark field — invisible, and it
   exposes no colour property, so `invert()` is the only handle CSS has on it.
   The *amount* is a token rather than a second selector: a palette-specific
   override here would have had to be written three times (explicit light, system
   light, and the dark default), and the third one is the one people forget. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
  filter: invert(var(--mf-picker-invert));
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--mf-dur-fast) var(--mf-ease);
}

input[type="date"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}


/* =============================================================================
   6. FILE INPUT

   `::file-selector-button` restyles the button without touching the control, so
   the file name, the picker and the drop target all stay the browser's. The
   alternative — hiding the input behind a styled label — breaks drag-and-drop
   onto the field in several browsers, and this app uploads module packages.
   ============================================================================= */

input[type="file"] {
  width: 100%;
  padding: var(--mf-space-2);
  background-color: var(--mf-bg-sunken);
  border: 1px dashed var(--mf-border-strong);
  border-radius: var(--mf-radius-sm);
  color: var(--mf-fg-muted);
  font-family: inherit;
  font-size: var(--mf-text-sm);
  cursor: pointer;
  transition: border-color var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
}

input[type="file"]:hover:not(:disabled) {
  border-color: var(--mf-accent);
  background-color: var(--mf-accent-wash);
}

input[type="file"]:focus-visible {
  outline: none;
  box-shadow: var(--mf-focus-ring);
}

input[type="file"]::file-selector-button {
  margin-right: var(--mf-space-3);
  padding: 0 var(--mf-space-4);
  height: var(--mf-control-height-sm);
  background-color: var(--mf-surface-raised);
  border: 1px solid var(--mf-border-strong);
  border-radius: var(--mf-radius-xs);
  color: var(--mf-fg);
  font-family: inherit;
  font-size: var(--mf-text-sm);
  font-weight: var(--mf-weight-medium);
  cursor: pointer;
  transition: background-color var(--mf-dur-fast) var(--mf-ease),
              border-color var(--mf-dur-fast) var(--mf-ease);
}

input[type="file"]::file-selector-button:hover {
  background-color: var(--mf-surface-hover);
  border-color: var(--mf-accent);
}


/* =============================================================================
   7. RANGE

   Not used in the app today. Included because an unstyled range is the single
   most out-of-place native control there is, and the moment somebody adds a
   retention slider to the telemetry settings it will already look right. Every
   engine needs its own thumb and track selectors, and they cannot be combined
   into one rule list — an unrecognised pseudo-element invalidates the whole
   selector, so a grouped rule silently applies to nothing.
   ============================================================================= */

input[type="range"] {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 18px;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--mf-surface-active);
  border-radius: var(--mf-radius-full);
}

input[type="range"]::-moz-range-track {
  height: 4px;
  background: var(--mf-surface-active);
  border-radius: var(--mf-radius-full);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -6px;
  background: var(--mf-accent);
  border: 2px solid var(--mf-surface);
  border-radius: var(--mf-radius-full);
  box-shadow: var(--mf-shadow-sm);
  transition: transform var(--mf-dur-fast) var(--mf-ease);
}

input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  background: var(--mf-accent);
  border: 2px solid var(--mf-surface);
  border-radius: var(--mf-radius-full);
  box-shadow: var(--mf-shadow-sm);
  transition: transform var(--mf-dur-fast) var(--mf-ease);
}

input[type="range"]:hover::-webkit-slider-thumb {
  transform: scale(1.15);
}

input[type="range"]:hover::-moz-range-thumb {
  transform: scale(1.15);
}

input[type="range"]:focus-visible {
  outline: none;
}

input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: var(--mf-focus-ring);
}

input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: var(--mf-focus-ring);
}


/* =============================================================================
   8. TOGGLE SWITCH

   The app already has one, built from real markup (`.toggle-switch` wrapping an
   input plus a track and a thumb) in dashboard.css. It is not rebuilt here —
   this only adds the physical cues a switch needs to read as a switch rather
   than as a rounded checkbox: the thumb squashes along its travel when you press
   it, and springs into place when you let go.

   Scoped to `.toggle-switch` so it cannot reach any other control.
   ============================================================================= */

.toggle-switch .toggle-switch-thumb {
  transition: transform var(--mf-dur-normal) cubic-bezier(0.34, 1.4, 0.64, 1),
              width var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-normal) var(--mf-ease);
}

/* Held down: the thumb stretches towards where it is about to go. Two pixels,
   and nobody will consciously notice it — that is the point. It is the
   difference between a control that responds and one that just updates.

   20px and not more: the track is 44px with a 1px border each side, the thumb
   sits at left: 2px and travels 20px when checked, so 2 + 20 + 20 = 42 is
   exactly the inner width. A wider squash would clip against the far edge in
   the "on" position. */
.toggle-switch:active .toggle-switch-thumb {
  width: 20px;
}

.toggle-switch .toggle-switch-track {
  transition: background-color var(--mf-dur-normal) var(--mf-ease),
              border-color var(--mf-dur-normal) var(--mf-ease),
              box-shadow var(--mf-dur-fast) var(--mf-ease);
}

.toggle-switch:hover .toggle-switch-track {
  border-color: var(--mf-border-strong);
}


/* =============================================================================
   9. GROUPED CONTROL LAYOUT

   Additive helpers for the shapes that already exist in the templates. A
   checkbox followed by its label wants to be a flex row with the box on the text
   baseline; before this it was an inline-block with whatever gap the whitespace
   in the HTML happened to produce.

   `label:has(> input[type="checkbox"])` upgrades the existing markup with no
   template change. `:has()` is unsupported in older engines — where it does not
   apply, the layout is exactly what it is today, so this can only improve.
   ============================================================================= */

label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]),
.toggle-row label,
.auth-checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--mf-space-3);
  cursor: pointer;
  /* A label is a click target for the control inside it; dragging across it
     should not select the text and leave a highlight behind. */
  user-select: none;
}

label:has(> input[type="checkbox"]:disabled),
label:has(> input[type="radio"]:disabled) {
  cursor: not-allowed;
  color: var(--mf-fg-subtle);
}


/* =============================================================================
   10. THEME TOGGLE

   Three segments in one track: System, Light, Dark. It lives here rather than in
   dashboard.css or landing.css because it is drawn in the public nav, in the
   dashboard topbar and in the auth footer — three stylesheets that are never all
   loaded at once, so any one of them would have been the wrong home and all three
   would have been three copies.

   The buttons are real submit buttons (see templates/_theme_toggle.html), which
   means the browser gives them a default appearance, a default font and a default
   focus behaviour, all of which have to be undone before the segment reads as a
   segment rather than as three little OS buttons in a row.
   ============================================================================= */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  background: var(--mf-bg-sunken);
  border: 1px solid var(--mf-border);
  border-radius: var(--mf-radius-full);
  flex-shrink: 0;
}

.theme-opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 30px, not 24px. This is a tap target on a phone and it sits next to two
     others; below about 30px the wrong one gets hit often enough to notice. */
  width: var(--mf-control-height-sm);
  height: var(--mf-control-height-sm);
  padding: 0;
  border: none;
  border-radius: var(--mf-radius-full);
  background: transparent;
  color: var(--mf-fg-subtle);
  cursor: pointer;
  font-family: inherit;
  transition: color var(--mf-dur-fast) var(--mf-ease),
              background-color var(--mf-dur-fast) var(--mf-ease);
}

.theme-opt svg {
  width: 15px;
  height: 15px;
  display: block;
}

.theme-opt:hover {
  color: var(--mf-fg);
  background: var(--mf-surface-hover);
}

/* The selected segment. Both a fill *and* a colour change, deliberately: a fill
   alone is invisible to anyone who cannot distinguish it from the track, and this
   control has no text label to fall back on. `aria-pressed` carries the same fact
   for a screen reader. */
.theme-opt.is-on {
  background: var(--mf-surface-active);
  color: var(--mf-accent-text);
}

/* Restated because the ring is a box-shadow and the segment sits inside a 2px
   track — without a radius of its own the ring squares off against the pill. */
.theme-opt:focus-visible {
  box-shadow: var(--mf-focus-ring);
  border-radius: var(--mf-radius-full);
}


/* =============================================================================
   11. REDUCED MOTION

   tokens.css already collapses every transition globally when the OS asks for
   reduced motion. The two rules here go further and remove the *displacement*,
   not just its duration: a control that still jumps 3px, instantly, is worse for
   someone with a vestibular disorder than one that never moves.
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
  input[type="checkbox"]:active:not(:disabled),
  input[type="radio"]:active:not(:disabled) {
    transform: none;
  }

  .toggle-switch:active .toggle-switch-thumb {
    width: 18px;
  }
}
