/*
 * Button
 *
 * The focus ring and the elevation are two independent slots composed into one
 * box-shadow, rather than separate rules racing on specificity. A variant sets
 * its elevation, `:focus-visible` sets its ring, and neither has to know about
 * the other — otherwise `--primary-brand:active` (0,2,0) and `:focus-visible`
 * (0,2,0) tie, and whichever is written last silently wins.
 *
 * Ring and elevation both come from elevation.css, generated from the Figma
 * effect styles. Those are not variable-bound and so cannot be tokens — there
 * is nothing for them to alias — which is why the values are exported and
 * committed instead of aliased.
 */
.ion-button {
  --ion-button-ring: var(--ion-shadow-none);
  --ion-button-elevation: var(--ion-shadow-none);

  /*
   * ELEVATION IS INDEXED BY SIZE; THE VARIANT PICKS THE FAMILY
   *
   * Each of these is a rung reassigned by the size modifiers, and a variant
   * selects one. That is the whole structure — no variant restates a shadow
   * value, and no size modifier knows which variant is asking. It replaces the
   * old per-variant `button-raised` / `button-surface` pair, which encoded a
   * distinction Figma no longer draws.
   *
   * `raised` and `shadow` are two DIFFERENT rest families, and which one a
   * variant takes is a real design decision rather than a spelling of the same
   * idea. `Raised/Lifted` is embossed — two inset highlights that read as a
   * bevel — and it only works over a solid, saturated fill. `Shadow` is a plain
   * drop shadow. The two light-surfaced variants, Secondary and Primary Soft,
   * are on `shadow`; the four solid ones are on `raised`. Measured across all
   * four sizes of both Figma sets on 2026-08-28.
   *
   * The two ramps are not the same shape, which is the trap here. `raised` runs
   * xs / sm / lg / lg, but `shadow` runs xs / sm / md / md — Large and XLarge
   * share `Shadow/md`, where the raised family jumps to `lg`. Assuming one ramp
   * from the other puts Large one rung too heavy.
   */
  --ion-button-raised: var(--ion-shadow-raised-lifted-sm);
  --ion-button-shadow: var(--ion-shadow-shadow-sm);
  --ion-button-inset-lifted: var(--ion-shadow-inset-lifted-sm);
  --ion-button-inset-flush: var(--ion-shadow-inset-flush-sm);

  /* Which inset family this variant presses into; overridden per variant. */
  --ion-button-pressed: var(--ion-button-inset-lifted);

  /*
   * Size is a set of slots defaulting to medium, so a size modifier reassigns
   * four variables instead of restating four declarations. It also means the
   * icon rule below needs no per-size overrides at all.
   *
   * These read the spacing scale directly. They used to read a `control/*`
   * group in Semantics, which was deleted: its twelve tokens were all aliases
   * of these same spacing steps, its icon sizes duplicated the `icon-size`
   * ladder outright, and only Button and Tabs ever bound it — because it had
   * been derived from Button's own measurements and fitted nothing else.
   *
   * 32/40/48 and 12/16/20 are Button's choices, not a system-wide rule. The
   * spacing scale is what everything shares; which rung you pick is yours.
   */
  --ion-button-size: var(--spacing-40);
  --ion-button-padding-x: var(--spacing-16);
  --ion-button-gap: var(--spacing-8);
  --ion-button-icon-size: var(--icon-size-md);

  /*
   * Typography comes from the type ramp, not from button tokens. In Figma the
   * Label carries a text style — Body/Default Emphasis at Medium, Body/Small
   * Emphasis at Small, Body/Medium Emphasis at Large — and a text style owns
   * fontSize/fontStyle/lineHeight outright. Button-level type tokens were
   * tried and deleted: they are a second source of truth that the text style
   * silently overrides, so they can never win.
   *
   * Weight is Medium (500) across all three sizes — the Emphasis body styles.
   */
  --ion-button-font-size: var(--type-body);
  --ion-button-line-height: var(--type-body-line-height);

  box-shadow: var(--ion-button-ring), var(--ion-button-elevation);
  display: inline-flex;
  align-items: center;
  justify-content: center;

  /*
   * Figma pins each size to a fixed height (32/40/48) with no vertical padding.
   * `min-height` reproduces that while still growing if a label ever wraps —
   * deriving height from padding instead is what left all three sizes 2px off.
   */
  min-height: var(--ion-button-size);
  padding-inline: var(--ion-button-padding-x);
  gap: var(--ion-button-gap);
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--ion-button-font-size);
  line-height: var(--ion-button-line-height);
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  border-style: solid;
  border-width: var(--border-width-default);
  border-radius: var(--radius-md);

  /*
   * `base` + `ease-out`, from the shared motion ladder in motion.css.
   *
   * This was `150ms cubic-bezier(0.4, 0, 0.2, 1)`, hardcoded here and in eleven
   * other stylesheets. Both halves were wrong for a state change: 150ms is
   * short enough to read as a jump rather than a change, and that curve is
   * symmetric — its slow tail means a colour appears to arrive late and then
   * snap. `ease-out` leaves immediately and decelerates in, so the same change
   * reads as settling.
   *
   * THE SHADOW SNAPS, ON PURPOSE. `box-shadow` was on this list until 0.136.0
   * and did almost nothing: a shadow list only interpolates when every layer
   * pairs with one of the same kind, inset with inset, and the raised bevel,
   * the insets and the ring do not pair. So press and focus already snapped on
   * four variants, while three changes that did pair — Primary Neutral's press,
   * Primary Soft's and Secondary's focus — morphed one shadow into an unrelated
   * one, repainting every frame. Figma draws no shadow motion at all. Hover,
   * the one change that is animated, does not touch the shadow.
   */
  transition:
    background-color var(--ion-duration-base) var(--ion-ease-out),
    border-color var(--ion-duration-base) var(--ion-ease-out);
}

/*
 * Press drops to `fast`.
 *
 * A press must resolve while the pointer is still down or the feedback reads as
 * lag rather than as a press — it is the one interaction the user is actively
 * waiting on. Release still returns at `base`, because the rule only overrides
 * the duration while the pressed state holds.
 */
.ion-button:active,
.ion-button[data-pressed='true'] {
  transition-duration: var(--ion-duration-fast);
}

/* Size modifiers — radius, border-width and font-weight are size-invariant. */
.ion-button--sm {
  --ion-button-size: var(--spacing-32);
  --ion-button-padding-x: var(--spacing-12);
  --ion-button-gap: var(--spacing-6);

  /*
   * 14, not 16 — the one rung that is not simply "one step down".
   *
   * Small's label is 14px, and a 16px icon beside it out-weighted the text it
   * was meant to sit with. Medium and Large are unchanged: their icons run
   * ahead of their labels (20 vs 16, 24 vs 18) and read correctly that way,
   * because at those sizes the icon has room to carry detail. Small does not.
   */
  --ion-button-icon-size: var(--icon-size-xs);
  --ion-button-font-size: var(--type-body-sm);
  --ion-button-line-height: var(--type-body-sm-line-height);
  --ion-button-raised: var(--ion-shadow-raised-lifted-xs);
  --ion-button-shadow: var(--ion-shadow-shadow-xs);
  --ion-button-inset-lifted: var(--ion-shadow-inset-lifted-xs);
  --ion-button-inset-flush: var(--ion-shadow-inset-flush-xs);
}

.ion-button--lg {
  --ion-button-size: var(--spacing-48);
  --ion-button-padding-x: var(--spacing-20);
  --ion-button-gap: var(--spacing-8);
  --ion-button-icon-size: var(--icon-size-lg);
  --ion-button-font-size: var(--type-body-md);
  --ion-button-line-height: var(--type-body-md-line-height);
  --ion-button-raised: var(--ion-shadow-raised-lifted-lg);

  /* `md`, not `lg` — the shadow ramp tops out a rung below the raised one. */
  --ion-button-shadow: var(--ion-shadow-shadow-md);
  --ion-button-inset-lifted: var(--ion-shadow-inset-lifted-lg);
  --ion-button-inset-flush: var(--ion-shadow-inset-flush-lg);
}

/*
 * XLarge — fully bound, and the only rung that needed a new token.
 *
 * Height reads `spacing/56`, a primitive that did not exist when this size was
 * drawn: the scale ran 40, 48, 64, so the height was typed onto the frame as a
 * raw 56 that no export could see. Added and bound, so `verify-geometry.mjs`
 * now covers it like every other size.
 *
 * THE LABEL WAS A DETACHED TEXT STYLE, which is worth recognising because it
 * does not look like one. Small, Medium and Large carry Body/Small, Body/Default
 * and Body/Medium Emphasis; XLarge carried NO style at all — someone bumped 18
 * to 20 by hand and Figma silently detached it. The tell was not the font size,
 * which looked deliberate. It was that `type/body-md/line-height` was bound 70
 * times where `type/body-md` was bound 35: a detached label still counted
 * against the rung it had left, and no ramp step existed for the rung it should
 * have joined. Every size ramp now binds 35/35 and the asymmetry is gone.
 *
 * So this size reads Body/Large Emphasis (20/32) and line height moved 28 -> 32.
 * Nothing shifts visually — the button is a fixed 56 with one centred line — but
 * the label is back on the ramp and responds to breakpoint like the other three.
 *
 * Icons need no override beyond the slot: Figma binds `icon-size/lg` on XLarge,
 * the same rung as Large. That is a real decision, not an omission — XLarge is
 * the one rung where the icon does NOT run ahead of its label (24 vs 20, where
 * Large is 24 vs 18). Reproduced as measured.
 *
 * Elevation reuses the Large rungs, and this is where "there is no xl rung"
 * would be the easy wrong answer. `Raised/Lifted/xl` and `Raised/Flush/xl` DO
 * exist as effect styles — it is the *inset* families that stop at lg, so there
 * is no complete xl rung to press into. Figma binds Raised/Lifted/lg,
 * Inset/Lifted/lg and Inset/Flush/lg on XLarge exactly as on Large, and
 * `Shadow/md` for the two shadow-family variants — also exactly as on Large,
 * even though `Shadow/lg` and `Shadow/xl` both exist. These four lines restate
 * the inherited defaults on purpose, so the next reader does not have to go and
 * check which half of each family exists.
 */
.ion-button--xl {
  --ion-button-size: var(--spacing-56);
  --ion-button-padding-x: var(--spacing-24);
  --ion-button-gap: var(--spacing-8);
  --ion-button-icon-size: var(--icon-size-lg);
  --ion-button-font-size: var(--type-body-lg);
  --ion-button-line-height: var(--type-body-lg-line-height);
  --ion-button-raised: var(--ion-shadow-raised-lifted-lg);
  --ion-button-shadow: var(--ion-shadow-shadow-md);
  --ion-button-inset-lifted: var(--ion-shadow-inset-lifted-lg);
  --ion-button-inset-flush: var(--ion-shadow-inset-flush-lg);
}

/*
 * Focus ring — one ring for every variant and size, from the Figma `Focus/md`
 * effect style.
 *
 * There used to be two, `Button/Raised Focus` and `Button/Surface Focus`, with
 * secondary and tertiary opting into the second. They were byte-identical: two
 * names over one value, which is why they collapsed into `Focus/md` and why
 * there is no per-variant ring slot here any more.
 *
 * Before that they were assembled from `--button-offset-ring` / `--button-ring`,
 * component tokens hand-mapped to the nearest solid semantic colour. That made
 * the outer ring fully opaque where Figma renders it at 50% alpha, and nothing
 * caught it: effect styles are not variable-bound, so the token pipeline cannot
 * see them at all.
 */
.ion-button:focus-visible,
.ion-button[data-focused='true'] {
  --ion-button-ring: var(--ion-shadow-focus-md);

  outline: none;
}

/*
 * Hover is declared twice per variant, on purpose.
 *
 * `:hover` is wrapped in `@media (hover: hover)` because native hover latches
 * on touch — tap a button and it stays visually hovered until you tap
 * elsewhere. `[data-hovered]` comes from React Aria's useHover, which is
 * already pointer-aware. The media query keeps the stylesheet working without
 * React; the attribute makes it correct with it.
 *
 * HOVER DOES NOT CHANGE ELEVATION — only the surface colour moves. Measured:
 * every Hover variant in Figma carries the same effect style as its Default,
 * across all six elevated types and both Figma sets. Reproduced as measured
 * rather than preserved from the old `button-raised-hover` step, which encodes
 * a difference the current design no longer draws.
 */

/* Primary Brand Variant */
.ion-button--primary-brand {
  --ion-button-elevation: var(--ion-button-raised);

  background-color: var(--surface-primary);
  color: var(--text-on-color);
  border-color: var(--border-primary-strong);
}

@media (hover: hover) {
  .ion-button--primary-brand:hover {
    background-color: var(--surface-primary-hover);
  }
}

.ion-button--primary-brand[data-hovered='true'] {
  background-color: var(--surface-primary-hover);
}

.ion-button--primary-brand:active,
.ion-button--primary-brand[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-primary-pressed);
}

/*
 * Primary Neutral Variant
 *
 * The one variant whose press does NOT track its own rung: Figma pins all three
 * sizes to `Inset/Flush/lg` while their rest states scale xs/sm/lg like every
 * other type. Reproduced as measured, not normalised — but flagged, because it
 * is the only place in the component where size stops mattering, and it reads
 * more like an unfinished edit than a decision.
 */
.ion-button--primary-neutral {
  --ion-button-elevation: var(--ion-button-raised);
  --ion-button-pressed: var(--ion-shadow-inset-flush-lg);

  background-color: var(--surface-inverse);

  /* `text/inverse`, not `text/on-color`. This type's ground is the inverse
   * neutral, which flips with the theme; on-color is for accent grounds. The
   * two coincided until on-color settled on white for both modes, at which
   * point this button was white on white. */
  color: var(--text-inverse);
  border-color: var(--border-inverse);
}

@media (hover: hover) {
  .ion-button--primary-neutral:hover {
    background-color: var(--surface-inverse-subtle);
  }
}

.ion-button--primary-neutral[data-hovered='true'] {
  background-color: var(--surface-inverse-subtle);
}

.ion-button--primary-neutral:active,
.ion-button--primary-neutral[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-inverse);
}

/*
 * Primary Soft Variant
 *
 * The tinted counterpart to Primary Brand: brand colour carried by the label
 * instead of by the surface. It is the only raised variant whose text is
 * `text/primary` rather than `text/on-color`, which is the whole point of it —
 * a brand-weighted action that does not compete with a solid Primary Brand
 * beside it.
 *
 * Its press is a two-step it does not share with any other variant: the surface
 * moves `primary-subtle` -> `primary-tint` AND the border jumps two steps,
 * `border/primary-subtle` -> `border/primary`. Every other type either holds
 * its border (brand, neutral, success, destructive) or steps it once
 * (secondary). Measured on all four sizes.
 *
 * Elevation presses flush like Secondary, not lifted like Brand — a tinted
 * surface has too little contrast for the lifted inset to register. It rests on
 * the `shadow` family for the same reason: the embossed `Raised/Lifted` bevel
 * needs a solid saturated fill under it, and a subtle tint is not one.
 */
.ion-button--primary-soft {
  --ion-button-elevation: var(--ion-button-shadow);
  --ion-button-pressed: var(--ion-button-inset-flush);

  background-color: var(--surface-primary-subtle);
  color: var(--text-primary);
  border-color: var(--border-primary-subtle);
}

@media (hover: hover) {
  .ion-button--primary-soft:hover {
    background-color: var(--surface-primary-subtle-hover);
  }
}

.ion-button--primary-soft[data-hovered='true'] {
  background-color: var(--surface-primary-subtle-hover);
}

.ion-button--primary-soft:active,
.ion-button--primary-soft[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-primary-tint);
  border-color: var(--border-primary);
}

/*
 * Secondary Variant — presses flush, losing its lift; brand and destructive keep
 * theirs.
 *
 * Rests on the `shadow` family, not `raised`. Figma moved it there along with
 * Primary Soft: both sit on a light surface, and `Raised/Lifted`'s inset
 * highlight reads as a smudge over white rather than as a bevel.
 */
.ion-button--secondary {
  --ion-button-elevation: var(--ion-button-shadow);
  --ion-button-pressed: var(--ion-button-inset-flush);

  background-color: var(--surface-default);
  color: var(--text-secondary);
  border-color: var(--border-strong);
}

/*
 * The border does NOT move on hover — Figma binds `border/strong` on Default,
 * Hover and Focus alike, and only steps to `border/stronger` on Pressed. The
 * hover step was previously reproduced here and is a difference the current
 * design does not draw.
 */
@media (hover: hover) {
  .ion-button--secondary:hover {
    background-color: var(--surface-page);
  }
}

.ion-button--secondary[data-hovered='true'] {
  background-color: var(--surface-page);
}

.ion-button--secondary:active,
.ion-button--secondary[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-muted);
  border-color: var(--border-stronger);
}

/* Tertiary Variant — intentionally flat; a ghost button has no surface to raise. */
.ion-button--tertiary {
  background-color: transparent;
  color: var(--text-secondary);
  border-color: transparent;
}

@media (hover: hover) {
  .ion-button--tertiary:hover {
    background-color: var(--surface-page);
  }
}

.ion-button--tertiary[data-hovered='true'] {
  background-color: var(--surface-page);
}

.ion-button--tertiary:active,
.ion-button--tertiary[data-pressed='true'] {
  background-color: var(--surface-muted);
}

/* Destructive Variant */
.ion-button--destructive {
  --ion-button-elevation: var(--ion-button-raised);

  background-color: var(--surface-error);
  color: var(--text-on-color);
  border-color: var(--border-error-strong);
}

@media (hover: hover) {
  .ion-button--destructive:hover {
    background-color: var(--surface-error-hover);
  }
}

.ion-button--destructive[data-hovered='true'] {
  background-color: var(--surface-error-hover);
}

.ion-button--destructive:active,
.ion-button--destructive[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-error-pressed);
}

/*
 * Success Variant
 *
 * Structurally identical to Destructive — solid accent surface, `text/on-color`
 * label, `*-strong` border held across every state, lifted press. It is the
 * same shape with a different intent, which is exactly what the v2 ramps were
 * for: `success/*` and `error/*` have identical slots, so the variant needed no
 * new tokens at all.
 *
 * `--ion-button-pressed` is left at the inherited `inset-lifted`, so there is no
 * override here. Figma binds Inset/Lifted at every size.
 */
.ion-button--success {
  --ion-button-elevation: var(--ion-button-raised);

  background-color: var(--surface-success);
  color: var(--text-on-color);
  border-color: var(--border-success-strong);
}

@media (hover: hover) {
  .ion-button--success:hover {
    background-color: var(--surface-success-hover);
  }
}

.ion-button--success[data-hovered='true'] {
  background-color: var(--surface-success-hover);
}

.ion-button--success:active,
.ion-button--success[data-pressed='true'] {
  --ion-button-elevation: var(--ion-button-pressed);

  background-color: var(--surface-success-pressed);
}

/* Disabled State */
.ion-button:disabled,
.ion-button[data-disabled='true'] {
  --ion-button-ring: var(--ion-shadow-none);
  --ion-button-elevation: var(--ion-shadow-none);

  background-color: var(--surface-disabled);
  color: var(--text-disabled);
  border-color: var(--border-disabled);
  cursor: not-allowed;
}

.ion-button--tertiary:disabled,
.ion-button--tertiary[data-disabled='true'] {
  background-color: transparent;
  border-color: transparent;
}

/* Icon Containers & Alignment */
.ion-button__icon-start,
.ion-button__icon-end {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* One rule for every size — the slot is reassigned by the size modifier. */
.ion-button__icon-start svg,
.ion-button__icon-end svg {
  width: var(--ion-button-icon-size);
  height: var(--ion-button-icon-size);
}
