/* =========================================================
   ControlIO — Control Primitives
   Handle: wb-controls · depends on wb-tokens · Step 3 Pass B

   Buttons, links, form fields, checkbox/radio.

   Replaces 31 competing button selectors across 12 prefix
   families. A rendered audit of four pages found 14 distinct
   visual button treatments, with heights of 22/31/35/38/40/
   42/45/48px and radii of 0/8/12/999/9999px.

   Rules:
   - Consumes tokens only. Declares no design values of its own.
   - Every interactive control is at least --touch-target-min
     (44px) on its smallest axis unless explicitly --sm.
   - Every control has default/hover/focus-visible/active/
     disabled/loading. Loading preserves dimensions.
   ========================================================= */

/* ---------------------------------------------------------
   BUTTON
   --------------------------------------------------------- */

.wbc-btn{
  --_bg: var(--surface);
  --_fg: var(--text);
  --_bd: var(--border);
  --_bg-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);

  appearance: none;
  -webkit-appearance: none;
  position: relative;              /* loading spinner anchor */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  box-sizing: border-box;

  min-height: var(--control-height-md);
  padding: .6rem 1rem;

  border: 1px solid var(--_bd);
  border-radius: var(--radius-pill);
  background: var(--_bg);
  color: var(--_fg);
  box-shadow: var(--shadow);

  font: inherit;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;

  transition:
    background-color var(--duration-fast) var(--ease-standard),
    border-color     var(--duration-fast) var(--ease-standard),
    color            var(--duration-fast) var(--ease-standard),
    box-shadow       var(--duration-fast) var(--ease-standard),
    transform        var(--duration-fast) var(--ease-standard);
}

.wbc-btn:hover{ background: var(--_bg-hover); }
.wbc-btn:active{ transform: translateY(1px); }

/* One focus treatment for every control on the site. Not :focus —
   :focus-visible keeps the ring off mouse clicks without removing it
   from keyboard users, which is what `outline:none` usually breaks. */
.wbc-btn:focus-visible{
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}

.wbc-btn:disabled,
.wbc-btn[aria-disabled="true"],
.wbc-btn.is-disabled{
  opacity: .55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* ----- Variants ----- */

.wbc-btn--primary{
  --_bg: var(--accent-strong);
  --_fg: var(--on-accent);
  --_bd: transparent;
  --_bg-hover: var(--accent-3);
}

.wbc-btn--secondary{
  --_bg: transparent;
  --_fg: var(--accent-strong);
  --_bd: var(--accent-strong);
  --_bg-hover: color-mix(in srgb, var(--accent) 10%, transparent);
}

.wbc-btn--ghost{
  --_bg: transparent;
  --_fg: var(--text);
  --_bd: var(--border);
  --_bg-hover: color-mix(in srgb, var(--text) 6%, transparent);
  box-shadow: none;
}

.wbc-btn--danger{
  --_bg: var(--status-error);
  --_fg: #fff;
  --_bd: transparent;
  --_bg-hover: color-mix(in srgb, var(--status-error) 85%, #000 15%);
}

/* Text button — no chrome. Still meets the touch target. */
.wbc-btn--text{
  --_bg: transparent;
  --_fg: var(--accent-strong);
  --_bd: transparent;
  --_bg-hover: color-mix(in srgb, var(--accent) 10%, transparent);
  box-shadow: none;
  padding-inline: var(--space-2);
}

/* ----- Sizes ----- */

.wbc-btn--sm{
  min-height: var(--control-height-sm);
  padding: .35rem .75rem;
  font-size: var(--text-sm);
}

.wbc-btn--lg{
  min-height: var(--control-height-lg);
  padding: .85rem 1.5rem;
  font-size: var(--text-lg);
}

/* Full-width, for card actions and mobile CTAs */
.wbc-btn--block{ width: 100%; }

/* ----- Icon-only button -----
   Square, and never below the touch target. An icon-only control
   MUST carry an accessible name; see .wbc-btn--icon:not([aria-label])
   below, which makes a missing one visible in development. */
.wbc-btn--icon{
  min-width: var(--control-height-md);
  min-height: var(--control-height-md);
  padding: 0;
  border-radius: var(--radius-md);
  gap: 0;
}
.wbc-btn--icon.wbc-btn--sm{
  min-width: var(--control-height-sm);
  min-height: var(--control-height-sm);
}
.wbc-btn--icon > svg{ width: 1.25em; height: 1.25em; flex: none; }

/* ----- Loading -----
   Dimensions must not change (brief 3.16). The label is hidden with
   visibility rather than display, so the button keeps its width and
   nothing around it shifts. */
.wbc-btn[aria-busy="true"],
.wbc-btn.is-loading{
  cursor: progress;
  pointer-events: none;
}
.wbc-btn[aria-busy="true"] > *,
.wbc-btn.is-loading > *{ visibility: hidden; }

.wbc-btn[aria-busy="true"]::after,
.wbc-btn.is-loading::after{
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 1.1em;
  height: 1.1em;
  margin: -0.55em 0 0 -0.55em;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: wbc-spin var(--duration-slow) linear infinite;
}

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

/* With reduced motion the duration tokens collapse to 1ms, which would
   make the spinner strobe. Replace spin with a static pulse instead. */
@media (prefers-reduced-motion: reduce){
  .wbc-btn[aria-busy="true"]::after,
  .wbc-btn.is-loading::after{
    animation: none;
    border-top-color: color-mix(in srgb, currentColor 30%, transparent);
    opacity: .6;
  }
}

/* ---------------------------------------------------------
   LINKS
   --------------------------------------------------------- */

.wbc-link{
  color: var(--accent-strong);
  text-decoration: underline;
  text-underline-offset: .15em;
  text-decoration-thickness: max(1px, .06em);
  border-radius: var(--radius-xs);
  transition: color var(--duration-fast) var(--ease-standard);
}
.wbc-link:hover{ color: var(--accent-3); }
.wbc-link:focus-visible{
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}

/* Navigation and card links carry meaning from position, not underline. */
.wbc-link--nav,
.wbc-link--card{ text-decoration: none; color: inherit; }
.wbc-link--nav:hover,
.wbc-link--card:hover{ color: var(--accent-strong); }

.wbc-link--utility{
  color: var(--muted);
  text-decoration: none;
  font-size: var(--text-sm);
}
.wbc-link--utility:hover{ color: var(--text); text-decoration: underline; }

.wbc-link--danger{ color: var(--status-error); }
.wbc-link--danger:hover{ color: color-mix(in srgb, var(--status-error) 80%, #000 20%); }

/* Standalone action links — nav, utility, destructive — are controls, not prose,
   so on a coarse pointer they need a real target. Measured at 21–24px tall
   before this. The base .wbc-link is deliberately excluded: it appears inside
   running text, where forcing a 44px line box would wreck the paragraph. */
@media (pointer: coarse){
  .wbc-link--nav,
  .wbc-link--utility,
  .wbc-link--danger{
    display: inline-flex;
    align-items: center;
    min-height: var(--touch-target-min);
  }
}

/* ---------------------------------------------------------
   FORM FIELDS
   --------------------------------------------------------- */

.wbc-field{
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

/* Labels are always visible. Placeholder-only forms fail as soon as the
   field has content, and fail permanently for screen readers (brief 3.20). */
.wbc-label{
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  line-height: var(--leading-snug);
}
.wbc-label__optional{ font-weight: 400; color: var(--muted); }

.wbc-input,
.wbc-textarea,
.wbc-select{
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  width: 100%;
  min-height: var(--control-height-md);
  padding: .55rem .75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  line-height: var(--leading-snug);
  transition:
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow   var(--duration-fast) var(--ease-standard);
}

/* 16px minimum on touch: iOS Safari zooms the viewport on focus for
   anything smaller, which is a layout break, not a style preference. */
@media (pointer: coarse){
  .wbc-input,
  .wbc-textarea,
  .wbc-select{ font-size: max(16px, var(--text-base)); }
}

.wbc-textarea{ min-height: calc(var(--control-height-lg) * 2); resize: vertical; }

/* Native select is kept deliberately (brief 3.23) — it gives the best
   mobile and assistive-technology behaviour. Only the chevron is ours. */
.wbc-select{
  padding-right: 2.25rem;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 1.05rem center, right .8rem center;
  background-size: .35rem .35rem, .35rem .35rem;
  background-repeat: no-repeat;
}

.wbc-input::placeholder,
.wbc-textarea::placeholder{ color: var(--muted); opacity: 1; }

.wbc-input:focus-visible,
.wbc-textarea:focus-visible,
.wbc-select:focus-visible{
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
  border-color: var(--accent);
}

.wbc-input:disabled,
.wbc-textarea:disabled,
.wbc-select:disabled{
  background: var(--surface-muted);
  color: var(--muted);
  cursor: not-allowed;
}

/* ----- Validation -----
   Colour is never the only signal: the border changes, an icon-bearing
   message appears, and aria-invalid carries the state programmatically. */
.wbc-input[aria-invalid="true"],
.wbc-textarea[aria-invalid="true"],
.wbc-select[aria-invalid="true"]{ border-color: var(--status-error); }

.wbc-field--success .wbc-input{ border-color: var(--status-success); }
.wbc-field--warning .wbc-input{ border-color: var(--status-warning); }

.wbc-help{ font-size: var(--text-sm); color: var(--muted); }

.wbc-error{
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--status-error);
}
.wbc-error::before{ content: "!"; flex: none; font-weight: 800; }

/* ----- Search field primitive (brief 3.24) ----- */
.wbc-search{
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}
.wbc-search__input{ padding-left: 2.5rem; padding-right: 2.5rem; }
.wbc-search__icon{
  position: absolute;
  left: .8rem;
  display: flex;
  pointer-events: none;
  color: var(--muted);
}
.wbc-search__clear{
  position: absolute;
  right: .35rem;
  min-width: var(--control-height-sm);
  min-height: var(--control-height-sm);
}
.wbc-search__clear[hidden]{ display: none; }

/* ---------------------------------------------------------
   CHECKBOX / RADIO
   The control itself may be small, but the LABEL is the hit
   area and that is what has to meet the touch target.
   --------------------------------------------------------- */

.wbc-choice{
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-min);
  cursor: pointer;
}
.wbc-choice input[type="checkbox"],
.wbc-choice input[type="radio"]{
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--accent);
  cursor: pointer;
}
.wbc-choice input:focus-visible{
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}
.wbc-choice input:disabled,
.wbc-choice:has(input:disabled){ opacity: .55; cursor: not-allowed; }

/* ---------------------------------------------------------
   DEVELOPMENT GUARD
   An icon-only control with no accessible name is invisible to
   a screen reader. This makes it visible to everyone else, but
   only when the site opts in via <html class="wbc-a11y-debug">.
   --------------------------------------------------------- */
.wbc-a11y-debug .wbc-btn--icon:not([aria-label]):not([aria-labelledby]):not([title]){
  outline: 2px dashed var(--status-error);
  outline-offset: 2px;
}

/* ---------------------------------------------------------
   TOGGLE

   A checkbox that reads as an on/off switch. It IS a checkbox:
   real <input type="checkbox">, so it is keyboard-operable,
   announced correctly, and works with the form it sits in
   without any JavaScript. Only the appearance is ours.

   Do not use a toggle for something that needs confirming —
   a toggle implies the change is already saved.
   --------------------------------------------------------- */

.wbc-toggle{
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-min);
  cursor: pointer;
}

.wbc-toggle__input{
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 2.75rem;
  height: 1.6rem;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text) 22%, var(--surface));
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard);
}

.wbc-toggle__input::after{
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: .2rem;
  width: 1.2rem;
  height: 1.2rem;
  margin-block-start: -.6rem;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  transition: translate var(--duration-fast) var(--ease-standard);
}

.wbc-toggle__input:checked{ background: var(--accent-strong); }
.wbc-toggle__input:checked::after{ translate: 1.15rem 0; }

.wbc-toggle__input:focus-visible{
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}

.wbc-toggle__input:disabled{ opacity: .55; cursor: not-allowed; }
.wbc-toggle:has(.wbc-toggle__input:disabled){ cursor: not-allowed; }

/* State is carried by the checkbox itself, so it survives high-contrast modes
   and forced-colors where a background swap alone would vanish. */
@media (forced-colors: active){
  .wbc-toggle__input{ border: 2px solid ButtonText; }
  .wbc-toggle__input:checked{ background: Highlight; }
}
