diff --git a/src/app/expo-hall-map/expo-hall-map.component.html b/src/app/expo-hall-map/expo-hall-map.component.html index 4a2a07f6..318ae1e8 100644 --- a/src/app/expo-hall-map/expo-hall-map.component.html +++ b/src/app/expo-hall-map/expo-hall-map.component.html @@ -25,7 +25,7 @@
- +
+ diff --git a/src/app/expo-hall-map/expo-hall-map.component.scss b/src/app/expo-hall-map/expo-hall-map.component.scss index deb74fc8..d609ea6c 100644 --- a/src/app/expo-hall-map/expo-hall-map.component.scss +++ b/src/app/expo-hall-map/expo-hall-map.component.scss @@ -60,8 +60,19 @@ } .booth-img { - max-width: 85%; - max-height: 85%; + // Anchored to the upper portion of the booth (top 8%, ~60% height) + // because the floor-plan PNG prints the booth number in the lower + // third of the box. Centering vertically caused wide logos like + // Tetrix and TimeCopilot to bleed over the printed number; pinning + // to the top keeps the lower band clean. + position: absolute; + top: 8%; + left: 50%; + width: 60%; + height: 60%; + max-width: 60%; + max-height: 60%; + transform: translateX(-50%); object-fit: contain; pointer-events: none; } @@ -136,10 +147,21 @@ display: flex; align-items: center; justify-content: space-between; + gap: 12px; background: var(--ion-background-color, #fff); + border: 2px solid #DD04D2; border-radius: 12px; padding: 14px 16px; - box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.15); + // Pink-tinted shadow so the popup also lifts visually off white booth tiles + box-shadow: 0 -4px 24px rgba(221, 4, 210, 0.25); +} + +.booth-popup-logo { + width: 48px; + height: 48px; + object-fit: contain; + flex-shrink: 0; + border-radius: 6px; } .booth-popup-content { diff --git a/src/app/expo-hall-map/expo-hall-map.component.ts b/src/app/expo-hall-map/expo-hall-map.component.ts index f89c3f2b..53096f6c 100644 --- a/src/app/expo-hall-map/expo-hall-map.component.ts +++ b/src/app/expo-hall-map/expo-hall-map.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; import { IonSearchbar, LoadingController } from '@ionic/angular'; import { ConferenceData } from '../providers/conference-data'; @@ -22,8 +22,9 @@ export interface BoothData { templateUrl: './expo-hall-map.component.html', styleUrls: ['./expo-hall-map.component.scss'], }) -export class ExpoHallMapComponent implements OnInit { +export class ExpoHallMapComponent implements OnInit, AfterViewInit { @ViewChild('searchBar') searchBar!: IonSearchbar; + @ViewChild('pinchZoom') pinchZoomCmp?: { pinchZoom?: { maxScale: number } }; showSearchbar = false; searchQuery = ''; @@ -31,6 +32,13 @@ export class ExpoHallMapComponent implements OnInit { selectedBooth: BoothData | null = null; highlightedBoothId: string | null = null; + // Static logo fallbacks for booths that don't come through the sponsor API + // (PSF, community booths, attendee lounge, etc.). Keyed by booth id. + private readonly STATIC_BOOTH_LOGOS: { [id: string]: string } = { + '407': 'assets/img/python-logo.png', + '606': 'assets/img/pycon-us-2026-logo.svg', + }; + // 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. @@ -108,7 +116,38 @@ export class ExpoHallMapComponent implements OnInit { this.loadSponsors(); } + ngAfterViewInit() { + // @ciag/ngx-pinch-zoom hardcodes defaultMaxScale=3 and only auto-derives + // a higher cap when the first descendant is an AND limitZoom is + // the string "original image size". Our content is wrapped in a div so + // the auto-detect path is never taken; passing a numeric limitZoom is + // silently ignored. Reach into the underlying IvyPinch instance after + // it's constructed and bump maxScale directly. Polled because the + // instance is created during ngOnInit on the child component. + const start = Date.now(); + const tick = () => { + const inner = this.pinchZoomCmp?.pinchZoom; + if (inner) { + inner.maxScale = 25; + return; + } + if (Date.now() - start < 2000) { + setTimeout(tick, 50); + } + }; + tick(); + } + loadSponsors(showLoader = false) { + // Seed static fallbacks first so the API merge can override them when a + // booth does come through as a sponsor; otherwise PSF/community booths + // remain blank. + for (const booth of this.booths) { + if (!booth.logoUrl && this.STATIC_BOOTH_LOGOS[booth.id]) { + booth.logoUrl = this.STATIC_BOOTH_LOGOS[booth.id]; + } + } + const apply = (sponsors: any) => { for (const list of Object.values(sponsors || {})) { for (const sponsor of list as any[]) { diff --git a/src/assets/img/pycon-us-2026-logo.svg b/src/assets/img/pycon-us-2026-logo.svg new file mode 100644 index 00000000..606619e5 --- /dev/null +++ b/src/assets/img/pycon-us-2026-logo.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/python-logo.png b/src/assets/img/python-logo.png new file mode 100644 index 00000000..71f94207 Binary files /dev/null and b/src/assets/img/python-logo.png differ