diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs
--- a/crates/ui-egui/src/canvas.rs
+++ b/crates/ui-egui/src/canvas.rs
@@ -307,6 +307,9 @@ pub struct DocView {
     /// (fractions of the displayed page; `None` leaves that axis to `goto`) to put `rel` points
     /// from the viewport's top-left corner.
     goto_point: Option<(usize, Option<f32>, Option<f32>, Vec2)>,
+    /// Active destination flash indicator after following an internal link:
+    /// `(page, normalized_rect [x0, y0, x1, y1], start_time)`.
+    pub dest_flash: Option<(usize, [f32; 4], f64)>,
     /// Pending keyboard scrolling, in points down (negative: up): ↓ / ↑, and Page Down /
     /// Page Up where the pages scroll.
     pub key_scroll: f32,
@@ -619,6 +622,7 @@ impl DocView {
             forward: Vec::new(),
             goto: None,
             goto_point: None,
+            dest_flash: None,
             key_scroll: 0.0,
             single_edges: None,
             flash: None,
@@ -744,6 +748,7 @@ impl DocView {
         }
         self.goto = None;
         self.goto_point = None;
+        self.dest_flash = None;
         self.zoom_anchor = None;
         self.flash = None;
         // Thumbnail drags hold page indexes from before the change.
@@ -806,6 +811,60 @@ impl DocView {
         self.wheel_boost_x.reset();
         self.wheel_boost_y.reset();
     }
+
+    /// Computes the normalized destination indicator rectangle `[x0, y0, x1, y1]` (as fractions
+    /// `0.0..=1.0` of the displayed page) for `dest` on `page`, to flash after following a link.
+    pub(crate) fn dest_indicator_rect(&self, page: usize, dest: DestView, info: &DocInfo) -> Option<[f32; 4]> {
+        let p = info.pages.get(page)?;
+        let (dw, dh) = self.display_size(p);
+        let (dw, dh) = (dw.max(1.0), dh.max(1.0));
+        let band_x = (24.0 / dw).clamp(0.02, 0.3);
+        let band_y = (24.0 / dh).clamp(0.02, 0.3);
+        let point = |x: Option<f32>, y: Option<f32>| p.dest_fraction(x, y, self.rotation);
+        // The destination coordinate lands on one displayed axis only; a quarter turn swaps it.
+        let band = |[fx, fy]: [Option<f32>; 2], default_vertical: bool| match (fx, fy) {
+            (Some(x), _) => {
+                let x1 = (x + band_x).min(1.0);
+                [(x1 - band_x).max(0.0), 0.0, x1, 1.0]
+            }
+            (None, Some(y)) => {
+                let y1 = (y + band_y).min(1.0);
+                [0.0, (y1 - band_y).max(0.0), 1.0, y1]
+            }
+            (None, None) if default_vertical => [0.0, 0.0, band_x, 1.0],
+            (None, None) => [0.0, 0.0, 1.0, band_y],
+        };
+        let rect = match dest {
+            DestView::Top | DestView::Fit => [0.0, 0.0, 1.0, band_y],
+            DestView::FitH { top } => band(point(None, top), (u32::from(p.rotation) + u32::from(self.rotation)) % 180 == 90),
+            DestView::FitV { left } => band(point(left, None), (u32::from(p.rotation) + u32::from(self.rotation)).is_multiple_of(180)),
+            DestView::Xyz { left, top, .. } => {
+                let [fx, fy] = point(left, top);
+                let (x0, x1) = match fx {
+                    Some(x) => {
+                        let x1 = (x + (120.0 / dw).clamp(0.1, 0.5)).min(1.0);
+                        ((x1 - band_x).min(x).max(0.0), x1)
+                    }
+                    None => (0.0, 1.0),
+                };
+                let (y0, y1) = match fy {
+                    Some(y) => {
+                        let y1 = (y + band_y).min(1.0);
+                        ((y1 - band_y).max(0.0), y1)
+                    }
+                    None => (0.0, band_y),
+                };
+                [x0, y0, x1, y1]
+            }
+            DestView::FitR { rect } => {
+                let ([Some(ax), Some(ay)], [Some(bx), Some(by)]) = (point(Some(rect[0]), Some(rect[3])), point(Some(rect[2]), Some(rect[1]))) else {
+                    return None;
+                };
+                [ax.min(bx), ay.min(by), ax.max(bx), ay.max(by)]
+            }
+        };
+        Some(rect)
+    }
 }
 
 /// Draws the document tab at `index`: its notice bars, then its pages (or the Organize grid) in a
@@ -2071,6 +2130,28 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
                     view.flash = None;
                 }
             }
+            if let Some((fp, [x0, y0, x1, y1], t0)) = view.dest_flash
+                && fp == i
+            {
+                let now = ui.input(|inp| inp.time);
+                let t0 = if t0 == 0.0 { now } else { t0 };
+                view.dest_flash = Some((fp, [x0, y0, x1, y1], t0));
+                let age = (now - t0).max(0.0);
+                if age < 1.5 {
+                    let step = (age / 0.25) as usize;
+                    if step.is_multiple_of(2) {
+                        let sr = Rect::from_min_max(
+                            pos2(r.left() + x0 * r.width(), r.top() + y0 * r.height()),
+                            pos2(r.left() + x1 * r.width(), r.top() + y1 * r.height()),
+                        );
+                        painter.rect_stroke(sr, CornerRadius::ZERO, Stroke::new(2.0, Color32::BLACK), egui::StrokeKind::Inside);
+                    }
+                    let next = ((step + 1) as f64 * 0.25).min(1.5);
+                    ui.ctx().request_repaint_after(std::time::Duration::from_secs_f64((next - age).max(0.01)));
+                } else {
+                    view.dest_flash = None;
+                }
+            }
             // Validation is viewer chrome over the signer's original artwork, which
             // may still say "Signature Not Verified". These badges never enter document
             // streams, image/text exports, vector printing or Print as Image.
@@ -2519,7 +2600,11 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
         }
     }
     match clicked_link {
-        Some(LinkTarget::Page(p, dest)) => view.go_to_dest(p, dest, info),
+        Some(LinkTarget::Page(p, dest)) => {
+            view.go_to_dest(p, dest, info);
+            let target = view.current;
+            view.dest_flash = view.dest_indicator_rect(target, dest, info).map(|r| (target, r, now));
+        }
         Some(LinkTarget::Uri(u)) => app.request_document_url(&u, crate::LinkOrigin::Link),
         Some(LinkTarget::SetLayers { changes, preserve_rb }) => set_layers(app, index, &changes, preserve_rb),
         Some(LinkTarget::RemoteGoTo { file, destination, .. }) => remote_open = Some((view.id, file, destination)),
diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs
--- a/crates/ui-egui/src/canvas/tests.rs
+++ b/crates/ui-egui/src/canvas/tests.rs
@@ -1889,6 +1889,45 @@ fn destinations_set_the_zoom_mode_and_the_point_to_show() {
     assert_eq!((v.current, v.goto_point), (1, None));
 }
 
+#[test]
+fn dest_indicator_rect_covers_each_destination_view() {
+    let info = pdfcraft_render::DocInfo {
+        pages: vec![pdfcraft_render::PageInfo { width: 300.0, height: 400.0, label: String::new(), crop: [0.0, 0.0, 300.0, 400.0], rotation: 0 }],
+        ..Default::default()
+    };
+    let v = view(1, PageLayout::Single, true);
+
+    let top = v.dest_indicator_rect(0, DestView::Top, &info).expect("top rect");
+    assert_eq!([top[0], top[1], top[2]], [0.0, 0.0, 1.0]);
+    assert!((top[3] - 24.0 / 400.0).abs() < 1e-4);
+
+    let fit = v.dest_indicator_rect(0, DestView::Fit, &info).expect("fit rect");
+    assert_eq!(fit, top);
+
+    let fith = v.dest_indicator_rect(0, DestView::FitH { top: Some(300.0) }, &info).expect("fith rect");
+    assert!((fith[0] - 0.0).abs() < 1e-4 && (fith[1] - 0.25).abs() < 1e-4 && (fith[2] - 1.0).abs() < 1e-4);
+
+    let fitv = v.dest_indicator_rect(0, DestView::FitV { left: Some(75.0) }, &info).expect("fitv rect");
+    assert!((fitv[0] - 0.25).abs() < 1e-4 && (fitv[1] - 0.0).abs() < 1e-4 && (fitv[3] - 1.0).abs() < 1e-4);
+
+    let xyz = v.dest_indicator_rect(0, DestView::Xyz { left: Some(150.0), top: Some(200.0), zoom: None }, &info).expect("xyz rect");
+    assert!((xyz[0] - 0.5).abs() < 1e-4 && (xyz[1] - 0.5).abs() < 1e-4 && xyz[2] > xyz[0] && xyz[3] > xyz[1]);
+
+    let fitr = v.dest_indicator_rect(0, DestView::FitR { rect: [60.0, 100.0, 150.0, 300.0] }, &info).expect("fitr rect");
+    assert!((fitr[0] - 0.2).abs() < 1e-4 && (fitr[1] - 0.25).abs() < 1e-4 && (fitr[2] - 0.5).abs() < 1e-4 && (fitr[3] - 0.75).abs() < 1e-4);
+
+    // Quarter-turn rotation swaps the displayed axis for FitH and FitV.
+    let mut rotated = v;
+    rotated.rotation = 90;
+    let fith_rot = rotated.dest_indicator_rect(0, DestView::FitH { top: Some(300.0) }, &info).expect("rotated fith rect");
+    assert!((fith_rot[0] - 0.75).abs() < 1e-4 && (fith_rot[1] - 0.0).abs() < 1e-4 && (fith_rot[3] - 1.0).abs() < 1e-4);
+
+    let fitv_rot = rotated.dest_indicator_rect(0, DestView::FitV { left: Some(75.0) }, &info).expect("rotated fitv rect");
+    assert!((fitv_rot[0] - 0.0).abs() < 1e-4 && (fitv_rot[1] - 0.25).abs() < 1e-4 && (fitv_rot[2] - 1.0).abs() < 1e-4);
+
+    assert!(rotated.dest_indicator_rect(5, DestView::Top, &info).is_none());
+}
+
 #[test]
 fn page_text_failure_preserves_valid_search_match_without_jitter() {
     let mut v = view(3, PageLayout::Single, true);
diff --git a/crates/ui-egui/tests/it/pdf_links.rs b/crates/ui-egui/tests/it/pdf_links.rs
--- a/crates/ui-egui/tests/it/pdf_links.rs
+++ b/crates/ui-egui/tests/it/pdf_links.rs
@@ -145,3 +145,45 @@ fn the_link_context_menu_shows_only_for_a_right_click_on_a_link() {
     assert!(links(&h).is_empty());
     assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_undo(), Some("Remove all links"));
 }
+
+const INTERNAL_LINK_PDF: &[u8] = b"%PDF-1.7
+1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
+2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 /MediaBox [0 0 300 400] >> endobj
+3 0 obj << /Type /Page /Parent 2 0 R /Annots [20 0 R] >> endobj
+4 0 obj << /Type /Page /Parent 2 0 R >> endobj
+20 0 obj << /Type /Annot /Subtype /Link /Rect [50 300 250 350] /Border [0 0 0] /Dest [4 0 R /FitH 300] >> endobj
+trailer << /Root 1 0 R >>
+%%EOF";
+
+/// Following an internal page link activates the flashing destination indicator on the target page
+/// and clears it once the animation duration elapses (#4787).
+#[test]
+fn following_an_internal_link_flashes_the_destination_indicator() {
+    let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 60.0).build_eframe(|_cc| {
+        let mut app = PdfCraftApp::new();
+        app.set_option("language", "en").unwrap();
+        app.open_bytes("internal.pdf", None, INTERNAL_LINK_PDF.to_vec()).unwrap();
+        app.set_option("zoom", "100").unwrap();
+        app
+    });
+    h.run_steps(6);
+    assert!(h.state().views[0].dest_flash.is_none());
+
+    let click_pos = at(&h, 150.0, 75.0);
+    h.hover_at(click_pos);
+    h.run_steps(2);
+    h.drag_at(click_pos);
+    h.drop_at(click_pos);
+    h.run_steps(2);
+
+    let v = &h.state().views[0];
+    assert_eq!(v.current, 1, "internal link navigates to page 2");
+    let (flash_page, rect, _) = v.dest_flash.expect("destination flash indicator is active after following an internal link");
+    assert_eq!(flash_page, 1);
+    assert!((rect[0] - 0.0).abs() < 1e-4 && (rect[2] - 1.0).abs() < 1e-4);
+    assert!((rect[1] - 0.25).abs() < 1e-4 && rect[3] > rect[1]);
+
+    // Advance past the 1.5s animation window (100 steps at 1/60s > 1.5s) and verify it clears.
+    h.run_steps(100);
+    assert!(h.state().views[0].dest_flash.is_none(), "destination flash indicator clears after the animation finishes");
+}
diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml
--- a/parity/acrobat-features.toml
+++ b/parity/acrobat-features.toml
@@ -1190,8 +1190,8 @@ milestone = "M3"
 status = "shipped"
 commands = []
 tools = ["link_open"]
-evidence = ["crates/ui-egui/tests/it/view.rs::clicking_a_link_goes_to_its_destination", "crates/ui-egui/tests/it/view.rs::link_destinations_scroll_to_their_position", "crates/ui-egui/tests/it/view.rs::a_link_without_a_position_keeps_the_place_on_the_next_page", "crates/ui-egui/src/canvas/tests.rs::a_null_xyz_coordinate_keeps_the_readers_place_on_the_new_page", "crates/render/src/inspect/tests.rs::bookmarks_and_links_keep_their_destination_view", "crates/render/src/inspect/tests.rs::links_read_bounded_local_remote_go_to_actions", "crates/automation/tests/it/automation.rs::local_gotor_link_open_applies_destinations_and_honors_root", "crates/ui-egui/tests/it/document_links.rs::a_local_gotor_link_opens_in_a_tab_and_applies_its_destination", "crates/ui-egui/tests/it/document_links.rs::an_absolute_gotor_waits_for_consent_and_keeps_its_destination_across_tabs", "crates/ui-egui/tests/it/document_links.rs::cancelling_an_absolute_gotor_by_button_escape_or_dismissal_changes_nothing", "crates/automation/tests/it/automation.rs::absolute_gotor_consent_does_not_bypass_validation_or_passwords", "crates/render/src/inspect/tests.rs::launch_actions_that_name_a_pdf_open_it_like_a_remote_link", "crates/automation/tests/it/automation.rs::a_launch_link_to_a_pdf_opens_it_like_a_remote_link"]
-notes = "GoTo and URI links plus local-PDF GoToR links: source-relative or local absolute /F paths, /UF preferred in file-spec dictionaries, explicit numeric page/view destinations, and internal tabs. Absolute local GoToR targets require desktop Allow/Cancel confirmation or an explicit allow_absolute opt-in in link_open; relative links open normally. link_open honors --root. Named remote destinations open at default view with a warning; /NewWindow is a tab hint. A Launch action whose /F names a .pdf file (and has no /Win launch parameters) opens that PDF the same way, backslash separators read as / (#4168); URL/network file systems, GoToE, other Launch actions and other actions are not executed. Same-document destinations retain their supported /XYZ, /Fit, /FitH, /FitV and /FitR views; /FitB* is read as whole-page fit. A null /XYZ left or top keeps the reader's place, as ISO 32000-2 §12.3.2.2 says and Acrobat Reader was observed to do (synthetic fixture): the user-space point at the window's top-left stays there on the target page at the same zoom, along each axis the page overflows; CAD exports (WSCAD) write every cross-reference this way. Null /FitH top and /FitV left still go to the page edge."
+evidence = ["crates/ui-egui/tests/it/view.rs::clicking_a_link_goes_to_its_destination", "crates/ui-egui/tests/it/view.rs::link_destinations_scroll_to_their_position", "crates/ui-egui/tests/it/view.rs::a_link_without_a_position_keeps_the_place_on_the_next_page", "crates/ui-egui/src/canvas/tests.rs::a_null_xyz_coordinate_keeps_the_readers_place_on_the_new_page", "crates/ui-egui/src/canvas/tests.rs::dest_indicator_rect_covers_each_destination_view", "crates/ui-egui/tests/it/pdf_links.rs::following_an_internal_link_flashes_the_destination_indicator", "crates/render/src/inspect/tests.rs::bookmarks_and_links_keep_their_destination_view", "crates/render/src/inspect/tests.rs::links_read_bounded_local_remote_go_to_actions", "crates/automation/tests/it/automation.rs::local_gotor_link_open_applies_destinations_and_honors_root", "crates/ui-egui/tests/it/document_links.rs::a_local_gotor_link_opens_in_a_tab_and_applies_its_destination", "crates/ui-egui/tests/it/document_links.rs::an_absolute_gotor_waits_for_consent_and_keeps_its_destination_across_tabs", "crates/ui-egui/tests/it/document_links.rs::cancelling_an_absolute_gotor_by_button_escape_or_dismissal_changes_nothing", "crates/automation/tests/it/automation.rs::absolute_gotor_consent_does_not_bypass_validation_or_passwords", "crates/render/src/inspect/tests.rs::launch_actions_that_name_a_pdf_open_it_like_a_remote_link", "crates/automation/tests/it/automation.rs::a_launch_link_to_a_pdf_opens_it_like_a_remote_link"]
+notes = "GoTo and URI links plus local-PDF GoToR links: source-relative or local absolute /F paths, /UF preferred in file-spec dictionaries, explicit numeric page/view destinations, and internal tabs. Absolute local GoToR targets require desktop Allow/Cancel confirmation or an explicit allow_absolute opt-in in link_open; relative links open normally. link_open honors --root. Named remote destinations open at default view with a warning; /NewWindow is a tab hint. A Launch action whose /F names a .pdf file (and has no /Win launch parameters) opens that PDF the same way, backslash separators read as / (#4168); URL/network file systems, GoToE, other Launch actions and other actions are not executed. Same-document destinations retain their supported /XYZ, /Fit, /FitH, /FitV and /FitR views and briefly flash a black destination indicator rectangle after following an internal link (#4787); /FitB* is read as whole-page fit. A null /XYZ left or top keeps the reader's place, as ISO 32000-2 §12.3.2.2 says and Acrobat Reader was observed to do (synthetic fixture): the user-space point at the window's top-left stays there on the target page at the same zoom, along each axis the page overflows; CAD exports (WSCAD) write every cross-reference this way. Null /FitH top and /FitV left still go to the page edge."
 
 [[feature]]
 id = "view.annotation-hover"
