diff --git a/lazy_player/main_window.py b/lazy_player/main_window.py
index bb4337c..c4362af 100644
--- a/lazy_player/main_window.py
+++ b/lazy_player/main_window.py
@@ -5,32 +5,36 @@ from datetime import datetime
 from pathlib import Path
 from typing import Any, cast
 
-from gi.repository import Gdk, GLib, Gtk, Pango
+from gi.repository import Gdk, GLib, Gtk
 
 from .file_model import FileItem, FileListModel, FileType
 from .reactive import Watcher, update_all_computed
 from .thumbnailer import Thumbnailer
+from .video_overlay import VideoOverlay
 from .video_player import VideoPlayer
 
 
 class MainWindow(Gtk.ApplicationWindow, Watcher):
+    player: VideoPlayer
+    overlay: VideoOverlay
+
     stack: Gtk.Stack
+
     list_view: Gtk.ListView
     list_model: FileListModel
     selection_model: Gtk.SingleSelection
-    video_widget: Gtk.Picture
-    tick_callback_id: int
-    overlay_label: Gtk.Label
-    overlay_hide_time: float
-    last_position_save: float
-    grid_segments: list[list[Gtk.Box]]
-    grid_overlay: Gtk.Grid
-    grid_clock: Gtk.Label
-    main_clock: Gtk.Label
+
+    video_picture: Gtk.Picture
+    thumbnail_picture: Gtk.Picture
+
+    clock: Gtk.Label
 
     directory_history: list[Path]
     selection_history: dict[str, int]
 
+    last_position_save: float
+    now: float
+
     def __init__(self, *args: Any, thumbnailer: Thumbnailer, **kwargs: Any):
         super().__init__(*args, **kwargs)
         self.thumbnailer = thumbnailer
@@ -39,18 +43,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
         self.directory_history = []
         self.selection_history = {}
 
-        # For overlay text timeout
-        self.overlay_hide_time = 0.0
+        # Last time we've saved playback position.
         self.last_position_save = 0.0
-        self.overlay_label = Gtk.Label()
-        self.overlay_label.set_name("overlay-text")
-        self.overlay_label.set_valign(Gtk.Align.CENTER)
-        self.overlay_label.set_halign(Gtk.Align.CENTER)
-        self.overlay_label.set_visible(False)
-        self.overlay_label.set_wrap(True)
-        self.overlay_label.set_wrap_mode(Pango.WrapMode.WORD_CHAR)
-
-        self.tick_callback_id = self.add_tick_callback(self._on_tick, None)
 
         # Make window fullscreen and borderless
         self.set_decorated(False)
@@ -69,107 +63,29 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
             if cursor:
                 self.set_cursor(cursor)
 
-        # Main horizontal box to split the screen
-        main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
-
-        # Create stack to hold our widgets
-        self.stack = Gtk.Stack()
+        # Process frame ticks.
+        self.add_tick_callback(self._on_tick, None)
 
         # Create video widget and overlay
-        self.video_widget = Gtk.Picture()
-        self.video_widget.set_can_shrink(True)
-        self.video_widget.set_keep_aspect_ratio(True)
-        self.video_widget.set_vexpand(True)
-        self.video_widget.set_hexpand(True)
-
-        video_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        video_box.set_name("black-overlay")
-        video_box.set_vexpand(True)
-        video_box.set_hexpand(True)
-
-        # Create grid overlay with 3x3 boxes
-        self.grid_overlay = Gtk.Grid()
-        self.grid_overlay.set_hexpand(True)
-        self.grid_overlay.set_vexpand(True)
-        self.grid_overlay.set_name("grid-overlay")
-        self.grid_overlay.set_column_homogeneous(True)
-        self.grid_overlay.set_row_homogeneous(True)
-
-        # Store grid segments in a 3x3 array
-        self.grid_segments = [[Gtk.Box() for _ in range(3)] for _ in range(3)]
-
-        # Setup 3x3 grid of boxes
-        for row in range(3):
-            for col in range(3):
-                box = self.grid_segments[row][col]
-                box.set_name("grid-box")
-                box.set_hexpand(True)
-                box.set_vexpand(True)
-                self.grid_overlay.attach(box, col, row, 1, 1)
-
-        # Add clock to top-left grid box
-        self.grid_clock = Gtk.Label()
-        self.grid_clock.set_name("grid-clock")
-        self.grid_clock.set_halign(Gtk.Align.CENTER)
-        self.grid_clock.set_valign(Gtk.Align.START)
-        self.grid_clock.set_hexpand(True)
-        self.grid_clock.set_vexpand(True)
-        self.grid_clock.set_text(datetime.now().strftime("%H:%M"))
-
-        # Attach to top-left grid box
-        self.grid_segments[0][0].append(self.grid_clock)
-
-        # Create an overlay container
-        overlay = Gtk.Overlay()
-        overlay.set_child(self.video_widget)
-        overlay.add_overlay(self.grid_overlay)
-        overlay.add_overlay(self.overlay_label)
-
-        video_box.append(overlay)
-
-        # Setup video player
-        self.video_player = VideoPlayer(self.video_widget)
-
-        # Add both main menu and overlay to stack
-        self.stack.add_named(main_box, "menu")
-        self.stack.add_named(video_box, "player")
-        self.stack.set_visible_child_name("menu")
-
-        # Create a grid to handle the 1:2 ratio
-        grid = Gtk.Grid()
-        grid.set_column_homogeneous(True)
-        grid.set_hexpand(True)
-
-        # Left third (1/3 width)
-        left_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        left_box.set_valign(Gtk.Align.START)
-        left_box.set_halign(Gtk.Align.FILL)
+        self.video_picture = Gtk.Picture()
+        self.video_picture.set_can_shrink(True)
+        self.video_picture.set_keep_aspect_ratio(True)
+        self.video_picture.set_vexpand(True)
+        self.video_picture.set_hexpand(True)
 
         # Create main menu clock
-        self.main_clock = Gtk.Label()
-        self.main_clock.set_name("digital-clock")
-        self.main_clock.set_halign(Gtk.Align.CENTER)
-        self.main_clock.set_valign(Gtk.Align.CENTER)
-        left_box.append(self.main_clock)
+        self.clock = Gtk.Label()
+        self.clock.set_name("main-clock")
+        self.clock.set_halign(Gtk.Align.CENTER)
+        self.clock.set_valign(Gtk.Align.CENTER)
 
         # Create image widget for thumbnail
-        self.thumbnail_image = Gtk.Picture()
-        self.thumbnail_image.set_name("thumbnail-image")
-        self.thumbnail_image.set_size_request(384, 216)  # 16:9 aspect ratio
-        self.thumbnail_image.set_can_shrink(True)
-        self.thumbnail_image.set_keep_aspect_ratio(True)
-        self.thumbnail_image.set_resource(None)
-        left_box.append(self.thumbnail_image)
-
-        # Right two-thirds (2/3 width)
-        right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        right_box.set_hexpand(True)
-
-        # Attach boxes to grid with specific column spans
-        grid.attach(left_box, 0, 0, 1, 1)
-        grid.attach(right_box, 1, 0, 2, 1)
-
-        main_box.append(grid)
+        self.thumbnail_picture = Gtk.Picture()
+        self.thumbnail_picture.set_name("thumbnail-picture")
+        self.thumbnail_picture.set_size_request(384, 216)
+        self.thumbnail_picture.set_can_shrink(True)
+        self.thumbnail_picture.set_keep_aspect_ratio(True)
+        self.thumbnail_picture.set_resource(None)
 
         # Create list model and view
         self.list_model = FileListModel()
@@ -178,35 +94,7 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
         self.selection_model.connect("selection-changed", self._on_selection_changed)
         self.list_view.set_model(self.selection_model)
         self.list_view.set_vexpand(True)
-
-        def on_activate(widget: Gtk.ListView, index: int):
-            selected_item = self.selection_model.get_item(index)
-            if selected_item:
-                file_item = cast(FileItem, selected_item)
-
-                if file_item.file_type == FileType.DIRECTORY:
-                    self._navigate_to(file_item.full_path)
-                    return
-
-                position = file_item.saved_position.value
-                duration = file_item.saved_duration.value
-
-                if (position / duration) >= 0.99:
-                    position = 0
-
-                # Start playing the video
-                self.video_player.play(
-                    file_item.full_path,
-                    position,
-                    file_item.saved_subtitle_track.value,
-                    file_item.saved_audio_track.value,
-                )
-                self.last_position_save = self.now
-
-                self.stack.set_visible_child_name("player")
-                self.show_overlay_text(f"Playing: {file_item.name}")
-
-        self.list_view.connect("activate", on_activate)
+        self.list_view.connect("activate", self._on_activate)
 
         # Factory for list items
         factory = Gtk.SignalListItemFactory()
@@ -214,25 +102,52 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
         factory.connect("bind", self._bind_list_item)
         self.list_view.set_factory(factory)
 
-        # Populate the list store
-        self._populate_file_list()
-
         # Add list view to a scrolled window
         scrolled = Gtk.ScrolledWindow()
         scrolled.set_child(self.list_view)
+
+        # Setup video player.
+        self.player = VideoPlayer(self.video_picture)
+
+        # Setup video overlay using that player.
+        self.overlay = VideoOverlay(self.player)
+        self.overlay.set_name("overlay")
+
+        # Left third (1/3 width).
+        left_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        left_box.set_valign(Gtk.Align.START)
+        left_box.set_halign(Gtk.Align.FILL)
+        left_box.append(self.clock)
+        left_box.append(self.thumbnail_picture)
+
+        # Right two-thirds (2/3 width).
+        right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        right_box.set_hexpand(True)
         right_box.append(scrolled)
 
+        # Create a grid to handle the 1:2 ratio we want.
+        grid = Gtk.Grid()
+        grid.set_column_homogeneous(True)
+        grid.set_hexpand(True)
+        grid.attach(left_box, 0, 0, 1, 1)
+        grid.attach(right_box, 1, 0, 2, 1)
+
+        # Add both main menu and overlay to stack.
+        self.stack = Gtk.Stack()
+        self.stack.add_named(grid, "menu")
+        self.stack.add_named(self.overlay, "player")
+
+        # Start with main menu visible.
+        self.stack.set_visible_child_name("menu")
+
+        # Stack is our root.
         self.set_child(self.stack)
 
+        # Enable all watch methods.
         self.watch_all()
 
-    @property
-    def now(self) -> float:
-        frame_clock = self.get_frame_clock()
-        if frame_clock is None:
-            return 0
-
-        return frame_clock.get_frame_time() / 1_000_000
+        # Populate the list store.
+        self._populate_file_list()
 
     @property
     def selection(self) -> FileItem | None:
@@ -285,6 +200,33 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
         item.connect("notify::saved-duration", update_icon)
         update_icon()
 
+    def _on_activate(self, widget: Gtk.ListView, index: int):
+        selected_item = self.selection_model.get_item(index)
+        if selected_item:
+            file_item = cast(FileItem, selected_item)
+
+            if file_item.file_type == FileType.DIRECTORY:
+                self._navigate_to(file_item.full_path)
+                return
+
+            position = file_item.saved_position.value
+            duration = file_item.saved_duration.value
+
+            if (position / duration) >= 0.99:
+                position = 0
+
+            # Start playing the video
+            self.player.play(
+                file_item.full_path,
+                position,
+                file_item.saved_subtitle_track.value,
+                file_item.saved_audio_track.value,
+            )
+            self.last_position_save = self.now
+
+            self.stack.set_visible_child_name("player")
+            self.overlay.show_message(f"Playing: {file_item.name}")
+
     def _restore_selection(self):
         pos = self.selection_history.get(os.getcwd(), 0)
         self.selection_model.set_selected(pos)
@@ -327,14 +269,14 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
             if file_item.thumbnail.value:
                 gbytes = GLib.Bytes.new(cast(Any, file_item.thumbnail.value))
                 texture = Gdk.Texture.new_from_bytes(gbytes)
-                self.thumbnail_image.set_paintable(texture)
+                self.thumbnail_picture.set_paintable(texture)
             else:
                 self.thumbnailer.generate_thumbnail(file_item)
-                self.thumbnail_image.set_paintable(None)
+                self.thumbnail_picture.set_paintable(None)
 
     def _toggle_play_pause(self) -> None:
         """Toggle between play and pause states"""
-        self.video_player.toggle_play_pause()
+        self.player.toggle_play_pause()
 
     def _on_player_key_pressed(
         self,
@@ -348,62 +290,62 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
 
         elif keyval == Gdk.keyval_from_name("Escape"):
             self._save_position()
-            self.video_player.stop()
+            self.player.stop()
             self.stack.set_visible_child_name("menu")
             self.list_view.grab_focus()
             return True
 
         elif keyval == Gdk.keyval_from_name("Left"):
-            self.video_player.seek_relative(-10)
+            self.player.seek_relative(-10)
             return True
 
         elif keyval == Gdk.keyval_from_name("Right"):
-            self.video_player.seek_relative(10)
+            self.player.seek_relative(10)
             return True
 
         elif keyval == Gdk.keyval_from_name("Down"):
-            self.video_player.seek_relative(-60)
+            self.player.seek_relative(-60)
             return True
 
         elif keyval == Gdk.keyval_from_name("Up"):
-            self.video_player.seek_relative(60)
+            self.player.seek_relative(60)
             return True
 
         elif keyval == Gdk.keyval_from_name("Home"):
-            self.video_player.seek_start()
+            self.player.seek_start()
             return True
 
         elif keyval == Gdk.keyval_from_name("End"):
-            self.video_player.seek_end()
+            self.player.seek_end()
             return True
 
         elif keyval == Gdk.keyval_from_name("j"):
-            has_subs, index, lang = self.video_player.cycle_subtitles()
+            has_subs, index, lang = self.player.cycle_subtitles()
 
             if has_subs:
                 if index:
-                    self.show_overlay_text(f"Subtitles #{index} ({lang})")
+                    self.overlay.show_message(f"Subtitles #{index} ({lang})")
                 else:
-                    self.show_overlay_text("Subtitles turned off")
+                    self.overlay.show_message("Subtitles turned off")
 
                 file_item = self.selection
                 if file_item is not None:
                     file_item.saved_subtitle_track.value = index - 1
             else:
-                self.show_overlay_text("No subtitles available")
+                self.overlay.show_message("No subtitles available")
 
             return True
 
         elif keyval == Gdk.keyval_from_name("a"):
-            has_audio, index, lang = self.video_player.cycle_audio()
+            has_audio, index, lang = self.player.cycle_audio()
 
             if has_audio:
-                self.show_overlay_text(f"Audio #{index} ({lang})")
+                self.overlay.show_message(f"Audio #{index} ({lang})")
                 file_item = self.selection
                 if file_item is not None:
                     file_item.saved_audio_track.value = index - 1
             else:
-                self.show_overlay_text("No audio tracks available")
+                self.overlay.show_message("No audio tracks available")
 
             return True
 
@@ -470,7 +412,7 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
     def _save_position(self) -> None:
         """Save current playback position as xattr"""
 
-        if not self.video_player.is_playing:
+        if not self.player.is_playing:
             return
 
         file_item = self.selection
@@ -478,55 +420,35 @@ class MainWindow(Gtk.ApplicationWindow, Watcher):
         if file_item is None or file_item.file_type == FileType.DIRECTORY:
             return
 
-        position = self.video_player.get_position()
+        position = self.player.get_position()
         if position is not None:
             file_item.saved_position.value = position
 
-        duration = self.video_player.get_duration()
+        duration = self.player.get_duration()
         if duration is not None:
             file_item.saved_duration.value = duration
 
-    def show_overlay_text(self, text: str, timeout_seconds: float = 1.0) -> None:
-        """Show text in a centered overlay that disappears after timeout"""
-
-        self.overlay_label.set_text(text)
-        self.overlay_label.set_visible(True)
-
-        # Set absolute time when overlay should hide
-        self.overlay_hide_time = self.now + timeout_seconds
-
-    def _watch_player_state(self):
-        is_playing = self.video_player.is_playing.value
-        is_paused = self.video_player.is_paused.value
-        self.grid_overlay.set_visible(is_playing and is_paused)
-
     def _on_tick(self, widget: Gtk.Widget, frame_clock: Gdk.FrameClock, data: Any) -> bool:
-        # Update all reactive values.
+        # Update all reactive values for whole application.
         update_all_computed()
 
-        current_time = frame_clock.get_frame_time() / 1_000_000
+        self.clock.set_text(datetime.now().strftime("%H:%M"))
 
-        current_time_str = datetime.now().strftime("%H:%M")
-        self.main_clock.set_text(current_time_str)
-        self.grid_clock.set_text(current_time_str)
+        self.now = frame_clock.get_frame_time() / 1_000_000
 
-        if current_time >= self.overlay_hide_time:
-            self.overlay_label.set_visible(False)
-
-        # Save position every 60 seconds
-        frame_time = frame_clock.get_frame_time() / 1_000_000
-        if frame_time - self.last_position_save >= 60.0:
+        # Save playback position every 60 seconds.
+        if self.now - self.last_position_save >= 60.0:
             self._save_position()
-            self.last_position_save = frame_time
+            self.last_position_save = self.now
 
         # Update thumbnail if available
         if file_item := self.selection:
             self.thumbnailer.generate_thumbnail(file_item)
 
-            if file_item.thumbnail.value and not self.thumbnail_image.get_paintable():
+            if file_item.thumbnail.value and not self.thumbnail_picture.get_paintable():
                 gbytes = GLib.Bytes.new(cast(Any, file_item.thumbnail.value))
                 texture = Gdk.Texture.new_from_bytes(gbytes)
-                self.thumbnail_image.set_paintable(texture)
+                self.thumbnail_picture.set_paintable(texture)
 
         return True
 
diff --git a/lazy_player/style.css b/lazy_player/style.css
index 9d7b088..07a5fb6 100644
--- a/lazy_player/style.css
+++ b/lazy_player/style.css
@@ -20,11 +20,11 @@ listview > row {
   color: #0f0;
 }
 
-#black-overlay {
+#overlay {
   background-color: black;
 }
 
-#overlay-text {
+#overlay-message {
   color: white;
   font-size: 24px;
   font-family: monospace;
@@ -34,19 +34,19 @@ listview > row {
   margin: 32px;
 }
 
-#thumbnail-image {
+#thumbnail-picture {
   margin: 8px;
 }
 
-#digital-clock,
-#grid-clock {
+#main-clock,
+#overlay-clock {
   color: white;
   font-size: 48px;
   font-family: monospace;
   padding: 12px;
 }
 
-#grid-clock {
+#overlay-clock {
   background-color: rgba(32, 32, 32, 0.5);
   border-radius: 8px;
 }
diff --git a/lazy_player/video_overlay.py b/lazy_player/video_overlay.py
new file mode 100644
index 0000000..54695df
--- /dev/null
+++ b/lazy_player/video_overlay.py
@@ -0,0 +1,113 @@
+from __future__ import annotations
+
+from datetime import datetime
+from typing import Any
+
+from gi.repository import Gdk, Gtk, Pango
+
+from .reactive import Watcher
+from .video_player import VideoPlayer
+
+
+class VideoOverlay(Gtk.Overlay, Watcher):
+    player: VideoPlayer
+
+    message: Gtk.Label
+    message_expiration: float
+
+    grid: Gtk.Grid
+    grid_expiration: float
+
+    clock_box: Gtk.Box
+    clock: Gtk.Label
+
+    now: float
+
+    def __init__(self, player: VideoPlayer):
+        super().__init__()
+
+        self.now = 0.0
+        self.player = player
+
+        # Message is appears at the center of the screen,
+        # above everything else. Usually to indicate change
+        # of subtitle or audio track or something similar.
+        self.message = Gtk.Label()
+        self.message.set_name("overlay-message")
+        self.message.set_valign(Gtk.Align.CENTER)
+        self.message.set_halign(Gtk.Align.CENTER)
+        self.message.set_visible(False)
+        self.message.set_wrap(True)
+        self.message.set_wrap_mode(Pango.WrapMode.WORD_CHAR)
+
+        # Once specific time passes, message disappears.
+        self.message_expiration = 0.0
+
+        # Grid overlay is between the video at the bottom and
+        # the message at the top. It is only shown when user
+        # interacts with the player.
+        self.grid = Gtk.Grid()
+        self.grid.set_hexpand(True)
+        self.grid.set_vexpand(True)
+        self.grid.set_column_homogeneous(True)
+        self.grid.set_row_homogeneous(True)
+
+        # Grid visibility can also expire after a while.
+        self.grid_expiration = 0.0
+
+        # Create grid boxes.
+        self.clock_box = Gtk.Box(hexpand=True, vexpand=True)
+        self.grid.attach(self.clock_box, 0, 0, 1, 1)
+
+        self.grid.attach(Gtk.Box(), 1, 0, 2, 1)
+        self.grid.attach(Gtk.Box(), 0, 1, 3, 1)
+        self.grid.attach(Gtk.Box(), 0, 2, 3, 1)
+
+        # Add clock to the top-left grid box.
+        self.clock = Gtk.Label(hexpand=True, vexpand=True)
+        self.clock.set_name("overlay-clock")
+        self.clock.set_halign(Gtk.Align.CENTER)
+        self.clock.set_valign(Gtk.Align.START)
+        self.clock.set_text(datetime.now().strftime("%H:%M"))
+        self.clock_box.append(self.clock)
+
+        # Add children.
+        self.set_child(self.player.picture)
+        self.add_overlay(self.grid)
+        self.add_overlay(self.message)
+
+        # Consume ticks for the clock and overlay expiration.
+        self.add_tick_callback(self._on_tick, None)
+
+        # Register all watches.
+        self.watch_all()
+
+    def _on_tick(self, widget: Gtk.Widget, frame_clock: Gdk.FrameClock, data: Any) -> bool:
+        self.clock.set_text(datetime.now().strftime("%H:%M"))
+
+        self.now = frame_clock.get_frame_time() / 1_000_000
+
+        if self.grid_expiration <= self.now:
+            self.grid.hide()
+
+        if self.message_expiration <= self.now:
+            self.message.hide()
+
+        return True
+
+    def show_message(self, text: str, timeout: float = 1.0) -> None:
+        """Show text in a centered overlay that disappears after timeout."""
+
+        self.message.set_text(text)
+        self.message.show()
+        self.message_expiration = self.now + timeout
+
+    def _watch_player_state(self):
+        is_playing = self.player.is_playing.value
+        is_paused = self.player.is_paused.value
+
+        if is_playing and is_paused:
+            self.grid.show()
+            self.grid_expiration = 1e20
+        else:
+            self.grid_expiration = 0
diff --git a/lazy_player/video_player.py b/lazy_player/video_player.py
index 194ff1e..ca71639 100644
--- a/lazy_player/video_player.py
+++ b/lazy_player/video_player.py
@@ -10,6 +10,7 @@ DEFAULT_SEEK_FLAGS = Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT
 
 
 class VideoPlayer(GObject.Object):
+    picture: Gtk.Picture
     pipeline: Gst.Pipeline
     playbin: Gst.Element
 
@@ -21,6 +22,8 @@ class VideoPlayer(GObject.Object):
     def __init__(self, picture: Gtk.Picture):
         super().__init__()
 
+        self.picture = picture
+
         self.is_playing = Ref(False)
         self.is_paused = Ref(True)
 
@@ -41,7 +44,7 @@ class VideoPlayer(GObject.Object):
 
         # Link picture to sink
         paintable = video_sink.get_property("paintable")
-        picture.set_paintable(paintable)
+        self.picture.set_paintable(paintable)
 
     def play(
         self,