diff --git a/modules/study/src/main/StudyMultiBoard.scala b/modules/study/src/main/StudyMultiBoard.scala
index 972a10704e919..38371c5c37cd9 100644
--- a/modules/study/src/main/StudyMultiBoard.scala
+++ b/modules/study/src/main/StudyMultiBoard.scala
@@ -1,7 +1,7 @@
 package lila.study
 
 import BSONHandlers.given
-import chess.{ ByColor, Color, Outcome }
+import chess.{ ByColor, Centis, Color, Outcome, Ply }
 import chess.format.pgn.Tags
 import chess.format.{ Fen, Uci }
 import com.github.blemale.scaffeine.AsyncLoadingCache
@@ -67,13 +67,42 @@ final class StudyMultiBoard(
                       "lang" -> "js",
                       "args" -> $arr("$root", "$tags"),
                       "body" -> """function(root, tags) {
-                    |tags = tags.filter(t => t.startsWith('White') || t.startsWith('Black') || t.startsWith('Result'));
-                    |const node = tags.length ? Object.keys(root).reduce(([path, node], i) => (root[i].p > node.p && i.startsWith(path)) ? [i, root[i]] : [path, node], ['', root['_']])[1] : root['_'];
-                    |return {node:{fen:node.f,uci:node.u},tags} }""".stripMargin
+                                    tags = tags.filter(t => t.startsWith('White') || t.startsWith('Black') || t.startsWith('Result'));
+                                    const [node, clockTicking] = tags.length ?
+                                      Object.keys(root).reduce(
+                                        ([node, clockTicking, path, pathTicking], i) => {
+                                          if (root[i].p > node.p && i.startsWith(path)) {
+                                            clockTicking = node;
+                                            pathTicking = path;
+                                            node = root[i];
+                                            path = i;
+                                          } else if (clockTicking && root[i].p > clockTicking.p && i.startsWith(pathTicking)) {
+                                            clockTicking = root[i];
+                                            pathTicking = i;
+                                          }
+                                          return [node, clockTicking, path, pathTicking]
+                                        },
+                                        [root['_'], undefined, '', undefined]
+                                      ).slice(0, 2) : [root['_'], undefined];
+                                    const [whiteClock, blackClock] = clockTicking ? node.f.includes(" b") ? [node.l, clockTicking.l] : [clockTicking.l, node.l] : [undefined, undefined]
+                                    
+                                    return {
+                                      node: {
+                                        fen: node.f,
+                                        uci: node.u,
+                                      },
+                                      tags,
+                                      clocks: {
+                                        black: blackClock,
+                                        white: whiteClock,
+                                      }
+                                    }
+                                  }""".stripMargin
                     )
                   ),
                   "orientation" -> "$setup.orientation",
-                  "name"        -> true
+                  "name"        -> true,
+                  "lastMoveAt"  -> "$relay.lastMoveAt"
                 )
               )
             )
@@ -84,18 +113,23 @@ final class StudyMultiBoard(
             doc  <- r
             id   <- doc.getAsOpt[StudyChapterId]("_id")
             name <- doc.getAsOpt[StudyChapterName]("name")
-            comp <- doc.getAsOpt[Bdoc]("comp")
-            node <- comp.getAsOpt[Bdoc]("node")
-            fen  <- node.getAsOpt[Fen.Epd]("fen")
-            lastMove = node.getAsOpt[Uci]("uci")
-            tags     = comp.getAsOpt[Tags]("tags")
+            lastMoveAt = doc.getAsOpt[Instant]("lastMoveAt")
+            comp   <- doc.getAsOpt[Bdoc]("comp")
+            node   <- comp.getAsOpt[Bdoc]("node")
+            fen    <- node.getAsOpt[Fen.Epd]("fen")
+            clocks <- comp.getAsOpt[Bdoc]("clocks")
+            lastMove   = node.getAsOpt[Uci]("uci")
+            tags       = comp.getAsOpt[Tags]("tags")
+            blackClock = clocks.getAsOpt[Centis]("black")
+            whiteClock = clocks.getAsOpt[Centis]("white")
           yield ChapterPreview(
             id = id,
             name = name,
-            players = tags flatMap ChapterPreview.players,
+            players = tags flatMap ChapterPreview.players(blackClock = blackClock, whiteClock = whiteClock),
             orientation = doc.getAsOpt[Color]("orientation") | Color.White,
             fen = fen,
             lastMove = lastMove,
+            lastMoveAt = lastMoveAt,
             playing = lastMove.isDefined && tags.flatMap(_(_.Result)).has("*"),
             outcome = tags.flatMap(_.outcome)
           )
@@ -108,13 +142,14 @@ final class StudyMultiBoard(
       .obj("name" -> p.name)
       .add("title" -> p.title)
       .add("rating" -> p.rating)
+      .add("clock" -> p.clock)
   }
 
   given Writes[ChapterPreview.Players] = Writes[ChapterPreview.Players] { players =>
     Json.obj("white" -> players.white, "black" -> players.black)
   }
 
-  given Writes[Outcome] = writeAs(_.toString)
+  given Writes[Outcome] = writeAs(_.toString.replace("1/2", "½"))
 
   given Writes[ChapterPreview] = Json.writes
 
@@ -127,21 +162,22 @@ object StudyMultiBoard:
       orientation: Color,
       fen: Fen.Epd,
       lastMove: Option[Uci],
+      lastMoveAt: Option[Instant],
       playing: Boolean,
       outcome: Option[Outcome]
   )
 
   object ChapterPreview:
 
-    case class Player(name: String, title: Option[String], rating: Option[Int])
+    case class Player(name: String, title: Option[String], rating: Option[Int], clock: Option[Centis])
 
     type Players = ByColor[Player]
 
-    def players(tags: Tags): Option[Players] =
+    def players(blackClock: Option[Centis], whiteClock: Option[Centis])(tags: Tags): Option[Players] =
       for
         wName <- tags(_.White)
         bName <- tags(_.Black)
       yield ByColor(
-        white = Player(wName, tags(_.WhiteTitle), tags(_.WhiteElo).flatMap(_.toIntOption)),
-        black = Player(bName, tags(_.BlackTitle), tags(_.BlackElo).flatMap(_.toIntOption))
+        white = Player(wName, tags(_.WhiteTitle), tags(_.WhiteElo).flatMap(_.toIntOption), whiteClock),
+        black = Player(bName, tags(_.BlackTitle), tags(_.BlackElo).flatMap(_.toIntOption), blackClock)
       )
diff --git a/ui/analyse/src/explorer/explorerCtrl.ts b/ui/analyse/src/explorer/explorerCtrl.ts
index ede78696463ed..d76e781238387 100644
--- a/ui/analyse/src/explorer/explorerCtrl.ts
+++ b/ui/analyse/src/explorer/explorerCtrl.ts
@@ -1,10 +1,11 @@
 import { Prop, prop, defined } from 'common';
 import { storedBooleanProp } from 'common/storage';
+import { fenColor } from 'common/mini-game';
 import debounce from 'common/debounce';
 import { sync, Sync } from 'common/sync';
 import { opposite } from 'chessground/util';
 import * as xhr from './explorerXhr';
-import { winnerOf, colorOf } from './explorerUtil';
+import { winnerOf } from './explorerUtil';
 import * as gameUtil from 'game';
 import AnalyseCtrl from '../ctrl';
 import { Hovering, ExplorerData, ExplorerDb, OpeningData, SimpleTablebaseHit, ExplorerOpts } from './interfaces';
@@ -217,7 +218,7 @@ export default class ExplorerCtrl {
     return {
       fen,
       best: move && move.uci,
-      winner: res.checkmate ? opposite(colorOf(fen)) : res.stalemate ? undefined : winnerOf(fen, move!),
+      winner: res.checkmate ? opposite(fenColor(fen)) : res.stalemate ? undefined : winnerOf(fen, move!),
     } as SimpleTablebaseHit;
   };
 }
diff --git a/ui/analyse/src/explorer/explorerUtil.ts b/ui/analyse/src/explorer/explorerUtil.ts
index 11a3e0c682614..8663ede023aa6 100644
--- a/ui/analyse/src/explorer/explorerUtil.ts
+++ b/ui/analyse/src/explorer/explorerUtil.ts
@@ -1,14 +1,11 @@
 import { TablebaseMoveStats } from './interfaces';
 import { opposite } from 'chessops/util';
+import { fenColor } from 'common/mini-game';
 import { VNode } from 'snabbdom';
 import AnalyseCtrl from '../ctrl';
 
-export function colorOf(fen: Fen): Color {
-  return fen.split(' ')[1] === 'w' ? 'white' : 'black';
-}
-
 export function winnerOf(fen: Fen, move: TablebaseMoveStats): Color | undefined {
-  const stm = colorOf(fen);
+  const stm = fenColor(fen);
   if (move.checkmate || move.variant_loss || (move.dtz && move.dtz < 0)) return stm;
   if (move.variant_win || (move.dtz && move.dtz > 0)) return opposite(stm);
   return undefined;
diff --git a/ui/analyse/src/study/interfaces.ts b/ui/analyse/src/study/interfaces.ts
index 042b9e1697a5f..3805af067ab93 100644
--- a/ui/analyse/src/study/interfaces.ts
+++ b/ui/analyse/src/study/interfaces.ts
@@ -158,7 +158,7 @@ export interface StudyChapterMeta {
   id: string;
   name: string;
   ongoing?: boolean;
-  res?: string;
+  res?: '1-0' | '0-1' | '½-½' | '*';
 }
 
 export interface StudyChapterConfig extends StudyChapterMeta {
@@ -234,14 +234,16 @@ export interface ChapterPreview {
   orientation: Color;
   fen: string;
   lastMove?: string;
+  lastMoveAt?: number;
   playing: boolean;
-  outcome?: '1-0' | '0-1' | '1/2-1/2';
+  outcome?: '1-0' | '0-1' | '½-½';
 }
 
 export interface ChapterPreviewPlayer {
   name: string;
   title?: string;
   rating?: number;
+  clock?: number;
 }
 
 export type Orientation = 'black' | 'white' | 'auto';
diff --git a/ui/analyse/src/study/multiBoard.ts b/ui/analyse/src/study/multiBoard.ts
index 8d342cb53d85a..d686932e7cf95 100644
--- a/ui/analyse/src/study/multiBoard.ts
+++ b/ui/analyse/src/study/multiBoard.ts
@@ -1,10 +1,12 @@
 import debounce from 'common/debounce';
+import { renderClock, fenColor } from 'common/mini-game';
 import { bind, MaybeVNodes } from 'common/snabbdom';
 import { spinnerVdom as spinner } from 'common/spinner';
 import { h, VNode } from 'snabbdom';
 import { multiBoard as xhrLoad } from './studyXhr';
-import { opposite } from 'chessground/util';
-import { StudyCtrl, ChapterPreview, ChapterPreviewPlayer, Position } from './interfaces';
+import { opposite as CgOpposite } from 'chessground/util';
+import { opposite as oppositeColor } from 'chessops/util';
+import { StudyCtrl, ChapterPreview, ChapterPreviewPlayer, Position, StudyChapterMeta } from './interfaces';
 
 export class MultiBoardCtrl {
   loading = false;
@@ -19,10 +21,28 @@ export class MultiBoardCtrl {
     if (cp?.playing) {
       cp.fen = node.fen;
       cp.lastMove = node.uci;
+      const playerWhoMoved = cp.players && cp.players[oppositeColor(fenColor(cp.fen))];
+      playerWhoMoved && (playerWhoMoved.clock = node.clock);
+      // at this point `(cp: ChapterPreview).lastMoveAt` becomes outdated but should be ok since not in use anymore
+      // to mitigate bad usage, setting it as `undefined`
+      cp.lastMoveAt = undefined;
       this.redraw();
     }
   };
 
+  addResult = (metas: StudyChapterMeta[]) => {
+    let changed = false;
+    for (const meta of metas) {
+      const cp = this.pager && this.pager.currentPageResults.find(cp => cp.id == meta.id);
+      if (cp?.playing) {
+        const oldOutcome = cp.outcome;
+        cp.outcome = meta.res !== '*' ? meta.res : undefined;
+        changed = changed || cp.outcome !== oldOutcome;
+      }
+    }
+    if (changed) this.redraw();
+  };
+
   reload = (onInsert?: boolean) => {
     if (this.pager && !onInsert) {
       this.loading = true;
@@ -153,16 +173,29 @@ const makePreview = (study: StudyCtrl) => (preview: ChapterPreview) =>
         },
         postpatch(old, vnode) {
           if (old.data!.fen !== preview.fen) {
-            lichess.miniGame.update(vnode.elm as HTMLElement, {
-              lm: preview.lastMove!,
-              fen: preview.fen,
-            });
+            if (preview.outcome) {
+              lichess.miniGame.finish(
+                vnode.elm as HTMLElement,
+                preview.outcome === '1-0' ? 'white' : preview.outcome === '0-1' ? 'black' : undefined
+              );
+            } else {
+              lichess.miniGame.update(vnode.elm as HTMLElement, {
+                lm: preview.lastMove!,
+                fen: preview.fen,
+                wc: computeTimeLeft(preview, 'white'),
+                bc: computeTimeLeft(preview, 'black'),
+              });
+            }
           }
           vnode.data!.fen = preview.fen;
         },
       },
     },
-    [boardPlayer(preview, opposite(preview.orientation)), h('span.cg-wrap'), boardPlayer(preview, preview.orientation)]
+    [
+      boardPlayer(preview, CgOpposite(preview.orientation)),
+      h('span.cg-wrap'),
+      boardPlayer(preview, preview.orientation),
+    ]
   );
 
 const userName = (u: ChapterPreviewPlayer) => (u.title ? [h('span.utitle', u.title), ' ' + u.name] : [u.name]);
@@ -179,11 +212,25 @@ function renderPlayer(player: ChapterPreviewPlayer | undefined): VNode | undefin
   );
 }
 
+const computeTimeLeft = (preview: ChapterPreview, color: Color): number | undefined => {
+  const player = preview.players && preview.players[color];
+  if (player && player.clock) {
+    if (preview.lastMoveAt && fenColor(preview.fen) == color) {
+      const spent = (Date.now() - preview.lastMoveAt) / 1000;
+      return Math.max(0, player.clock / 100 - spent);
+    } else {
+      return player.clock / 100;
+    }
+  } else {
+    return;
+  }
+};
+
 const boardPlayer = (preview: ChapterPreview, color: Color) => {
   const player = preview.players && preview.players[color];
   const result = preview.outcome?.split('-')[color === 'white' ? 0 : 1];
-  return h('span.mini-game__player', [
-    h('span.mini-game__user', [renderPlayer(player)]),
-    result && h('span.mini-game__result', result.replace('1/2', '½')),
-  ]);
+  const resultNode = result && h('span.mini-game__result', result);
+  const timeleft = computeTimeLeft(preview, color);
+  const clock = timeleft && renderClock(color, timeleft);
+  return h('span.mini-game__player', [h('span.mini-game__user', [renderPlayer(player)]), resultNode ?? clock]);
 };
diff --git a/ui/analyse/src/study/studyCtrl.ts b/ui/analyse/src/study/studyCtrl.ts
index 3ef14f132e317..faea75c5405b5 100644
--- a/ui/analyse/src/study/studyCtrl.ts
+++ b/ui/analyse/src/study/studyCtrl.ts
@@ -530,6 +530,7 @@ export default function (
     },
     chapters(d) {
       chapters.list(d);
+      if (vm.toolTab() == 'multiBoard' || (relay && relay.tourShow.active)) multiBoard.addResult(d);
       if (!currentChapter()) {
         vm.chapterId = d[0].id;
         if (!vm.mode.sticky) xhrReload();
diff --git a/ui/common/src/mini-game.ts b/ui/common/src/mini-game.ts
new file mode 100644
index 0000000000000..2284cce2bead4
--- /dev/null
+++ b/ui/common/src/mini-game.ts
@@ -0,0 +1,11 @@
+import { h } from 'snabbdom';
+
+export const fenColor = (fen: string) => (fen.includes(' w') ? 'white' : 'black');
+
+export const renderClock = (color: Color, time: number) =>
+  h(`span.mini-game__clock.mini-game__clock--${color}`, {
+    attrs: {
+      'data-time': time,
+      'data-managed': 1,
+    },
+  });
diff --git a/ui/simul/src/view/pairings.ts b/ui/simul/src/view/pairings.ts
index 0e603b799f280..1d57d829e3d8e 100644
--- a/ui/simul/src/view/pairings.ts
+++ b/ui/simul/src/view/pairings.ts
@@ -1,5 +1,6 @@
 import { h } from 'snabbdom';
 import { onInsert } from 'common/snabbdom';
+import { renderClock } from 'common/mini-game';
 import SimulCtrl from '../ctrl';
 import { Pairing } from '../interfaces';
 import { opposite } from 'chessground/util';
@@ -8,14 +9,6 @@ export default function (ctrl: SimulCtrl) {
   return h('div.game-list.now-playing.box__pad', ctrl.data.pairings.map(miniPairing(ctrl)));
 }
 
-const renderClock = (color: Color, time: number) =>
-  h(`span.mini-game__clock.mini-game__clock--${color}`, {
-    attrs: {
-      'data-time': time,
-      'data-managed': 1,
-    },
-  });
-
 const miniPairing = (ctrl: SimulCtrl) => (pairing: Pairing) => {
   const game = pairing.game,
     player = pairing.player;
diff --git a/ui/site/src/component/mini-game.ts b/ui/site/src/component/mini-game.ts
index c00d0c2f0eef2..f89b0a1fe15ec 100644
--- a/ui/site/src/component/mini-game.ts
+++ b/ui/site/src/component/mini-game.ts
@@ -1,10 +1,9 @@
 import { uciToMove } from 'chessground/util';
+import { fenColor } from 'common/mini-game';
 import * as domData from 'common/data';
 import clockWidget from './clock-widget';
 import StrongSocket from './socket';
 
-const fenColor = (fen: string) => (fen.indexOf(' b') > 0 ? 'black' : 'white');
-
 export const init = (node: HTMLElement) => {
   if (!window.Chessground) setTimeout(() => init(node), 200);
   else {
@@ -55,21 +54,21 @@ export const update = (node: HTMLElement, data: MiniGameUpdateData) => {
       lastMove: uciToMove(lm),
     });
   const turnColor = fenColor(data.fen);
-  const renderClock = (time: number | undefined, color: Color) => {
+  const updateClock = (time: number | undefined, color: Color) => {
     if (!isNaN(time!))
       clockWidget($el[0]?.querySelector('.mini-game__clock--' + color) as HTMLElement, {
         time: time!,
         pause: color != turnColor || !clockIsRunning(data.fen, color),
       });
   };
-  renderClock(data.wc, 'white');
-  renderClock(data.bc, 'black');
+  updateClock(data.wc, 'white');
+  updateClock(data.bc, 'black');
 };
 
-export const finish = (node: HTMLElement, win?: string) =>
+export const finish = (node: HTMLElement, win?: 'black' | 'white') =>
   ['white', 'black'].forEach(color => {
     const $clock = $(node).find('.mini-game__clock--' + color);
     // don't interfere with snabbdom clocks
     if (!$clock.data('managed'))
-      $clock.replaceWith(`${win ? (win == color[0] ? 1 : 0) : '½'}`);
+      $clock.replaceWith(`${win ? (win === color[0] ? 1 : 0) : '½'}`);
   });
diff --git a/ui/swiss/src/view/boards.ts b/ui/swiss/src/view/boards.ts
index 2b74b64e7ac2d..231aae788e7ca 100644
--- a/ui/swiss/src/view/boards.ts
+++ b/ui/swiss/src/view/boards.ts
@@ -1,4 +1,5 @@
 import { Board, SwissOpts } from '../interfaces';
+import { renderClock } from 'common/mini-game';
 import { h, VNode } from 'snabbdom';
 import { opposite } from 'chessground/util';
 import { player as renderPlayer } from './util';
@@ -44,12 +45,7 @@ function boardPlayer(board: Board, color: Color, opts: SwissOpts) {
   return h('span.mini-game__player', [
     h('span.mini-game__user', [h('strong', '#' + player.rank), renderPlayer(player, true, opts.showRatings)]),
     board.clock
-      ? h(`span.mini-game__clock.mini-game__clock--${color}`, {
-          attrs: {
-            'data-time': board.clock[color],
-            'data-managed': 1,
-          },
-        })
+      ? renderClock(color, board.clock[color])
       : h('span.mini-game__result', board.winner ? (board.winner == color ? 1 : 0) : '½'),
   ]);
 }