|
264 | 264 | pointer-events: none; z-index: 5; |
265 | 265 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18); |
266 | 266 | } |
267 | | - .ux-delivery-badge.show { opacity: 1; transform: translateY(0); } |
| 267 | + .ux-delivery-badge.show { |
| 268 | + animation: uxDeliveryBadgeIn 280ms ease forwards; |
| 269 | + } |
| 270 | + .ux-delivery-badge.show-instant { |
| 271 | + opacity: 1; transform: translateY(0); |
| 272 | + } |
| 273 | + @keyframes uxDeliveryBadgeIn { |
| 274 | + from { opacity: 0; transform: translateY(-4px); } |
| 275 | + to { opacity: 1; transform: translateY(0); } |
| 276 | + } |
268 | 277 | .ux-delivery-badge i { width: 11px; height: 11px; color: #00BFFF; } |
269 | 278 | .ux-delivery-badge .ux-delivery-label { color: rgba(255,255,255,0.7); font-weight: 600; font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; } |
270 | 279 | .ux-delivery-badge .ux-delivery-method { color: #ffffff; } |
|
358 | 367 | 100% { transform: scale(2.4); opacity: 0; border-width: 1px; } |
359 | 368 | } |
360 | 369 |
|
361 | | - /* Teams app inside frame */ |
| 370 | + /* Teams app inside frame. |
| 371 | + Uses a CSS keyframe so the entrance plays on mount — innerHTML |
| 372 | + re-renders skip transitions (the element is parsed already in |
| 373 | + the final state), but keyframe animations always run on insert. */ |
362 | 374 | .ux-frame-app { |
363 | 375 | flex: 1; background: #ffffff; display: flex; |
364 | 376 | opacity: 0; transform: translateY(8px) scale(0.985); |
365 | | - transition: opacity 360ms ease, transform 360ms cubic-bezier(0.22, 1, 0.36, 1); |
366 | 377 | } |
367 | | - .ux-frame-app.open, .ux-frame-app.open-instant { opacity: 1; transform: translateY(0) scale(1); } |
368 | | - .ux-frame-app.open-instant { transition: none; } |
| 378 | + .ux-frame-app.open { |
| 379 | + animation: uxFrameAppIn 360ms cubic-bezier(0.22, 1, 0.36, 1) forwards; |
| 380 | + } |
| 381 | + .ux-frame-app.open-instant { |
| 382 | + opacity: 1; transform: translateY(0) scale(1); |
| 383 | + } |
| 384 | + @keyframes uxFrameAppIn { |
| 385 | + from { opacity: 0; transform: translateY(8px) scale(0.985); } |
| 386 | + to { opacity: 1; transform: translateY(0) scale(1); } |
| 387 | + } |
369 | 388 |
|
370 | 389 | .ux-teams { display: flex; flex: 1; min-height: 0; } |
371 | 390 | .ux-teams-side { |
|
390 | 409 | .ux-teams-msg { |
391 | 410 | display: flex; align-items: flex-start; gap: 6px; |
392 | 411 | opacity: 0; transform: translateY(6px); |
393 | | - transition: opacity 260ms ease, transform 260ms ease; |
394 | 412 | } |
395 | | - .ux-teams-msg.show { opacity: 1; transform: translateY(0); } |
396 | | - .ux-teams-msg.show-instant { opacity: 1; transform: translateY(0); transition: none; } |
| 413 | + .ux-teams-msg.show { |
| 414 | + animation: uxTeamsMsgIn 260ms ease forwards; |
| 415 | + } |
| 416 | + .ux-teams-msg.show-instant { |
| 417 | + opacity: 1; transform: translateY(0); |
| 418 | + } |
| 419 | + @keyframes uxTeamsMsgIn { |
| 420 | + from { opacity: 0; transform: translateY(6px); } |
| 421 | + to { opacity: 1; transform: translateY(0); } |
| 422 | + } |
397 | 423 | .ux-teams-avatar { |
398 | 424 | width: 18px; height: 18px; border-radius: 50%; |
399 | 425 | display: grid; place-items: center; |
|
552 | 578 | .ux-closer { |
553 | 579 | margin-top: 18px; text-align: center; |
554 | 580 | opacity: 0; transform: translateY(6px); |
555 | | - transition: opacity 480ms ease 850ms, transform 480ms ease 850ms; |
556 | 581 | } |
557 | | - .ux-closer.show { opacity: 1; transform: translateY(0); } |
| 582 | + .ux-closer.show { |
| 583 | + animation: uxCloserIn 480ms ease 850ms forwards; |
| 584 | + } |
| 585 | + @keyframes uxCloserIn { |
| 586 | + from { opacity: 0; transform: translateY(6px); } |
| 587 | + to { opacity: 1; transform: translateY(0); } |
| 588 | + } |
558 | 589 | .ux-closer-tension { |
559 | 590 | display: block; |
560 | 591 | font-size: 11px; font-weight: 600; letter-spacing: 0.02em; |
@@ -5612,6 +5643,11 @@ <h2 class="text-xl font-bold tracking-tight text-[#0A1C60]">Ask a Question</h2> |
5612 | 5643 | // Per-frame divergent backend path is visible during the routing/config beats. |
5613 | 5644 | // It snaps off as the frames converge on the identical Teams chat. |
5614 | 5645 | const showDeliveryBadge = live && ['routing','config'].includes(effectivePhase); |
| 5646 | + // Animate the badge in once on first appearance; keep it instant on subsequent phase ticks. |
| 5647 | + const firstDeliveryBadge = showDeliveryBadge && !STATE._firstDeliveryBadgeDone; |
| 5648 | + if (firstDeliveryBadge) STATE._firstDeliveryBadgeDone = true; |
| 5649 | + if (!showDeliveryBadge) STATE._firstDeliveryBadgeDone = false; |
| 5650 | + const deliveryBadgeClass = showDeliveryBadge ? (firstDeliveryBadge ? 'show' : 'show-instant') : ''; |
5615 | 5651 |
|
5616 | 5652 | // Tighter catalog: 6 apps, 2 rows of 3. The viewer's eye lands on Teams. |
5617 | 5653 | const TEAMS_APP = (CATALOG_APPS.find(a => a.id === 'teams')) || { id:'teams', letter:'T', name:'Teams', color:'#5B53D6' }; |
@@ -5701,7 +5737,7 @@ <h2 class="text-xl font-bold tracking-tight text-[#0A1C60]">Ask a Question</h2> |
5701 | 5737 | // out as the three frames converge on identical Teams chat. |
5702 | 5738 | const deviceFrame = (variant) => { |
5703 | 5739 | const badge = variant.deliveryMethod ? ` |
5704 | | - <div class="ux-delivery-badge ${showDeliveryBadge ? 'show' : ''}"> |
| 5740 | + <div class="ux-delivery-badge ${deliveryBadgeClass}"> |
5705 | 5741 | <i data-lucide="${variant.deliveryIcon}"></i> |
5706 | 5742 | <span class="ux-delivery-label">Delivered via</span> |
5707 | 5743 | <span class="ux-delivery-method">${variant.deliveryMethod}</span> |
|
0 commit comments