|
580 | 580 | text-transform: uppercase; |
581 | 581 | transition: opacity .18s; |
582 | 582 | } |
583 | | - /* Column number header — sits above the first shelf, no border, no padding */ |
| 583 | + /* Column number header — sits above the first shelf, no border, no padding. |
| 584 | + Height is reserved permanently (was height: 0 / 14px on hover): the height |
| 585 | + transition forced a reflow of every slot on rack-hover, which re-rasterized |
| 586 | + the compositor layers of `.rp-slot--match` slots (transform: scale animation) |
| 587 | + and produced a visible flash when sweeping between racks that both contained |
| 588 | + matches. Reveal is now opacity-only on `.rp-col-label` (compositor-only). */ |
584 | 589 | .rp-row--header { |
585 | 590 | padding-bottom: 4px; |
586 | 591 | border-bottom: none; |
587 | | - /* Collapse the row when coordinates are hidden so the rack is more compact */ |
588 | | - height: 0; |
| 592 | + height: 14px; |
589 | 593 | overflow: hidden; |
590 | 594 | padding-top: 0; |
591 | 595 | margin-bottom: 0; |
592 | | - transition: height .18s, padding-top .18s; |
593 | 596 | } |
594 | 597 | .rp-row--header .rp-col-label { |
595 | 598 | font-size: 10px; font-weight: 700; color: var(--muted); |
|
604 | 607 | body.is-dragging-spool .rp-row-label { |
605 | 608 | opacity: .65; |
606 | 609 | } |
607 | | - .rp-rack:hover .rp-row--header, |
608 | | - body.is-dragging-spool .rp-row--header { |
609 | | - height: 14px; |
610 | | - padding-top: 0; |
611 | | - } |
612 | 610 | .rp-rack:hover .rp-row--header .rp-col-label, |
613 | 611 | body.is-dragging-spool .rp-row--header .rp-col-label { |
614 | 612 | opacity: .55; |
|
746 | 744 | /* Search dim — applied to filled rack slots that don't match the query. |
747 | 745 | The slot stays in place but loses color saturation and most opacity, so |
748 | 746 | the matching slots visually pop. */ |
| 747 | + /* No `transition: filter` — animating filter:grayscale per-frame on 100-300 |
| 748 | + slots when the search churns the dim set on every keystroke flashed the |
| 749 | + entire grid. opacity is compositor-friendly so its transition stays. */ |
749 | 750 | .rp-slot--filled.rp-dim { |
750 | 751 | opacity: .18; |
751 | 752 | filter: grayscale(.85); |
752 | | - transition: opacity .15s, filter .15s; |
| 753 | + transition: opacity .15s; |
753 | 754 | } |
754 | 755 | .rp-slot--filled.rp-dim:hover { opacity: .4; } /* hint that it's still there */ |
755 | 756 |
|
756 | 757 | /* Search-match indicator on the slot CONTAINER — makes the slot stand |
757 | 758 | out via a border+glow on the box itself, NOT via the inner color fill. |
758 | 759 | This is critical for depleted spools (.rp-fill height ~ 0): the slot's |
759 | 760 | box still glows in orange so the user sees it when they search. */ |
| 761 | + /* Static shadow uses the midpoint of the old keyframe so visibility is |
| 762 | + preserved. Pulse is driven by `transform: scale` (compositor-only) instead |
| 763 | + of animating box-shadow — animating shadow forced a GPU repaint on every |
| 764 | + frame, saturating Chromium's tile memory (`tile_manager.cc:997 WARNING: |
| 765 | + tile memory limits exceeded`) when ~15+ matches were on screen. */ |
760 | 766 | .rp-slot--filled.rp-slot--match { |
761 | 767 | border-color: var(--primary) !important; |
762 | 768 | box-shadow: |
763 | | - 0 0 0 2px rgba(249,115,22,.55), |
764 | | - 0 0 8px rgba(249,115,22,.55), |
| 769 | + 0 0 0 2.5px rgba(249,115,22,.7), |
| 770 | + 0 0 11px rgba(249,115,22,.7), |
765 | 771 | inset 0 0 0 1px rgba(255,255,255,.28); |
766 | 772 | z-index: 2; |
767 | 773 | animation: rp-slot-match-pulse 1.6s ease-in-out infinite; |
| 774 | + /* Pre-promote to a compositor layer so the rack-hover reveal (which expands |
| 775 | + `.rp-row--header` from 0 → 14px and shifts every slot below it) doesn't |
| 776 | + re-rasterize the scaled match-slots — that re-rasterization was visible |
| 777 | + as a brief flash and also re-triggered `tile_manager` warnings. */ |
| 778 | + will-change: transform; |
768 | 779 | } |
769 | 780 | @keyframes rp-slot-match-pulse { |
770 | | - 0%, 100% { box-shadow: |
771 | | - 0 0 0 2px rgba(249,115,22,.55), |
772 | | - 0 0 8px rgba(249,115,22,.55), |
773 | | - inset 0 0 0 1px rgba(255,255,255,.28); } |
774 | | - 50% { box-shadow: |
775 | | - 0 0 0 3px rgba(249,115,22,.85), |
776 | | - 0 0 14px rgba(249,115,22,.85), |
777 | | - inset 0 0 0 1px rgba(255,255,255,.28); } |
| 781 | + 0%, 100% { transform: scale(1); } |
| 782 | + 50% { transform: scale(1.06); } |
| 783 | + } |
| 784 | + /* Hover on a match slot: pause the pulse and snap back to scale(1) — the |
| 785 | + parent `.rp-slot--filled:hover` rule applies `transform: translateY(-1px)`, |
| 786 | + which would override the animation's `transform: scale` and cause a visible |
| 787 | + flash whenever the mouse swept across multiple animating match-slots. The |
| 788 | + permanent orange ring already provides ample hover affordance here. */ |
| 789 | + .rp-slot--filled.rp-slot--match:hover { |
| 790 | + animation: none; |
| 791 | + transform: scale(1); |
778 | 792 | } |
779 | 793 |
|
780 | 794 | /* Locked slot — solid amber tint + small lock badge top-right. |
|
0 commit comments