-
+
+
0">
+
+ {{ booth.name }}
+ Booth {{ booth.id }}
+
-
+
+
+
+
+
+

+
+
+
+
+
![]()
+
+
+
+
+
+
+
-
+
diff --git a/src/app/pages/expo-hall/expo-hall.page.scss b/src/app/pages/expo-hall/expo-hall.page.scss
index 7129a359..75320a9d 100644
--- a/src/app/pages/expo-hall/expo-hall.page.scss
+++ b/src/app/pages/expo-hall/expo-hall.page.scss
@@ -1,52 +1,170 @@
-ion-content{
- white-space: nowrap;
-}
+// Expo Hall — interactive floor plan with sponsor logo overlays.
.map-container {
+ width: 100%;
height: 100%;
- overflow-x: scroll!important;
- overflow-y: hidden;
+ overflow: hidden;
}
.expo-hall-map {
+ width: 100%;
height: 100%;
- aspect-ratio: 4096/2885;
- background-image:url(/assets/img/pycon-us-2026-floorplan.png);
- background-size: contain;
- background-repeat: no-repeat;
+}
+
+.map-inner {
position: relative;
+ display: inline-block;
+ width: 100%;
+
+ img {
+ width: 100%;
+ height: auto;
+ display: block;
+ }
+}
+
+// Booth overlay positioned on the floor plan
+.boothgroup {
+ position: absolute;
+ cursor: pointer;
+
+ .boothgroupinner {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+
+ .booth {
+ position: absolute;
+ inset: 0;
+ border-radius: 4px;
+ transition: background 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ }
+
+ .booth-img {
+ max-width: 85%;
+ max-height: 85%;
+ object-fit: contain;
+ pointer-events: none;
+ }
+
+ &:active .booth,
+ &:hover .booth {
+ background: rgba(240, 192, 64, 0.28);
+ outline: 2px solid rgba(240, 192, 64, 0.85);
+ }
+
+ &.highlighted .booth {
+ background: rgba(240, 192, 64, 0.35);
+ outline: 3px solid #f0c040;
+ animation: pulse 1.2s ease-in-out 2;
+ }
+}
+
+@keyframes pulse {
+ 0% { background: rgba(240, 192, 64, 0.15); }
+ 50% { background: rgba(240, 192, 64, 0.45); }
+ 100% { background: rgba(240, 192, 64, 0.15); }
+}
+
+// Search results dropdown
+.search-results {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ background: var(--ion-background-color, #fff);
+ border-bottom: 1px solid var(--ion-color-light, #e0e0e0);
+ max-height: 40vh;
+ overflow-y: auto;
+}
+
+.search-result-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--ion-color-light, #f0f0f0);
+ cursor: pointer;
+
+ &:active {
+ background: var(--ion-color-light, #f5f5f5);
+ }
+
+ .booth-name {
+ font-size: clamp(0.8rem, 2.5vw, 1rem);
+ font-weight: 600;
+ color: var(--ion-text-color, #222);
+ }
+
+ .booth-number {
+ font-size: clamp(0.7rem, 2vw, 0.85rem);
+ color: var(--ion-color-medium, #888);
+ }
+}
+
+// Tap popup at the bottom of the screen
+.booth-popup {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 200;
+ padding: 0 16px 16px;
}
-.booth {
- margin: auto;
- text-align: center;
- border-radius: .33em;
- scroll-margin-left: 3em;
- scroll-margin-right: 3em;
+.booth-popup-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: var(--ion-background-color, #fff);
+ border-radius: 12px;
+ padding: 14px 16px;
+ box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.15);
}
-.booth-highlight {
- border-style: solid;
- border-width: 3%;
- border-color: rgba(255, 252, 127, 0.75);
- background: rgba(255, 252, 127, 0.33);
+.booth-popup-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex: 1;
+ text-decoration: none;
+ color: inherit;
+ position: relative;
+
+ // The chevron sits inline at the right side of the link content so the
+ // tappable target spans the whole sponsor name + level row.
+ .booth-popup-chevron {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--ion-color-medium, #888);
+ font-size: 1rem;
+ }
}
-.booth-img {
- max-height: 33%;
- max-width: 85%;
- margin-top: 20%;
+a.booth-popup-content {
+ padding-right: 24px;
}
-.tall-booth .booth-img {
- margin-top: 80%;
+.booth-popup-name {
+ font-size: clamp(0.9rem, 3vw, 1.1rem);
+ font-weight: 700;
+ color: var(--ion-text-color, #111);
}
-.sq-booth .booth-img {
- margin-top: 20%;
- max-height: 50%;
+.booth-popup-number {
+ font-size: clamp(0.75rem, 2.5vw, 0.9rem);
+ color: var(--ion-color-medium, #888);
}
-.xtall-booth .booth-img {
- margin-top: 50%
+.close-btn {
+ --color: var(--ion-color-medium, #888);
+ margin-left: 8px;
}
diff --git a/src/app/pages/expo-hall/expo-hall.page.spec.ts b/src/app/pages/expo-hall/expo-hall.page.spec.ts
deleted file mode 100644
index 6e909aad..00000000
--- a/src/app/pages/expo-hall/expo-hall.page.spec.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { IonicModule } from '@ionic/angular';
-
-import { ExpoHallPage } from './expo-hall.page';
-
-describe('ExpoHallPage', () => {
- let component: ExpoHallPage;
- let fixture: ComponentFixture
;
-
- beforeEach(waitForAsync(() => {
- TestBed.configureTestingModule({
- declarations: [ ExpoHallPage ],
- imports: [IonicModule.forRoot()]
- }).compileComponents();
-
- fixture = TestBed.createComponent(ExpoHallPage);
- component = fixture.componentInstance;
- fixture.detectChanges();
- }));
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/src/app/pages/expo-hall/expo-hall.page.ts b/src/app/pages/expo-hall/expo-hall.page.ts
index 345c6306..f31ef673 100644
--- a/src/app/pages/expo-hall/expo-hall.page.ts
+++ b/src/app/pages/expo-hall/expo-hall.page.ts
@@ -1,128 +1,198 @@
-import { Component, OnInit, ChangeDetectorRef, ViewChild, ViewEncapsulation, AfterViewChecked } from '@angular/core';
-import { KeyValue } from '@angular/common';
-import { Keyboard } from '@capacitor/keyboard';
-import { LoadingController } from '@ionic/angular';
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { IonSearchbar, LoadingController } from '@ionic/angular';
import { ConferenceData } from '../../providers/conference-data';
import { LiveUpdateService } from '../../providers/live-update.service';
+export interface BoothData {
+ id: string;
+ name: string;
+ top: number;
+ left: number;
+ width: number;
+ height: number;
+ imgW: number;
+ imgH: number;
+ logoUrl?: string;
+ level?: string;
+ description?: string;
+}
@Component({
selector: 'app-expo-hall',
templateUrl: './expo-hall.page.html',
styleUrls: ['./expo-hall.page.scss'],
- encapsulation: ViewEncapsulation.None,
})
-export class ExpoHallPage implements OnInit, AfterViewChecked {
- sponsors: any;
- @ViewChild('search') search : any;
- @ViewChild('mapContainer') mapContainer: any;
- searchQueryText = '';
- queryResults: any[] = [];
- ios: boolean;
- showSearchbar: boolean;
- private scrolled: boolean = false;
- private scrollTarget: any;
- private iterableDiffer;
+export class ExpoHallPage implements OnInit {
+ @ViewChild('searchBar') searchBar!: IonSearchbar;
+
+ showSearchbar = false;
+ searchQuery = '';
+ searchResults: BoothData[] = [];
+ selectedBooth: BoothData | null = null;
+ highlightedBoothId: string | null = null;
+
+ // Booth coordinates in the original 8000×5655 floor plan image.
+ // Names are the seed labels — they get overwritten with the live sponsor
+ // name (and gain logoUrl/level/description) once the API responds.
+ booths: BoothData[] = [
+ { id: '245', name: 'Lerner Python Training', top: 640, left: 3271, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '344', name: 'Zyte', top: 649, left: 3556, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '243', name: 'Analog Devices', top: 925, left: 3271, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '342', name: 'marimo', top: 934, left: 3547, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '638', name: 'AlphaSense', top: 978, left: 5911, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '639', name: 'Snowflake', top: 978, left: 6409, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '140', name: 'Chonkie', top: 1183, left: 1982, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '141', name: 'Tetrix', top: 1191, left: 2418, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '240', name: 'Mission', top: 1191, left: 2684, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '138', name: 'Sublimage', top: 1458, left: 1991, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '238', name: 'Jinja.App', top: 1458, left: 2684, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '635', name: 'ClickHouse', top: 1592, left: 6409, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '735', name: 'Python en Español', top: 1592, left: 7449, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '734', name: 'Djangonauts / DSF', top: 1600, left: 6693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '136', name: 'Capisclo', top: 1725, left: 1991, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '137', name: 'Arcjet', top: 1725, left: 2418, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '336', name: 'Astral', top: 1734, left: 3547, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '236', name: 'Minimus', top: 1743, left: 2693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '237', name: 'Tower Research Capital', top: 1743, left: 3280, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '335', name: 'Pydantic', top: 1743, left: 4080, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '434', name: 'Red Hat', top: 1743, left: 4347, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '633', name: 'Reflex', top: 1858, left: 6418, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '732', name: 'Black Python Devs', top: 1867, left: 6693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '733', name: 'EuroPython Society', top: 1867, left: 7449, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '134', name: 'PixelTable', top: 1992, left: 1991, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '135', name: 'TimeCopilot', top: 2009, left: 2427, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '234', name: 'Python Institute', top: 2009, left: 2693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '235', name: 'Elastic', top: 2009, left: 3280, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '334', name: 'Apify', top: 2009, left: 3556, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '531', name: 'Chainguard', top: 2125, left: 5644, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '630', name: 'Hex', top: 2134, left: 5929, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '730', name: 'CodeDay', top: 2134, left: 6693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '731', name: 'PyLadies', top: 2134, left: 7458, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '631', name: 'Auth0', top: 2143, left: 6418, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '126', name: 'Cloudflare', top: 2321, left: 1991, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '127', name: 'Hudson River Trading', top: 2321, left: 2427, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '226', name: 'Kraken Tech', top: 2330, left: 2684, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '427', name: 'SerpApi', top: 2383, left: 4987, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '729', name: 'Python Asia Organization', top: 2401, left: 7449, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '629', name: 'Temporal', top: 2410, left: 6409, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '728', name: 'SCaLE / Data Con LA', top: 2410, left: 6693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '727', name: 'PyCon Africa / Seneg. / Mozambique',top: 2667, left: 7449, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '627', name: 'ReversingLabs', top: 2676, left: 6418, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '726', name: 'SoCal Python / Inland Empire PUG', top: 2685, left: 6693, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '122', name: 'Sentry', top: 2845, left: 1991, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '621', name: 'Codespeed', top: 3032, left: 6400, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '421', name: 'JetBrains', top: 3041, left: 4987, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '720', name: 'QUBE Research & Technologies', top: 3041, left: 6684, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '119', name: 'AWS', top: 3254, left: 2409, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '717', name: 'Posit, PBC', top: 3397, left: 7209, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '116', name: 'Cubist Systematic Strategies', top: 3414, left: 1991, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '413', name: 'Meta', top: 3681, left: 4987, width: 500, height: 780, imgW: 8000, imgH: 5655 },
+ { id: '613', name: 'Bloomberg', top: 3681, left: 6391, width: 500, height: 780, imgW: 8000, imgH: 5655 },
+ { id: '213', name: 'GitHub', top: 3690, left: 3280, width: 500, height: 780, imgW: 8000, imgH: 5655 },
+ { id: '513', name: 'Vercel', top: 3930, left: 5653, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '112', name: 'Jane Street', top: 3939, left: 1991, width: 250, height: 500, imgW: 8000, imgH: 5655 },
+ { id: '313', name: 'Anaconda', top: 3939, left: 4018, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '113', name: 'Capital One', top: 3948, left: 2409, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '709', name: 'Python Packaging Ecosystem Survey', top: 4668, left: 7209, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ { id: '407', name: 'PSF', top: 4677, left: 4978, width: 525, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '606', name: 'Attendee Lounge', top: 4686, left: 5502, width: 750, height: 525, imgW: 8000, imgH: 5655 },
+ { id: '707', name: 'Codeflash', top: 4935, left: 7209, width: 250, height: 250, imgW: 8000, imgH: 5655 },
+ ];
constructor(
- private loadingCtrl: LoadingController,
private confData: ConferenceData,
- private changeDetection: ChangeDetectorRef,
+ private loadingCtrl: LoadingController,
public liveUpdateService: LiveUpdateService,
- ) {
- this.scrolled = false;
- Keyboard.addListener('keyboardWillShow', (info) => {
- const height = this.mapContainer.nativeElement.offsetHeight;
- this.mapContainer.nativeElement.style.height = height + 'px';
- });
- Keyboard.addListener('keyboardDidShow', info => {
- });
- Keyboard.addListener('keyboardWillHide', () => {
- });
- Keyboard.addListener('keyboardDidHide', () => {
- this.mapContainer.nativeElement.style.height = '100%';
- this.scrollTarget.scrollIntoView();
- });
+ ) {}
+
+ ngOnInit() {
+ this.loadSponsors();
}
- reloadSponsors() {
- this.loadingCtrl.create({
- message: 'Fetching latest...',
- duration: 10000,
- }).then((loader) => {
- loader.present();
- this.confData.getSponsors().subscribe((sponsors: any[]) => {
- this.sponsors = sponsors;
- for (const [level, sponsorss] of Object.entries(this.sponsors)) {
- for(const [index, sponsor] of Object.entries(sponsorss)) {
- if (sponsor.booth_number !== null) {
- let elem = document.getElementById("booth"+sponsor.booth_number);
- if (elem) {
- elem.innerHTML = "
";
- } else {
- console.log('No booth: ' + sponsor.booth_number);
- }
- }
- }
+ loadSponsors(showLoader = false) {
+ const apply = (sponsors: any) => {
+ for (const list of Object.values(sponsors || {})) {
+ for (const sponsor of list as any[]) {
+ if (sponsor.booth_number == null) continue;
+ const booth = this.booths.find(b => b.id === String(sponsor.booth_number));
+ if (!booth) continue;
+ booth.logoUrl = sponsor.logo_url;
+ booth.level = sponsor.level;
+ booth.description = sponsor.description;
+ if (sponsor.name) booth.name = sponsor.name;
}
- this.scrolled = false;
- this.changeDetection.detectChanges();
- setTimeout(() => {loader.dismiss()}, 100);
+ }
+ };
+
+ if (!showLoader) {
+ this.confData.getSponsors().subscribe(apply);
+ return;
+ }
+ this.loadingCtrl.create({ message: 'Fetching latest...', duration: 10000 }).then(loader => {
+ loader.present();
+ this.confData.getSponsors().subscribe((sponsors: any) => {
+ apply(sponsors);
+ setTimeout(() => loader.dismiss(), 100);
});
});
}
- resetSearch() {
- this.searchQueryText = "";
- this.queryResults = [];
- let elems = document.getElementsByClassName('booth-highlight')
- Array.from(elems).forEach((elem: any) => {elem.classList.remove('booth-highlight');})
+ getBoothStyle(booth: BoothData): { [key: string]: string } {
+ return {
+ 'top': `calc(${booth.top} / ${booth.imgH} * 100%)`,
+ 'left': `calc(${booth.left} / ${booth.imgW} * 100%)`,
+ 'width': `calc(${booth.width} / ${booth.imgW} * 100%)`,
+ 'height': `calc(${booth.height} / ${booth.imgH} * 100%)`,
+ };
}
- searchSponsors() {
- if (this.searchQueryText === "" || this.searchQueryText === " ") {
- this.resetSearch();
- return;
+ toggleSearch() {
+ this.showSearchbar = !this.showSearchbar;
+ if (!this.showSearchbar) {
+ this.clearSearch();
+ } else {
+ setTimeout(() => this.searchBar?.setFocus(), 150);
}
- this.queryResults = [];
- let elems = document.getElementsByClassName('booth-highlight')
- Array.from(elems).forEach((elem: any) => {elem.classList.remove('booth-highlight');})
- this.confData.querySponsors(this.searchQueryText).subscribe((sponsors: any[]) => {
- this.queryResults = sponsors;
- this.queryResults.forEach((sponsor: any) => {
- let elem = document.getElementById("booth"+sponsor.booth_number);
- elem.classList.add("booth-highlight");
- })
- });
}
- selectSponsor(sponsor) {
- this.showSearchbar = false;
- this.resetSearch();
- let elem = document.getElementById("booth"+sponsor.booth_number);
- elem.classList.add("booth-highlight");
- this.scrollTarget = elem;
- this.scrollTarget.scrollIntoView();
+ onSearch() {
+ const q = this.searchQuery.toLowerCase().trim();
+ if (!q) { this.searchResults = []; return; }
+ this.searchResults = this.booths.filter(b =>
+ b.name.toLowerCase().includes(q) || b.id.includes(q)
+ );
}
- async focusButton() {
- setTimeout(() => {
- this.search.setFocus();
- }, 500); // ms delay
- }
+ clearSearch() {
+ this.searchQuery = '';
+ this.searchResults = [];
+ this.showSearchbar = false;
+ this.highlightedBoothId = null;
+ }
- ngOnInit() {
- this.reloadSponsors();
+ selectBooth(booth: BoothData) {
+ this.searchResults = [];
+ this.showSearchbar = false;
+ this.searchQuery = '';
+ this.highlightedBoothId = booth.id;
+ this.selectedBooth = booth;
+ setTimeout(() => {
+ const el = document.getElementById('boothgroup-' + booth.id);
+ el?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
+ }, 100);
}
- ngAfterViewChecked() {
- if (this.scrolled === true) {
- return;
- } else {
- document.getElementById("mapContainer").scrollLeft = 200;
- this.scrolled = true;
- }
+ onBoothTap(booth: BoothData) {
+ this.selectedBooth = booth;
+ this.highlightedBoothId = booth.id;
}
+ // Mirror sponsors page slug logic so the popup can deep-link into the
+ // existing sponsor detail page. Booths without a sponsor match (community
+ // booths like SoCal Python) won't have a level set; the template hides the
+ // link in that case.
+ getSponsorSlug(name: string): string {
+ return name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
+ }
}
diff --git a/src/app/providers/live-update.service.ts b/src/app/providers/live-update.service.ts
index 11be3635..81fae028 100644
--- a/src/app/providers/live-update.service.ts
+++ b/src/app/providers/live-update.service.ts
@@ -11,6 +11,10 @@ export class LiveUpdateService {
needsUpdate: boolean = false;
build: string = "base";
appVersion: string = "";
+ // Active live-update snapshot from Appflow. null when running the bundled
+ // native assets (no OTA applied yet, or running via livereload).
+ snapshot: { id: string; buildId: string } | null = null;
+ channel: string = "";
constructor(private loadingCtrl: LoadingController) {
App.addListener('appStateChange', ({ isActive }) => {
@@ -35,6 +39,8 @@ export class LiveUpdateService {
await this.updateAppInfo();
const result = await LiveUpdates.sync();
this.updateAvailable = result;
+ this.snapshot = result.snapshot;
+ this.channel = result.liveUpdate?.channel || '';
if (this.updateAvailable.activeApplicationPathChanged) {
this.needsUpdate = true;
}
diff --git a/src/assets/img/pycon-us-2026-floorplan-nologo.png b/src/assets/img/pycon-us-2026-floorplan-nologo.png
new file mode 100644
index 00000000..f728cd10
Binary files /dev/null and b/src/assets/img/pycon-us-2026-floorplan-nologo.png differ