/* busy.css — the one part of rastrillo's busy rule that lives in tokens.css: the spinner takes the label's place. Copied byte for byte from rastrillo's ui/tokens.css (estilo busy/provenance.json), for apps whose vendored tokens.css predates it. Link it after tokens.css. */ /* With no busy label there are no words to show while it works, so the spinner takes the label's place instead of joining it: joining widens the button mid-click and shoves whatever sits beside it. The shim marks a button that HAS a busy label with data-idle-label, which is what the :not() keys on — those words were asked for, so they stay. :has(> spinner) because aria-busy is ordinary ARIA an app may set on its own fetch button with no shim and no spinner; keyed on aria-busy alone, that button would lose its label and show nothing at all. The label is unpainted, never removed: aria-busy plus the untouched text is what a screen reader announces. -webkit-text-fill-color rather than color, because the ring is drawn in currentColor — a transparent color would take the spinner with it. Element children (an icon) are not reached by the text fill and take opacity 0, which unlike visibility keeps them in the accessibility tree. The media query stands the swap down for two readers a lone ring fails. Under reduced motion the ring is still and dimmed, and on its own reads as a stray "C". Under forced colours the engine repaints the text fill, so the label paints anyway and a centred ring would land on top of it. Both keep the older spinner-beside-label look. */ @media (prefers-reduced-motion: no-preference) and (forced-colors: none) { .rst-btn[aria-busy="true"]:not([data-idle-label]):has(> .rst-spin), [rst-btn][aria-busy="true"]:not([data-idle-label]):has(> [rst-spin]) { -webkit-text-fill-color: transparent; } .rst-btn[aria-busy="true"]:not([data-idle-label]):has(> .rst-spin) > :not(.rst-spin), [rst-btn][aria-busy="true"]:not([data-idle-label]):has(> [rst-spin]) > :not([rst-spin]) { opacity: 0; } .rst-btn[aria-busy="true"]:not([data-idle-label]) > .rst-spin, [rst-btn][aria-busy="true"]:not([data-idle-label]) > [rst-spin] { inset: 0; margin: auto; position: absolute; } /* The spinner's containing block. Zero specificity, so an app's own position: fixed or sticky on a floating Save still wins — either of those contains the spinner just as well, and losing it would drop the button back into the page flow mid-click. */ :where(.rst-btn[aria-busy="true"]:not([data-idle-label])), :where([rst-btn][aria-busy="true"]:not([data-idle-label])) { position: relative; } }