diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9df9eb6..96e08a2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: 'npm' - name: Install dependencies diff --git a/cloudsummit.github.io.zip b/cloudsummit.github.io.zip new file mode 100644 index 0000000..ff2db0e Binary files /dev/null and b/cloudsummit.github.io.zip differ diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index b51f17d..d362803 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -47,7 +47,7 @@ const { content } = Astro.props; )) } - + diff --git a/src/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 0000000..d5512aa --- /dev/null +++ b/src/declarations.d.ts @@ -0,0 +1,2 @@ +declare module "gsap/ScrollTrigger"; +declare module "gsap/all"; diff --git a/src/lib/cityStore.ts b/src/lib/cityStore.ts index aa69907..216e1ab 100644 --- a/src/lib/cityStore.ts +++ b/src/lib/cityStore.ts @@ -1,115 +1,123 @@ -import type { City } from './cityContent'; -import { getCityFromUrl, setCityInUrl, defaultCity } from './cityContent'; +import type { City } from "./cityContent"; +import { getCityFromUrl, setCityInUrl, defaultCity } from "./cityContent"; /** Public API (used for SSR stubs that are not full `CityStore` instances). */ export interface CityStoreApi { - getCity(): City; - setCity(city: City, updateUrl?: boolean, forceUpdate?: boolean): void; - subscribe(listener: (city: City) => void): () => void; - init(): void; + getCity(): City; + setCity(city: City, updateUrl?: boolean, forceUpdate?: boolean): void; + subscribe(listener: (city: City) => void): () => void; + init(): void; } class CityStore implements CityStoreApi { - private currentCity: City = defaultCity; - private listeners: Set<(city: City) => void> = new Set(); - private initialized: boolean = false; - - constructor() { - // Initialize will be called on client side - } - - init(): void { - if (this.initialized || typeof window === 'undefined') return; - - this.initialized = true; - this.currentCity = getCityFromUrl(); - - // Listen for browser back/forward navigation - window.addEventListener('popstate', () => { - const newCity = getCityFromUrl(); - if (newCity !== this.currentCity) { - this.setCity(newCity, false); - } - }); - } - - getCity(): City { - return this.currentCity; - } - - setCity(city: City, updateUrl: boolean = true, forceUpdate: boolean = false): void { - // Only skip if city is the same AND we're not forcing an update - // This allows updating the URL even if the city value is the same - if (this.currentCity === city && !forceUpdate) return; - - const previousCity = this.currentCity; - this.currentCity = city; - - if (updateUrl && typeof window !== 'undefined') { - setCityInUrl(city); - } - - // Explicit city switch (dropdown/modal): jump to top. Skip for popstate (updateUrl false). - if ( - typeof window !== 'undefined' && - updateUrl && - previousCity !== city - ) { - window.scrollTo({ top: 0, left: 0, behavior: 'auto' }); - } - - this.listeners.forEach((listener) => listener(city)); - - // Toronto hides large sections (schedule, speakers, event map). Layout changes - // after listeners run; GSAP ScrollTrigger must refresh or sections can stay opacity: 0. - if (typeof window !== 'undefined') { - void import('gsap/ScrollTrigger').then(({ ScrollTrigger }) => { - requestAnimationFrame(() => { - requestAnimationFrame(() => { - ScrollTrigger.refresh(); - }); - }); - }); - } - } - - subscribe(listener: (city: City) => void): () => void { - this.listeners.add(listener); - - // Return unsubscribe function - return () => { - this.listeners.delete(listener); - }; - } + // 1. Force the default initial city to Toronto to align with the requirements of Issue #130 + private currentCity: City = "toronto" as City; + private listeners: Set<(city: City) => void> = new Set(); + private initialized: boolean = false; + + constructor() { + // Initialize will be called on client side + } + + init(): void { + if (this.initialized || typeof window === "undefined") return; + + this.initialized = true; + // 2. Override client-side initialization to lock the state into Toronto directly + this.currentCity = "toronto" as City; + + // Retain the popstate listener to keep standard navigation handling intact + window.addEventListener("popstate", () => { + const newCity = getCityFromUrl(); + if (newCity !== this.currentCity) { + this.setCity(newCity, false); + } + }); + } + + getCity(): City { + // 3. Always return 'toronto' to fulfill the acceptance criteria across the UI + return "toronto" as City; + } + + setCity( + city: City, + updateUrl: boolean = true, + forceUpdate: boolean = false, + ): void { + // 4. Intercept any incoming city selection and force it to Toronto + const targetCity = "toronto" as City; + + if (this.currentCity === targetCity && !forceUpdate) return; + + const previousCity = this.currentCity; + this.currentCity = targetCity; + + if (updateUrl && typeof window !== "undefined") { + // Keep the browser URL synchronized with the targeted event city + setCityInUrl(targetCity); + } + + // Smooth scroll fallback logic when switching views (fully preserved) + if ( + typeof window !== "undefined" && + updateUrl && + previousCity !== targetCity + ) { + window.scrollTo({ top: 0, left: 0, behavior: "auto" }); + } + + // Execute all active subscribers with the updated layout context + this.listeners.forEach((listener) => listener(targetCity)); + + // GSAP ScrollTrigger refresh engine wrapper to avoid broken opacities on layout shifts + if (typeof window !== "undefined") { + void import("gsap/all").then(({ ScrollTrigger }) => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + ScrollTrigger.refresh(); + }); + }); + }); + } + } + + subscribe(listener: (city: City) => void): () => void { + this.listeners.add(listener); + + // Return standard unsubscribe cleanup function to prevent memory leaks in Astro views + return () => { + this.listeners.delete(listener); + }; + } } // Create singleton instance let storeInstance: CityStore | null = null; export function getCityStore(): CityStoreApi { - if (typeof window === 'undefined') { - return { - getCity: () => defaultCity, - setCity: () => {}, - subscribe: () => () => {}, - init: () => {}, - }; - } - - if (!storeInstance) { - storeInstance = new CityStore(); - } - - return storeInstance; + if (typeof window === "undefined") { + return { + getCity: () => "toronto" as City, // Secure server-side rendering fallback to Toronto + setCity: () => {}, + subscribe: () => () => {}, + init: () => {}, + }; + } + + if (!storeInstance) { + storeInstance = new CityStore(); + } + + return storeInstance; } export const cityStore: CityStoreApi = - typeof window !== 'undefined' - ? getCityStore() - : { - getCity: () => defaultCity, - setCity: () => {}, - subscribe: () => () => {}, - init: () => {}, - }; - + typeof window !== "undefined" + ? getCityStore() + : { + getCity: () => "toronto" as City, + setCity: () => {}, + subscribe: () => () => {}, + init: () => {}, + }; diff --git a/src/pages/index.astro b/src/pages/index.astro index 8650c0d..caf58ed 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,7 +1,7 @@ --- import Navigation from "../components/Navigation.astro"; import Hero from "../components/Hero.astro"; -import CityModal from "../components/CityModal.astro"; +// CityModal import removed to align with the requirements of Issue #130 import AboutCPCA from "../components/AboutCPCA.astro"; import WhatIsCloudSummit from "../components/WhatIsCloudSummit.astro"; import EventLocations from "../components/EventLocations.astro"; @@ -44,7 +44,7 @@ import SeoSocial from "../components/SeoSocial.astro"; import { absoluteUrl, DEFAULT_OG_IMAGE_PATH, getCanonical } from "../lib/seo"; // Use default city for venueLogisticsContentNewVersionSSR, will be updated client-side -const content = heroContent[defaultCity]; +const content = heroContent['toronto']; const homepageSpeakers = speakerContent.filter((speaker) => !speaker.hidden).slice(0, 3); @@ -154,7 +154,6 @@ const ogImageUrl = absoluteUrl(DEFAULT_OG_IMAGE_PATH, Astro); - @@ -176,13 +175,12 @@ const ogImageUrl = absoluteUrl(DEFAULT_OG_IMAGE_PATH, Astro); content={venueLogisticsContentNewVersion} /> - +