/* One button system: structure only.
 *
 * Sharon approved one shared button. It rebuilds the structure of an Arc
 * Button (MIT) in plain CSS: the behavior is rebuilt here, none of its code
 * is copied. Colors are NOT in this file. Every page keeps its own fills,
 * inks, borders, Dark theme, and hover colors. This file owns what should
 * be the same everywhere: height, padding, radius, type, press, loading,
 * the label swap, and the focus ring.
 *
 * Variants (colors stay on the page, the structure is shared):
 *   primary    .btn  .btn-primary  .btn-p  .btn-g  .btn-green  .btn-finance  .btn.solid
 *              .btn-accent  .btn-gold  .btn-blue  .btn-give  .btn-sun  .btn-mz
 *   secondary  .btn-outline  .btn-o  .btn-out  .btn.outline  .btn-white  .btn-secondary
 *   ghost      .btn-quiet  .btn-ghost
 *   danger     .btn-danger  .btn-red
 * Sizes:
 *   sm  .btn-sm   32px (44px on a touch screen), 12px sides, 8px radius, 13px type
 *   md  .btn      44px, 18px sides, 10px radius, 15px type
 *   lg  .btn-lg   52px, 24px sides, 12px radius, 17px type
 * Type is Outfit at --fw-ui (600 public, 500 Back Office). The same list is
 * in assets/buttons.js (JFSDButton.MAP) and tests/buttons.test.mjs.
 *
 * Mosaic. .mz-pill, .mz-pill-ghost, .mz-btn, .mz-btn-ghost and .mz-btn-ocean
 * keep the Mosaic palette, Lexend at its own size and weight, and the pill
 * shape, which are the sub-brand. They take everything else: the lg height
 * and side padding (so a Mosaic pill sits on the same line as a .btn-lg
 * beside it), the press, loading, and the focus ring.
 *
 * Shape. A page with its own shape language sets --btn-round (Federation
 * at 90 and the Mosaic fundraiser: pills), and a sub-brand page that uses
 * .btn sets --btn-font (the Mosaic fundraiser: Lexend). Height, padding
 * and type size stay shared.
 *
 * WHY THE ODD SELECTOR. `:not(#_btn)` adds one id of weight (no element has
 * that id). Pages define .btn, .btn-sm and the rest inline after this file,
 * and most also re-pad buttons inside a panel, a modal, or a phone
 * breakpoint. Without the weight, each of those wins and one size ends up
 * with five heights. With it, only a rule that names an id and a class
 * chain still wins, which is where a page genuinely needs its own button.
 * Layout (display, width, flex, justify) stays zero weight, so a page's
 * full-width or hidden button is left alone.
 *
 * Loading: data-loading or aria-busy="true" shows a centered spinner over
 * the hidden label. The label keeps its space, so nothing moves. A button
 * that is disabled AND aria-busy is the Honest Wait pattern
 * (assets/honest-wait.js), which says its progress in words, so it keeps
 * its words. Clicks are swallowed by assets/buttons.js.
 *
 * Reduced motion: prefers-reduced-motion, Back Office Settings, Motion
 * (html[data-motion="reduce"]), and the public accessibility panel's motion
 * switch (html[data-a11y-motion="off"]) all turn every movement into a
 * plain fade. */

:root{
  --btn-h-sm:32px; --btn-h-md:44px; --btn-h-lg:52px;
  --btn-px-sm:12px; --btn-px-md:18px; --btn-px-lg:24px;
  --btn-r-sm:8px; --btn-r-md:10px; --btn-r-lg:12px;
  --btn-fs-sm:.8125rem; --btn-fs-md:.9375rem; --btn-fs-lg:1.0625rem;
  --btn-spring:cubic-bezier(.34,1.56,.64,1);
  --btn-settle:cubic-bezier(.2,.8,.2,1);
}
@media (pointer:coarse){
  :root{--btn-h-sm:44px}
}

/* Layout defaults. Zero weight: any page rule wins. */
:where(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean){
  display:inline-flex;align-items:center;justify-content:center;gap:.5em;
  vertical-align:middle;text-decoration:none;box-sizing:border-box;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
}

/* Structure. The height is min-height. The top and bottom padding is kept
   under what one line needs, so an icon or an emoji does not add a pixel,
   and align-content centers the line where a page made the button a block
   instead of a flex box. Two lines grow the button, they are not clipped. */
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):not(select){
  --_h:var(--btn-h-md);--_px:var(--btn-px-md);--_r:var(--btn-r-md);--_fs:var(--btn-fs-md);
  box-sizing:border-box;
  min-height:var(--_h);
  padding-block:max(4px,calc((var(--_h) - 1.2em) / 2 - 5px));
  padding-inline:var(--_px);
  align-content:center;
  line-height:1.2;
  letter-spacing:0;
  text-transform:none;
  transition:
    scale .34s var(--btn-spring),
    transform .15s ease,
    background-color .15s ease,
    border-color .15s ease,
    color .15s ease,
    box-shadow .15s ease,
    opacity .15s ease,
    filter .15s ease;
}
/* JFSD buttons: radius and Outfit at the size's type. Mosaic keeps its
   pill and Lexend at its own size. A page with its own shape language
   (Federation at 90's pills) sets --btn-round once; nothing else changes. */
:is(.btn,.btn-out):not(#_btn):not(select){
  border-radius:var(--btn-round,var(--_r));
  font-family:var(--btn-font,var(--font-display,'Outfit')),system-ui,sans-serif;
  font-size:var(--_fs);
  font-weight:var(--fw-ui,600);
}
.btn-sm:not(#_btn):not(select){--_h:var(--btn-h-sm);--_px:var(--btn-px-sm);--_r:var(--btn-r-sm);--_fs:var(--btn-fs-sm)}
:is(.btn-lg,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):not(select){--_h:var(--btn-h-lg);--_px:var(--btn-px-lg);--_r:var(--btn-r-lg);--_fs:var(--btn-fs-lg)}

/* Press. A short dip on pointer down and a spring back on release.
   assets/buttons.js sets --btn-press per button: .985 wider than 220px,
   .96 for an icon button 48px or smaller, .97 otherwise. A button that
   opens a menu, popover, or dialog never dips. Reduced motion never gets
   here at all. */
@media (prefers-reduced-motion:no-preference){
  html:not([data-motion="reduce"]):not([data-a11y-motion="off"]) :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):active:not([aria-haspopup]):not([role="combobox"]):not([aria-expanded]):not([data-state]):not([data-loading]):not([aria-busy="true"]):not([aria-disabled="true"]):not(:disabled){
    scale:var(--btn-press,.97);
    transition:
      scale .1s var(--btn-settle),
      transform .15s ease,
      background-color .15s ease,
      border-color .15s ease,
      color .15s ease,
      box-shadow .15s ease,
      opacity .15s ease,
      filter .15s ease;
  }
}

/* Focus. One ring, the medium blue, in Light and Dark. */
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):focus-visible{
  outline:3px solid var(--accent,#1C88ED);
  outline-offset:2px;
}

/* Loading. The label keeps its width and goes invisible; the spinner is
   centered over it in the button's own ink. */
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):is([data-loading],[aria-busy="true"]:not(:disabled):not([data-morph-busy])){
  cursor:progress;
  -webkit-text-fill-color:transparent;
  opacity:1;
}
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):is([data-loading],[aria-busy="true"]:not(:disabled):not([data-morph-busy])):not([data-btn-pos]){
  position:relative;
}
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):is([data-loading],[aria-busy="true"]:not(:disabled):not([data-morph-busy])) > *{
  opacity:0;transition:opacity .15s ease;
}
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):is([data-loading],[aria-busy="true"]:not(:disabled):not([data-morph-busy]))::after{
  content:"";position:absolute;inset:0;margin:auto;
  width:1.125em;height:1.125em;box-sizing:border-box;
  border-radius:50%;
  border:2px solid currentColor;border-right-color:transparent;
  -webkit-text-fill-color:currentColor;
  animation:jfsd-btn-spin .7s linear infinite;
  pointer-events:none;
}
@keyframes jfsd-btn-spin{to{rotate:1turn}}
@keyframes jfsd-btn-pulse{from{opacity:.35}to{opacity:1}}

/* Label crossfade (JFSDButton.setLabel where letters cannot move: reduced
   motion, a browser without Web Animations, or words with markup inside).
   The new label rises about 4px out of a 2px blur while the old one fades,
   and the width eases to the new size. */
.btn-swap{display:inline-grid;align-items:center;justify-items:center}
.btn-swap > *{grid-area:1 / 1;display:inline-flex;align-items:center;justify-content:center;gap:inherit;white-space:nowrap}
.btn-swap-in{animation:jfsd-btn-rise .28s var(--btn-settle) both}
.btn-swap-out{animation:jfsd-btn-sink .2s ease both}
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn)[data-swapping]{
  overflow:hidden;
  transition:
    width .28s var(--btn-settle),
    scale .34s var(--btn-spring),
    transform .15s ease,
    background-color .15s ease,
    border-color .15s ease,
    color .15s ease,
    box-shadow .15s ease,
    opacity .15s ease,
    filter .15s ease;
}
@keyframes jfsd-btn-rise{from{opacity:0;transform:translateY(4px);filter:blur(2px)}to{opacity:1;transform:none;filter:none}}
@keyframes jfsd-btn-sink{from{opacity:1}to{opacity:0;transform:translateY(-3px);filter:blur(2px)}}
@keyframes jfsd-btn-fade-in{from{opacity:0}to{opacity:1}}
@keyframes jfsd-btn-fade-out{from{opacity:1}to{opacity:0}}

/* Letter morph (JFSDButton.setLabel, run, copy, and the automatic morph).
   The words are split into letters only while they move; buttons.js puts
   plain text back when they settle. A leaving letter keeps its place
   absolutely and draws itself from data-ch, so the button's textContent is
   always just the words it is heading for. */
.btn-morph{display:inline-block;position:relative;white-space:pre;text-align:start;vertical-align:top;max-width:none}
.btn-label > .btn-morph{vertical-align:baseline}
.btn-glyph{display:inline-block;white-space:pre}
.btn-glyph-out{position:absolute;top:0;pointer-events:none}
.btn-glyph-out::before{content:attr(data-ch)}
:is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn)[data-morph-busy]{cursor:progress}

/* The icon slot: one icon at a time, stacked so two can trade places. */
.btn-icon{display:inline-grid;place-items:center;flex:none;width:1.1em;height:1.1em;line-height:0;vertical-align:-.15em}
.btn-icon-inline{margin-right:.35em}
.btn-icon-inline.btn-icon-end{margin-right:0;margin-left:.35em}
.btn-icon > .btn-icon-phase{grid-area:1 / 1;display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}
.btn-icon svg{display:block;width:100%;height:100%}
.btn-icon .btn-draw{stroke-dasharray:1;stroke-dashoffset:0}
.btn-spin{display:block;width:.95em;height:.95em;box-sizing:border-box;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;animation:jfsd-btn-spin .7s linear infinite}
@media (prefers-reduced-motion:reduce){.btn-spin{animation:jfsd-btn-pulse .8s ease-in-out infinite alternate}}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) .btn-spin{animation:jfsd-btn-pulse .8s ease-in-out infinite alternate}

/* A live region for "Saved" and the like (JFSDButton.setLabel). */
.jfsd-btn-live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* Reduced motion: a plain fade. No dip, no lift, no blur, no rise, no
   width glide, and the spinner pulses instead of turning. Written twice
   because a media query and an attribute cannot share one rule. */
@media (prefers-reduced-motion:reduce){
  :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):not(select),
  :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn)[data-swapping]{
    scale:none;
    transition:opacity .15s ease,background-color .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease;
  }
  :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):hover{transform:none}
  .btn-swap-in{animation:jfsd-btn-fade-in .2s ease both}
  .btn-swap-out{animation:jfsd-btn-fade-out .2s ease both}
  :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):is([data-loading],[aria-busy="true"]:not(:disabled))::after{
    animation:jfsd-btn-pulse .8s ease-in-out infinite alternate;
  }
}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):not(select),
html:is([data-motion="reduce"],[data-a11y-motion="off"]) :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn)[data-swapping]{
  scale:none;
  transition:opacity .15s ease,background-color .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease;
}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):not(#_btn):hover{transform:none}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) .btn-swap-in{animation:jfsd-btn-fade-in .2s ease both}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) .btn-swap-out{animation:jfsd-btn-fade-out .2s ease both}
html:is([data-motion="reduce"],[data-a11y-motion="off"]) :is(.btn,.btn-out,.mz-pill,.mz-pill-ghost,.mz-btn,.mz-btn-ghost,.mz-btn-ocean):is([data-loading],[aria-busy="true"]:not(:disabled))::after{
  animation:jfsd-btn-pulse .8s ease-in-out infinite alternate;
}
