ads-nuxt 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +567 -546
  3. package/app/app.config.ts +25 -25
  4. package/app/assets/ads/checkbox-check.svg +5 -5
  5. package/app/assets/ads/checkbox-minus.svg +5 -5
  6. package/app/assets/brand/farmarcas.svg +6 -6
  7. package/app/assets/brand/radar.svg +9 -9
  8. package/app/assets/css/component-tokens.css +403 -403
  9. package/app/assets/css/main.css +617 -617
  10. package/app/components/AdsBadge.vue +48 -48
  11. package/app/components/AdsBroadcast.vue +45 -45
  12. package/app/components/AdsButton.vue +569 -569
  13. package/app/components/AdsButtonGroup.vue +39 -39
  14. package/app/components/AdsButtonMenu.vue +48 -48
  15. package/app/components/AdsCard.vue +117 -117
  16. package/app/components/AdsCharttip.vue +78 -78
  17. package/app/components/AdsCheckbox.vue +77 -77
  18. package/app/components/AdsDatePicker.vue +473 -473
  19. package/app/components/AdsDropdownMenu.vue +19 -19
  20. package/app/components/AdsEmptyState.vue +58 -58
  21. package/app/components/AdsFarmarcasPill.vue +74 -74
  22. package/app/components/AdsFeatureCard.vue +217 -217
  23. package/app/components/AdsFileCard.vue +101 -101
  24. package/app/components/AdsFilterDropdown.vue +174 -174
  25. package/app/components/AdsFilterOption.vue +72 -72
  26. package/app/components/AdsFilterSelect.vue +202 -202
  27. package/app/components/AdsFooter.vue +25 -25
  28. package/app/components/AdsInput.vue +143 -143
  29. package/app/components/AdsListDropdown.vue +44 -44
  30. package/app/components/AdsLoading.vue +109 -109
  31. package/app/components/AdsMenuBar.vue +103 -103
  32. package/app/components/AdsMetricCard.vue +119 -119
  33. package/app/components/AdsMiniPill.vue +40 -40
  34. package/app/components/AdsModal.vue +157 -157
  35. package/app/components/AdsMonthPicker.vue +80 -80
  36. package/app/components/AdsNavbarApp.vue +67 -67
  37. package/app/components/AdsNavbarDesk.vue +62 -62
  38. package/app/components/AdsNotabilityPill.vue +27 -27
  39. package/app/components/AdsNumberInput.vue +166 -166
  40. package/app/components/AdsPagination.vue +91 -91
  41. package/app/components/AdsProductTag.vue +61 -61
  42. package/app/components/AdsRadio.vue +70 -70
  43. package/app/components/AdsSearchInput.vue +113 -113
  44. package/app/components/AdsSegmentedTabs.vue +28 -28
  45. package/app/components/AdsSideMenu.vue +101 -101
  46. package/app/components/AdsSmalltip.vue +44 -44
  47. package/app/components/AdsSteptip.vue +101 -101
  48. package/app/components/AdsSwitch.vue +59 -59
  49. package/app/components/AdsTable.vue +100 -100
  50. package/app/components/AdsTableToolbar.vue +44 -44
  51. package/app/components/AdsTabs.vue +51 -51
  52. package/app/components/AdsTextarea.vue +109 -109
  53. package/app/components/AdsToast.vue +181 -181
  54. package/app/components/AdsTooltip.vue +66 -66
  55. package/app/components/InvertedTooltip.vue +40 -40
  56. package/app/components/OverlayLoading.vue +173 -173
  57. package/app/components/ProgressBarLoading.vue +256 -256
  58. package/app/composables/useOverlayLoading.ts +66 -66
  59. package/app/locale/pt-br.ts +6 -6
  60. package/app/plugins/ads-toast.client.ts +24 -24
  61. package/app/theme/ads-theme.ts +370 -370
  62. package/app/theme/component-tokens.json +402 -402
  63. package/app/theme/component-tokens.ts +815 -815
  64. package/app/types/overlay-loading.types.ts +26 -26
  65. package/app/utils/ads-filter-select.ts +172 -172
  66. package/app/utils/ads-table.ts +204 -204
  67. package/app/utils/calendar-date.ts +128 -128
  68. package/app/utils/category-role.ts +45 -45
  69. package/app/utils/money.ts +77 -77
  70. package/app/utils/notability.ts +8 -8
  71. package/app/utils/parser.ts +134 -134
  72. package/app/utils/percentage.ts +54 -54
  73. package/nuxt.config.ts +31 -32
  74. package/package.json +48 -46
@@ -1,569 +1,569 @@
1
- <template>
2
- <component
3
- :is="to ? NuxtLink : 'button'"
4
- ref="root"
5
- v-bind="rootAttrs"
6
- class="inline-flex items-center justify-center [font-weight:var(--ads-button-weight)] whitespace-nowrap transition-colors duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
7
- :class="[
8
- type === 'ia' ? 'rounded-[var(--ads-button-ia-radius)]' : 'rounded-[var(--ads-button-radius)]',
9
- type !== 'ia' && 'overflow-hidden',
10
- sizeClass,
11
- faceClass,
12
- focusClass,
13
- type === 'ia' && 'ads-button-ia-face',
14
- type === 'ia' && visualState() === 'disabled' && 'is-ia-muted',
15
- iaMotion,
16
- iaPaused && 'is-ia-paused',
17
- to && isDisabled ? 'pointer-events-none' : '',
18
- ]"
19
- @click="onRootClick"
20
- >
21
- <UIcon
22
- v-if="loading"
23
- name="i-lucide-loader-circle"
24
- :class="[iconClass, 'animate-spin']"
25
- data-loading
26
- />
27
- <UIcon v-else-if="iconOnly || leading" :name="icon" :class="iconClass" />
28
- <span v-if="hasLabel"><slot>{{ label }}</slot></span>
29
- <UIcon v-if="hasLabel && trailing && !loading" :name="trailingIcon || icon" :class="iconClass" />
30
- </component>
31
- </template>
32
-
33
- <script setup lang="ts">
34
- import { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from "vue";
35
- import type { RouteLocationRaw } from "vue-router";
36
- import { NuxtLink } from "#components";
37
-
38
- export type AdsButtonType = "filled" | "outline" | "unfilled" | "secondary" | "ia";
39
- export type AdsButtonSize = "md" | "sm";
40
- export type AdsButtonState = "active" | "hover" | "pressed" | "disabled";
41
- export type AdsButtonColor = "primary" | "success" | "attention" | "warning" | "new" | "innovation" | "blocked" | "neutral";
42
-
43
- const props = withDefaults(
44
- defineProps<{
45
- type?: AdsButtonType;
46
- /** Mesmas cores do badge, mais neutral para ação sem status. */
47
- color?: AdsButtonColor;
48
- size?: AdsButtonSize;
49
- label?: string;
50
- /** Ícone à esquerda. No Figma, o exemplo é sparkles. Sem rótulo, o ícone ocupa o botão sozinho. */
51
- leading?: boolean;
52
- /** Ícone à direita. Sem `trailingIcon`, usa `icon`. */
53
- trailing?: boolean;
54
- icon?: string;
55
- trailingIcon?: string;
56
- /** Vira link. Sem valor, continua `<button>`. */
57
- to?: RouteLocationRaw;
58
- disabled?: boolean;
59
- /** Troca o ícone por spinner e bloqueia o clique. */
60
- loading?: boolean;
61
- /** `type` do `<button>`. A prop `type` já é o visual do ADS. */
62
- nativeType?: "button" | "submit" | "reset";
63
- /**
64
- * Congela um estado para a documentação.
65
- * Sem valor, hover e pressed vêm do ponteiro.
66
- */
67
- state?: AdsButtonState | null;
68
- }>(),
69
- {
70
- type: "filled",
71
- color: "primary",
72
- size: "md",
73
- label: "",
74
- leading: false,
75
- trailing: false,
76
- icon: "i-lucide-sparkles",
77
- trailingIcon: "",
78
- to: undefined,
79
- disabled: false,
80
- loading: false,
81
- nativeType: "button",
82
- state: null,
83
- },
84
- );
85
-
86
- const slots = useSlots();
87
- const hasLabel = computed(() => props.label.length > 0 || Boolean(slots.default));
88
- /** Sem rótulo o botão é quadrado: 40px no desktop, 32px no mobile. */
89
- const iconOnly = computed(() => !hasLabel.value);
90
- const isDisabled = computed(() => props.disabled || props.loading || props.state === "disabled");
91
-
92
- const rootAttrs = computed(() =>
93
- props.to
94
- ? {
95
- to: props.to,
96
- tabindex: isDisabled.value ? -1 : undefined,
97
- "aria-disabled": isDisabled.value || undefined,
98
- "aria-busy": props.loading || undefined,
99
- }
100
- : {
101
- type: props.nativeType,
102
- disabled: isDisabled.value,
103
- "aria-busy": props.loading || undefined,
104
- },
105
- );
106
-
107
- const onRootClick = (event: MouseEvent) => {
108
- if (props.to && isDisabled.value) event.preventDefault();
109
- };
110
-
111
- const sizeClass = computed(() => {
112
- const iaLine = props.type === "ia" && props.size !== "sm" ? " leading-[var(--ads-button-ia-line-height)]" : "";
113
- if (iconOnly.value) {
114
- return props.size === "sm"
115
- ? "size-[var(--ads-button-sm-icon-box)] shrink-0"
116
- : "size-[var(--ads-button-md-icon-box)] shrink-0";
117
- }
118
- return props.size === "sm"
119
- ? `h-[var(--ads-button-sm-height)] gap-[var(--ads-button-sm-gap)] px-[var(--ads-button-sm-padding-x)] text-[length:var(--ads-button-sm-font-size)] leading-[var(--ads-button-sm-line-height)]${iaLine}`
120
- : `gap-[var(--ads-button-md-gap)] px-[var(--ads-button-md-padding-x)] py-[var(--ads-button-md-padding-y)] text-[length:var(--ads-button-md-font-size)] leading-[var(--ads-button-md-line-height)]${iaLine}`;
121
- });
122
-
123
- const iconClass = computed(() =>
124
- props.size === "sm"
125
- ? "size-[var(--ads-button-sm-icon)] shrink-0 text-[length:var(--ads-button-sm-icon)]"
126
- : "size-[var(--ads-button-md-icon)] shrink-0 text-[length:var(--ads-button-md-icon)]",
127
- );
128
-
129
- const faceClass = computed(() => faceFor(props.type, props.color, visualState()));
130
- const focusClass = computed(() =>
131
- props.type === "ia" ? "focus-visible:outline-[var(--ads-button-ia-border)]" : focusFor[props.color],
132
- );
133
- /** Um brilho de gradiente gira em volta da borda do tipo IA. Hover, pressed e disabled seguram a borda fechada. */
134
- const iaMotion = computed(() => {
135
- if (props.type !== "ia" || isDisabled.value) return "";
136
- if (props.state === "hover" || props.state === "pressed") return "";
137
- return "ads-button-ia";
138
- });
139
-
140
- const root = ref<Element | { $el: Element } | null>(null);
141
- const iaPaused = ref(false);
142
- let iaFrameObserver: IntersectionObserver | null = null;
143
-
144
- const watchIaRing = () => {
145
- iaFrameObserver?.disconnect();
146
- iaFrameObserver = null;
147
- iaPaused.value = false;
148
- if (!iaMotion.value) return;
149
- nextTick(() => {
150
- const node = root.value instanceof Element ? root.value : root.value?.$el;
151
- if (!node || typeof IntersectionObserver === "undefined") return;
152
- iaFrameObserver = new IntersectionObserver(([entry]) => {
153
- iaPaused.value = !entry?.isIntersecting;
154
- });
155
- iaFrameObserver.observe(node);
156
- });
157
- };
158
-
159
- watch(iaMotion, watchIaRing, { immediate: true });
160
- onBeforeUnmount(() => {
161
- iaFrameObserver?.disconnect();
162
- });
163
-
164
- const visualState = (): AdsButtonState => {
165
- if (props.disabled || props.state === "disabled") return "disabled";
166
- if (props.state === "hover" || props.state === "pressed") return props.state;
167
- return "active";
168
- };
169
-
170
- const contrast = "text-[var(--ads-text-contrast)]";
171
- const ink = "text-[var(--ads-text-primary)]";
172
- const filledPrimaryText = "text-[var(--ads-button-primary-text)]";
173
- const secondaryText = "text-[var(--ads-button-secondary-text)]";
174
-
175
- const focusFor: Record<AdsButtonColor, string> = {
176
- primary: "focus-visible:outline-[var(--ads-text-brand)]",
177
- success: "focus-visible:outline-[var(--ads-status-success-700)]",
178
- attention: "focus-visible:outline-[var(--ads-status-attention-800)]",
179
- warning: "focus-visible:outline-[var(--ads-status-warning-600)]",
180
- new: "focus-visible:outline-[var(--ads-status-new-700)]",
181
- innovation: "focus-visible:outline-[var(--ads-status-innovation-700)]",
182
- blocked: "focus-visible:outline-[var(--ads-status-void-800)]",
183
- neutral: "focus-visible:outline-[var(--ads-text-primary)]",
184
- };
185
-
186
- /** Fundo e cor da borda ficam em `.ads-button-ia-face`: a borda é pintada pelo fundo, na mesma espessura em todos os estados. */
187
- const iaFace = (state: AdsButtonState) => {
188
- const base =
189
- "border-(length:--ads-border-width-slim) border-solid text-[var(--ads-status-innovation-300)]";
190
- if (state === "disabled") {
191
- return `${base} text-[color-mix(in_srgb,var(--ads-status-innovation-300)_45%,transparent)]`;
192
- }
193
- if (state === "hover" || state === "pressed" || props.loading) {
194
- return `${base} shadow-[0_0_16px_2px_var(--ads-button-ia-glow)]`;
195
- }
196
- return base;
197
- };
198
-
199
- const faceFor = (type: AdsButtonType, color: AdsButtonColor, state: AdsButtonState) => {
200
- if (type === "ia") return iaFace(state);
201
- if (color === "primary") return primaryFace(type, state);
202
- if (type === "filled") return filledStatus(color, state);
203
- if (type === "outline") return outlineStatus(color, state);
204
- if (type === "unfilled") return unfilledStatus(color, state);
205
- return secondaryStatus(color, state);
206
- };
207
-
208
- const primaryFace = (type: AdsButtonType, state: AdsButtonState) => {
209
- if (type === "filled") return filledPrimary(state);
210
- if (type === "outline") return outlinePrimary(state);
211
- if (type === "unfilled") return unfilledPrimary(state);
212
- return secondaryPrimary(state);
213
- };
214
-
215
- const filledPrimary = (state: AdsButtonState) => {
216
- if (state === "hover") return `bg-[var(--ads-button-primary-hover)] ${filledPrimaryText}`;
217
- if (state === "pressed") return `bg-[var(--ads-button-primary-pressed)] ${filledPrimaryText}`;
218
- if (state === "disabled") return `bg-[var(--ads-button-primary-disabled)] ${filledPrimaryText}`;
219
- return `bg-[var(--ads-button-primary)] ${filledPrimaryText} hover:bg-[var(--ads-button-primary-hover)] active:bg-[var(--ads-button-primary-pressed)]`;
220
- };
221
-
222
- const outlinePrimaryHover =
223
- "border border-[var(--ads-text-brand)] bg-[var(--ads-button-secondary-hover)] text-[var(--ads-text-brand)] shadow-[inset_0_0_0_1px_var(--ads-text-brand)]";
224
-
225
- const outlinePrimary = (state: AdsButtonState) => {
226
- if (state === "hover" || state === "pressed") return outlinePrimaryHover;
227
- if (state === "disabled") {
228
- return "border border-[var(--ads-button-primary-disabled)] bg-[color-mix(in_srgb,var(--ads-button-secondary)_50%,transparent)] text-[var(--ads-button-primary-disabled)]";
229
- }
230
- return "border border-[var(--ads-text-brand)] text-[var(--ads-text-brand)] hover:bg-[var(--ads-button-secondary-hover)] hover:shadow-[inset_0_0_0_1px_var(--ads-text-brand)]";
231
- };
232
-
233
- const unfilledPrimary = (state: AdsButtonState) => {
234
- if (state === "hover" || state === "pressed") return "text-[var(--ads-button-primary-hover)]";
235
- if (state === "disabled") return "text-[var(--ads-button-primary-disabled)]";
236
- return "text-[var(--ads-text-brand)] hover:text-[var(--ads-button-primary-hover)]";
237
- };
238
-
239
- const secondaryDisabled = "bg-[var(--ads-button-secondary-disabled)] text-[var(--ads-button-secondary-disabled-text)]";
240
-
241
- const secondaryPrimary = (state: AdsButtonState) => {
242
- if (state === "hover") return `bg-[var(--ads-button-secondary-hover)] ${secondaryText}`;
243
- if (state === "pressed") return `bg-[var(--ads-button-secondary-pressed)] ${secondaryText}`;
244
- if (state === "disabled") return secondaryDisabled;
245
- return `bg-[var(--ads-button-secondary)] ${secondaryText} hover:bg-[var(--ads-button-secondary-hover)] active:bg-[var(--ads-button-secondary-pressed)]`;
246
- };
247
-
248
- /**
249
- * Sucesso, atenção e new usam texto primary no sólido 500: branco não chega no AA.
250
- * Warning e blocked seguram branco. Pressed escuro volta para o texto de contraste.
251
- */
252
- const filledStatus = (color: AdsButtonColor, state: AdsButtonState) => {
253
- if (color === "success") {
254
- if (state === "hover") return `bg-[var(--ads-status-success-600)] ${ink}`;
255
- if (state === "pressed") return `bg-[var(--ads-status-success-800)] ${contrast}`;
256
- if (state === "disabled") return "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]";
257
- return `bg-[var(--ads-status-success-500)] ${ink} hover:bg-[var(--ads-status-success-600)] active:bg-[var(--ads-status-success-800)] active:text-[var(--ads-text-contrast)]`;
258
- }
259
- if (color === "attention") {
260
- if (state === "hover") return `bg-[var(--ads-status-attention-600)] ${ink}`;
261
- if (state === "pressed") return `bg-[var(--ads-status-attention-800)] ${contrast}`;
262
- if (state === "disabled") return "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]";
263
- return `bg-[var(--ads-status-attention-500)] ${ink} hover:bg-[var(--ads-status-attention-600)] active:bg-[var(--ads-status-attention-800)] active:text-[var(--ads-text-contrast)]`;
264
- }
265
- if (color === "warning") {
266
- if (state === "hover") return `bg-[var(--ads-status-warning-600)] ${contrast}`;
267
- if (state === "pressed") return `bg-[var(--ads-status-warning-800)] ${contrast}`;
268
- if (state === "disabled") return "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)]";
269
- return `bg-[var(--ads-status-warning-500)] ${contrast} hover:bg-[var(--ads-status-warning-600)] active:bg-[var(--ads-status-warning-800)]`;
270
- }
271
- if (color === "new") {
272
- if (state === "hover") return `bg-[var(--ads-status-new-600)] ${ink}`;
273
- if (state === "pressed") return `bg-[var(--ads-status-new-800)] ${contrast}`;
274
- if (state === "disabled") return "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)]";
275
- return `bg-[var(--ads-status-new-500)] ${ink} hover:bg-[var(--ads-status-new-600)] active:bg-[var(--ads-status-new-800)] active:text-[var(--ads-text-contrast)]`;
276
- }
277
- if (color === "innovation") {
278
- if (state === "hover") return `bg-[var(--ads-status-innovation-600)] ${contrast}`;
279
- if (state === "pressed") return `bg-[var(--ads-status-innovation-800)] ${contrast}`;
280
- if (state === "disabled") return "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)]";
281
- return `bg-[var(--ads-status-innovation-500)] ${contrast} hover:bg-[var(--ads-status-innovation-600)] active:bg-[var(--ads-status-innovation-800)]`;
282
- }
283
- if (color === "neutral") {
284
- if (state === "hover") return `bg-[var(--color-neutral-800)] ${contrast}`;
285
- if (state === "pressed") return `bg-[var(--color-neutral-900)] ${contrast}`;
286
- if (state === "disabled") return "bg-[var(--color-neutral-100)] text-[var(--ads-text-tertiary)]";
287
- return `bg-[var(--color-neutral-700)] ${contrast} hover:bg-[var(--color-neutral-800)] active:bg-[var(--color-neutral-900)]`;
288
- }
289
- if (state === "hover") return `bg-[color-mix(in_srgb,var(--ads-status-void-800)_78%,white)] ${contrast}`;
290
- if (state === "pressed") return `bg-[color-mix(in_srgb,var(--ads-status-void-800)_70%,black)] ${contrast}`;
291
- if (state === "disabled") return "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-600)]";
292
- return `bg-[var(--ads-status-void-800)] ${contrast} hover:bg-[color-mix(in_srgb,var(--ads-status-void-800)_78%,white)] active:bg-[color-mix(in_srgb,var(--ads-status-void-800)_70%,black)]`;
293
- };
294
-
295
- const outlineStatus = (color: AdsButtonColor, state: AdsButtonState) => {
296
- const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
297
- success: {
298
- active:
299
- "border border-[var(--ads-status-success-700)] text-[var(--ads-status-success-700)] hover:bg-[var(--ads-status-success-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
300
- hover:
301
- "border border-[var(--ads-status-success-700)] bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)] shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
302
- pressed:
303
- "border border-[var(--ads-status-success-700)] bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)] shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
304
- disabled:
305
- "border border-[var(--ads-status-success-300)] bg-[color-mix(in_srgb,var(--ads-status-success-100)_50%,transparent)] text-[var(--ads-status-success-300)]",
306
- },
307
- attention: {
308
- active:
309
- "border border-[var(--ads-status-attention-800)] text-[var(--ads-status-attention-800)] hover:bg-[var(--ads-status-attention-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
310
- hover:
311
- "border border-[var(--ads-status-attention-800)] bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)] shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
312
- pressed:
313
- "border border-[var(--ads-status-attention-800)] bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)] shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
314
- disabled:
315
- "border border-[var(--ads-status-attention-300)] bg-[color-mix(in_srgb,var(--ads-status-attention-100)_50%,transparent)] text-[var(--ads-status-attention-300)]",
316
- },
317
- warning: {
318
- active:
319
- "border border-[var(--ads-status-warning-600)] text-[var(--ads-status-warning-600)] hover:bg-[var(--ads-status-warning-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
320
- hover:
321
- "border border-[var(--ads-status-warning-600)] bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)] shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
322
- pressed:
323
- "border border-[var(--ads-status-warning-600)] bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)] shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
324
- disabled:
325
- "border border-[var(--ads-status-warning-300)] bg-[color-mix(in_srgb,var(--ads-status-warning-100)_50%,transparent)] text-[var(--ads-status-warning-300)]",
326
- },
327
- new: {
328
- active:
329
- "border border-[var(--ads-status-new-700)] text-[var(--ads-status-new-700)] hover:bg-[var(--ads-status-new-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
330
- hover:
331
- "border border-[var(--ads-status-new-700)] bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)] shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
332
- pressed:
333
- "border border-[var(--ads-status-new-700)] bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)] shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
334
- disabled:
335
- "border border-[var(--ads-status-new-300)] bg-[color-mix(in_srgb,var(--ads-status-new-100)_50%,transparent)] text-[var(--ads-status-new-300)]",
336
- },
337
- innovation: {
338
- active:
339
- "border border-[var(--ads-status-innovation-700)] text-[var(--ads-status-innovation-700)] hover:bg-[var(--ads-status-innovation-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
340
- hover:
341
- "border border-[var(--ads-status-innovation-700)] bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)] shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
342
- pressed:
343
- "border border-[var(--ads-status-innovation-700)] bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)] shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
344
- disabled:
345
- "border border-[var(--ads-status-innovation-300)] bg-[color-mix(in_srgb,var(--ads-status-innovation-100)_50%,transparent)] text-[var(--ads-status-innovation-300)]",
346
- },
347
- blocked: {
348
- active:
349
- "border border-[var(--ads-status-void-800)] text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
350
- hover:
351
- "border border-[var(--ads-status-void-800)] bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
352
- pressed:
353
- "border border-[var(--ads-status-void-800)] bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
354
- disabled:
355
- "border border-[var(--ads-status-void-300)] bg-[color-mix(in_srgb,var(--ads-status-void-100)_50%,transparent)] text-[var(--ads-status-void-300)]",
356
- },
357
- neutral: {
358
- active:
359
- "border border-[var(--color-neutral-300)] text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-100)] hover:shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
360
- hover:
361
- "border border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
362
- pressed:
363
- "border border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
364
- disabled:
365
- "border border-[var(--color-neutral-300)] bg-[color-mix(in_srgb,var(--color-neutral-100)_50%,transparent)] text-[var(--ads-text-tertiary)]",
366
- },
367
- };
368
- return faces[color === "primary" ? "success" : color][state];
369
- };
370
-
371
- const unfilledStatus = (color: AdsButtonColor, state: AdsButtonState) => {
372
- const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
373
- success: {
374
- active: "text-[var(--ads-status-success-700)] hover:bg-[var(--ads-status-success-100)]",
375
- hover: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]",
376
- pressed: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]",
377
- disabled: "text-[var(--ads-status-success-300)]",
378
- },
379
- attention: {
380
- active: "text-[var(--ads-status-attention-800)] hover:bg-[var(--ads-status-attention-100)]",
381
- hover: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
382
- pressed: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
383
- disabled: "text-[var(--ads-status-attention-300)]",
384
- },
385
- warning: {
386
- active: "text-[var(--ads-status-warning-600)] hover:bg-[var(--ads-status-warning-100)]",
387
- hover: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)]",
388
- pressed: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)]",
389
- disabled: "text-[var(--ads-status-warning-300)]",
390
- },
391
- new: {
392
- active: "text-[var(--ads-status-new-700)] hover:bg-[var(--ads-status-new-100)]",
393
- hover: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)]",
394
- pressed: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)]",
395
- disabled: "text-[var(--ads-status-new-300)]",
396
- },
397
- innovation: {
398
- active: "text-[var(--ads-status-innovation-700)] hover:bg-[var(--ads-status-innovation-100)]",
399
- hover: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)]",
400
- pressed: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)]",
401
- disabled: "text-[var(--ads-status-innovation-300)]",
402
- },
403
- blocked: {
404
- active: "text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-100)]",
405
- hover: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
406
- pressed: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
407
- disabled: "text-[var(--ads-status-void-300)]",
408
- },
409
- neutral: {
410
- active: "text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-100)]",
411
- hover: "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)]",
412
- pressed: "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)]",
413
- disabled: "text-[var(--ads-text-tertiary)]",
414
- },
415
- };
416
- return faces[color === "primary" ? "success" : color][state];
417
- };
418
-
419
- const secondaryStatus = (color: AdsButtonColor, state: AdsButtonState) => {
420
- const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
421
- success: {
422
- active:
423
- "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)] hover:bg-[var(--ads-status-success-200)] active:bg-[var(--ads-status-success-300)]",
424
- hover: "bg-[var(--ads-status-success-200)] text-[var(--ads-status-success-800)]",
425
- pressed: "bg-[var(--ads-status-success-300)] text-[var(--ads-status-success-800)]",
426
- disabled: secondaryDisabled,
427
- },
428
- attention: {
429
- active:
430
- "bg-[var(--ads-status-attention-100)] text-[var(--ads-text-primary)] hover:bg-[var(--ads-status-attention-200)] active:bg-[var(--ads-status-attention-300)]",
431
- hover: "bg-[var(--ads-status-attention-200)] text-[var(--ads-text-primary)]",
432
- pressed: "bg-[var(--ads-status-attention-300)] text-[var(--ads-text-primary)]",
433
- disabled: secondaryDisabled,
434
- },
435
- warning: {
436
- active:
437
- "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)] hover:bg-[var(--ads-status-warning-200)] active:bg-[var(--ads-status-warning-300)]",
438
- hover: "bg-[var(--ads-status-warning-200)] text-[var(--ads-status-warning-800)]",
439
- pressed: "bg-[var(--ads-status-warning-300)] text-[var(--ads-status-warning-800)]",
440
- disabled: secondaryDisabled,
441
- },
442
- new: {
443
- active:
444
- "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)] hover:bg-[var(--ads-status-new-200)] active:bg-[var(--ads-status-new-300)]",
445
- hover: "bg-[var(--ads-status-new-200)] text-[var(--ads-status-new-800)]",
446
- pressed: "bg-[var(--ads-status-new-300)] text-[var(--ads-status-new-800)]",
447
- disabled: secondaryDisabled,
448
- },
449
- innovation: {
450
- active:
451
- "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)] hover:bg-[var(--ads-status-innovation-200)] active:bg-[var(--ads-status-innovation-300)]",
452
- hover: "bg-[var(--ads-status-innovation-200)] text-[var(--ads-status-innovation-800)]",
453
- pressed: "bg-[var(--ads-status-innovation-300)] text-[var(--ads-status-innovation-800)]",
454
- disabled: secondaryDisabled,
455
- },
456
- blocked: {
457
- active:
458
- "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-200)] active:bg-[var(--ads-status-void-300)]",
459
- hover: "bg-[var(--ads-status-void-200)] text-[var(--ads-status-void-800)]",
460
- pressed: "bg-[var(--ads-status-void-300)] text-[var(--ads-status-void-800)]",
461
- disabled: secondaryDisabled,
462
- },
463
- neutral: {
464
- active:
465
- "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-200)] active:bg-[var(--color-neutral-300)]",
466
- hover: "bg-[var(--color-neutral-200)] text-[var(--ads-text-primary)]",
467
- pressed: "bg-[var(--color-neutral-300)] text-[var(--ads-text-primary)]",
468
- disabled: secondaryDisabled,
469
- },
470
- };
471
- return faces[color === "primary" ? "success" : color][state];
472
- };
473
- </script>
474
-
475
- <style>
476
- /* Sem o registro, o navegador não interpola o ângulo e o tamanho: o brilho e a sombra saltariam. */
477
- @property --ads-button-ia-angle {
478
- syntax: "<angle>";
479
- inherits: true;
480
- initial-value: 0deg;
481
- }
482
-
483
- @property --ads-button-ia-glow-size {
484
- syntax: "<length>";
485
- inherits: false;
486
- initial-value: 2px;
487
- }
488
-
489
- /*
490
- * A sombra puxa para o lado do brilho: o centro do brilho fica 72deg (20% da volta) à frente do ângulo.
491
- * O ângulo do conic-gradient conta a partir do topo, no sentido horário.
492
- */
493
- .ads-button-ia {
494
- --ads-button-ia-glow-offset: calc(var(--ads-button-ia-glow-size) / 4);
495
- box-shadow: calc(sin(var(--ads-button-ia-angle) + 72deg) * var(--ads-button-ia-glow-offset))
496
- calc(cos(var(--ads-button-ia-angle) + 72deg) * -1 * var(--ads-button-ia-glow-offset))
497
- var(--ads-button-ia-glow-size) calc(var(--ads-button-ia-glow-size) / 8) var(--ads-button-ia-glow);
498
- animation:
499
- ads-button-ia-spin 3s linear infinite,
500
- ads-button-ia-glow 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
501
- }
502
-
503
- .ads-button-ia.is-ia-paused {
504
- animation-play-state: paused;
505
- }
506
-
507
- /*
508
- * A borda é transparente e o fundo pinta por baixo dela: o miolo fica no padding-box e a cor da borda no border-box.
509
- * Assim a espessura e o raio são os da borda nativa, sem camada extra desalinhada no subpixel.
510
- */
511
- .ads-button-ia-face {
512
- --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
513
- border-color: transparent;
514
- background:
515
- linear-gradient(var(--ads-button-ia-background) 0 0) padding-box,
516
- var(--ads-button-ia-edge) border-box;
517
- }
518
-
519
- .ads-button-ia-face.is-ia-muted {
520
- --ads-button-ia-edge: linear-gradient(
521
- color-mix(in srgb, var(--ads-button-ia-border) 40%, var(--ads-button-ia-background)) 0 0
522
- );
523
- }
524
-
525
- .ads-button-ia {
526
- --ads-button-ia-track: color-mix(in srgb, var(--ads-button-ia-border) 20%, var(--ads-button-ia-background));
527
- --ads-button-ia-edge: conic-gradient(
528
- from var(--ads-button-ia-angle),
529
- var(--ads-button-ia-track) 0%,
530
- var(--ads-button-ia-glow) 12%,
531
- var(--ads-status-innovation-500) 20%,
532
- var(--ads-button-ia-border) 24%,
533
- var(--ads-button-ia-track) 28%
534
- );
535
- }
536
-
537
- @keyframes ads-button-ia-spin {
538
- to {
539
- --ads-button-ia-angle: 360deg;
540
- }
541
- }
542
-
543
- /* Cresce durante a volta e recolhe no fim, para o loop não saltar. */
544
- @keyframes ads-button-ia-glow {
545
- 0% {
546
- --ads-button-ia-glow-size: 2px;
547
- }
548
- 80% {
549
- --ads-button-ia-glow-size: 16px;
550
- }
551
- 100% {
552
- --ads-button-ia-glow-size: 2px;
553
- }
554
- }
555
-
556
- .ads-button-ia:hover,
557
- .ads-button-ia:active {
558
- --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
559
- box-shadow: 0 0 16px 2px var(--ads-button-ia-glow);
560
- }
561
-
562
- @media (prefers-reduced-motion: reduce) {
563
- .ads-button-ia {
564
- --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
565
- box-shadow: 0 0 8px 0 var(--ads-button-ia-glow);
566
- animation: none;
567
- }
568
- }
569
- </style>
1
+ <template>
2
+ <component
3
+ :is="to ? NuxtLink : 'button'"
4
+ ref="root"
5
+ v-bind="rootAttrs"
6
+ class="inline-flex items-center justify-center [font-weight:var(--ads-button-weight)] whitespace-nowrap transition-colors duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
7
+ :class="[
8
+ type === 'ia' ? 'rounded-[var(--ads-button-ia-radius)]' : 'rounded-[var(--ads-button-radius)]',
9
+ type !== 'ia' && 'overflow-hidden',
10
+ sizeClass,
11
+ faceClass,
12
+ focusClass,
13
+ type === 'ia' && 'ads-button-ia-face',
14
+ type === 'ia' && visualState() === 'disabled' && 'is-ia-muted',
15
+ iaMotion,
16
+ iaPaused && 'is-ia-paused',
17
+ to && isDisabled ? 'pointer-events-none' : '',
18
+ ]"
19
+ @click="onRootClick"
20
+ >
21
+ <UIcon
22
+ v-if="loading"
23
+ name="i-lucide-loader-circle"
24
+ :class="[iconClass, 'animate-spin']"
25
+ data-loading
26
+ />
27
+ <UIcon v-else-if="iconOnly || leading" :name="icon" :class="iconClass" />
28
+ <span v-if="hasLabel"><slot>{{ label }}</slot></span>
29
+ <UIcon v-if="hasLabel && trailing && !loading" :name="trailingIcon || icon" :class="iconClass" />
30
+ </component>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from "vue";
35
+ import type { RouteLocationRaw } from "vue-router";
36
+ import { NuxtLink } from "#components";
37
+
38
+ export type AdsButtonType = "filled" | "outline" | "unfilled" | "secondary" | "ia";
39
+ export type AdsButtonSize = "md" | "sm";
40
+ export type AdsButtonState = "active" | "hover" | "pressed" | "disabled";
41
+ export type AdsButtonColor = "primary" | "success" | "attention" | "warning" | "new" | "innovation" | "blocked" | "neutral";
42
+
43
+ const props = withDefaults(
44
+ defineProps<{
45
+ type?: AdsButtonType;
46
+ /** Mesmas cores do badge, mais neutral para ação sem status. */
47
+ color?: AdsButtonColor;
48
+ size?: AdsButtonSize;
49
+ label?: string;
50
+ /** Ícone à esquerda. No Figma, o exemplo é sparkles. Sem rótulo, o ícone ocupa o botão sozinho. */
51
+ leading?: boolean;
52
+ /** Ícone à direita. Sem `trailingIcon`, usa `icon`. */
53
+ trailing?: boolean;
54
+ icon?: string;
55
+ trailingIcon?: string;
56
+ /** Vira link. Sem valor, continua `<button>`. */
57
+ to?: RouteLocationRaw;
58
+ disabled?: boolean;
59
+ /** Troca o ícone por spinner e bloqueia o clique. */
60
+ loading?: boolean;
61
+ /** `type` do `<button>`. A prop `type` já é o visual do ADS. */
62
+ nativeType?: "button" | "submit" | "reset";
63
+ /**
64
+ * Congela um estado para a documentação.
65
+ * Sem valor, hover e pressed vêm do ponteiro.
66
+ */
67
+ state?: AdsButtonState | null;
68
+ }>(),
69
+ {
70
+ type: "filled",
71
+ color: "primary",
72
+ size: "md",
73
+ label: "",
74
+ leading: false,
75
+ trailing: false,
76
+ icon: "i-lucide-sparkles",
77
+ trailingIcon: "",
78
+ to: undefined,
79
+ disabled: false,
80
+ loading: false,
81
+ nativeType: "button",
82
+ state: null,
83
+ },
84
+ );
85
+
86
+ const slots = useSlots();
87
+ const hasLabel = computed(() => props.label.length > 0 || Boolean(slots.default));
88
+ /** Sem rótulo o botão é quadrado: 40px no desktop, 32px no mobile. */
89
+ const iconOnly = computed(() => !hasLabel.value);
90
+ const isDisabled = computed(() => props.disabled || props.loading || props.state === "disabled");
91
+
92
+ const rootAttrs = computed(() =>
93
+ props.to
94
+ ? {
95
+ to: props.to,
96
+ tabindex: isDisabled.value ? -1 : undefined,
97
+ "aria-disabled": isDisabled.value || undefined,
98
+ "aria-busy": props.loading || undefined,
99
+ }
100
+ : {
101
+ type: props.nativeType,
102
+ disabled: isDisabled.value,
103
+ "aria-busy": props.loading || undefined,
104
+ },
105
+ );
106
+
107
+ const onRootClick = (event: MouseEvent) => {
108
+ if (props.to && isDisabled.value) event.preventDefault();
109
+ };
110
+
111
+ const sizeClass = computed(() => {
112
+ const iaLine = props.type === "ia" && props.size !== "sm" ? " leading-[var(--ads-button-ia-line-height)]" : "";
113
+ if (iconOnly.value) {
114
+ return props.size === "sm"
115
+ ? "size-[var(--ads-button-sm-icon-box)] shrink-0"
116
+ : "size-[var(--ads-button-md-icon-box)] shrink-0";
117
+ }
118
+ return props.size === "sm"
119
+ ? `h-[var(--ads-button-sm-height)] gap-[var(--ads-button-sm-gap)] px-[var(--ads-button-sm-padding-x)] text-[length:var(--ads-button-sm-font-size)] leading-[var(--ads-button-sm-line-height)]${iaLine}`
120
+ : `gap-[var(--ads-button-md-gap)] px-[var(--ads-button-md-padding-x)] py-[var(--ads-button-md-padding-y)] text-[length:var(--ads-button-md-font-size)] leading-[var(--ads-button-md-line-height)]${iaLine}`;
121
+ });
122
+
123
+ const iconClass = computed(() =>
124
+ props.size === "sm"
125
+ ? "size-[var(--ads-button-sm-icon)] shrink-0 text-[length:var(--ads-button-sm-icon)]"
126
+ : "size-[var(--ads-button-md-icon)] shrink-0 text-[length:var(--ads-button-md-icon)]",
127
+ );
128
+
129
+ const faceClass = computed(() => faceFor(props.type, props.color, visualState()));
130
+ const focusClass = computed(() =>
131
+ props.type === "ia" ? "focus-visible:outline-[var(--ads-button-ia-border)]" : focusFor[props.color],
132
+ );
133
+ /** Um brilho de gradiente gira em volta da borda do tipo IA. Hover, pressed e disabled seguram a borda fechada. */
134
+ const iaMotion = computed(() => {
135
+ if (props.type !== "ia" || isDisabled.value) return "";
136
+ if (props.state === "hover" || props.state === "pressed") return "";
137
+ return "ads-button-ia";
138
+ });
139
+
140
+ const root = ref<Element | { $el: Element } | null>(null);
141
+ const iaPaused = ref(false);
142
+ let iaFrameObserver: IntersectionObserver | null = null;
143
+
144
+ const watchIaRing = () => {
145
+ iaFrameObserver?.disconnect();
146
+ iaFrameObserver = null;
147
+ iaPaused.value = false;
148
+ if (!iaMotion.value) return;
149
+ nextTick(() => {
150
+ const node = root.value instanceof Element ? root.value : root.value?.$el;
151
+ if (!node || typeof IntersectionObserver === "undefined") return;
152
+ iaFrameObserver = new IntersectionObserver(([entry]) => {
153
+ iaPaused.value = !entry?.isIntersecting;
154
+ });
155
+ iaFrameObserver.observe(node);
156
+ });
157
+ };
158
+
159
+ watch(iaMotion, watchIaRing, { immediate: true });
160
+ onBeforeUnmount(() => {
161
+ iaFrameObserver?.disconnect();
162
+ });
163
+
164
+ const visualState = (): AdsButtonState => {
165
+ if (props.disabled || props.state === "disabled") return "disabled";
166
+ if (props.state === "hover" || props.state === "pressed") return props.state;
167
+ return "active";
168
+ };
169
+
170
+ const contrast = "text-[var(--ads-text-contrast)]";
171
+ const ink = "text-[var(--ads-text-primary)]";
172
+ const filledPrimaryText = "text-[var(--ads-button-primary-text)]";
173
+ const secondaryText = "text-[var(--ads-button-secondary-text)]";
174
+
175
+ const focusFor: Record<AdsButtonColor, string> = {
176
+ primary: "focus-visible:outline-[var(--ads-text-brand)]",
177
+ success: "focus-visible:outline-[var(--ads-status-success-700)]",
178
+ attention: "focus-visible:outline-[var(--ads-status-attention-800)]",
179
+ warning: "focus-visible:outline-[var(--ads-status-warning-600)]",
180
+ new: "focus-visible:outline-[var(--ads-status-new-700)]",
181
+ innovation: "focus-visible:outline-[var(--ads-status-innovation-700)]",
182
+ blocked: "focus-visible:outline-[var(--ads-status-void-800)]",
183
+ neutral: "focus-visible:outline-[var(--ads-text-primary)]",
184
+ };
185
+
186
+ /** Fundo e cor da borda ficam em `.ads-button-ia-face`: a borda é pintada pelo fundo, na mesma espessura em todos os estados. */
187
+ const iaFace = (state: AdsButtonState) => {
188
+ const base =
189
+ "border-(length:--ads-border-width-slim) border-solid text-[var(--ads-status-innovation-300)]";
190
+ if (state === "disabled") {
191
+ return `${base} text-[color-mix(in_srgb,var(--ads-status-innovation-300)_45%,transparent)]`;
192
+ }
193
+ if (state === "hover" || state === "pressed" || props.loading) {
194
+ return `${base} shadow-[0_0_16px_2px_var(--ads-button-ia-glow)]`;
195
+ }
196
+ return base;
197
+ };
198
+
199
+ const faceFor = (type: AdsButtonType, color: AdsButtonColor, state: AdsButtonState) => {
200
+ if (type === "ia") return iaFace(state);
201
+ if (color === "primary") return primaryFace(type, state);
202
+ if (type === "filled") return filledStatus(color, state);
203
+ if (type === "outline") return outlineStatus(color, state);
204
+ if (type === "unfilled") return unfilledStatus(color, state);
205
+ return secondaryStatus(color, state);
206
+ };
207
+
208
+ const primaryFace = (type: AdsButtonType, state: AdsButtonState) => {
209
+ if (type === "filled") return filledPrimary(state);
210
+ if (type === "outline") return outlinePrimary(state);
211
+ if (type === "unfilled") return unfilledPrimary(state);
212
+ return secondaryPrimary(state);
213
+ };
214
+
215
+ const filledPrimary = (state: AdsButtonState) => {
216
+ if (state === "hover") return `bg-[var(--ads-button-primary-hover)] ${filledPrimaryText}`;
217
+ if (state === "pressed") return `bg-[var(--ads-button-primary-pressed)] ${filledPrimaryText}`;
218
+ if (state === "disabled") return `bg-[var(--ads-button-primary-disabled)] ${filledPrimaryText}`;
219
+ return `bg-[var(--ads-button-primary)] ${filledPrimaryText} hover:bg-[var(--ads-button-primary-hover)] active:bg-[var(--ads-button-primary-pressed)]`;
220
+ };
221
+
222
+ const outlinePrimaryHover =
223
+ "border border-[var(--ads-text-brand)] bg-[var(--ads-button-secondary-hover)] text-[var(--ads-text-brand)] shadow-[inset_0_0_0_1px_var(--ads-text-brand)]";
224
+
225
+ const outlinePrimary = (state: AdsButtonState) => {
226
+ if (state === "hover" || state === "pressed") return outlinePrimaryHover;
227
+ if (state === "disabled") {
228
+ return "border border-[var(--ads-button-primary-disabled)] bg-[color-mix(in_srgb,var(--ads-button-secondary)_50%,transparent)] text-[var(--ads-button-primary-disabled)]";
229
+ }
230
+ return "border border-[var(--ads-text-brand)] text-[var(--ads-text-brand)] hover:bg-[var(--ads-button-secondary-hover)] hover:shadow-[inset_0_0_0_1px_var(--ads-text-brand)]";
231
+ };
232
+
233
+ const unfilledPrimary = (state: AdsButtonState) => {
234
+ if (state === "hover" || state === "pressed") return "text-[var(--ads-button-primary-hover)]";
235
+ if (state === "disabled") return "text-[var(--ads-button-primary-disabled)]";
236
+ return "text-[var(--ads-text-brand)] hover:text-[var(--ads-button-primary-hover)]";
237
+ };
238
+
239
+ const secondaryDisabled = "bg-[var(--ads-button-secondary-disabled)] text-[var(--ads-button-secondary-disabled-text)]";
240
+
241
+ const secondaryPrimary = (state: AdsButtonState) => {
242
+ if (state === "hover") return `bg-[var(--ads-button-secondary-hover)] ${secondaryText}`;
243
+ if (state === "pressed") return `bg-[var(--ads-button-secondary-pressed)] ${secondaryText}`;
244
+ if (state === "disabled") return secondaryDisabled;
245
+ return `bg-[var(--ads-button-secondary)] ${secondaryText} hover:bg-[var(--ads-button-secondary-hover)] active:bg-[var(--ads-button-secondary-pressed)]`;
246
+ };
247
+
248
+ /**
249
+ * Sucesso, atenção e new usam texto primary no sólido 500: branco não chega no AA.
250
+ * Warning e blocked seguram branco. Pressed escuro volta para o texto de contraste.
251
+ */
252
+ const filledStatus = (color: AdsButtonColor, state: AdsButtonState) => {
253
+ if (color === "success") {
254
+ if (state === "hover") return `bg-[var(--ads-status-success-600)] ${ink}`;
255
+ if (state === "pressed") return `bg-[var(--ads-status-success-800)] ${contrast}`;
256
+ if (state === "disabled") return "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]";
257
+ return `bg-[var(--ads-status-success-500)] ${ink} hover:bg-[var(--ads-status-success-600)] active:bg-[var(--ads-status-success-800)] active:text-[var(--ads-text-contrast)]`;
258
+ }
259
+ if (color === "attention") {
260
+ if (state === "hover") return `bg-[var(--ads-status-attention-600)] ${ink}`;
261
+ if (state === "pressed") return `bg-[var(--ads-status-attention-800)] ${contrast}`;
262
+ if (state === "disabled") return "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]";
263
+ return `bg-[var(--ads-status-attention-500)] ${ink} hover:bg-[var(--ads-status-attention-600)] active:bg-[var(--ads-status-attention-800)] active:text-[var(--ads-text-contrast)]`;
264
+ }
265
+ if (color === "warning") {
266
+ if (state === "hover") return `bg-[var(--ads-status-warning-600)] ${contrast}`;
267
+ if (state === "pressed") return `bg-[var(--ads-status-warning-800)] ${contrast}`;
268
+ if (state === "disabled") return "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)]";
269
+ return `bg-[var(--ads-status-warning-500)] ${contrast} hover:bg-[var(--ads-status-warning-600)] active:bg-[var(--ads-status-warning-800)]`;
270
+ }
271
+ if (color === "new") {
272
+ if (state === "hover") return `bg-[var(--ads-status-new-600)] ${ink}`;
273
+ if (state === "pressed") return `bg-[var(--ads-status-new-800)] ${contrast}`;
274
+ if (state === "disabled") return "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)]";
275
+ return `bg-[var(--ads-status-new-500)] ${ink} hover:bg-[var(--ads-status-new-600)] active:bg-[var(--ads-status-new-800)] active:text-[var(--ads-text-contrast)]`;
276
+ }
277
+ if (color === "innovation") {
278
+ if (state === "hover") return `bg-[var(--ads-status-innovation-600)] ${contrast}`;
279
+ if (state === "pressed") return `bg-[var(--ads-status-innovation-800)] ${contrast}`;
280
+ if (state === "disabled") return "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)]";
281
+ return `bg-[var(--ads-status-innovation-500)] ${contrast} hover:bg-[var(--ads-status-innovation-600)] active:bg-[var(--ads-status-innovation-800)]`;
282
+ }
283
+ if (color === "neutral") {
284
+ if (state === "hover") return `bg-[var(--color-neutral-800)] ${contrast}`;
285
+ if (state === "pressed") return `bg-[var(--color-neutral-900)] ${contrast}`;
286
+ if (state === "disabled") return "bg-[var(--color-neutral-100)] text-[var(--ads-text-tertiary)]";
287
+ return `bg-[var(--color-neutral-700)] ${contrast} hover:bg-[var(--color-neutral-800)] active:bg-[var(--color-neutral-900)]`;
288
+ }
289
+ if (state === "hover") return `bg-[color-mix(in_srgb,var(--ads-status-void-800)_78%,white)] ${contrast}`;
290
+ if (state === "pressed") return `bg-[color-mix(in_srgb,var(--ads-status-void-800)_70%,black)] ${contrast}`;
291
+ if (state === "disabled") return "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-600)]";
292
+ return `bg-[var(--ads-status-void-800)] ${contrast} hover:bg-[color-mix(in_srgb,var(--ads-status-void-800)_78%,white)] active:bg-[color-mix(in_srgb,var(--ads-status-void-800)_70%,black)]`;
293
+ };
294
+
295
+ const outlineStatus = (color: AdsButtonColor, state: AdsButtonState) => {
296
+ const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
297
+ success: {
298
+ active:
299
+ "border border-[var(--ads-status-success-700)] text-[var(--ads-status-success-700)] hover:bg-[var(--ads-status-success-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
300
+ hover:
301
+ "border border-[var(--ads-status-success-700)] bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)] shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
302
+ pressed:
303
+ "border border-[var(--ads-status-success-700)] bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)] shadow-[inset_0_0_0_1px_var(--ads-status-success-700)]",
304
+ disabled:
305
+ "border border-[var(--ads-status-success-300)] bg-[color-mix(in_srgb,var(--ads-status-success-100)_50%,transparent)] text-[var(--ads-status-success-300)]",
306
+ },
307
+ attention: {
308
+ active:
309
+ "border border-[var(--ads-status-attention-800)] text-[var(--ads-status-attention-800)] hover:bg-[var(--ads-status-attention-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
310
+ hover:
311
+ "border border-[var(--ads-status-attention-800)] bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)] shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
312
+ pressed:
313
+ "border border-[var(--ads-status-attention-800)] bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)] shadow-[inset_0_0_0_1px_var(--ads-status-attention-800)]",
314
+ disabled:
315
+ "border border-[var(--ads-status-attention-300)] bg-[color-mix(in_srgb,var(--ads-status-attention-100)_50%,transparent)] text-[var(--ads-status-attention-300)]",
316
+ },
317
+ warning: {
318
+ active:
319
+ "border border-[var(--ads-status-warning-600)] text-[var(--ads-status-warning-600)] hover:bg-[var(--ads-status-warning-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
320
+ hover:
321
+ "border border-[var(--ads-status-warning-600)] bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)] shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
322
+ pressed:
323
+ "border border-[var(--ads-status-warning-600)] bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)] shadow-[inset_0_0_0_1px_var(--ads-status-warning-600)]",
324
+ disabled:
325
+ "border border-[var(--ads-status-warning-300)] bg-[color-mix(in_srgb,var(--ads-status-warning-100)_50%,transparent)] text-[var(--ads-status-warning-300)]",
326
+ },
327
+ new: {
328
+ active:
329
+ "border border-[var(--ads-status-new-700)] text-[var(--ads-status-new-700)] hover:bg-[var(--ads-status-new-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
330
+ hover:
331
+ "border border-[var(--ads-status-new-700)] bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)] shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
332
+ pressed:
333
+ "border border-[var(--ads-status-new-700)] bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)] shadow-[inset_0_0_0_1px_var(--ads-status-new-700)]",
334
+ disabled:
335
+ "border border-[var(--ads-status-new-300)] bg-[color-mix(in_srgb,var(--ads-status-new-100)_50%,transparent)] text-[var(--ads-status-new-300)]",
336
+ },
337
+ innovation: {
338
+ active:
339
+ "border border-[var(--ads-status-innovation-700)] text-[var(--ads-status-innovation-700)] hover:bg-[var(--ads-status-innovation-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
340
+ hover:
341
+ "border border-[var(--ads-status-innovation-700)] bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)] shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
342
+ pressed:
343
+ "border border-[var(--ads-status-innovation-700)] bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)] shadow-[inset_0_0_0_1px_var(--ads-status-innovation-700)]",
344
+ disabled:
345
+ "border border-[var(--ads-status-innovation-300)] bg-[color-mix(in_srgb,var(--ads-status-innovation-100)_50%,transparent)] text-[var(--ads-status-innovation-300)]",
346
+ },
347
+ blocked: {
348
+ active:
349
+ "border border-[var(--ads-status-void-800)] text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-100)] hover:shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
350
+ hover:
351
+ "border border-[var(--ads-status-void-800)] bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
352
+ pressed:
353
+ "border border-[var(--ads-status-void-800)] bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] shadow-[inset_0_0_0_1px_var(--ads-status-void-800)]",
354
+ disabled:
355
+ "border border-[var(--ads-status-void-300)] bg-[color-mix(in_srgb,var(--ads-status-void-100)_50%,transparent)] text-[var(--ads-status-void-300)]",
356
+ },
357
+ neutral: {
358
+ active:
359
+ "border border-[var(--color-neutral-300)] text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-100)] hover:shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
360
+ hover:
361
+ "border border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
362
+ pressed:
363
+ "border border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] shadow-[inset_0_0_0_1px_var(--color-neutral-300)]",
364
+ disabled:
365
+ "border border-[var(--color-neutral-300)] bg-[color-mix(in_srgb,var(--color-neutral-100)_50%,transparent)] text-[var(--ads-text-tertiary)]",
366
+ },
367
+ };
368
+ return faces[color === "primary" ? "success" : color][state];
369
+ };
370
+
371
+ const unfilledStatus = (color: AdsButtonColor, state: AdsButtonState) => {
372
+ const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
373
+ success: {
374
+ active: "text-[var(--ads-status-success-700)] hover:bg-[var(--ads-status-success-100)]",
375
+ hover: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]",
376
+ pressed: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-700)]",
377
+ disabled: "text-[var(--ads-status-success-300)]",
378
+ },
379
+ attention: {
380
+ active: "text-[var(--ads-status-attention-800)] hover:bg-[var(--ads-status-attention-100)]",
381
+ hover: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
382
+ pressed: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
383
+ disabled: "text-[var(--ads-status-attention-300)]",
384
+ },
385
+ warning: {
386
+ active: "text-[var(--ads-status-warning-600)] hover:bg-[var(--ads-status-warning-100)]",
387
+ hover: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)]",
388
+ pressed: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-600)]",
389
+ disabled: "text-[var(--ads-status-warning-300)]",
390
+ },
391
+ new: {
392
+ active: "text-[var(--ads-status-new-700)] hover:bg-[var(--ads-status-new-100)]",
393
+ hover: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)]",
394
+ pressed: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-700)]",
395
+ disabled: "text-[var(--ads-status-new-300)]",
396
+ },
397
+ innovation: {
398
+ active: "text-[var(--ads-status-innovation-700)] hover:bg-[var(--ads-status-innovation-100)]",
399
+ hover: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)]",
400
+ pressed: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-700)]",
401
+ disabled: "text-[var(--ads-status-innovation-300)]",
402
+ },
403
+ blocked: {
404
+ active: "text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-100)]",
405
+ hover: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
406
+ pressed: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
407
+ disabled: "text-[var(--ads-status-void-300)]",
408
+ },
409
+ neutral: {
410
+ active: "text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-100)]",
411
+ hover: "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)]",
412
+ pressed: "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)]",
413
+ disabled: "text-[var(--ads-text-tertiary)]",
414
+ },
415
+ };
416
+ return faces[color === "primary" ? "success" : color][state];
417
+ };
418
+
419
+ const secondaryStatus = (color: AdsButtonColor, state: AdsButtonState) => {
420
+ const faces: Record<Exclude<AdsButtonColor, "primary">, Record<AdsButtonState, string>> = {
421
+ success: {
422
+ active:
423
+ "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)] hover:bg-[var(--ads-status-success-200)] active:bg-[var(--ads-status-success-300)]",
424
+ hover: "bg-[var(--ads-status-success-200)] text-[var(--ads-status-success-800)]",
425
+ pressed: "bg-[var(--ads-status-success-300)] text-[var(--ads-status-success-800)]",
426
+ disabled: secondaryDisabled,
427
+ },
428
+ attention: {
429
+ active:
430
+ "bg-[var(--ads-status-attention-100)] text-[var(--ads-text-primary)] hover:bg-[var(--ads-status-attention-200)] active:bg-[var(--ads-status-attention-300)]",
431
+ hover: "bg-[var(--ads-status-attention-200)] text-[var(--ads-text-primary)]",
432
+ pressed: "bg-[var(--ads-status-attention-300)] text-[var(--ads-text-primary)]",
433
+ disabled: secondaryDisabled,
434
+ },
435
+ warning: {
436
+ active:
437
+ "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)] hover:bg-[var(--ads-status-warning-200)] active:bg-[var(--ads-status-warning-300)]",
438
+ hover: "bg-[var(--ads-status-warning-200)] text-[var(--ads-status-warning-800)]",
439
+ pressed: "bg-[var(--ads-status-warning-300)] text-[var(--ads-status-warning-800)]",
440
+ disabled: secondaryDisabled,
441
+ },
442
+ new: {
443
+ active:
444
+ "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)] hover:bg-[var(--ads-status-new-200)] active:bg-[var(--ads-status-new-300)]",
445
+ hover: "bg-[var(--ads-status-new-200)] text-[var(--ads-status-new-800)]",
446
+ pressed: "bg-[var(--ads-status-new-300)] text-[var(--ads-status-new-800)]",
447
+ disabled: secondaryDisabled,
448
+ },
449
+ innovation: {
450
+ active:
451
+ "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)] hover:bg-[var(--ads-status-innovation-200)] active:bg-[var(--ads-status-innovation-300)]",
452
+ hover: "bg-[var(--ads-status-innovation-200)] text-[var(--ads-status-innovation-800)]",
453
+ pressed: "bg-[var(--ads-status-innovation-300)] text-[var(--ads-status-innovation-800)]",
454
+ disabled: secondaryDisabled,
455
+ },
456
+ blocked: {
457
+ active:
458
+ "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)] hover:bg-[var(--ads-status-void-200)] active:bg-[var(--ads-status-void-300)]",
459
+ hover: "bg-[var(--ads-status-void-200)] text-[var(--ads-status-void-800)]",
460
+ pressed: "bg-[var(--ads-status-void-300)] text-[var(--ads-status-void-800)]",
461
+ disabled: secondaryDisabled,
462
+ },
463
+ neutral: {
464
+ active:
465
+ "bg-[var(--color-neutral-100)] text-[var(--ads-text-primary)] hover:bg-[var(--color-neutral-200)] active:bg-[var(--color-neutral-300)]",
466
+ hover: "bg-[var(--color-neutral-200)] text-[var(--ads-text-primary)]",
467
+ pressed: "bg-[var(--color-neutral-300)] text-[var(--ads-text-primary)]",
468
+ disabled: secondaryDisabled,
469
+ },
470
+ };
471
+ return faces[color === "primary" ? "success" : color][state];
472
+ };
473
+ </script>
474
+
475
+ <style>
476
+ /* Sem o registro, o navegador não interpola o ângulo e o tamanho: o brilho e a sombra saltariam. */
477
+ @property --ads-button-ia-angle {
478
+ syntax: "<angle>";
479
+ inherits: true;
480
+ initial-value: 0deg;
481
+ }
482
+
483
+ @property --ads-button-ia-glow-size {
484
+ syntax: "<length>";
485
+ inherits: false;
486
+ initial-value: 2px;
487
+ }
488
+
489
+ /*
490
+ * A sombra puxa para o lado do brilho: o centro do brilho fica 72deg (20% da volta) à frente do ângulo.
491
+ * O ângulo do conic-gradient conta a partir do topo, no sentido horário.
492
+ */
493
+ .ads-button-ia {
494
+ --ads-button-ia-glow-offset: calc(var(--ads-button-ia-glow-size) / 4);
495
+ box-shadow: calc(sin(var(--ads-button-ia-angle) + 72deg) * var(--ads-button-ia-glow-offset))
496
+ calc(cos(var(--ads-button-ia-angle) + 72deg) * -1 * var(--ads-button-ia-glow-offset))
497
+ var(--ads-button-ia-glow-size) calc(var(--ads-button-ia-glow-size) / 8) var(--ads-button-ia-glow);
498
+ animation:
499
+ ads-button-ia-spin 3s linear infinite,
500
+ ads-button-ia-glow 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
501
+ }
502
+
503
+ .ads-button-ia.is-ia-paused {
504
+ animation-play-state: paused;
505
+ }
506
+
507
+ /*
508
+ * A borda é transparente e o fundo pinta por baixo dela: o miolo fica no padding-box e a cor da borda no border-box.
509
+ * Assim a espessura e o raio são os da borda nativa, sem camada extra desalinhada no subpixel.
510
+ */
511
+ .ads-button-ia-face {
512
+ --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
513
+ border-color: transparent;
514
+ background:
515
+ linear-gradient(var(--ads-button-ia-background) 0 0) padding-box,
516
+ var(--ads-button-ia-edge) border-box;
517
+ }
518
+
519
+ .ads-button-ia-face.is-ia-muted {
520
+ --ads-button-ia-edge: linear-gradient(
521
+ color-mix(in srgb, var(--ads-button-ia-border) 40%, var(--ads-button-ia-background)) 0 0
522
+ );
523
+ }
524
+
525
+ .ads-button-ia {
526
+ --ads-button-ia-track: color-mix(in srgb, var(--ads-button-ia-border) 20%, var(--ads-button-ia-background));
527
+ --ads-button-ia-edge: conic-gradient(
528
+ from var(--ads-button-ia-angle),
529
+ var(--ads-button-ia-track) 0%,
530
+ var(--ads-button-ia-glow) 12%,
531
+ var(--ads-status-innovation-500) 20%,
532
+ var(--ads-button-ia-border) 24%,
533
+ var(--ads-button-ia-track) 28%
534
+ );
535
+ }
536
+
537
+ @keyframes ads-button-ia-spin {
538
+ to {
539
+ --ads-button-ia-angle: 360deg;
540
+ }
541
+ }
542
+
543
+ /* Cresce durante a volta e recolhe no fim, para o loop não saltar. */
544
+ @keyframes ads-button-ia-glow {
545
+ 0% {
546
+ --ads-button-ia-glow-size: 2px;
547
+ }
548
+ 80% {
549
+ --ads-button-ia-glow-size: 16px;
550
+ }
551
+ 100% {
552
+ --ads-button-ia-glow-size: 2px;
553
+ }
554
+ }
555
+
556
+ .ads-button-ia:hover,
557
+ .ads-button-ia:active {
558
+ --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
559
+ box-shadow: 0 0 16px 2px var(--ads-button-ia-glow);
560
+ }
561
+
562
+ @media (prefers-reduced-motion: reduce) {
563
+ .ads-button-ia {
564
+ --ads-button-ia-edge: linear-gradient(var(--ads-button-ia-border) 0 0);
565
+ box-shadow: 0 0 8px 0 var(--ads-button-ia-glow);
566
+ animation: none;
567
+ }
568
+ }
569
+ </style>