markupr 2.1.8

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 (299) hide show
  1. package/.claude/commands/review-feedback.md +47 -0
  2. package/.eslintrc.json +35 -0
  3. package/.github/CODEOWNERS +16 -0
  4. package/.github/FUNDING.yml +1 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +56 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +54 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +89 -0
  8. package/.github/dependabot.yml +70 -0
  9. package/.github/workflows/ci.yml +184 -0
  10. package/.github/workflows/deploy-landing.yml +134 -0
  11. package/.github/workflows/nightly.yml +288 -0
  12. package/.github/workflows/release.yml +318 -0
  13. package/CHANGELOG.md +127 -0
  14. package/CLAUDE.md +137 -0
  15. package/CODE_OF_CONDUCT.md +9 -0
  16. package/CONTRIBUTING.md +390 -0
  17. package/LICENSE +21 -0
  18. package/PRODUCT_VISION.md +277 -0
  19. package/README.md +517 -0
  20. package/SECURITY.md +51 -0
  21. package/SIGNING_INSTRUCTIONS.md +284 -0
  22. package/assets/DMG_BACKGROUND_INSTRUCTIONS.md +130 -0
  23. package/assets/svg-source/dmg-background.svg +70 -0
  24. package/assets/svg-source/icon.svg +20 -0
  25. package/assets/svg-source/tray-icon-processing.svg +7 -0
  26. package/assets/svg-source/tray-icon-recording.svg +7 -0
  27. package/assets/svg-source/tray-icon.svg +6 -0
  28. package/assets/tray-complete.png +0 -0
  29. package/assets/tray-complete@2x.png +0 -0
  30. package/assets/tray-completeTemplate.png +0 -0
  31. package/assets/tray-completeTemplate@2x.png +0 -0
  32. package/assets/tray-error.png +0 -0
  33. package/assets/tray-error@2x.png +0 -0
  34. package/assets/tray-errorTemplate.png +0 -0
  35. package/assets/tray-errorTemplate@2x.png +0 -0
  36. package/assets/tray-icon-processing.png +0 -0
  37. package/assets/tray-icon-processing@2x.png +0 -0
  38. package/assets/tray-icon-processingTemplate.png +0 -0
  39. package/assets/tray-icon-processingTemplate@2x.png +0 -0
  40. package/assets/tray-icon-recording.png +0 -0
  41. package/assets/tray-icon-recording@2x.png +0 -0
  42. package/assets/tray-icon-recordingTemplate.png +0 -0
  43. package/assets/tray-icon-recordingTemplate@2x.png +0 -0
  44. package/assets/tray-icon.png +0 -0
  45. package/assets/tray-icon@2x.png +0 -0
  46. package/assets/tray-iconTemplate.png +0 -0
  47. package/assets/tray-iconTemplate@2x.png +0 -0
  48. package/assets/tray-idle.png +0 -0
  49. package/assets/tray-idle@2x.png +0 -0
  50. package/assets/tray-idleTemplate.png +0 -0
  51. package/assets/tray-idleTemplate@2x.png +0 -0
  52. package/assets/tray-processing-0.png +0 -0
  53. package/assets/tray-processing-0@2x.png +0 -0
  54. package/assets/tray-processing-0Template.png +0 -0
  55. package/assets/tray-processing-0Template@2x.png +0 -0
  56. package/assets/tray-processing-1.png +0 -0
  57. package/assets/tray-processing-1@2x.png +0 -0
  58. package/assets/tray-processing-1Template.png +0 -0
  59. package/assets/tray-processing-1Template@2x.png +0 -0
  60. package/assets/tray-processing-2.png +0 -0
  61. package/assets/tray-processing-2@2x.png +0 -0
  62. package/assets/tray-processing-2Template.png +0 -0
  63. package/assets/tray-processing-2Template@2x.png +0 -0
  64. package/assets/tray-processing-3.png +0 -0
  65. package/assets/tray-processing-3@2x.png +0 -0
  66. package/assets/tray-processing-3Template.png +0 -0
  67. package/assets/tray-processing-3Template@2x.png +0 -0
  68. package/assets/tray-processing.png +0 -0
  69. package/assets/tray-processing@2x.png +0 -0
  70. package/assets/tray-processingTemplate.png +0 -0
  71. package/assets/tray-processingTemplate@2x.png +0 -0
  72. package/assets/tray-recording.png +0 -0
  73. package/assets/tray-recording@2x.png +0 -0
  74. package/assets/tray-recordingTemplate.png +0 -0
  75. package/assets/tray-recordingTemplate@2x.png +0 -0
  76. package/build/DMG_BACKGROUND_SPEC.md +50 -0
  77. package/build/dmg-background.png +0 -0
  78. package/build/dmg-background@2x.png +0 -0
  79. package/build/entitlements.mac.inherit.plist +27 -0
  80. package/build/entitlements.mac.plist +41 -0
  81. package/build/favicon-16.png +0 -0
  82. package/build/favicon-180.png +0 -0
  83. package/build/favicon-192.png +0 -0
  84. package/build/favicon-32.png +0 -0
  85. package/build/favicon-48.png +0 -0
  86. package/build/favicon-512.png +0 -0
  87. package/build/favicon-64.png +0 -0
  88. package/build/icon-128.png +0 -0
  89. package/build/icon-16.png +0 -0
  90. package/build/icon-24.png +0 -0
  91. package/build/icon-256.png +0 -0
  92. package/build/icon-32.png +0 -0
  93. package/build/icon-48.png +0 -0
  94. package/build/icon-64.png +0 -0
  95. package/build/icon.icns +0 -0
  96. package/build/icon.ico +0 -0
  97. package/build/icon.iconset/icon_128x128.png +0 -0
  98. package/build/icon.iconset/icon_128x128@2x.png +0 -0
  99. package/build/icon.iconset/icon_16x16.png +0 -0
  100. package/build/icon.iconset/icon_16x16@2x.png +0 -0
  101. package/build/icon.iconset/icon_256x256.png +0 -0
  102. package/build/icon.iconset/icon_256x256@2x.png +0 -0
  103. package/build/icon.iconset/icon_32x32.png +0 -0
  104. package/build/icon.iconset/icon_32x32@2x.png +0 -0
  105. package/build/icon.iconset/icon_512x512.png +0 -0
  106. package/build/icon.iconset/icon_512x512@2x.png +0 -0
  107. package/build/icon.png +0 -0
  108. package/build/installer-header.bmp +0 -0
  109. package/build/installer-header.png +0 -0
  110. package/build/installer-sidebar.bmp +0 -0
  111. package/build/installer-sidebar.png +0 -0
  112. package/build/installer.nsh +45 -0
  113. package/build/overlay-processing.png +0 -0
  114. package/build/overlay-recording.png +0 -0
  115. package/build/toolbar-record.png +0 -0
  116. package/build/toolbar-screenshot.png +0 -0
  117. package/build/toolbar-settings.png +0 -0
  118. package/build/toolbar-stop.png +0 -0
  119. package/dist/main/index.mjs +12612 -0
  120. package/dist/preload/index.mjs +907 -0
  121. package/dist/renderer/assets/index-CCmUjl9K.js +19495 -0
  122. package/dist/renderer/assets/index-CUqz_Gs6.css +2270 -0
  123. package/dist/renderer/index.html +27 -0
  124. package/docs/AI_AGENT_QUICKSTART.md +42 -0
  125. package/docs/AI_PIPELINE_DESIGN.md +595 -0
  126. package/docs/API.md +514 -0
  127. package/docs/ARCHITECTURE.md +460 -0
  128. package/docs/CONFIGURATION.md +336 -0
  129. package/docs/DEVELOPMENT.md +508 -0
  130. package/docs/EXPORT_FORMATS.md +451 -0
  131. package/docs/GETTING_STARTED.md +236 -0
  132. package/docs/KEYBOARD_SHORTCUTS.md +334 -0
  133. package/docs/TROUBLESHOOTING.md +418 -0
  134. package/docs/landing/index.html +672 -0
  135. package/docs/landing/script.js +342 -0
  136. package/docs/landing/styles.css +1543 -0
  137. package/electron-builder.yml +140 -0
  138. package/electron.vite.config.ts +63 -0
  139. package/package.json +108 -0
  140. package/railway.json +12 -0
  141. package/scripts/build.mjs +51 -0
  142. package/scripts/generate-icons.mjs +314 -0
  143. package/scripts/generate-installer-images.cjs +253 -0
  144. package/scripts/generate-tray-icons.mjs +258 -0
  145. package/scripts/notarize.cjs +180 -0
  146. package/scripts/one-click-clean-test.sh +147 -0
  147. package/scripts/postinstall.mjs +36 -0
  148. package/scripts/setup-markupr.sh +55 -0
  149. package/setup +17 -0
  150. package/site/index.html +1835 -0
  151. package/site/package.json +11 -0
  152. package/site/railway.json +12 -0
  153. package/site/server.js +31 -0
  154. package/src/main/AutoUpdater.ts +392 -0
  155. package/src/main/CrashRecovery.ts +655 -0
  156. package/src/main/ErrorHandler.ts +703 -0
  157. package/src/main/HotkeyManager.ts +399 -0
  158. package/src/main/MenuManager.ts +529 -0
  159. package/src/main/PermissionManager.ts +420 -0
  160. package/src/main/SessionController.ts +1465 -0
  161. package/src/main/TrayManager.ts +540 -0
  162. package/src/main/ai/AIPipelineManager.ts +199 -0
  163. package/src/main/ai/ClaudeAnalyzer.ts +339 -0
  164. package/src/main/ai/ImageOptimizer.ts +176 -0
  165. package/src/main/ai/StructuredMarkdownBuilder.ts +379 -0
  166. package/src/main/ai/index.ts +16 -0
  167. package/src/main/ai/types.ts +258 -0
  168. package/src/main/analysis/ClarificationGenerator.ts +385 -0
  169. package/src/main/analysis/FeedbackAnalyzer.ts +531 -0
  170. package/src/main/analysis/index.ts +19 -0
  171. package/src/main/audio/AudioCapture.ts +978 -0
  172. package/src/main/audio/audioUtils.ts +100 -0
  173. package/src/main/audio/index.ts +20 -0
  174. package/src/main/capture/index.ts +1 -0
  175. package/src/main/index.ts +1693 -0
  176. package/src/main/ipc/captureHandlers.ts +272 -0
  177. package/src/main/ipc/index.ts +45 -0
  178. package/src/main/ipc/outputHandlers.ts +302 -0
  179. package/src/main/ipc/sessionHandlers.ts +56 -0
  180. package/src/main/ipc/settingsHandlers.ts +471 -0
  181. package/src/main/ipc/types.ts +56 -0
  182. package/src/main/ipc/windowHandlers.ts +277 -0
  183. package/src/main/output/ClipboardService.ts +369 -0
  184. package/src/main/output/ExportService.ts +539 -0
  185. package/src/main/output/FileManager.ts +416 -0
  186. package/src/main/output/MarkdownGenerator.ts +791 -0
  187. package/src/main/output/MarkdownPatcher.ts +299 -0
  188. package/src/main/output/index.ts +186 -0
  189. package/src/main/output/sessionAdapter.ts +207 -0
  190. package/src/main/output/templates/html-template.ts +553 -0
  191. package/src/main/pipeline/FrameExtractor.ts +330 -0
  192. package/src/main/pipeline/PostProcessor.ts +399 -0
  193. package/src/main/pipeline/TranscriptAnalyzer.ts +226 -0
  194. package/src/main/pipeline/index.ts +36 -0
  195. package/src/main/platform/WindowsTaskbar.ts +600 -0
  196. package/src/main/platform/index.ts +16 -0
  197. package/src/main/settings/SettingsManager.ts +730 -0
  198. package/src/main/settings/index.ts +19 -0
  199. package/src/main/transcription/ModelDownloadManager.ts +494 -0
  200. package/src/main/transcription/TierManager.ts +219 -0
  201. package/src/main/transcription/TranscriptionRecoveryService.ts +340 -0
  202. package/src/main/transcription/WhisperService.ts +748 -0
  203. package/src/main/transcription/index.ts +56 -0
  204. package/src/main/transcription/types.ts +135 -0
  205. package/src/main/windows/PopoverManager.ts +284 -0
  206. package/src/main/windows/TaskbarIntegration.ts +452 -0
  207. package/src/main/windows/index.ts +23 -0
  208. package/src/preload/index.ts +1047 -0
  209. package/src/renderer/App.tsx +515 -0
  210. package/src/renderer/AppWrapper.tsx +28 -0
  211. package/src/renderer/assets/logo-dark.svg +7 -0
  212. package/src/renderer/assets/logo.svg +7 -0
  213. package/src/renderer/audio/AudioCaptureRenderer.ts +454 -0
  214. package/src/renderer/capture/ScreenRecordingRenderer.ts +492 -0
  215. package/src/renderer/components/AnnotationOverlay.tsx +836 -0
  216. package/src/renderer/components/AudioWaveform.tsx +811 -0
  217. package/src/renderer/components/ClarificationQuestions.tsx +656 -0
  218. package/src/renderer/components/CountdownTimer.tsx +495 -0
  219. package/src/renderer/components/CrashRecoveryDialog.tsx +632 -0
  220. package/src/renderer/components/DonateButton.tsx +127 -0
  221. package/src/renderer/components/ErrorBoundary.tsx +308 -0
  222. package/src/renderer/components/ExportDialog.tsx +872 -0
  223. package/src/renderer/components/HotkeyHint.tsx +261 -0
  224. package/src/renderer/components/KeyboardShortcuts.tsx +787 -0
  225. package/src/renderer/components/ModelDownloadDialog.tsx +844 -0
  226. package/src/renderer/components/Onboarding.tsx +1830 -0
  227. package/src/renderer/components/ProcessingOverlay.tsx +157 -0
  228. package/src/renderer/components/RecordingOverlay.tsx +423 -0
  229. package/src/renderer/components/SessionHistory.tsx +1746 -0
  230. package/src/renderer/components/SessionReview.tsx +1321 -0
  231. package/src/renderer/components/SettingsPanel.tsx +217 -0
  232. package/src/renderer/components/Skeleton.tsx +347 -0
  233. package/src/renderer/components/StatusIndicator.tsx +86 -0
  234. package/src/renderer/components/ThemeProvider.tsx +429 -0
  235. package/src/renderer/components/Tooltip.tsx +370 -0
  236. package/src/renderer/components/TranscriptionPreview.tsx +183 -0
  237. package/src/renderer/components/TranscriptionTierSelector.tsx +640 -0
  238. package/src/renderer/components/UpdateNotification.tsx +377 -0
  239. package/src/renderer/components/WindowSelector.tsx +947 -0
  240. package/src/renderer/components/index.ts +99 -0
  241. package/src/renderer/components/primitives/ApiKeyInput.tsx +98 -0
  242. package/src/renderer/components/primitives/ColorPicker.tsx +65 -0
  243. package/src/renderer/components/primitives/DangerButton.tsx +45 -0
  244. package/src/renderer/components/primitives/DirectoryPicker.tsx +41 -0
  245. package/src/renderer/components/primitives/Dropdown.tsx +34 -0
  246. package/src/renderer/components/primitives/KeyRecorder.tsx +117 -0
  247. package/src/renderer/components/primitives/SettingsSection.tsx +32 -0
  248. package/src/renderer/components/primitives/Slider.tsx +43 -0
  249. package/src/renderer/components/primitives/Toggle.tsx +36 -0
  250. package/src/renderer/components/primitives/index.ts +10 -0
  251. package/src/renderer/components/settings/AdvancedTab.tsx +174 -0
  252. package/src/renderer/components/settings/AppearanceTab.tsx +77 -0
  253. package/src/renderer/components/settings/GeneralTab.tsx +40 -0
  254. package/src/renderer/components/settings/HotkeysTab.tsx +79 -0
  255. package/src/renderer/components/settings/RecordingTab.tsx +84 -0
  256. package/src/renderer/components/settings/index.ts +9 -0
  257. package/src/renderer/components/settings/settingsStyles.ts +673 -0
  258. package/src/renderer/components/settings/tabConfig.tsx +85 -0
  259. package/src/renderer/components/settings/useSettingsPanel.ts +447 -0
  260. package/src/renderer/contexts/ProcessingContext.tsx +227 -0
  261. package/src/renderer/contexts/RecordingContext.tsx +683 -0
  262. package/src/renderer/contexts/UIContext.tsx +326 -0
  263. package/src/renderer/contexts/index.ts +24 -0
  264. package/src/renderer/donateMessages.ts +69 -0
  265. package/src/renderer/hooks/index.ts +75 -0
  266. package/src/renderer/hooks/useAnimation.tsx +544 -0
  267. package/src/renderer/hooks/useTheme.ts +313 -0
  268. package/src/renderer/index.html +26 -0
  269. package/src/renderer/main.tsx +52 -0
  270. package/src/renderer/styles/animations.css +1093 -0
  271. package/src/renderer/styles/app-shell.css +662 -0
  272. package/src/renderer/styles/globals.css +515 -0
  273. package/src/renderer/styles/theme.ts +578 -0
  274. package/src/renderer/types/electron.d.ts +385 -0
  275. package/src/shared/hotkeys.ts +283 -0
  276. package/src/shared/types.ts +809 -0
  277. package/tests/clipboard.test.ts +228 -0
  278. package/tests/e2e/criticalPaths.test.ts +594 -0
  279. package/tests/feedbackAnalyzer.test.ts +303 -0
  280. package/tests/integration/sessionFlow.test.ts +583 -0
  281. package/tests/markdownGenerator.test.ts +418 -0
  282. package/tests/output.test.ts +96 -0
  283. package/tests/setup.ts +486 -0
  284. package/tests/unit/appIntegration.test.ts +676 -0
  285. package/tests/unit/appViewState.test.ts +281 -0
  286. package/tests/unit/audioIpcChannels.test.ts +17 -0
  287. package/tests/unit/exportService.test.ts +492 -0
  288. package/tests/unit/hotkeys.test.ts +92 -0
  289. package/tests/unit/navigationPreload.test.ts +94 -0
  290. package/tests/unit/onboardingFlow.test.ts +345 -0
  291. package/tests/unit/permissionManager.test.ts +175 -0
  292. package/tests/unit/permissionManagerExpanded.test.ts +296 -0
  293. package/tests/unit/screenRecordingRenderer.test.ts +368 -0
  294. package/tests/unit/sessionController.test.ts +515 -0
  295. package/tests/unit/tierManager.test.ts +61 -0
  296. package/tests/unit/tierManagerExpanded.test.ts +142 -0
  297. package/tests/unit/transcriptAnalyzer.test.ts +64 -0
  298. package/tsconfig.json +25 -0
  299. package/vitest.config.ts +46 -0
@@ -0,0 +1,1093 @@
1
+ /**
2
+ * markupr - Premium Animation System
3
+ *
4
+ * A comprehensive collection of micro-animations designed for 60fps performance.
5
+ * Uses GPU-accelerated properties (transform, opacity) for smooth rendering.
6
+ *
7
+ * Includes:
8
+ * - Spring physics for dialogs
9
+ * - Staggered list animations
10
+ * - State transitions (recording, processing, complete, error)
11
+ * - Loading skeletons with shimmer
12
+ * - Success/error feedback
13
+ * - Button press effects
14
+ * - Tooltip animations
15
+ * - Smooth scrolling
16
+ */
17
+
18
+ /* ============================================================================
19
+ CSS Custom Properties (Design Tokens)
20
+ ============================================================================ */
21
+
22
+ :root {
23
+ /* Timing Functions - Spring Physics */
24
+ --spring-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
25
+ --spring-smooth: cubic-bezier(0.16, 1, 0.3, 1);
26
+ --spring-snap: cubic-bezier(0.68, -0.55, 0.265, 1.55);
27
+
28
+ /* Timing Functions - Standard */
29
+ --ease-out: cubic-bezier(0.0, 0, 0.2, 1);
30
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
31
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
32
+
33
+ /* Durations */
34
+ --duration-instant: 100ms;
35
+ --duration-fast: 150ms;
36
+ --duration-normal: 250ms;
37
+ --duration-slow: 400ms;
38
+ --duration-slower: 600ms;
39
+
40
+ /* Colors for state animations — sourced from theme tokens */
41
+ --color-recording: var(--status-error);
42
+ --color-processing: var(--status-warning);
43
+ --color-success: var(--status-success);
44
+ --color-error: var(--status-error);
45
+ --color-info: var(--status-info);
46
+ }
47
+
48
+ /* ============================================================================
49
+ Reduced Motion Support
50
+ ============================================================================ */
51
+
52
+ @media (prefers-reduced-motion: reduce) {
53
+ *,
54
+ *::before,
55
+ *::after {
56
+ animation-duration: 0.01ms !important;
57
+ animation-iteration-count: 1 !important;
58
+ transition-duration: 0.01ms !important;
59
+ }
60
+ }
61
+
62
+ /* ============================================================================
63
+ Dialog / Modal Animations
64
+ ============================================================================ */
65
+
66
+ /* Spring entrance for dialogs */
67
+ @keyframes dialogEnter {
68
+ 0% {
69
+ opacity: 0;
70
+ transform: scale(0.95) translateY(10px);
71
+ }
72
+ 100% {
73
+ opacity: 1;
74
+ transform: scale(1) translateY(0);
75
+ }
76
+ }
77
+
78
+ @keyframes dialogExit {
79
+ 0% {
80
+ opacity: 1;
81
+ transform: scale(1) translateY(0);
82
+ }
83
+ 100% {
84
+ opacity: 0;
85
+ transform: scale(0.95) translateY(10px);
86
+ }
87
+ }
88
+
89
+ /* Backdrop fade */
90
+ @keyframes backdropFadeIn {
91
+ from {
92
+ opacity: 0;
93
+ backdrop-filter: blur(0px);
94
+ }
95
+ to {
96
+ opacity: 1;
97
+ backdrop-filter: blur(8px);
98
+ }
99
+ }
100
+
101
+ @keyframes backdropFadeOut {
102
+ from {
103
+ opacity: 1;
104
+ backdrop-filter: blur(8px);
105
+ }
106
+ to {
107
+ opacity: 0;
108
+ backdrop-filter: blur(0px);
109
+ }
110
+ }
111
+
112
+ .ff-dialog-enter {
113
+ animation: dialogEnter var(--duration-normal) var(--spring-bounce);
114
+ }
115
+
116
+ .ff-dialog-exit {
117
+ animation: dialogExit var(--duration-fast) var(--ease-out);
118
+ }
119
+
120
+ .ff-backdrop-enter {
121
+ animation: backdropFadeIn var(--duration-normal) var(--ease-out);
122
+ }
123
+
124
+ .ff-backdrop-exit {
125
+ animation: backdropFadeOut var(--duration-fast) var(--ease-out);
126
+ }
127
+
128
+ /* ============================================================================
129
+ Staggered List Animations
130
+ ============================================================================ */
131
+
132
+ @keyframes listItemEnter {
133
+ from {
134
+ opacity: 0;
135
+ transform: translateY(8px);
136
+ }
137
+ to {
138
+ opacity: 1;
139
+ transform: translateY(0);
140
+ }
141
+ }
142
+
143
+ @keyframes listItemEnterLeft {
144
+ from {
145
+ opacity: 0;
146
+ transform: translateX(-12px);
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ transform: translateX(0);
151
+ }
152
+ }
153
+
154
+ @keyframes listItemEnterRight {
155
+ from {
156
+ opacity: 0;
157
+ transform: translateX(12px);
158
+ }
159
+ to {
160
+ opacity: 1;
161
+ transform: translateX(0);
162
+ }
163
+ }
164
+
165
+ @keyframes listItemEnterScale {
166
+ from {
167
+ opacity: 0;
168
+ transform: scale(0.9);
169
+ }
170
+ to {
171
+ opacity: 1;
172
+ transform: scale(1);
173
+ }
174
+ }
175
+
176
+ .ff-list-item-enter {
177
+ animation: listItemEnter var(--duration-normal) var(--spring-smooth) both;
178
+ }
179
+
180
+ .ff-list-item-enter-left {
181
+ animation: listItemEnterLeft var(--duration-normal) var(--spring-smooth) both;
182
+ }
183
+
184
+ .ff-list-item-enter-right {
185
+ animation: listItemEnterRight var(--duration-normal) var(--spring-smooth) both;
186
+ }
187
+
188
+ .ff-list-item-enter-scale {
189
+ animation: listItemEnterScale var(--duration-normal) var(--spring-bounce) both;
190
+ }
191
+
192
+ /* Stagger delays (use with data-stagger-index or inline style) */
193
+ .ff-stagger-1 { animation-delay: 50ms; }
194
+ .ff-stagger-2 { animation-delay: 100ms; }
195
+ .ff-stagger-3 { animation-delay: 150ms; }
196
+ .ff-stagger-4 { animation-delay: 200ms; }
197
+ .ff-stagger-5 { animation-delay: 250ms; }
198
+ .ff-stagger-6 { animation-delay: 300ms; }
199
+ .ff-stagger-7 { animation-delay: 350ms; }
200
+ .ff-stagger-8 { animation-delay: 400ms; }
201
+ .ff-stagger-9 { animation-delay: 450ms; }
202
+ .ff-stagger-10 { animation-delay: 500ms; }
203
+
204
+ /* ============================================================================
205
+ Recording State Animations
206
+ ============================================================================ */
207
+
208
+ /* Pulsing glow for recording state */
209
+ @keyframes recordingPulse {
210
+ 0%, 100% {
211
+ box-shadow:
212
+ 0 0 0 0 rgba(239, 68, 68, 0.4),
213
+ 0 4px 12px -2px rgba(239, 68, 68, 0.3);
214
+ }
215
+ 50% {
216
+ box-shadow:
217
+ 0 0 0 8px rgba(239, 68, 68, 0),
218
+ 0 8px 20px -4px rgba(239, 68, 68, 0.4);
219
+ }
220
+ }
221
+
222
+ @keyframes recordingDotPulse {
223
+ 0%, 100% {
224
+ opacity: 1;
225
+ transform: scale(1);
226
+ }
227
+ 50% {
228
+ opacity: 0.6;
229
+ transform: scale(0.9);
230
+ }
231
+ }
232
+
233
+ @keyframes recordingRing {
234
+ 0% {
235
+ transform: scale(1);
236
+ opacity: 0.6;
237
+ }
238
+ 100% {
239
+ transform: scale(2);
240
+ opacity: 0;
241
+ }
242
+ }
243
+
244
+ .ff-recording-pulse {
245
+ animation: recordingPulse 2s ease-in-out infinite;
246
+ }
247
+
248
+ .ff-recording-dot {
249
+ animation: recordingDotPulse 1.5s ease-in-out infinite;
250
+ }
251
+
252
+ .ff-recording-ring {
253
+ animation: recordingRing 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
254
+ }
255
+
256
+ /* ============================================================================
257
+ Processing State Animations
258
+ ============================================================================ */
259
+
260
+ @keyframes processingGradient {
261
+ 0% {
262
+ background-position: 200% 0;
263
+ }
264
+ 100% {
265
+ background-position: -200% 0;
266
+ }
267
+ }
268
+
269
+ @keyframes processingPulse {
270
+ 0%, 100% {
271
+ opacity: 1;
272
+ box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
273
+ }
274
+ 50% {
275
+ opacity: 0.8;
276
+ box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
277
+ }
278
+ }
279
+
280
+ @keyframes processingRotate {
281
+ from {
282
+ transform: rotate(0deg);
283
+ }
284
+ to {
285
+ transform: rotate(360deg);
286
+ }
287
+ }
288
+
289
+ .ff-processing-gradient {
290
+ background: linear-gradient(
291
+ 90deg,
292
+ rgba(245, 158, 11, 0.1) 0%,
293
+ rgba(245, 158, 11, 0.3) 50%,
294
+ rgba(245, 158, 11, 0.1) 100%
295
+ );
296
+ background-size: 200% 100%;
297
+ animation: processingGradient 1.5s linear infinite;
298
+ }
299
+
300
+ .ff-processing-pulse {
301
+ animation: processingPulse 1.5s ease-in-out infinite;
302
+ }
303
+
304
+ .ff-processing-rotate {
305
+ animation: processingRotate 1s linear infinite;
306
+ }
307
+
308
+ /* ============================================================================
309
+ Success State Animations
310
+ ============================================================================ */
311
+
312
+ @keyframes successPulse {
313
+ 0%, 100% {
314
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
315
+ }
316
+ 50% {
317
+ box-shadow: 0 0 0 12px rgba(16, 185, 129, 0);
318
+ }
319
+ }
320
+
321
+ @keyframes successBounce {
322
+ 0%, 100% {
323
+ transform: scale(1);
324
+ }
325
+ 25% {
326
+ transform: scale(1.1);
327
+ }
328
+ 50% {
329
+ transform: scale(0.95);
330
+ }
331
+ 75% {
332
+ transform: scale(1.02);
333
+ }
334
+ }
335
+
336
+ @keyframes successCheckDraw {
337
+ 0% {
338
+ stroke-dashoffset: 24;
339
+ }
340
+ 100% {
341
+ stroke-dashoffset: 0;
342
+ }
343
+ }
344
+
345
+ @keyframes successPop {
346
+ 0% {
347
+ transform: scale(0);
348
+ opacity: 0;
349
+ }
350
+ 50% {
351
+ transform: scale(1.2);
352
+ }
353
+ 100% {
354
+ transform: scale(1);
355
+ opacity: 1;
356
+ }
357
+ }
358
+
359
+ .ff-success-pulse {
360
+ animation: successPulse 1.5s ease-in-out;
361
+ }
362
+
363
+ .ff-success-bounce {
364
+ animation: successBounce 0.5s var(--spring-bounce);
365
+ }
366
+
367
+ .ff-success-check {
368
+ stroke-dasharray: 24;
369
+ stroke-dashoffset: 24;
370
+ animation: successCheckDraw 0.4s var(--ease-out) 0.2s forwards;
371
+ }
372
+
373
+ .ff-success-pop {
374
+ animation: successPop 0.4s var(--spring-bounce);
375
+ }
376
+
377
+ /* ============================================================================
378
+ Error State Animations
379
+ ============================================================================ */
380
+
381
+ @keyframes errorShake {
382
+ 0%, 100% {
383
+ transform: translateX(0);
384
+ }
385
+ 10%, 30%, 50%, 70%, 90% {
386
+ transform: translateX(-4px);
387
+ }
388
+ 20%, 40%, 60%, 80% {
389
+ transform: translateX(4px);
390
+ }
391
+ }
392
+
393
+ @keyframes errorPulse {
394
+ 0%, 100% {
395
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
396
+ border-color: rgba(239, 68, 68, 0.5);
397
+ }
398
+ 50% {
399
+ box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);
400
+ border-color: rgba(239, 68, 68, 0.8);
401
+ }
402
+ }
403
+
404
+ @keyframes errorFlash {
405
+ 0%, 100% {
406
+ background-color: transparent;
407
+ }
408
+ 50% {
409
+ background-color: rgba(239, 68, 68, 0.1);
410
+ }
411
+ }
412
+
413
+ .ff-error-shake {
414
+ animation: errorShake 0.5s var(--ease-out);
415
+ }
416
+
417
+ .ff-error-pulse {
418
+ animation: errorPulse 1s ease-in-out 2;
419
+ }
420
+
421
+ .ff-error-flash {
422
+ animation: errorFlash 0.3s ease-in-out 2;
423
+ }
424
+
425
+ /* ============================================================================
426
+ Skeleton Loading Animations
427
+ ============================================================================ */
428
+
429
+ @keyframes shimmer {
430
+ 0% {
431
+ background-position: -200% 0;
432
+ }
433
+ 100% {
434
+ background-position: 200% 0;
435
+ }
436
+ }
437
+
438
+ @keyframes skeletonPulse {
439
+ 0%, 100% {
440
+ opacity: 1;
441
+ }
442
+ 50% {
443
+ opacity: 0.5;
444
+ }
445
+ }
446
+
447
+ .ff-skeleton {
448
+ background: linear-gradient(
449
+ 90deg,
450
+ rgba(55, 65, 81, 0.3) 0%,
451
+ rgba(75, 85, 99, 0.5) 50%,
452
+ rgba(55, 65, 81, 0.3) 100%
453
+ );
454
+ background-size: 200% 100%;
455
+ animation: shimmer 1.5s ease-in-out infinite;
456
+ border-radius: 8px;
457
+ }
458
+
459
+ .ff-skeleton-pulse {
460
+ animation: skeletonPulse 1.5s ease-in-out infinite;
461
+ }
462
+
463
+ /* Skeleton variants */
464
+ .ff-skeleton-text {
465
+ height: 16px;
466
+ margin-bottom: 8px;
467
+ }
468
+
469
+ .ff-skeleton-title {
470
+ height: 24px;
471
+ width: 60%;
472
+ margin-bottom: 12px;
473
+ }
474
+
475
+ .ff-skeleton-avatar {
476
+ width: 40px;
477
+ height: 40px;
478
+ border-radius: 50%;
479
+ }
480
+
481
+ .ff-skeleton-thumbnail {
482
+ width: 100%;
483
+ aspect-ratio: 16/9;
484
+ border-radius: 8px;
485
+ }
486
+
487
+ .ff-skeleton-button {
488
+ height: 40px;
489
+ width: 120px;
490
+ border-radius: 8px;
491
+ }
492
+
493
+ /* ============================================================================
494
+ Button Press Effects
495
+ ============================================================================ */
496
+
497
+ .ff-btn-press {
498
+ transition: transform var(--duration-instant) var(--ease-out),
499
+ box-shadow var(--duration-instant) var(--ease-out);
500
+ will-change: transform;
501
+ }
502
+
503
+ .ff-btn-press:hover {
504
+ transform: translateY(-1px);
505
+ }
506
+
507
+ .ff-btn-press:active {
508
+ transform: scale(0.97) translateY(0);
509
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
510
+ }
511
+
512
+ /* Scale press effect */
513
+ .ff-btn-scale {
514
+ transition: transform var(--duration-instant) var(--spring-bounce);
515
+ will-change: transform;
516
+ }
517
+
518
+ .ff-btn-scale:hover {
519
+ transform: scale(1.02);
520
+ }
521
+
522
+ .ff-btn-scale:active {
523
+ transform: scale(0.95);
524
+ }
525
+
526
+ /* Ripple effect (requires JS for position) */
527
+ @keyframes ripple {
528
+ to {
529
+ transform: scale(4);
530
+ opacity: 0;
531
+ }
532
+ }
533
+
534
+ .ff-ripple {
535
+ position: absolute;
536
+ border-radius: 50%;
537
+ background: rgba(255, 255, 255, 0.3);
538
+ transform: scale(0);
539
+ animation: ripple 0.6s linear;
540
+ pointer-events: none;
541
+ }
542
+
543
+ /* ============================================================================
544
+ Tooltip Animations
545
+ ============================================================================ */
546
+
547
+ @keyframes tooltipFadeIn {
548
+ from {
549
+ opacity: 0;
550
+ transform: translateY(4px) scale(0.95);
551
+ }
552
+ to {
553
+ opacity: 1;
554
+ transform: translateY(0) scale(1);
555
+ }
556
+ }
557
+
558
+ @keyframes tooltipFadeOut {
559
+ from {
560
+ opacity: 1;
561
+ transform: translateY(0) scale(1);
562
+ }
563
+ to {
564
+ opacity: 0;
565
+ transform: translateY(4px) scale(0.95);
566
+ }
567
+ }
568
+
569
+ @keyframes tooltipFadeInTop {
570
+ from {
571
+ opacity: 0;
572
+ transform: translateY(-4px) scale(0.95);
573
+ }
574
+ to {
575
+ opacity: 1;
576
+ transform: translateY(0) scale(1);
577
+ }
578
+ }
579
+
580
+ .ff-tooltip-enter {
581
+ animation: tooltipFadeIn var(--duration-fast) var(--spring-smooth);
582
+ }
583
+
584
+ .ff-tooltip-exit {
585
+ animation: tooltipFadeOut var(--duration-instant) var(--ease-out);
586
+ }
587
+
588
+ .ff-tooltip-enter-top {
589
+ animation: tooltipFadeInTop var(--duration-fast) var(--spring-smooth);
590
+ }
591
+
592
+ /* ============================================================================
593
+ Toast / Notification Animations
594
+ ============================================================================ */
595
+
596
+ @keyframes toastSlideIn {
597
+ from {
598
+ opacity: 0;
599
+ transform: translateY(100%) scale(0.9);
600
+ }
601
+ to {
602
+ opacity: 1;
603
+ transform: translateY(0) scale(1);
604
+ }
605
+ }
606
+
607
+ @keyframes toastSlideOut {
608
+ from {
609
+ opacity: 1;
610
+ transform: translateY(0) scale(1);
611
+ }
612
+ to {
613
+ opacity: 0;
614
+ transform: translateY(100%) scale(0.9);
615
+ }
616
+ }
617
+
618
+ @keyframes toastSlideInTop {
619
+ from {
620
+ opacity: 0;
621
+ transform: translateY(-100%) scale(0.9);
622
+ }
623
+ to {
624
+ opacity: 1;
625
+ transform: translateY(0) scale(1);
626
+ }
627
+ }
628
+
629
+ .ff-toast-enter {
630
+ animation: toastSlideIn var(--duration-normal) var(--spring-smooth);
631
+ }
632
+
633
+ .ff-toast-exit {
634
+ animation: toastSlideOut var(--duration-fast) var(--ease-out);
635
+ }
636
+
637
+ .ff-toast-enter-top {
638
+ animation: toastSlideInTop var(--duration-normal) var(--spring-smooth);
639
+ }
640
+
641
+ /* Slide up animation for update notifications */
642
+ @keyframes slideUp {
643
+ from {
644
+ opacity: 0;
645
+ transform: translateY(20px);
646
+ }
647
+ to {
648
+ opacity: 1;
649
+ transform: translateY(0);
650
+ }
651
+ }
652
+
653
+ .animate-slide-up {
654
+ animation: slideUp 0.3s var(--spring-smooth);
655
+ }
656
+
657
+ /* ============================================================================
658
+ Card Hover Effects
659
+ ============================================================================ */
660
+
661
+ .ff-card-hover {
662
+ transition: transform var(--duration-fast) var(--ease-out),
663
+ box-shadow var(--duration-fast) var(--ease-out);
664
+ will-change: transform;
665
+ }
666
+
667
+ .ff-card-hover:hover {
668
+ transform: translateY(-2px);
669
+ box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.3);
670
+ }
671
+
672
+ .ff-card-lift {
673
+ transition: transform var(--duration-normal) var(--spring-smooth),
674
+ box-shadow var(--duration-normal) var(--ease-out);
675
+ }
676
+
677
+ .ff-card-lift:hover {
678
+ transform: translateY(-4px) scale(1.01);
679
+ box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.35);
680
+ }
681
+
682
+ /* ============================================================================
683
+ Badge / Counter Animations
684
+ ============================================================================ */
685
+
686
+ @keyframes badgePop {
687
+ 0% {
688
+ transform: scale(0);
689
+ opacity: 0;
690
+ }
691
+ 50% {
692
+ transform: scale(1.3);
693
+ }
694
+ 100% {
695
+ transform: scale(1);
696
+ opacity: 1;
697
+ }
698
+ }
699
+
700
+ @keyframes badgeBounce {
701
+ 0%, 100% {
702
+ transform: translateY(0);
703
+ }
704
+ 25% {
705
+ transform: translateY(-4px);
706
+ }
707
+ 50% {
708
+ transform: translateY(0);
709
+ }
710
+ 75% {
711
+ transform: translateY(-2px);
712
+ }
713
+ }
714
+
715
+ @keyframes counterIncrement {
716
+ 0% {
717
+ transform: scale(1);
718
+ }
719
+ 50% {
720
+ transform: scale(1.2);
721
+ }
722
+ 100% {
723
+ transform: scale(1);
724
+ }
725
+ }
726
+
727
+ .ff-badge-pop {
728
+ animation: badgePop var(--duration-normal) var(--spring-bounce);
729
+ }
730
+
731
+ .ff-badge-bounce {
732
+ animation: badgeBounce 0.5s var(--ease-out);
733
+ }
734
+
735
+ .ff-counter-increment {
736
+ animation: counterIncrement 0.3s var(--spring-bounce);
737
+ }
738
+
739
+ /* ============================================================================
740
+ Page / View Transitions
741
+ ============================================================================ */
742
+
743
+ @keyframes pageSlideInRight {
744
+ from {
745
+ opacity: 0;
746
+ transform: translateX(20px);
747
+ }
748
+ to {
749
+ opacity: 1;
750
+ transform: translateX(0);
751
+ }
752
+ }
753
+
754
+ @keyframes pageSlideInLeft {
755
+ from {
756
+ opacity: 0;
757
+ transform: translateX(-20px);
758
+ }
759
+ to {
760
+ opacity: 1;
761
+ transform: translateX(0);
762
+ }
763
+ }
764
+
765
+ @keyframes pageFadeIn {
766
+ from {
767
+ opacity: 0;
768
+ }
769
+ to {
770
+ opacity: 1;
771
+ }
772
+ }
773
+
774
+ @keyframes pageZoomIn {
775
+ from {
776
+ opacity: 0;
777
+ transform: scale(0.95);
778
+ }
779
+ to {
780
+ opacity: 1;
781
+ transform: scale(1);
782
+ }
783
+ }
784
+
785
+ .ff-page-slide-right {
786
+ animation: pageSlideInRight var(--duration-slow) var(--spring-smooth);
787
+ }
788
+
789
+ .ff-page-slide-left {
790
+ animation: pageSlideInLeft var(--duration-slow) var(--spring-smooth);
791
+ }
792
+
793
+ .ff-page-fade {
794
+ animation: pageFadeIn var(--duration-normal) var(--ease-out);
795
+ }
796
+
797
+ .ff-page-zoom {
798
+ animation: pageZoomIn var(--duration-normal) var(--spring-smooth);
799
+ }
800
+
801
+ /* ============================================================================
802
+ Scrolling Behavior
803
+ ============================================================================ */
804
+
805
+ .ff-smooth-scroll {
806
+ scroll-behavior: smooth;
807
+ }
808
+
809
+ .ff-scrollbar-hide {
810
+ -ms-overflow-style: none;
811
+ scrollbar-width: none;
812
+ }
813
+
814
+ .ff-scrollbar-hide::-webkit-scrollbar {
815
+ display: none;
816
+ }
817
+
818
+ /* Custom scrollbar styling */
819
+ .ff-scrollbar-styled {
820
+ scrollbar-width: thin;
821
+ scrollbar-color: rgba(107, 114, 128, 0.5) transparent;
822
+ }
823
+
824
+ .ff-scrollbar-styled::-webkit-scrollbar {
825
+ width: 6px;
826
+ height: 6px;
827
+ }
828
+
829
+ .ff-scrollbar-styled::-webkit-scrollbar-track {
830
+ background: transparent;
831
+ border-radius: 3px;
832
+ }
833
+
834
+ .ff-scrollbar-styled::-webkit-scrollbar-thumb {
835
+ background: rgba(107, 114, 128, 0.5);
836
+ border-radius: 3px;
837
+ }
838
+
839
+ .ff-scrollbar-styled::-webkit-scrollbar-thumb:hover {
840
+ background: rgba(107, 114, 128, 0.7);
841
+ }
842
+
843
+ /* ============================================================================
844
+ Focus States
845
+ ============================================================================ */
846
+
847
+ @keyframes focusRing {
848
+ 0% {
849
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5);
850
+ }
851
+ 100% {
852
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.3);
853
+ }
854
+ }
855
+
856
+ .ff-focus-ring:focus {
857
+ outline: none;
858
+ animation: focusRing 0.2s ease-out forwards;
859
+ }
860
+
861
+ .ff-focus-ring:focus:not(:focus-visible) {
862
+ box-shadow: none;
863
+ }
864
+
865
+ .ff-focus-ring:focus-visible {
866
+ outline: none;
867
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
868
+ }
869
+
870
+ /* ============================================================================
871
+ Utility Animations
872
+ ============================================================================ */
873
+
874
+ /* Fade */
875
+ .ff-fade-in {
876
+ animation: pageFadeIn var(--duration-normal) var(--ease-out);
877
+ }
878
+
879
+ /* Spin */
880
+ @keyframes spin {
881
+ from {
882
+ transform: rotate(0deg);
883
+ }
884
+ to {
885
+ transform: rotate(360deg);
886
+ }
887
+ }
888
+
889
+ .ff-spin {
890
+ animation: spin 1s linear infinite;
891
+ }
892
+
893
+ .ff-spin-slow {
894
+ animation: spin 2s linear infinite;
895
+ }
896
+
897
+ /* Pulse */
898
+ @keyframes pulse {
899
+ 0%, 100% {
900
+ opacity: 1;
901
+ }
902
+ 50% {
903
+ opacity: 0.5;
904
+ }
905
+ }
906
+
907
+ .ff-pulse {
908
+ animation: pulse 2s ease-in-out infinite;
909
+ }
910
+
911
+ /* Bounce */
912
+ @keyframes bounce {
913
+ 0%, 100% {
914
+ transform: translateY(0);
915
+ }
916
+ 50% {
917
+ transform: translateY(-10px);
918
+ }
919
+ }
920
+
921
+ .ff-bounce {
922
+ animation: bounce 1s ease-in-out infinite;
923
+ }
924
+
925
+ /* Ping */
926
+ @keyframes ping {
927
+ 75%, 100% {
928
+ transform: scale(2);
929
+ opacity: 0;
930
+ }
931
+ }
932
+
933
+ .ff-ping {
934
+ animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
935
+ }
936
+
937
+ /* Wiggle */
938
+ @keyframes wiggle {
939
+ 0%, 100% {
940
+ transform: rotate(0deg);
941
+ }
942
+ 25% {
943
+ transform: rotate(-5deg);
944
+ }
945
+ 75% {
946
+ transform: rotate(5deg);
947
+ }
948
+ }
949
+
950
+ .ff-wiggle {
951
+ animation: wiggle 0.5s ease-in-out;
952
+ }
953
+
954
+ /* ============================================================================
955
+ Component-Specific Centralized Keyframes
956
+ ============================================================================ */
957
+
958
+ /* Fade out */
959
+ @keyframes fadeOut {
960
+ from { opacity: 1; }
961
+ to { opacity: 0; }
962
+ }
963
+
964
+ .ff-fade-out {
965
+ animation: fadeOut var(--duration-fast) var(--ease-out);
966
+ }
967
+
968
+ /* Pulse with scale (recording dots, waveform indicators) */
969
+ @keyframes pulseScale {
970
+ 0%, 100% {
971
+ opacity: 1;
972
+ transform: scale(1);
973
+ }
974
+ 50% {
975
+ opacity: 0.6;
976
+ transform: scale(0.95);
977
+ }
978
+ }
979
+
980
+ .ff-pulse-scale {
981
+ animation: pulseScale 1.5s ease-in-out infinite;
982
+ }
983
+
984
+ /* Glow pulse (accent-colored box-shadow) */
985
+ @keyframes glowPulse {
986
+ 0%, 100% {
987
+ box-shadow: 0 0 20px var(--accent-muted);
988
+ }
989
+ 50% {
990
+ box-shadow: 0 0 40px var(--accent-muted);
991
+ }
992
+ }
993
+
994
+ .ff-glow-pulse {
995
+ animation: glowPulse 3s ease-in-out infinite;
996
+ }
997
+
998
+ /* Border color pulse */
999
+ @keyframes pulseBorder {
1000
+ 0%, 100% {
1001
+ border-color: rgba(59, 130, 246, 0.5);
1002
+ }
1003
+ 50% {
1004
+ border-color: rgba(59, 130, 246, 0.8);
1005
+ }
1006
+ }
1007
+
1008
+ .ff-pulse-border {
1009
+ animation: pulseBorder 1.5s ease-in-out infinite;
1010
+ }
1011
+
1012
+ /* Badge float up and fade */
1013
+ @keyframes badgeFloat {
1014
+ 0% {
1015
+ transform: scale(0) translateY(0);
1016
+ opacity: 0;
1017
+ }
1018
+ 20% {
1019
+ transform: scale(1.2) translateY(-2px);
1020
+ opacity: 1;
1021
+ }
1022
+ 40% {
1023
+ transform: scale(1) translateY(-4px);
1024
+ }
1025
+ 100% {
1026
+ transform: scale(0.8) translateY(-16px);
1027
+ opacity: 0;
1028
+ }
1029
+ }
1030
+
1031
+ .ff-badge-float {
1032
+ animation: badgeFloat 1.2s ease-out forwards;
1033
+ }
1034
+
1035
+ /* Annotation toolbar entrance */
1036
+ @keyframes toolbarFadeInUp {
1037
+ from {
1038
+ opacity: 0;
1039
+ transform: translateX(-50%) translateY(10px);
1040
+ }
1041
+ to {
1042
+ opacity: 1;
1043
+ transform: translateX(-50%) translateY(0);
1044
+ }
1045
+ }
1046
+
1047
+ .ff-toolbar-enter {
1048
+ animation: toolbarFadeInUp 0.3s var(--ease-out) forwards;
1049
+ }
1050
+
1051
+ /* ============================================================================
1052
+ GPU Acceleration Helpers
1053
+ ============================================================================ */
1054
+
1055
+ .ff-gpu {
1056
+ transform: translateZ(0);
1057
+ backface-visibility: hidden;
1058
+ perspective: 1000px;
1059
+ }
1060
+
1061
+ .ff-will-change-transform {
1062
+ will-change: transform;
1063
+ }
1064
+
1065
+ .ff-will-change-opacity {
1066
+ will-change: opacity;
1067
+ }
1068
+
1069
+ /* ============================================================================
1070
+ Transition Utilities
1071
+ ============================================================================ */
1072
+
1073
+ .ff-transition-colors {
1074
+ transition: background-color var(--duration-fast) var(--ease-out),
1075
+ border-color var(--duration-fast) var(--ease-out),
1076
+ color var(--duration-fast) var(--ease-out);
1077
+ }
1078
+
1079
+ .ff-transition-transform {
1080
+ transition: transform var(--duration-fast) var(--ease-out);
1081
+ }
1082
+
1083
+ .ff-transition-opacity {
1084
+ transition: opacity var(--duration-fast) var(--ease-out);
1085
+ }
1086
+
1087
+ .ff-transition-shadow {
1088
+ transition: box-shadow var(--duration-fast) var(--ease-out);
1089
+ }
1090
+
1091
+ .ff-transition-all {
1092
+ transition: all var(--duration-fast) var(--ease-out);
1093
+ }