Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added cloudsummit.github.io.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion src/components/Navigation.astro
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const { content } = Astro.props;
))
}
</ul>
<CitySelector />
<!-- <CitySelector /> -->
<!-- Disabled as part of Issue #3 (Header & Navigation update)
Reason: Old CTA removed according to new design requirements -->
<!-- <a href={content.ctaHref} class="nav-cta">{content.ctaText}</a> -->
Expand Down
2 changes: 2 additions & 0 deletions src/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare module "gsap/ScrollTrigger";
declare module "gsap/all";
208 changes: 108 additions & 100 deletions src/lib/cityStore.ts
Original file line number Diff line number Diff line change
@@ -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: () => {},
};
10 changes: 4 additions & 6 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -154,7 +154,6 @@ const ogImageUrl = absoluteUrl(DEFAULT_OG_IMAGE_PATH, Astro);
<GoogleAnalytics />
</head>
<body>
<CityModal />
<Navigation content={navigationContent} />
<Hero content={content} />
<WhatIsCloudSummit content={whatIsCloudSummitContent} />
Expand All @@ -176,13 +175,12 @@ const ogImageUrl = absoluteUrl(DEFAULT_OG_IMAGE_PATH, Astro);
content={venueLogisticsContentNewVersion}
/>


<FAQ content={faqContent} />
<Newsletter content={newsletterContent} />
<SeeYouThere />
<Footer content={footerContent} />
<ScrollAnimations />
<!-- <Hero content={content} splineUrl="https://prod.spline.design/WKCpgqTWJIavlH1L/scene.splinecode" /> -->
<script>
import { getCityStore } from "../lib/cityStore";
import type { City } from "../lib/content";
Expand Down Expand Up @@ -374,4 +372,4 @@ const ogImageUrl = absoluteUrl(DEFAULT_OG_IMAGE_PATH, Astro);
min-height: 100vh;
background: black;
}
</style>
</style>