New CSS-only overlay border glow component

Replaces the overlay shimmer canvas.

Guarded by the flag `LensOverlayVisualSelectionUpdates`

When on, the shimmer is disabled and the initial gradient (upper scrim) does not display.

Change-Id: I4214b8f3e7e2c093012b38c3f99ddf325a27977a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6547186
Reviewed-by: Juan Mojica <juanmojica@google.com>
Reviewed-by: Duncan Mercer <mercerd@google.com>
Commit-Queue: Shez Daredia <helloshez@google.com>
Cr-Commit-Position: refs/heads/main@{#1461682}
diff --git a/chrome/browser/resources/lens/overlay/BUILD.gn b/chrome/browser/resources/lens/overlay/BUILD.gn
index 34314cd..2f1edf0 100644
--- a/chrome/browser/resources/lens/overlay/BUILD.gn
+++ b/chrome/browser/resources/lens/overlay/BUILD.gn
@@ -75,6 +75,8 @@
     "screenshot_utils.ts",
     "searchbox_utils.ts",
     "selection_utils.ts",
+    "overlay_border_glow.ts",
+    "overlay_border_glow.html.ts",
     "side_panel/side_panel_browser_proxy.ts",
     "side_panel/side_panel_error_page.ts",
     "side_panel/feedback_toast.ts",
@@ -94,6 +96,7 @@
     "simplified_text_layer.css",
     "side_panel/side_panel_error_page.css",
     "side_panel/feedback_toast.css",
+    "overlay_border_glow.css",
   ]
 
   ts_deps = [
diff --git a/chrome/browser/resources/lens/overlay/lens_overlay_app.ts b/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
index 9431d931..bcd6c9e8 100644
--- a/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
+++ b/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
@@ -79,6 +79,10 @@
         type: Boolean,
         value: false,
       },
+      enableBorderGlow: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('enableBorderGlow'),
+      },
       forceHideSearchBox: {
         type: Boolean,
         value: false,
@@ -202,6 +206,8 @@
     };
   }
 
+  // Whether the border glow is enabled via feature flag.
+  declare enableBorderGlow: boolean;
   // Whether the user is currently focused into the searchbox.
   declare isSearchboxFocused: boolean;
   // Whether to purposely suppress the ghost loader. Done when escaping from
@@ -610,7 +616,9 @@
 
   // The user finished making their selection on the selection overlay.
   private handleSelectionFinished() {
-    this.$.initialGradient.triggerHideScrimAnimation();
+    if (!this.enableBorderGlow) {
+      this.$.initialGradient.triggerHideScrimAnimation();
+    }
     this.$.cursorTooltip.setPauseTooltipChanges(false);
     this.isPointerDown = false;
   }
@@ -621,7 +629,9 @@
 
   private onInitialFlashAnimationEnd() {
     this.initialFlashAnimationHasEnded = true;
-    this.$.initialGradient.setScrimVisible();
+    if (!this.enableBorderGlow) {
+      this.$.initialGradient.setScrimVisible();
+    }
     // The searchbox is not focusable until the animation has ended.
     if (this.autoFocusSearchbox &&
         this.isLensOverlayContextualSearchboxVisible) {
diff --git a/chrome/browser/resources/lens/overlay/overlay_border_glow.css b/chrome/browser/resources/lens/overlay/overlay_border_glow.css
new file mode 100644
index 0000000..3e127ee
--- /dev/null
+++ b/chrome/browser/resources/lens/overlay/overlay_border_glow.css
@@ -0,0 +1,97 @@
+/* Copyright 2025 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #scheme=relative
+ * #css_wrapper_metadata_end */
+
+:host {
+  --gradient-blue: #3186ff;
+  --gradient-green: #34a853;
+  --gradient-red: #ff4641;
+  --gradient-rotation-angle: 0deg;
+  --gradient-yellow: #ffd314;
+  height: 100%;
+  width: 100%;
+}
+
+@property --gradient-rotation-angle {
+  inherits: false;
+  initial-value: 0deg;
+  syntax: '<angle>';
+}
+
+@property --gradient-blue {
+  inherits: false;
+  initial-value: #3186ff;
+  syntax: '<color>';
+}
+
+@property --gradient-green {
+  inherits: false;
+  initial-value: #34a853;
+  syntax: '<color>';
+}
+
+@property --gradient-yellow {
+  inherits: false;
+  initial-value: #ffd314;
+  syntax: '<color>';
+}
+
+@property --gradient-red {
+  inherits: false;
+  initial-value: #ff4641;
+  syntax: '<color>';
+}
+
+@keyframes rotateGradient {
+  0% {
+    --gradient-rotation-angle: 0deg;
+  }
+  100% {
+    --gradient-rotation-angle: 360deg;
+  }
+}
+
+@keyframes shaderOpacityIn {
+  0% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 0.6;
+  }
+}
+
+@keyframes shaderOpacityToResting {
+  0% {
+    opacity: 0.5;
+  }
+  100% {
+    opacity: 0.3;
+  }
+}
+
+#borderGlowContainer {
+  animation: 117ms cubic-bezier(0, 0, 0, 1) 0s 1 normal forwards running shaderOpacityIn,
+      1016ms cubic-bezier(0.2, 0, 0, 1) 117ms 1 normal forwards running shaderOpacityToResting,
+      20s linear 0s infinite normal forwards running rotateGradient;
+  background: conic-gradient(from var(--gradient-rotation-angle, 0deg) at center,
+      var(--gradient-blue) 0deg, var(--gradient-blue) 10deg,
+      var(--gradient-red) 80deg, var(--gradient-yellow) 120deg,
+      var(--gradient-green) 160deg, var(--gradient-blue) 200deg,
+      var(--gradient-blue) 360deg);
+  content: '';
+  filter: blur(20px);
+  height: 100%;
+  left: 0;
+  -webkit-mask: url(#roundedFrameMask);
+  mask: url(#roundedFrameMask);
+  opacity: 0;
+  position: absolute;
+  top: 0;
+  transform: scale(140%);
+  width: 100%;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/lens/overlay/overlay_border_glow.html.ts b/chrome/browser/resources/lens/overlay/overlay_border_glow.html.ts
new file mode 100644
index 0000000..cd8667c3
--- /dev/null
+++ b/chrome/browser/resources/lens/overlay/overlay_border_glow.html.ts
@@ -0,0 +1,63 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {OverlayBorderGlowElement} from './overlay_border_glow.js';
+
+export function getHtml(this: OverlayBorderGlowElement) {
+  return html`<div id="borderGlowContainer">
+  <svg style="position: absolute; width: 0; height: 0; overflow: hidden">
+    <defs>
+      <filter id="blur-filter">
+        <feGaussianBlur id="blur" in="SourceGraphic" stdDeviation="0.02">
+          <animate
+            id="blur-animation"
+            attributeName="stdDeviation"
+            attributeType="XML"
+            from="0.02"
+            to="0.05"
+            dur="117ms"
+            begin="0s"
+            fill="freeze"
+            calcMode="spline"
+            keyTimes="0;1"
+            keySplines="0 0 0 0.1"
+          />
+
+          <animate
+            id="blur-animationToResting"
+            attributeName="stdDeviation"
+            attributeType="XML"
+            from="0.05"
+            to="0.1"
+            dur="1016ms"
+            begin="117ms"
+            fill="freeze"
+            calcMode="spline"
+            keyTimes="0;1"
+            keySplines="0.2 0 0 1"
+          />
+        </feGaussianBlur>
+      </filter>
+
+      <mask id="roundedFrameMask" maskContentUnits="objectBoundingBox">
+        <rect width="1" height="1" fill="white" />
+
+        <rect
+          id="mask-hole-rect"
+          x=".18"
+          y=".18"
+          width=".64"
+          height="0.64"
+          rx="0.25"
+          ry="0.25"
+          fill="black"
+          filter="url(#blur-filter)"
+        />
+      </mask>
+    </defs>
+  </svg>
+</div>`;
+}
diff --git a/chrome/browser/resources/lens/overlay/overlay_border_glow.ts b/chrome/browser/resources/lens/overlay/overlay_border_glow.ts
new file mode 100644
index 0000000..f2b15f9
--- /dev/null
+++ b/chrome/browser/resources/lens/overlay/overlay_border_glow.ts
@@ -0,0 +1,38 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
+
+import {getCss} from './overlay_border_glow.css.js';
+import {getHtml} from './overlay_border_glow.html.js';
+
+/*
+ * Element responsible for rendering the border glow as a replacement for the
+ * shimmer.
+ */
+export class OverlayBorderGlowElement extends CrLitElement {
+  static get is() {
+    return 'overlay-border-glow';
+  }
+
+  static override get properties() {
+    return {};
+  }
+
+  static override get styles() {
+    return getCss();
+  }
+
+  override render() {
+    return getHtml.bind(this)();
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'overlay-border-glow': OverlayBorderGlowElement;
+  }
+}
+
+customElements.define(OverlayBorderGlowElement.is, OverlayBorderGlowElement);
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.html b/chrome/browser/resources/lens/overlay/selection_overlay.html
index b9b7322b1..9a229a59 100644
--- a/chrome/browser/resources/lens/overlay/selection_overlay.html
+++ b/chrome/browser/resources/lens/overlay/selection_overlay.html
@@ -1,4 +1,4 @@
-<link rel="stylesheet" href="//resources/css/text_defaults_md.css">
+<link rel="stylesheet" href="//resources/css/text_defaults_md.css" />
 <style>
   :host {
     --cursor-img-url: url("lens.svg");
@@ -29,9 +29,10 @@
   }
 
   :host([is-initial-size]) #backgroundImageCanvas {
-    animation: initial-blur-animation 700ms cubic-bezier(0.2, 0.0, 0, 1.0),
-               initial-inset-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0),
-               initial-scale-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0);
+    animation:
+      initial-blur-animation 700ms cubic-bezier(0.2, 0, 0, 1),
+      initial-inset-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1),
+      initial-scale-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1);
     position: relative;
   }
 
@@ -59,8 +60,7 @@
   :host([suppress-copy-and-save-as-image]) .save-as-image-context-menu-item,
   :host(:not([enable-copy-as-image])) .copy-as-image-context-menu-item,
   :host(:not([enable-save-as-image])) .save-as-image-context-menu-item,
-  :host(:not([show-detected-text-context-menu-options]))
-      .detected-text-context-menu-option,
+  :host(:not([show-detected-text-context-menu-options])) .detected-text-context-menu-option,
   :host(:not([show-translate-context-menu-item])) .translate-context-menu-item,
   :host([disable-shimmer]) #overlayShimmerCanvas,
   :host([disable-shimmer]) #overlayShimmer {
@@ -69,9 +69,8 @@
 
   :host([shimmer-on-segmentation]) #objectSelectionLayer,
   :host([shimmer-on-segmentation]) #overlayShimmer,
-  :host([shimmer-on-segmentation][shimmer-fade-out-complete])
-      #overlayShimmerCanvas  {
-    z-index: 4;  /* Position above words but below selection corners. */
+  :host([shimmer-on-segmentation][shimmer-fade-out-complete]) #overlayShimmerCanvas {
+    z-index: 4; /* Position above words but below selection corners. */
   }
 
   :host([translate-mode-enabled]) #objectSelectionLayer {
@@ -100,7 +99,7 @@
   }
 
   #selectionElements {
-    background-color: rgba(var(--color-scrim-rgb), .05);
+    background-color: rgba(var(--color-scrim-rgb), 0.05);
   }
 
   :host([translate-mode-enabled]) #selectionElements {
@@ -116,9 +115,10 @@
   }
 
   :host([is-initial-size]) #initialFlashScrim {
-    animation: initial-inset-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0),
-               initial-scale-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0),
-               initial-flash-animation 700ms cubic-bezier(0.2, 0.0, 0, 1.0);
+    animation:
+      initial-inset-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1),
+      initial-scale-animation 400ms cubic-bezier(0.05, 0.7, 0.1, 1),
+      initial-flash-animation 700ms cubic-bezier(0.2, 0, 0, 1);
     background-color: rgb(255, 255, 255);
     height: 100%;
     max-height: 100vh;
@@ -190,7 +190,7 @@
 
   .context-menu {
     align-items: flex-start;
-    animation: scale-out 150ms cubic-bezier(0.2, 0.0, 0, 1.0);
+    animation: scale-out 150ms cubic-bezier(0.2, 0, 0, 1);
     background: var(--color-selection-element);
     border-radius: 8px;
     box-shadow: 0px 2px 6px 0px rgba(60, 64, 67, 0.16);
@@ -203,7 +203,9 @@
     pointer-events: auto;
     position: absolute;
     visibility: hidden;
-    transition: opacity 150ms, visibility 150ms;
+    transition:
+      opacity 150ms,
+      visibility 150ms;
     z-index: 6;
   }
 
@@ -213,7 +215,7 @@
 
   :host([show-selected-text-context-menu]) #selectedTextContextMenu,
   :host([show-selected-region-context-menu]) #selectedRegionContextMenu {
-    animation: scale-in 250ms cubic-bezier(0.2, 0.0, 0, 1.0);
+    animation: scale-in 250ms cubic-bezier(0.2, 0, 0, 1);
     opacity: 1;
     visibility: visible;
   }
@@ -232,7 +234,7 @@
   }
 
   .context-menu-item:active {
-    background-color: rgba(var(--color-scrim-rgb), 0.10);
+    background-color: rgba(var(--color-scrim-rgb), 0.1);
   }
 
   .menu-item-icon {
@@ -246,23 +248,23 @@
   }
 
   .menu-item-icon.copy {
-    mask-image: url('copy.svg');
+    mask-image: url("copy.svg");
   }
 
   .menu-item-icon.copy-image {
-    mask-image: url('copy_image.svg');
+    mask-image: url("copy_image.svg");
   }
 
   .menu-item-icon.download {
-    mask-image: url('download.svg');
+    mask-image: url("download.svg");
   }
 
   .menu-item-icon.translate {
-    mask-image: url('translate.svg');
+    mask-image: url("translate.svg");
   }
 
   .menu-item-icon.text {
-    mask-image: url('text.svg');
+    mask-image: url("text.svg");
   }
 
   .menu-item-label {
@@ -293,7 +295,7 @@
 
   @keyframes initial-inset-animation {
     50% {
-      animation-timing-function: cubic-bezier(0.2, 0.0, 0, 1.0);
+      animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
       border-radius: 8px;
     }
 
@@ -305,7 +307,7 @@
   @keyframes initial-blur-animation {
     /* 2/7 of the way through the animation. */
     28.6% {
-      filter: blur(2.5px)
+      filter: blur(2.5px);
     }
 
     to {
@@ -353,90 +355,134 @@
     }
   }
 </style>
-<div id="selectionOverlay" on-pointerenter="handlePointerEnter"
-    on-pointerleave="handlePointerLeave">
-  <canvas id="backgroundImageCanvas"
-      style$="height: [[canvasHeight]]px; width: [[canvasWidth]]px;"></canvas>
+<div
+  id="selectionOverlay"
+  on-pointerenter="handlePointerEnter"
+  on-pointerleave="handlePointerLeave"
+>
+  <canvas
+    id="backgroundImageCanvas"
+    style$="height: [[canvasHeight]]px; width: [[canvasWidth]]px;"
+  ></canvas>
   <!-- Wrapper div is needed to stack the selection elements on top of
   background image. -->
   <div id="selectionElements">
     <!-- Other elements that need to be bounded to the image go here -->
     <div id="extraScrim"></div>
-    <overlay-shimmer-canvas hidden$="[[disableShimmer]]" theme="[[theme]]"
-        id="overlayShimmerCanvas"></overlay-shimmer-canvas>
-    <post-selection-renderer id="postSelectionRenderer"
-        selection-overlay-rect="[[selectionOverlayRect]]">
+    <overlay-shimmer-canvas
+      hidden$="[[disableShimmer || enableBorderGlow]]"
+      theme="[[theme]]"
+      id="overlayShimmerCanvas"
+    ></overlay-shimmer-canvas>
+    <template is="dom-if" if="[[enableBorderGlow]]">
+      <overlay-border-glow id="overlayBorderGlowCanvas"></overlay-border-glow>
+    </template>
+    <post-selection-renderer
+      id="postSelectionRenderer"
+      selection-overlay-rect="[[selectionOverlayRect]]"
+    >
     </post-selection-renderer>
-    <lens-object-layer id="objectSelectionLayer"
-        screenshot-data-uri="[[screenshotDataUri]]" theme="[[theme]]">
+    <lens-object-layer
+      id="objectSelectionLayer"
+      screenshot-data-uri="[[screenshotDataUri]]"
+      theme="[[theme]]"
+    >
     </lens-object-layer>
-    <region-selection id="regionSelectionLayer" theme="[[theme]]"
-        screenshot-data-uri="[[screenshotDataUri]]"
-        selection-overlay-rect="[[selectionOverlayRect]]"></region-selection>
+    <region-selection
+      id="regionSelectionLayer"
+      theme="[[theme]]"
+      screenshot-data-uri="[[screenshotDataUri]]"
+      selection-overlay-rect="[[selectionOverlayRect]]"
+    ></region-selection>
     <template is="dom-if" if="[[!simplifiedSelectionEnabled]]">
-      <lens-text-layer id="textSelectionLayer"
-          selection-overlay-rect="[[selectionOverlayRect]]"></lens-text-layer>
+      <lens-text-layer
+        id="textSelectionLayer"
+        selection-overlay-rect="[[selectionOverlayRect]]"
+      ></lens-text-layer>
     </template>
     <template is="dom-if" if="[[simplifiedSelectionEnabled]]">
-      <lens-simplified-text-layer
-          selection-overlay-rect="[[selectionOverlayRect]]">
+      <lens-simplified-text-layer selection-overlay-rect="[[selectionOverlayRect]]">
       </lens-simplified-text-layer>
     </template>
   </div>
   <div id="initialFlashScrim"></div>
   <div id="contextMenuOverlay">
-    <div id="selectedTextContextMenu" class="context-menu" role="menu"
-        style$="[[getContextMenuStyle(selectedTextContextMenuX,
+    <div
+      id="selectedTextContextMenu"
+      class="context-menu"
+      role="menu"
+      style$="[[getContextMenuStyle(selectedTextContextMenuX,
                                       selectedTextContextMenuY)]]"
-        on-pointerenter="handlePointerEnterContextMenu"
-        on-pointerleave="handlePointerLeaveContextMenu">
+      on-pointerenter="handlePointerEnterContextMenu"
+      on-pointerleave="handlePointerLeaveContextMenu"
+    >
       <div class="context-menu-item" role="menuitem" on-pointerup="handleCopy">
         <span class="menu-item-icon copy"></span>
         <span class="menu-item-label">$i18n{copy}</span>
       </div>
-      <div class="context-menu-item translate-context-menu-item" role="menuitem"
-          on-pointerup="handleTranslate">
+      <div
+        class="context-menu-item translate-context-menu-item"
+        role="menuitem"
+        on-pointerup="handleTranslate"
+      >
         <span class="menu-item-icon translate"></span>
         <span class="menu-item-label">$i18n{translate}</span>
       </div>
     </div>
-    <div id="selectedRegionContextMenu" class="context-menu" role="menu"
-        style$="[[selectedRegionContextMenuHorizontalStyle]]
+    <div
+      id="selectedRegionContextMenu"
+      class="context-menu"
+      role="menu"
+      style$="[[selectedRegionContextMenuHorizontalStyle]]
                 [[selectedRegionContextMenuVerticalStyle]]"
-        on-pointerenter="handlePointerEnterContextMenu"
-        on-pointerleave="handlePointerLeaveContextMenu">
-      <div id="copyDetectedTextContextMenuItem"
-          class="context-menu-item detected-text-context-menu-option"
-          role="menuitem" on-pointerup="handleCopyDetectedText">
+      on-pointerenter="handlePointerEnterContextMenu"
+      on-pointerleave="handlePointerLeaveContextMenu"
+    >
+      <div
+        id="copyDetectedTextContextMenuItem"
+        class="context-menu-item detected-text-context-menu-option"
+        role="menuitem"
+        on-pointerup="handleCopyDetectedText"
+      >
         <span class="menu-item-icon copy"></span>
         <span class="menu-item-label">$i18n{copyText}</span>
       </div>
-      <div id="selectTextContextMenuItem"
-          class="context-menu-item detected-text-context-menu-option"
-          role="menuitem" on-pointerup="handleSelectText">
+      <div
+        id="selectTextContextMenuItem"
+        class="context-menu-item detected-text-context-menu-option"
+        role="menuitem"
+        on-pointerup="handleSelectText"
+      >
         <span class="menu-item-icon text"></span>
         <span class="menu-item-label">$i18n{selectText}</span>
       </div>
-      <div class="context-menu-item translate-context-menu-item
-          detected-text-context-menu-option" role="menuitem"
-          on-pointerup="handleTranslateDetectedText">
+      <div
+        class="context-menu-item translate-context-menu-item detected-text-context-menu-option"
+        role="menuitem"
+        on-pointerup="handleTranslateDetectedText"
+      >
         <span class="menu-item-icon translate"></span>
         <span class="menu-item-label">$i18n{translate}</span>
       </div>
-      <div class="context-menu-item copy-as-image-context-menu-item"
-          role="menuitem" on-pointerup="handleCopyAsImage">
+      <div
+        class="context-menu-item copy-as-image-context-menu-item"
+        role="menuitem"
+        on-pointerup="handleCopyAsImage"
+      >
         <span class="menu-item-icon copy-image"></span>
         <span class="menu-item-label">$i18n{copyAsImage}</span>
       </div>
-      <div class="context-menu-item save-as-image-context-menu-item"
-          role="menuitem" on-pointerup="handleSaveAsImage">
+      <div
+        class="context-menu-item save-as-image-context-menu-item"
+        role="menuitem"
+        on-pointerup="handleSaveAsImage"
+      >
         <span class="menu-item-icon download"></span>
         <span class="menu-item-label">$i18n{saveAsImage}</span>
       </div>
     </div>
   </div>
 </div>
-<div id="cursor"
-    class$="[[getHiddenCursorClass(isPointerInside, currentGesture.state)]]">
+<div id="cursor" class$="[[getHiddenCursorClass(isPointerInside, currentGesture.state)]]">
   <div id="cursorImg"></div>
 </div>
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.ts b/chrome/browser/resources/lens/overlay/selection_overlay.ts
index c104574..c84c9eb 100644
--- a/chrome/browser/resources/lens/overlay/selection_overlay.ts
+++ b/chrome/browser/resources/lens/overlay/selection_overlay.ts
@@ -7,6 +7,7 @@
 import './text_layer.js';
 import './region_selection.js';
 import './post_selection_renderer.js';
+import './overlay_border_glow.js';
 import './overlay_shimmer_canvas.js';
 import '/strings.m.js';
 import '//resources/cr_elements/cr_button/cr_button.js';
@@ -178,6 +179,10 @@
         readOnly: true,
         value: !loadTimeData.getBoolean('enableShimmer'),
       },
+      enableBorderGlow: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('enableBorderGlow'),
+      },
       enableCopyAsImage: {
         type: Boolean,
         reflectToAttribute: true,
@@ -282,6 +287,7 @@
   // gesture has started.
   declare private currentGesture: GestureEvent;
   declare private disableShimmer: boolean;
+  declare private enableBorderGlow: boolean;
   declare private enableCopyAsImage: boolean;
   declare private enableSaveAsImage: boolean;
   declare private suppressCopyAndSaveAsImage: boolean;
@@ -1242,7 +1248,7 @@
 
     // Don't start the shimmer animation until the initial flash animation is
     // finished.
-    if (!this.disableShimmer) {
+    if (!this.disableShimmer && !this.enableBorderGlow) {
       this.$.overlayShimmerCanvas.startAnimation();
     }
   }
diff --git a/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc b/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
index af9ce9f..df54fa6 100644
--- a/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
+++ b/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
@@ -240,6 +240,9 @@
       lens::features::GetLensOverlayTranslateRecentLanguagesAmount());
   html_source->AddBoolean("simplifiedSelectionEnabled",
                           lens::features::IsSimplifiedSelectionEnabled());
+  html_source->AddBoolean(
+      "enableBorderGlow",
+      lens::features::GetVisualSelectionUpdatesEnableBorderGlow());
   html_source->AddBoolean("autoFocusSearchbox",
                           lens::features::ShouldAutoFocusSearchbox());
   html_source->AddBoolean("cornerSlidersEnabled",
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 4ec318ea..bdaab00 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -63,6 +63,10 @@
              "LensOverlaySimplifiedSelection",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kLensOverlayVisualSelectionUpdates,
+             "LensOverlayVisualSelectionUpdates",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kLensOverlayUpdatedClientContext,
              "LensOverlayUpdatedClientContext",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -466,6 +470,10 @@
         &kLensOverlaySimplifiedSelection, "copy-command-copies-as-image",
         false};
 
+constexpr base::FeatureParam<bool>
+    kLensOverlayVisualSelectionUpdatesEnableBorderGlow{
+        &kLensOverlayVisualSelectionUpdates, "enable-border-glow", true};
+
 constexpr base::FeatureParam<std::string> kHomepageURLForLens{
     &kLensStandalone, "lens-homepage-url", "https://lens.google.com/v3/"};
 
@@ -982,6 +990,15 @@
   return kLensOverlaySimplifiedSelectionShouldCopyAsImage.Get();
 }
 
+bool IsLensOverlayVisualSelectionUpdatesEnabled() {
+  return base::FeatureList::IsEnabled(kLensOverlayVisualSelectionUpdates);
+}
+
+bool GetVisualSelectionUpdatesEnableBorderGlow() {
+  return base::FeatureList::IsEnabled(kLensOverlayVisualSelectionUpdates) &&
+         kLensOverlayVisualSelectionUpdatesEnableBorderGlow.Get();
+}
+
 bool PageContentUploadRequestIdFixEnabled() {
   return kPageContentUploadRequestIdFix.Get();
 }
diff --git a/components/lens/lens_features.h b/components/lens/lens_features.h
index 2e665bd5..d1933ab 100644
--- a/components/lens/lens_features.h
+++ b/components/lens/lens_features.h
@@ -57,6 +57,10 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kLensOverlaySimplifiedSelection);
 
+// Enables the Lens overlay visual selection updates.
+COMPONENT_EXPORT(LENS_FEATURES)
+BASE_DECLARE_FEATURE(kLensOverlayVisualSelectionUpdates);
+
 // Enables the Lens overlay updated client context.
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kLensOverlayUpdatedClientContext);
@@ -755,6 +759,11 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool GetShouldCopyAsImage();
 
+// Whether to enable the border glow for the visual selection updates. Enabling
+// this will disable the shimmer animation.
+COMPONENT_EXPORT(LENS_FEATURES)
+extern bool GetVisualSelectionUpdatesEnableBorderGlow();
+
 // Whether to fix the request id for page content upload requests. When enabled,
 // this will not increment the image upload request ID when the page content
 // upload request is sent.