Merge changes I2e5db66b,I38376916

* changes:
  Remove the "JJ VS Code extension at Google" talk as requested
  Add a venue display page
diff --git a/README.md b/README.md
index 7149788..82df772 100644
--- a/README.md
+++ b/README.md
@@ -18,3 +18,13 @@
 ## Local preview
 
 Open `_site/index.html` directly in a browser.
+
+## Venue display
+
+`now.html` is a full-screen page for the screens at the venue: it shows the
+talk in progress, or a countdown during breaks, and what comes next, from
+`_data/schedule.yml`. Open `https://gus26.gerritforge.com/now.html` and click
+once to go full screen. If the schedule slips, update `_data/schedule.yml` and
+redeploy: open displays pick up the new `schedule.json` within a minute.
+
+Add `?now=2026-11-09T10:50` (venue time) to preview any moment of the summit.
diff --git a/_data/schedule.yml b/_data/schedule.yml
index 3ca57f5..896add5 100644
--- a/_data/schedule.yml
+++ b/_data/schedule.yml
@@ -104,29 +104,22 @@
     - time: "09:00"
       title: "Doors open"
     - time: "09:45"
-      title: "JJ VS Code extension at Google"
-      talk: jj-vs-code-extension-at-google
-      speakers:
-        - name: Kevin Liao
-          id: kevin-liao
-          company: Google
-    - time: "10:15"
       title: "Version control and code formatting"
       talk: version-control-and-code-formatting
       speakers:
         - name: Danny Hooper
           id: danny-hooper
           company: Google
-    - time: "10:45"
+    - time: "10:15"
       title: "Where does your metadata live?"
       talk: where-does-your-metadata-live
       speakers:
         - name: Patrick Hiesel
           id: patrick-hiesel
           company: Google
-    - time: "11:15"
+    - time: "10:45"
       title: "Break"
-    - time: "11:30"
+    - time: "11:00"
       title: "Bridging the Gap: Adding native JJ support to a Git-based SCM"
       talk: bridging-the-gap-adding-native-jj-support-to-a-git-based-scm
       speakers:
@@ -136,16 +129,16 @@
         - name: Emily Shaffer
           id: emily-shaffer
           company: Google
-    - time: "12:00"
+    - time: "11:30"
       title: "cloud-jj: Open-Source Commit Cloud for jj"
       talk: cloud-jj-open-source-commit-cloud-for-jj
       speakers:
         - name: Daniel Sperry
           id: daniel-sperry
           company: Google
-    - time: "12:30"
+    - time: "12:00"
       title: "Lunch"
-    - time: "13:30"
+    - time: "13:00"
       title: "AI development at scale with Gerrit and Zuul"
       talk: ai-development-at-scale-with-gerrit-and-zuul
       speakers:
@@ -155,37 +148,37 @@
         - name: Luca Milanesio
           id: luca-milanesio
           company: GerritForge
-    - time: "14:15"
+    - time: "13:45"
       title: "SHA-256 in JGit: Why, What, and When"
       talk: sha-256-in-jgit-why-what-and-when
       speakers:
         - name: Xing Huang
           id: xing-huang
           company: Google
-    - time: "14:45"
+    - time: "14:15"
       title: "GHS: sustaining Git performance under AI workload"
       talk: ghs-sustaining-git-performance-under-production-workloads
       speakers:
         - name: Shane McIntosh
           id: shane-mcintosh
           company: University of Waterloo
-    - time: "15:15"
+    - time: "14:45"
       title: "Break"
-    - time: "15:35"
+    - time: "15:05"
       title: "Scaling Gerrit builds with K8s and real-time output sync"
       talk: scaling-gerrit-builds-with-kubernetes-and-real-time-output-sync
       speakers:
         - name: Shameer Erakkath Saidumuhammed
           id: shameer-erakkath-saidumuhammed
           company: Hewlett Packard Enterprise
-    - time: "16:05"
+    - time: "15:35"
       title: "How to Stack Changes like a pro"
       talk: how-to-stack-changes-like-a-pro
       speakers:
         - name: Jacek Centkowski
           id: jacek-centkowski
           company: GerritForge
-    - time: "16:20"
+    - time: "15:50"
       title: "Q&A with the maintainers"
       talk: q-a-with-the-maintainers
       speakers:
@@ -206,7 +199,7 @@
         - name: Nasser Grainawi
         - name: Patrick Hiesel
           id: patrick-hiesel
-    - time: "17:20"
+    - time: "16:50"
       title: "Socials & Networking"
     - time: "18:00"
       title: "Door closes"
diff --git a/assets/css/now.css b/assets/css/now.css
new file mode 100644
index 0000000..a24ad83
--- /dev/null
+++ b/assets/css/now.css
@@ -0,0 +1,278 @@
+/* Full-screen venue display (now.html), sized in rem so it scales with the
+   screen: 1rem is 20px on a 1920x1080 display. */
+html { font-size: min(1.85vh, 1.04vw); }
+
+body.now {
+  height: 100vh;
+  overflow: hidden;
+  display: grid;
+  grid-template-rows: auto 1fr auto;
+  gap: 1.4rem;
+  padding: 2rem 3.2rem 1.6rem;
+  font-size: 1rem;
+  line-height: 1.3;
+  cursor: default;
+}
+
+.now-grid {
+  position: fixed;
+  inset: 0;
+  z-index: -1;
+  background-image:
+    linear-gradient(rgba(249,115,22,0.05) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(249,115,22,0.05) 1px, transparent 1px);
+  background-size: 2.8rem 2.8rem;
+  mask-image: radial-gradient(ellipse 80% 70% at 35% 45%, black 20%, transparent 80%);
+  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 35% 45%, black 20%, transparent 80%);
+}
+
+/* ── Top bar ── */
+.now-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.now-logo { height: 4.6rem; width: auto; }
+.now-clock {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  line-height: 1.1;
+}
+.now-clock-day {
+  font-family: "DM Mono", monospace;
+  font-size: 1rem;
+  letter-spacing: 0.15em;
+  text-transform: uppercase;
+  color: var(--text-muted);
+}
+.now-clock-time {
+  font-weight: 800;
+  font-size: 3.2rem;
+  letter-spacing: -0.04em;
+  font-variant-numeric: tabular-nums;
+}
+
+/* ── Main area: stage + Q&A card ── */
+.now-main {
+  display: grid;
+  grid-template-columns: 1fr 17rem;
+  gap: 4rem;
+  align-items: center;
+  min-height: 0;
+}
+.now-stage {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  gap: 1.3rem;
+  min-width: 0;
+}
+
+.stage-label {
+  display: flex;
+  align-items: center;
+  gap: 0.8rem;
+  font-family: "DM Mono", monospace;
+  font-size: 1.3rem;
+  letter-spacing: 0.2em;
+  text-transform: uppercase;
+  color: var(--accent);
+}
+.live-dot {
+  width: 0.9rem;
+  height: 0.9rem;
+  border-radius: 50%;
+  background: var(--accent);
+  box-shadow: 0 0 0 0 var(--accent-glow);
+  animation: live-pulse 2s ease-out infinite;
+}
+@keyframes live-pulse {
+  from { box-shadow: 0 0 0 0 var(--accent-glow); }
+  to   { box-shadow: 0 0 0 1.2rem transparent; }
+}
+
+.stage-title {
+  font-weight: 800;
+  font-size: 3.7rem;
+  line-height: 1.08;
+  letter-spacing: -0.03em;
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+.stage-title.pause-title { font-size: 5.4rem; }
+.stage-sub {
+  font-weight: 600;
+  font-size: 2.3rem;
+  color: var(--text-muted);
+}
+
+/* ── Speakers ── */
+.speakers {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 1.2rem 2.4rem;
+}
+.speaker {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+.speaker-photo {
+  width: 4.2rem;
+  height: 4.2rem;
+  border-radius: 50%;
+  object-fit: cover;
+  border: 2px solid var(--border);
+  flex-shrink: 0;
+}
+.speaker-mono {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: var(--accent-dim);
+  color: var(--accent);
+  font-weight: 700;
+  font-size: 1.5rem;
+}
+.speaker-text { display: flex; flex-direction: column; }
+.speaker-name { font-weight: 700; font-size: 1.7rem; }
+.speaker-co { font-size: 1.25rem; color: var(--text-muted); }
+
+.speakers.many { gap: 0.4rem 1.6rem; }
+.speakers.many .speaker-name { font-size: 1.5rem; font-weight: 600; }
+
+.speakers.small { gap: 0.6rem 1.8rem; }
+.speakers.small .speaker-photo { width: 2.4rem; height: 2.4rem; font-size: 1rem; }
+.speakers.small .speaker-name { font-size: 1.3rem; }
+.speakers.small .speaker-co { font-size: 1.05rem; }
+.speakers.small.many .speaker-name { font-size: 1.1rem; }
+
+/* ── Talk progress ── */
+.progress {
+  height: 0.5rem;
+  border-radius: 0.25rem;
+  background: var(--surface-2);
+  overflow: hidden;
+  margin-top: 0.8rem;
+}
+.progress-bar {
+  height: 100%;
+  width: 0;
+  background: var(--accent);
+  border-radius: inherit;
+  transition: width 1s linear;
+}
+.progress-meta {
+  display: flex;
+  justify-content: space-between;
+  font-family: "DM Mono", monospace;
+  font-size: 1.1rem;
+  color: var(--text-muted);
+  margin-top: -0.8rem;
+}
+#left { color: var(--text); }
+
+/* ── Countdown ── */
+.countdown-label {
+  font-family: "DM Mono", monospace;
+  font-size: 1.3rem;
+  letter-spacing: 0.2em;
+  text-transform: uppercase;
+  color: var(--text-muted);
+  margin-bottom: -1.2rem;
+}
+.countdown {
+  font-weight: 800;
+  font-size: 8.5rem;
+  line-height: 1;
+  letter-spacing: -0.04em;
+  font-variant-numeric: tabular-nums;
+  color: var(--accent);
+}
+
+/* ── Up next ── */
+.next {
+  margin-top: 0.4rem;
+  padding: 1.1rem 1.6rem;
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-left: 0.3rem solid var(--accent);
+  border-radius: 0.6rem;
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+}
+.next-label {
+  font-family: "DM Mono", monospace;
+  font-size: 1.05rem;
+  letter-spacing: 0.15em;
+  text-transform: uppercase;
+  color: var(--accent);
+}
+.next-title {
+  font-weight: 700;
+  font-size: 1.8rem;
+  line-height: 1.15;
+}
+
+/* ── Q&A card ── */
+.qa-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 0.9rem;
+  text-align: center;
+  padding: 1.6rem 1.4rem;
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: 1rem;
+}
+.qa-title {
+  font-weight: 800;
+  font-size: 1.7rem;
+  line-height: 1.1;
+  letter-spacing: -0.02em;
+}
+.qa-qr {
+  width: 100%;
+  padding: 1rem;
+  background: #fff;
+  border-radius: 0.6rem;
+}
+.qa-qr img { display: block; width: 100%; height: auto; }
+.qa-note { font-size: 1.15rem; color: var(--text-muted); }
+.qa-note strong { color: var(--text); }
+.qa-when {
+  font-family: "DM Mono", monospace;
+  font-size: 1.05rem;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--accent);
+}
+
+/* ── Footer: host and organiser ── */
+.now-foot {
+  display: flex;
+  justify-content: center;
+  gap: 5rem;
+  padding: 1rem 0 0;
+  background: none;
+  border-top: 1px solid var(--border);
+}
+.now-org {
+  display: flex;
+  align-items: center;
+  gap: 1.2rem;
+}
+.now-org-label {
+  font-family: "DM Mono", monospace;
+  font-size: 0.95rem;
+  letter-spacing: 0.15em;
+  text-transform: uppercase;
+  color: var(--text-muted);
+}
+.now-org-logo { height: 2.4rem; width: auto; display: block; }
+.now-org-logo.gerritforge-logo { height: 3.6rem; }
diff --git a/assets/img/qa-slido-qr.svg b/assets/img/qa-slido-qr.svg
new file mode 100644
index 0000000..214759e
--- /dev/null
+++ b/assets/img/qa-slido-qr.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33 33"><path stroke="#07090f" d="M0 0.5h7m1 0h1m4 0h2m2 0h5m1 0h1m2 0h7m-33 1h1m5 0h1m1 0h3m1 0h2m4 0h1m1 0h5m1 0h1m5 0h1m-33 1h1m1 0h3m1 0h1m4 0h1m5 0h1m2 0h1m2 0h1m2 0h1m1 0h3m1 0h1m-33 1h1m1 0h3m1 0h1m1 0h2m1 0h2m1 0h3m3 0h3m3 0h1m1 0h3m1 0h1m-33 1h1m1 0h3m1 0h1m2 0h1m1 0h1m1 0h2m2 0h1m2 0h2m4 0h1m1 0h3m1 0h1m-33 1h1m5 0h1m3 0h1m2 0h1m4 0h3m3 0h1m1 0h1m5 0h1m-33 1h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7m-25 1h4m2 0h1m2 0h2m1 0h1m-21 1h1m1 0h2m1 0h3m5 0h2m2 0h5m2 0h1m1 0h1m2 0h1m1 0h2m-33 1h1m1 0h1m4 0h4m1 0h1m2 0h2m1 0h4m1 0h1m2 0h2m1 0h4m-31 1h2m2 0h1m4 0h1m2 0h1m1 0h2m3 0h3m1 0h5m1 0h2m-33 1h2m1 0h2m2 0h3m2 0h3m1 0h1m1 0h3m2 0h3m1 0h1m1 0h1m1 0h2m-33 1h1m3 0h1m1 0h1m3 0h1m1 0h1m2 0h1m2 0h1m2 0h4m3 0h2m-29 1h1m1 0h3m1 0h3m6 0h1m1 0h1m2 0h1m1 0h3m1 0h1m1 0h1m1 0h1m-32 1h1m1 0h1m3 0h2m1 0h1m4 0h5m3 0h1m3 0h2m2 0h1m-31 1h1m1 0h1m5 0h1m2 0h1m2 0h1m1 0h1m1 0h9m2 0h2m-31 1h2m1 0h1m1 0h2m3 0h2m2 0h3m1 0h4m1 0h1m1 0h2m1 0h3m-31 1h3m1 0h1m2 0h3m2 0h2m1 0h1m1 0h2m1 0h1m1 0h2m1 0h2m1 0h2m2 0h1m-33 1h2m1 0h2m1 0h2m3 0h2m1 0h2m1 0h1m2 0h3m1 0h1m1 0h3m1 0h2m-31 1h5m1 0h1m3 0h2m1 0h1m4 0h1m2 0h2m1 0h2m1 0h1m2 0h2m-32 1h1m1 0h1m2 0h2m2 0h1m1 0h1m2 0h1m2 0h1m1 0h1m1 0h1m1 0h1m2 0h5m-32 1h4m1 0h1m1 0h1m3 0h1m3 0h1m2 0h1m4 0h2m5 0h1m1 0h1m-31 1h3m1 0h1m1 0h1m1 0h1m2 0h1m2 0h3m2 0h4m1 0h2m3 0h2m-32 1h3m4 0h2m1 0h3m2 0h1m2 0h1m2 0h1m4 0h3m2 0h1m-33 1h1m2 0h1m2 0h4m1 0h1m1 0h1m1 0h1m1 0h2m3 0h1m1 0h6m1 0h2m-25 1h1m2 0h1m4 0h2m1 0h2m3 0h1m3 0h2m1 0h1m-32 1h7m1 0h1m4 0h2m4 0h2m1 0h3m1 0h1m1 0h1m-29 1h1m5 0h1m1 0h2m2 0h1m2 0h2m2 0h3m1 0h2m3 0h4m-32 1h1m1 0h3m1 0h1m3 0h4m3 0h1m1 0h2m2 0h6m1 0h3m-33 1h1m1 0h3m1 0h1m1 0h1m3 0h3m2 0h4m1 0h1m4 0h1m1 0h1m1 0h2m-33 1h1m1 0h3m1 0h1m1 0h3m2 0h2m2 0h1m1 0h1m1 0h2m1 0h4m1 0h1m-30 1h1m5 0h1m4 0h9m6 0h3m3 0h1m-33 1h7m1 0h3m1 0h1m2 0h1m1 0h1m3 0h1m1 0h1m1 0h2m1 0h1"/></svg>
\ No newline at end of file
diff --git a/assets/img/speakers/kevin-liao.jpg b/assets/img/speakers/kevin-liao.jpg
deleted file mode 100644
index 9661010..0000000
--- a/assets/img/speakers/kevin-liao.jpg
+++ /dev/null
Binary files differ
diff --git a/now.html b/now.html
new file mode 100644
index 0000000..2727b0a
--- /dev/null
+++ b/now.html
@@ -0,0 +1,241 @@
+---
+layout: null
+sitemap: false
+title: "Gerrit User Summit 2026 - Now"
+---
+{%- comment -%}
+  Full-screen display for the venue screens: shows the session in progress
+  and what comes next, from _data/schedule.yml. Open it in a browser and
+  click once to go full screen. Add ?now=2026-11-09T10:50 (venue time) to
+  preview any moment of the summit.
+
+  assets/img/qa-slido-qr.svg encodes the Slido event for the maintainers
+  Q&A: https://app.sli.do/event/5HqT8EqrrawEWt9BQ2C7GE
+{%- endcomment -%}
+{%- for day in site.data.schedule -%}
+  {%- for s in day.sessions -%}
+    {%- if s.talk == "q-a-with-the-maintainers" -%}
+      {%- assign qa_day = day.day | split: " " | first -%}
+      {%- assign qa_time = day.date | date: "%Y-%m-%d" | append: " " | append: s.time | date: "%-I:%M %p" -%}
+    {%- endif -%}
+  {%- endfor -%}
+{%- endfor -%}
+{%- assign photos = site.static_files | where_exp: "f", "f.path contains '/assets/img/speakers/'" -%}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="robots" content="noindex">
+  <title>{{ page.title }}</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;1,9..40,400&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="{{ site.baseurl }}/assets/css/base.css">
+  <link rel="stylesheet" href="{{ site.baseurl }}/assets/css/now.css">
+</head>
+<body class="now">
+  <div class="now-grid"></div>
+
+  <header class="now-top">
+    <img src="{{ site.baseurl }}/assets/img/gus-logo.png" alt="Gerrit User Summit 2026" class="now-logo">
+    <div class="now-clock">
+      <span class="now-clock-day" id="clock-day"></span>
+      <span class="now-clock-time" id="clock-time"></span>
+    </div>
+  </header>
+
+  <main class="now-main">
+    <section class="now-stage" id="stage"></section>
+
+    <aside class="qa-card">
+      <div class="qa-title">Ask the maintainers anything</div>
+      <div class="qa-qr">
+        <img src="{{ site.baseurl }}/assets/img/qa-slido-qr.svg" alt="QR code to submit questions on Slido">
+      </div>
+      <div class="qa-note">Scan to send your questions for the<br><strong>Q&amp;A with the maintainers</strong></div>
+      {%- if qa_day %}
+      <div class="qa-when">{{ qa_day }} · {{ qa_time }}</div>
+      {%- endif %}
+    </aside>
+  </main>
+
+  <footer class="now-foot">
+    <div class="now-org">
+      <span class="now-org-label">Hosted by</span>
+      <img src="{{ site.baseurl }}/assets/img/google-logo.svg" alt="Google" class="now-org-logo google-logo">
+    </div>
+    <div class="now-org">
+      <span class="now-org-label">Sponsored by</span>
+      <img src="{{ site.baseurl }}/assets/img/gerritforge-logo.svg" alt="GerritForge" class="now-org-logo gerritforge-logo">
+    </div>
+  </footer>
+
+  <script>
+    const VENUE_TZ = 'America/Los_Angeles';
+    const VENUE_OFFSET = '-08:00'; // PST, the summit is after the DST change
+    const SCHEDULE_URL = '{{ site.baseurl }}/schedule.json';
+    const PHOTOS = { {%- for f in photos -%}{{ f.basename | jsonify }}: {{ site.baseurl | append: f.path | jsonify }}{%- unless forloop.last -%}, {% endunless -%}{%- endfor -%} };
+
+    let schedule = {{ site.data.schedule | jsonify }};
+
+    // ── Clock, optionally shifted by ?now=<venue time> to preview ──
+    let skew = 0;
+    const nowParam = new URLSearchParams(location.search).get('now');
+    if (nowParam) {
+      const iso = /(Z|[+-]\d\d:?\d\d)$/i.test(nowParam) ? nowParam : nowParam + VENUE_OFFSET;
+      const t = Date.parse(iso);
+      if (!isNaN(t)) skew = t - Date.now();
+    }
+    const now = () => Date.now() + skew;
+
+    // ── Schedule as a flat list of sessions with start instants ──
+    // The last session of a day ("Doors close") only marks its end.
+    function timeline(days) {
+      const out = [];
+      days.forEach(d => d.sessions.forEach((s, i) => out.push(Object.assign({}, s, {
+        day: d.day,
+        start: Date.parse(`${d.date}T${s.time}:00${VENUE_OFFSET}`),
+        dayEnd: i === d.sessions.length - 1
+      }))));
+      return out;
+    }
+    let items = timeline(schedule);
+
+    // ── Formatting ──
+    const timeFmt = new Intl.DateTimeFormat('en-US', { timeZone: VENUE_TZ, hour: 'numeric', minute: '2-digit' });
+    const dayFmt = new Intl.DateTimeFormat('en-US', { timeZone: VENUE_TZ, weekday: 'long', month: 'long', day: 'numeric' });
+    const fmtTime = t => timeFmt.format(t);
+    const pad = n => String(n).padStart(2, '0');
+    function fmtDuration(ms) {
+      const s = Math.max(0, Math.ceil(ms / 1000));
+      const d = Math.floor(s / 86400), h = Math.floor(s % 86400 / 3600), m = Math.floor(s % 3600 / 60);
+      if (d) return `${d}d ${h}h ${pad(m)}m`;
+      if (h) return `${h}:${pad(m)}:${pad(s % 60)}`;
+      return `${pad(m)}:${pad(s % 60)}`;
+    }
+    const esc = s => String(s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
+    const initials = name => name.split(/\s+/).map(w => w[0]).slice(0, 2).join('');
+
+    function speakers(list, small) {
+      if (!list || !list.length) return '';
+      const many = list.length > 3;
+      return `<div class="speakers${many ? ' many' : ''}${small ? ' small' : ''}">` + list.map(sp => {
+        const avatar = many ? '' : (PHOTOS[sp.id]
+          ? `<img class="speaker-photo" src="${esc(PHOTOS[sp.id])}" alt="">`
+          : `<span class="speaker-photo speaker-mono">${esc(initials(sp.name))}</span>`);
+        const co = sp.company ? `<span class="speaker-co">${esc(sp.company)}</span>` : '';
+        return `<div class="speaker">${avatar}<div class="speaker-text"><span class="speaker-name">${esc(sp.name)}</span>${co}</div></div>`;
+      }).join('') + '</div>';
+    }
+
+    function upNext(s, label) {
+      return `<div class="next">
+        <div class="next-label">${esc(label || 'Up next')} · ${fmtTime(s.start)}</div>
+        <div class="next-title">${esc(s.title)}</div>
+        ${speakers(s.speakers, true)}
+      </div>`;
+    }
+
+    // ── What is on at instant t ──
+    function stateAt(t) {
+      let i = -1;
+      while (i + 1 < items.length && items[i + 1].start <= t) i++;
+      if (i === -1) return { kind: 'before', next: items[0], key: 'before' };
+      const cur = items[i];
+      if (cur.dayEnd) {
+        return i === items.length - 1
+          ? { kind: 'over', key: 'over' }
+          : { kind: 'overnight', next: items[i + 1], key: 'overnight' + i };
+      }
+      return { kind: cur.talk ? 'talk' : 'pause', cur, next: items[i + 1], key: 'at' + i };
+    }
+
+    // First talk at or after session index of s
+    function firstTalkFrom(s) {
+      return items.slice(items.indexOf(s)).find(x => x.talk && x.day === s.day);
+    }
+
+    function render(st) {
+      switch (st.kind) {
+        case 'talk':
+          return `<div class="stage-label"><span class="live-dot"></span>Now</div>
+            <h1 class="stage-title">${esc(st.cur.title)}</h1>
+            ${speakers(st.cur.speakers)}
+            <div class="progress"><div class="progress-bar" id="bar"></div></div>
+            <div class="progress-meta"><span>${fmtTime(st.cur.start)}</span><span id="left"></span><span>${fmtTime(st.next.start)}</span></div>
+            ${upNext(st.next)}`;
+        case 'pause':
+          return `<h1 class="stage-title pause-title">${esc(st.cur.title)}</h1>
+            <div class="countdown-label">${st.next.talk ? 'Next talk in' : esc(st.next.title) + ' in'}</div>
+            <div class="countdown" id="cd"></div>
+            ${st.next.dayEnd ? '' : upNext(st.next)}`;
+        case 'before':
+        case 'overnight': {
+          const talk = firstTalkFrom(st.next);
+          return `<div class="stage-label">${st.kind === 'before' ? 'Welcome' : 'That’s a wrap for today'}</div>
+            <h1 class="stage-title">${esc(st.next.day)}<br><span class="stage-sub">${esc(st.next.title)} at ${fmtTime(st.next.start)}</span></h1>
+            <div class="countdown-label">Starts in</div>
+            <div class="countdown" id="cd"></div>
+            ${talk ? upNext(talk, 'First talk') : ''}`;
+        }
+        case 'over':
+          return `<div class="stage-label">That’s a wrap</div>
+            <h1 class="stage-title">Thank you for joining the Gerrit User Summit 2026!</h1>`;
+      }
+    }
+
+    // ── Tick: rebuild the stage on a new session, update timers every second ──
+    const stage = document.getElementById('stage');
+    let shownKey = null;
+    function tick() {
+      const t = now();
+      document.getElementById('clock-day').textContent = dayFmt.format(t);
+      document.getElementById('clock-time').textContent = fmtTime(t);
+
+      const st = stateAt(t);
+      if (st.key !== shownKey) {
+        stage.innerHTML = render(st);
+        stage.dataset.kind = st.kind;
+        shownKey = st.key;
+      }
+      const cd = document.getElementById('cd');
+      if (cd) cd.textContent = fmtDuration(st.next.start - t);
+      const bar = document.getElementById('bar');
+      if (bar) {
+        const span = st.next.start - st.cur.start;
+        bar.style.width = Math.min(100, (t - st.cur.start) / span * 100) + '%';
+        const mins = Math.ceil((st.next.start - t) / 60000);
+        document.getElementById('left').textContent = mins === 1 ? '1 min left' : `${mins} min left`;
+      }
+    }
+    tick();
+    setInterval(tick, 1000);
+
+    // ── Pick up a redeployed schedule without reloading the page ──
+    setInterval(async () => {
+      try {
+        const res = await fetch(SCHEDULE_URL, { cache: 'no-store' });
+        if (!res.ok) return;
+        const fresh = await res.json();
+        if (JSON.stringify(fresh) === JSON.stringify(schedule)) return;
+        schedule = fresh;
+        items = timeline(schedule);
+        shownKey = null;
+        tick();
+      } catch (e) { /* offline: keep the schedule we have */ }
+    }, 60000);
+
+    // ── Kiosk: keep the screen awake, click to go full screen ──
+    async function keepAwake() {
+      try { await navigator.wakeLock.request('screen'); } catch (e) { /* unsupported or denied */ }
+    }
+    keepAwake();
+    document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') keepAwake(); });
+    document.addEventListener('click', e => {
+      if (e.target.closest('a') || document.fullscreenElement) return;
+      document.documentElement.requestFullscreen().catch(() => {});
+    });
+  </script>
+</body>
+</html>
diff --git a/schedule.json b/schedule.json
new file mode 100644
index 0000000..e2af761
--- /dev/null
+++ b/schedule.json
@@ -0,0 +1,5 @@
+---
+layout: null
+sitemap: false
+---
+{{ site.data.schedule | jsonify }}
diff --git a/speakers.md b/speakers.md
index fed5334..808021d 100644
--- a/speakers.md
+++ b/speakers.md
@@ -120,17 +120,6 @@
       <div class="speaker-sessions"><span class="label">Talk</span><a href="{{ site.baseurl }}/talks.html#version-control-and-code-formatting">Version control and code formatting</a></div>
     </div>
   </article>
-  <article class="speaker-card" id="kevin-liao">
-    <img class="speaker-photo" src="{{ site.baseurl }}/assets/img/speakers/kevin-liao.jpg" alt="Kevin Liao" loading="lazy">
-    <div class="speaker-info">
-      <h3 class="speaker-name">Kevin Liao</h3>
-      <p class="speaker-tagline">Software Engineer at Google</p>
-      <div class="speaker-bio">
-      <p>I&#x27;m a Software Engineer at Google, passionate about the open-source jj version control system. I&#x27;m part of Google&#x27;s Source team driving its adoption at Google and beyond.</p>
-      </div>
-      <div class="speaker-sessions"><span class="label">Talk</span><a href="{{ site.baseurl }}/talks.html#jj-vs-code-extension-at-google">JJ VS Code extension at Google</a></div>
-    </div>
-  </article>
   <article class="speaker-card" id="kaushik-lingarkar">
     <img class="speaker-photo" src="{{ site.baseurl }}/assets/img/speakers/kaushik-lingarkar.jpg" alt="Kaushik Lingarkar" loading="lazy">
     <div class="speaker-info">
diff --git a/talks.md b/talks.md
index 9e3843d..5c9985e 100644
--- a/talks.md
+++ b/talks.md
@@ -261,21 +261,9 @@
 
 </div>
   </article>
-  <article class="talk-card" id="jj-vs-code-extension-at-google">
-    <h3 class="talk-title">JJ VS Code extension at Google</h3>
-    <p class="talk-meta">Tuesday 10th November, 09:45 AM &middot; <a href="{{ site.baseurl }}/speakers.html#kevin-liao">Kevin Liao</a></p>
-    <div class="talk-abstract" markdown="1">
-
-The JJ team at Google developed a JJ VS Code extension that is used by thousands
-of engineers internally. See a demo of its UX and its integration with Piper,
-Google's internal source control backend. We are currently in the progress of
-open-sourcing it, and we want it to support Gerrit!
-
-</div>
-  </article>
   <article class="talk-card" id="version-control-and-code-formatting">
     <h3 class="talk-title">Version control and code formatting</h3>
-    <p class="talk-meta">Tuesday 10th November, 10:15 AM &middot; <a href="{{ site.baseurl }}/speakers.html#danny-hooper">Danny Hooper</a></p>
+    <p class="talk-meta">Tuesday 10th November, 09:45 AM &middot; <a href="{{ site.baseurl }}/speakers.html#danny-hooper">Danny Hooper</a></p>
     <div class="talk-abstract" markdown="1">
 
 There are many approaches to managing indentation and such in a code base over
@@ -285,7 +273,7 @@
   </article>
   <article class="talk-card" id="where-does-your-metadata-live">
     <h3 class="talk-title">Where does your metadata live?</h3>
-    <p class="talk-meta">Tuesday 10th November, 10:45 AM &middot; <a href="{{ site.baseurl }}/speakers.html#patrick-hiesel">Patrick Hiesel</a></p>
+    <p class="talk-meta">Tuesday 10th November, 10:15 AM &middot; <a href="{{ site.baseurl }}/speakers.html#patrick-hiesel">Patrick Hiesel</a></p>
     <div class="talk-abstract" markdown="1">
 
 Code review comments, approvals, repo permissions and now agent trajectories and
@@ -307,7 +295,7 @@
   </article>
   <article class="talk-card" id="bridging-the-gap-adding-native-jj-support-to-a-git-based-scm">
     <h3 class="talk-title">Bridging the Gap: Adding native JJ support to a Git-based SCM</h3>
-    <p class="talk-meta">Tuesday 10th November, 11:30 AM &middot; <a href="{{ site.baseurl }}/speakers.html#daniele-sassoli">Daniele Sassoli</a>, <a href="{{ site.baseurl }}/speakers.html#emily-shaffer">Emily Shaffer</a></p>
+    <p class="talk-meta">Tuesday 10th November, 11:00 AM &middot; <a href="{{ site.baseurl }}/speakers.html#daniele-sassoli">Daniele Sassoli</a>, <a href="{{ site.baseurl }}/speakers.html#emily-shaffer">Emily Shaffer</a></p>
     <div class="talk-abstract" markdown="1">
 
 In this talk, we'll walk through the philosophy behind JJ, why it works the way
@@ -327,7 +315,7 @@
   </article>
   <article class="talk-card" id="cloud-jj-open-source-commit-cloud-for-jj">
     <h3 class="talk-title">cloud-jj: Open-Source Commit Cloud for jj</h3>
-    <p class="talk-meta">Tuesday 10th November, 12:00 PM &middot; <a href="{{ site.baseurl }}/speakers.html#daniel-sperry">Daniel Sperry</a></p>
+    <p class="talk-meta">Tuesday 10th November, 11:30 AM &middot; <a href="{{ site.baseurl }}/speakers.html#daniel-sperry">Daniel Sperry</a></p>
     <div class="talk-abstract" markdown="1">
 
 cloud-jj will bring Google's cloud-backed Commit Cloud server architecture to
@@ -364,7 +352,7 @@
   </article>
   <article class="talk-card" id="ai-development-at-scale-with-gerrit-and-zuul">
     <h3 class="talk-title">AI Development at Scale with Gerrit and Zuul</h3>
-    <p class="talk-meta">Tuesday 10th November, 01:30 PM &middot; <a href="{{ site.baseurl }}/speakers.html#luca-milanesio">Luca Milanesio</a>, <a href="{{ site.baseurl }}/speakers.html#james-blair">James Blair</a></p>
+    <p class="talk-meta">Tuesday 10th November, 01:00 PM &middot; <a href="{{ site.baseurl }}/speakers.html#luca-milanesio">Luca Milanesio</a>, <a href="{{ site.baseurl }}/speakers.html#james-blair">James Blair</a></p>
     <div class="talk-abstract" markdown="1">
 
 The AI revolutionised the way we write software and introduced a new level of
@@ -382,7 +370,7 @@
   </article>
   <article class="talk-card" id="sha-256-in-jgit-why-what-and-when">
     <h3 class="talk-title">SHA-256 in JGit: Why, What, and When</h3>
-    <p class="talk-meta">Tuesday 10th November, 02:15 PM &middot; <a href="{{ site.baseurl }}/speakers.html#xing-huang">Xing Huang</a></p>
+    <p class="talk-meta">Tuesday 10th November, 01:45 PM &middot; <a href="{{ site.baseurl }}/speakers.html#xing-huang">Xing Huang</a></p>
     <div class="talk-abstract" markdown="1">
 
 Git has supported SHA-256 repositories since version 2.29, and upstream Git is
@@ -402,7 +390,7 @@
   </article>
   <article class="talk-card" id="ghs-sustaining-git-performance-under-production-workloads">
     <h3 class="talk-title">GHS: Sustaining Git Performance Under Production Workloads</h3>
-    <p class="talk-meta">Tuesday 10th November, 02:45 PM &middot; <a href="{{ site.baseurl }}/speakers.html#shane-mcintosh">Shane McIntosh</a></p>
+    <p class="talk-meta">Tuesday 10th November, 02:15 PM &middot; <a href="{{ site.baseurl }}/speakers.html#shane-mcintosh">Shane McIntosh</a></p>
     <div class="talk-abstract" markdown="1">
 
 Although decentralized Version Control Systems (VCSs) like Git support several
@@ -425,7 +413,7 @@
   </article>
   <article class="talk-card" id="scaling-gerrit-builds-with-kubernetes-and-real-time-output-sync">
     <h3 class="talk-title">Scaling Gerrit Builds with Kubernetes and Real-Time Output Sync</h3>
-    <p class="talk-meta">Tuesday 10th November, 03:35 PM &middot; <a href="{{ site.baseurl }}/speakers.html#shameer-erakkath-saidumuhammed">Shameer Erakkath Saidumuhammed</a></p>
+    <p class="talk-meta">Tuesday 10th November, 03:05 PM &middot; <a href="{{ site.baseurl }}/speakers.html#shameer-erakkath-saidumuhammed">Shameer Erakkath Saidumuhammed</a></p>
     <div class="talk-abstract" markdown="1">
 
 As repositories and development teams grow, Gerrit-triggered build and
@@ -465,7 +453,7 @@
   </article>
   <article class="talk-card" id="how-to-stack-changes-like-a-pro">
     <h3 class="talk-title">How to Stack Changes like a Pro</h3>
-    <p class="talk-meta">Tuesday 10th November, 04:05 PM &middot; <a href="{{ site.baseurl }}/speakers.html#jacek-centkowski">Jacek Centkowski</a></p>
+    <p class="talk-meta">Tuesday 10th November, 03:35 PM &middot; <a href="{{ site.baseurl }}/speakers.html#jacek-centkowski">Jacek Centkowski</a></p>
     <div class="talk-abstract" markdown="1">
 
 Last year the message was: focus the diff. Small, single-concern changes get
@@ -485,7 +473,7 @@
   </article>
   <article class="talk-card" id="q-a-with-the-maintainers">
     <h3 class="talk-title">Q&amp;A with the maintainers</h3>
-    <p class="talk-meta">Tuesday 10th November, 04:20 PM</p>
+    <p class="talk-meta">Tuesday 10th November, 03:50 PM</p>
     <div class="talk-abstract" markdown="1">
 
 Join us for what has become a staple session of the Gerrit User Summit where the