diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a4d391ae..271b8958 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -21,6 +21,7 @@ export class AppComponent implements OnInit { schedulePages = [ { title: 'Schedule', url: '/app/tabs/schedule', icon: 'calendar-outline' }, { title: 'Speakers', url: '/app/tabs/speakers', icon: 'people-outline' }, + { title: 'Keynote Speakers', url: '/app/tabs/keynote-speakers', icon: 'star-outline' }, ] presentationPages = [ { title: 'Talks', group: 'presentations', url: '/app/tabs/tracks/talks', icon: 'mic-outline'}, diff --git a/src/app/pages/about-psf/about-psf.page.html b/src/app/pages/about-psf/about-psf.page.html index 701063e2..305f160b 100644 --- a/src/app/pages/about-psf/about-psf.page.html +++ b/src/app/pages/about-psf/about-psf.page.html @@ -1,19 +1,18 @@ - + 1 - Python Software Foundation + Python Software Foundation - - -
-

- The Python Software Foundation is the charitable organization behind the Python programming language. -

+ +
+ +

Python Software Foundation

+

The organization behind the Python language

diff --git a/src/app/pages/about-psf/about-psf.page.scss b/src/app/pages/about-psf/about-psf.page.scss index 65881c25..ef25f069 100644 --- a/src/app/pages/about-psf/about-psf.page.scss +++ b/src/app/pages/about-psf/about-psf.page.scss @@ -1,17 +1,52 @@ -.psf-hero { - padding-top: 24px; - padding-bottom: 8px; - - .psf-logo { - width: 80px; - height: 80px; - margin-bottom: 12px; +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #FFD779; + margin-bottom: 10px; } h1 { - font-size: 1.5rem; - font-weight: 700; margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; } } diff --git a/src/app/pages/about-psf/about-psf.page.ts b/src/app/pages/about-psf/about-psf.page.ts index bdc7a30e..43c25180 100644 --- a/src/app/pages/about-psf/about-psf.page.ts +++ b/src/app/pages/about-psf/about-psf.page.ts @@ -1,5 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; -import { LoadingController } from '@ionic/angular'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent, LoadingController } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; @@ -10,7 +10,9 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./about-psf.page.scss'], }) export class AboutPsfPage implements OnInit { + @ViewChild(IonContent) ionContent: IonContent; content: any = ""; + showTitle = false; constructor( private loadingCtrl: LoadingController, @@ -33,6 +35,10 @@ export class AboutPsfPage implements OnInit { }); } + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + openUrl(url: string) { window.open(url, '_system', 'location=yes'); } diff --git a/src/app/pages/about-pycon/about-pycon.page.html b/src/app/pages/about-pycon/about-pycon.page.html index 1df6aaab..e06b9586 100644 --- a/src/app/pages/about-pycon/about-pycon.page.html +++ b/src/app/pages/about-pycon/about-pycon.page.html @@ -1,30 +1,29 @@ - + 1 - About PyCon US + About PyCon US - + +
+ +

PyCon US 2026

+

Long Beach, CA • May 14-18

+
- - - - - - Fetch latest update - - - - - - - - - + + + Fetch latest update + + + + + + diff --git a/src/app/pages/about-pycon/about-pycon.page.scss b/src/app/pages/about-pycon/about-pycon.page.scss index b84f78bb..eb8174aa 100644 --- a/src/app/pages/about-pycon/about-pycon.page.scss +++ b/src/app/pages/about-pycon/about-pycon.page.scss @@ -1,5 +1,82 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.about-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 40px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .about-hero-icon { + font-size: 40px; + color: #FFD779; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + +.update-btn { + margin: -16px 20px 0; + position: relative; + z-index: 2; +} + +.about-card { + margin: -20px 16px 16px; + border-radius: 16px; + box-shadow: 0 4px 16px rgba(16, 17, 54, 0.1); + position: relative; + z-index: 1; + + ion-card-content { + padding: 20px; + font-size: 0.95rem; + line-height: 1.6; + color: var(--ion-text-color); + } +} + +:host-context(.dark-theme) .about-card { + --background: #1e1e1e; +} + .dev-accordion { - margin-top: 16px; + margin: 0 16px 16px; } .dev-content { diff --git a/src/app/pages/about-pycon/about-pycon.page.ts b/src/app/pages/about-pycon/about-pycon.page.ts index 3700f7da..af6a7063 100644 --- a/src/app/pages/about-pycon/about-pycon.page.ts +++ b/src/app/pages/about-pycon/about-pycon.page.ts @@ -1,5 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; -import { LoadingController } from '@ionic/angular'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent, LoadingController } from '@ionic/angular'; import { Directory, Filesystem } from '@capacitor/filesystem'; import { Storage } from '@ionic/storage-angular'; import { Share } from '@capacitor/share'; @@ -17,7 +17,9 @@ import { environment } from '../../../environments/environment'; styleUrls: ['./about-pycon.page.scss'], }) export class AboutPyconPage implements OnInit { + @ViewChild(IonContent) ionContent: IonContent; content: any = ""; + showTitle = false; loggedIn: boolean = false; environmentUrl: string = environment.baseUrl; @@ -30,6 +32,10 @@ export class AboutPyconPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + reloadContent() { this.loadingCtrl.create({ message: 'Fetching latest...', diff --git a/src/app/pages/account/account.html b/src/app/pages/account/account.html index 40ee7871..10a196c4 100644 --- a/src/app/pages/account/account.html +++ b/src/app/pages/account/account.html @@ -1,47 +1,51 @@ - + - + 1 - Account + Account - -
- avatar -

Signed in as {{nickname}}

+ + - + - + Report an Issue - + -

Mobile App Issue

+

Mobile App Issue

Report a bug or suggest a feature for the app

- + -

Website Issue

+

Website Issue

Report an issue with us.pycon.org

+ +
diff --git a/src/app/pages/account/account.scss b/src/app/pages/account/account.scss index 2ce58cbd..e532f05e 100644 --- a/src/app/pages/account/account.scss +++ b/src/app/pages/account/account.scss @@ -1,4 +1,63 @@ -img { - max-width: 140px; - border-radius: 50%; +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.account-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .account-hero-icon { + font-size: 64px; + color: #FFD779; + margin-bottom: 8px; + } + + h1 { + margin: 0; + font-size: 1.5rem; + font-weight: 700; + } + + p { + margin: 4px 0 0; + font-size: 0.85rem; + opacity: 0.7; + + code { + background: none; + font-size: 0.85rem; + } + } +} + +.account-actions { + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px 20px; } diff --git a/src/app/pages/account/account.ts b/src/app/pages/account/account.ts index 31002f77..f01f2ff0 100644 --- a/src/app/pages/account/account.ts +++ b/src/app/pages/account/account.ts @@ -1,7 +1,7 @@ -import { AfterViewInit, Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; -import { NavController, AlertController } from '@ionic/angular'; +import { IonContent, NavController, AlertController } from '@ionic/angular'; import { InAppBrowser, DefaultWebViewOptions } from '@capacitor/inappbrowser'; import { AppComponent } from '../../app.component'; @@ -16,6 +16,8 @@ import { environment } from '../../../environments/environment'; styleUrls: ['./account.scss'], }) export class AccountPage implements OnInit, AfterViewInit { + @ViewChild(IonContent) content: IonContent; + showTitle = false; email: string; nickname: string; isSpeaker: boolean = false; @@ -29,6 +31,10 @@ export class AccountPage implements OnInit, AfterViewInit { public liveUpdateService: LiveUpdateService, ) { } + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + ngOnInit() { this.app.fetchFeatures(); } diff --git a/src/app/pages/coc/coc.page.html b/src/app/pages/coc/coc.page.html index ba7a5f37..1effcb41 100644 --- a/src/app/pages/coc/coc.page.html +++ b/src/app/pages/coc/coc.page.html @@ -1,14 +1,14 @@ - + 1 - Code of Conduct + Code of Conduct - +
diff --git a/src/app/pages/coc/coc.page.scss b/src/app/pages/coc/coc.page.scss index 2c7b19c6..b9fcc973 100644 --- a/src/app/pages/coc/coc.page.scss +++ b/src/app/pages/coc/coc.page.scss @@ -1,5 +1,29 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .coc-hero { - padding: 48px 24px 32px; + padding: 16px 24px 32px; background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); color: #fff; text-align: center; diff --git a/src/app/pages/coc/coc.page.ts b/src/app/pages/coc/coc.page.ts index 85784233..8ca3db66 100644 --- a/src/app/pages/coc/coc.page.ts +++ b/src/app/pages/coc/coc.page.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; @@ -8,7 +9,9 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./coc.page.scss'], }) export class CocPage implements OnInit { + @ViewChild(IonContent) ionContent: IonContent; content: any = ''; + showTitle = false; constructor( private confData: ConferenceData, @@ -16,6 +19,10 @@ export class CocPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + ngOnInit() { this.confData.getContent().subscribe((content: any) => { this.content = content; diff --git a/src/app/pages/help/help.page.html b/src/app/pages/help/help.page.html index 6193a8d7..1e2cd732 100644 --- a/src/app/pages/help/help.page.html +++ b/src/app/pages/help/help.page.html @@ -3,11 +3,17 @@ - Help & Safety + Help & Safety - + +
+ +

Help & Safety

+

Resources for a safe and welcoming conference

+
+ At the Conference diff --git a/src/app/pages/help/help.page.scss b/src/app/pages/help/help.page.scss index e69de29b..c5ac4160 100644 --- a/src/app/pages/help/help.page.scss +++ b/src/app/pages/help/help.page.scss @@ -0,0 +1,51 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #FFD779; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} diff --git a/src/app/pages/help/help.page.ts b/src/app/pages/help/help.page.ts index c898f327..2c408e05 100644 --- a/src/app/pages/help/help.page.ts +++ b/src/app/pages/help/help.page.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; @Component({ selector: 'app-help', @@ -6,6 +7,13 @@ import { Component } from '@angular/core'; styleUrls: ['./help.page.scss'], }) export class HelpPage { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + openUrl(url: string) { window.open(url, '_system', 'location=yes'); } diff --git a/src/app/pages/job-listings/job-listings.page.html b/src/app/pages/job-listings/job-listings.page.html index c517627f..eed5e86d 100644 --- a/src/app/pages/job-listings/job-listings.page.html +++ b/src/app/pages/job-listings/job-listings.page.html @@ -1,28 +1,28 @@ - + 1 - Job Listings + Job Listings - + +
+ +

Job Listings

+

Opportunities from PyCon US sponsors

+
+ -
-

- Job opportunities from PyCon US sponsors hiring in the Python community. -

-
-

No job listings have been posted yet. Check back closer to the conference!

diff --git a/src/app/pages/job-listings/job-listings.page.scss b/src/app/pages/job-listings/job-listings.page.scss index f5e38b98..6836cb19 100644 --- a/src/app/pages/job-listings/job-listings.page.scss +++ b/src/app/pages/job-listings/job-listings.page.scss @@ -1,7 +1,57 @@ -.page-intro { - font-size: 0.9rem; - opacity: 0.7; - margin-bottom: 0; +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #25C8EB; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + +.search-toolbar { + margin-top: 12px; } .listings-container { diff --git a/src/app/pages/job-listings/job-listings.page.ts b/src/app/pages/job-listings/job-listings.page.ts index 6ae7d0f9..1629f60f 100644 --- a/src/app/pages/job-listings/job-listings.page.ts +++ b/src/app/pages/job-listings/job-listings.page.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; @@ -9,6 +10,8 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./job-listings.page.scss'], }) export class JobListingsPage implements OnInit { + @ViewChild(IonContent) content: IonContent; + showTitle = false; allListings: any[] = []; listings: any[] = []; searchText: string = ''; @@ -19,6 +22,10 @@ export class JobListingsPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + processListings(raw: any[]): any[] { return raw.map(listing => { return { diff --git a/src/app/pages/keynote-speakers/keynote-speakers-routing.module.ts b/src/app/pages/keynote-speakers/keynote-speakers-routing.module.ts new file mode 100644 index 00000000..ddef1729 --- /dev/null +++ b/src/app/pages/keynote-speakers/keynote-speakers-routing.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { KeynoteSpeakersPage } from './keynote-speakers.page'; + +const routes: Routes = [{ path: '', component: KeynoteSpeakersPage }]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class KeynoteSpeakersPageRoutingModule {} diff --git a/src/app/pages/keynote-speakers/keynote-speakers.module.ts b/src/app/pages/keynote-speakers/keynote-speakers.module.ts new file mode 100644 index 00000000..f103fe72 --- /dev/null +++ b/src/app/pages/keynote-speakers/keynote-speakers.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { KeynoteSpeakersPageRoutingModule } from './keynote-speakers-routing.module'; +import { KeynoteSpeakersPage } from './keynote-speakers.page'; + +@NgModule({ + imports: [CommonModule, IonicModule, KeynoteSpeakersPageRoutingModule], + declarations: [KeynoteSpeakersPage] +}) +export class KeynoteSpeakersPageModule {} diff --git a/src/app/pages/keynote-speakers/keynote-speakers.page.html b/src/app/pages/keynote-speakers/keynote-speakers.page.html new file mode 100644 index 00000000..e25b09a4 --- /dev/null +++ b/src/app/pages/keynote-speakers/keynote-speakers.page.html @@ -0,0 +1,51 @@ + + + + + 1 + + Keynote Speakers + + + + +
+ +

Keynote Speakers

+

PyCon US 2026

+
+ + + +
+ +
+

{{ speaker.name }}

+

{{ speaker.bio }}

+ + + +

{{ speaker.session.name }}

+

{{ speaker.session.day }} {{ speaker.session.timeStart }} — {{ speaker.session.location }}

+
+
+
+
+ + + +
+
+ + {{ member.name }} +
+
+

{{ steeringCouncil.name }}

+

{{ steeringCouncil.bio }}

+
+
+ +
+
diff --git a/src/app/pages/keynote-speakers/keynote-speakers.page.scss b/src/app/pages/keynote-speakers/keynote-speakers.page.scss new file mode 100644 index 00000000..eab42019 --- /dev/null +++ b/src/app/pages/keynote-speakers/keynote-speakers.page.scss @@ -0,0 +1,150 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.ks-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 48px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .ks-hero-icon { + font-size: 56px; + color: #FFD779; + margin-bottom: 12px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 8px 0 0; + font-size: 0.95rem; + opacity: 0.85; + } +} + +.ks-card { + margin: 0 20px 16px; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(16, 17, 54, 0.15); + + &.ks-card-first { + margin-top: -24px; + position: relative; + z-index: 1; + } + + ion-card-content { + padding: 24px; + } + + h2 { + font-size: 1.15rem; + font-weight: 700; + margin: 0 0 8px; + text-align: center; + } + + p { + font-size: 0.95rem; + line-height: 1.5; + margin: 0; + color: var(--ion-text-color); + } +} + +.ks-speaker-photo-wrap { + display: flex; + justify-content: center; + margin-bottom: 16px; +} + +.ks-speaker-photo { + width: 120px; + height: 120px; + border-radius: 50%; + object-fit: cover; + border: 3px solid #3B3EA9; +} + +.ks-card-council { + ion-card-content { + padding: 24px; + } +} + +.ks-session-link { + --padding-start: 12px; + --background: var(--ion-color-step-50, #f5f5f5); + --border-radius: 10px; + margin-top: 12px; + + h3 { + font-size: 0.9rem; + font-weight: 600; + } + + p { + font-size: 0.8rem; + color: var(--ion-color-medium); + margin: 0; + } +} + +.ks-council-photos { + display: flex; + justify-content: center; + gap: 8px; + margin-bottom: 16px; +} + +.ks-council-member { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + width: 56px; +} + +.ks-council-photo { + width: 48px; + height: 48px; + border-radius: 50%; + object-fit: cover; + border: 2px solid #3B3EA9; +} + +.ks-council-name { + font-size: 0.6rem; + color: var(--ion-color-medium); + text-align: center; + max-width: 56px; + line-height: 1.2; +} diff --git a/src/app/pages/keynote-speakers/keynote-speakers.page.ts b/src/app/pages/keynote-speakers/keynote-speakers.page.ts new file mode 100644 index 00000000..60945f20 --- /dev/null +++ b/src/app/pages/keynote-speakers/keynote-speakers.page.ts @@ -0,0 +1,99 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { IonContent } from '@ionic/angular'; +import { LiveUpdateService } from '../../providers/live-update.service'; +import { ConferenceData } from '../../providers/conference-data'; + +interface KeynoteSpeaker { + name: string; + photo: string; + bio: string; + session?: any; +} + +interface SteeringCouncilMember { + name: string; + photo: string; +} + +interface SteeringCouncil { + name: string; + members: SteeringCouncilMember[]; + bio: string; +} + +@Component({ + selector: 'app-keynote-speakers', + templateUrl: './keynote-speakers.page.html', + styleUrls: ['./keynote-speakers.page.scss'], +}) +export class KeynoteSpeakersPage implements OnInit { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + + speakers: KeynoteSpeaker[] = [ + { + name: 'Lin Qiao', + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/lin_qiao.original.jpg', + bio: 'Lin Qiao is the CEO and co-founder of global AI inference cloud and infrastructure platform Fireworks AI, enables teams like Cursor, Uber, DoorDash, and Shopify to build, tune, and scale highly optimized generative AI applications. Prior to founding Fireworks, Lin was the co-creator and head of Meta\'s PyTorch.', + }, + { + name: 'amanda casari', + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/amcasari-headshot.original.png', + bio: 'amanda casari is an engineer and researcher who has worked in many technical and socio-technical disciplines for over 20 years, including developer relations, product management, data science, and underwater robotics. amanda was named an External Faculty member of the Vermont Complex Systems Center in 2021 and co-authored Feature Engineering for Machine Learning Principles and Techniques for Data Scientists for O\'Reilly.', + }, + { + name: 'Tim Schilling', + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Tim_Schilling.original.jpg', + bio: 'I\'m a software engineer that loves Django and our community. I\'m on the Django Steering Council, a cofounder of Djangonaut Space and an admin of Django Commons. I\'ve been helping maintain django-debug-toolbar and a few other packages.', + }, + { + name: 'Rachell Calhoun', + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/rachell_calhoun.original.jpg', + bio: 'I\'m Rachell, co-founder of Djangonaut Space and a Django developer. I love building practical, user-friendly tools and creating communities where people walk away with new skills, confidence, and some new friends. I\'ve organized Django Girls workshops across multiple countries and continents for over 10 years.', + }, + { + name: 'Pablo Galindo Salgado', + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Pablo_Galindo_Salgado.original.jpg', + bio: 'Pablo Galindo Salgado works in the Python team of Hudson River Trading. He is a CPython core developer and a Theoretical Physicist specializing in general relativity and black hole physics. He is currently serving on the Python Steering Council in his 6th term and he is the release manager for Python 3.10 and 3.11.', + }, + ]; + + steeringCouncil: SteeringCouncil = { + name: 'Python Steering Council', + members: [ + { name: 'Barry Warsaw', photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Barry_PyCon.max-165x165.jpg' }, + { name: 'Donghee Na', photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/donghee_na.max-165x165.jpg' }, + { name: 'Pablo Galindo Salgado', photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Pablo_Galindo_Salgado.max-165x165.jpg' }, + { name: 'Savannah Ostrowski', photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/savannah.max-165x165.jpg' }, + { name: 'Thomas Wouters', photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Thomas_Wouters.max-165x165.jpg' }, + ], + bio: 'The Python Steering Council is a 5-person elected committee that assumes a mandate to maintain the quality and stability of the Python language and CPython interpreter, improve the contributor experience, formalize and maintain a relationship between the Python core team and the PSF, establish decision making processes for Python Enhancement Proposals, seek consensus among contributors and the Python core team, and resolve decisions and disputes in decision making among the language.', + }; + + constructor( + public liveUpdateService: LiveUpdateService, + private confData: ConferenceData, + ) {} + + ngOnInit() { + this.confData.load().subscribe((data: any) => { + if (data?.sessions) { + const keynoteSessions = data.sessions.filter( + (s: any) => s.track === 'Keynote' || s.tracks?.includes('keynote') + ); + this.speakers.forEach(speaker => { + const match = keynoteSessions.find( + (s: any) => s.name?.toLowerCase().includes(speaker.name.toLowerCase()) + ); + if (match) { + speaker.session = match; + } + }); + } + }); + } + + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } +} diff --git a/src/app/pages/lightning-talks/lightning-talks.page.html b/src/app/pages/lightning-talks/lightning-talks.page.html index 2254255c..4b30c343 100644 --- a/src/app/pages/lightning-talks/lightning-talks.page.html +++ b/src/app/pages/lightning-talks/lightning-talks.page.html @@ -1,14 +1,14 @@ - + 1 - Lightning Talks + Lightning Talks - +

Lightning Talks

diff --git a/src/app/pages/lightning-talks/lightning-talks.page.scss b/src/app/pages/lightning-talks/lightning-talks.page.scss index 916464a4..800a3be2 100644 --- a/src/app/pages/lightning-talks/lightning-talks.page.scss +++ b/src/app/pages/lightning-talks/lightning-talks.page.scss @@ -1,8 +1,32 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .lt-hero { display: flex; flex-direction: column; align-items: center; - padding: 48px 24px 32px; + padding: 16px 24px 32px; background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); color: #fff; text-align: center; diff --git a/src/app/pages/lightning-talks/lightning-talks.page.ts b/src/app/pages/lightning-talks/lightning-talks.page.ts index 2c83982f..e974ed0d 100644 --- a/src/app/pages/lightning-talks/lightning-talks.page.ts +++ b/src/app/pages/lightning-talks/lightning-talks.page.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { LiveUpdateService } from '../../providers/live-update.service'; @Component({ @@ -7,8 +8,15 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./lightning-talks.page.scss'], }) export class LightningTalksPage { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + constructor(public liveUpdateService: LiveUpdateService) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + signUp() { window.open('https://us.pycon.org/2026/events/lightning-talks/', '_system', 'location=yes'); } diff --git a/src/app/pages/now/now.page.html b/src/app/pages/now/now.page.html index 03c4f068..1468c7c3 100644 --- a/src/app/pages/now/now.page.html +++ b/src/app/pages/now/now.page.html @@ -1,17 +1,23 @@ - + - + - Now + Now & Next - + +
+ +

Now & Next

+

What's happening at PyCon US right now

+
+

No sessions today

diff --git a/src/app/pages/now/now.page.scss b/src/app/pages/now/now.page.scss index e8718372..660ca443 100644 --- a/src/app/pages/now/now.page.scss +++ b/src/app/pages/now/now.page.scss @@ -1,3 +1,55 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #DD04D2; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + .section { padding: 0 0 1em; } diff --git a/src/app/pages/now/now.page.ts b/src/app/pages/now/now.page.ts index e7ba7d64..69dc9516 100644 --- a/src/app/pages/now/now.page.ts +++ b/src/app/pages/now/now.page.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { environment } from '../../../environments/environment'; @@ -8,6 +9,8 @@ import { environment } from '../../../environments/environment'; styleUrls: ['./now.page.scss'], }) export class NowPage implements OnInit, OnDestroy { + @ViewChild(IonContent) content: IonContent; + showTitle = false; nowSessions: any[] = []; nextSessions: any[] = []; nextTime: string = ''; @@ -17,6 +20,10 @@ export class NowPage implements OnInit, OnDestroy { constructor(private confData: ConferenceData) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + ngOnInit() { this.refresh(); this.refreshInterval = setInterval(() => this.refresh(), 60000); diff --git a/src/app/pages/schedule-list/schedule-list.page.html b/src/app/pages/schedule-list/schedule-list.page.html index 90d38cb2..a8611fd6 100644 --- a/src/app/pages/schedule-list/schedule-list.page.html +++ b/src/app/pages/schedule-list/schedule-list.page.html @@ -1,19 +1,24 @@ - + - + 1 - - {{trackName | trackName : 'plural'}} - + {{trackName | trackName : 'plural'}} New track! - + +
+ +

{{trackName | trackName : 'plural'}}

+

Browse presentations in this track

+

Attendee-organized sessions

+
+ diff --git a/src/app/pages/schedule-list/schedule-list.page.scss b/src/app/pages/schedule-list/schedule-list.page.scss index dc395c59..9193d9d0 100644 --- a/src/app/pages/schedule-list/schedule-list.page.scss +++ b/src/app/pages/schedule-list/schedule-list.page.scss @@ -1,3 +1,55 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #FFD779; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + ::ng-deep ion-title.track-title { font-size: 0.95rem; diff --git a/src/app/pages/schedule-list/schedule-list.page.ts b/src/app/pages/schedule-list/schedule-list.page.ts index e8ddfbf7..a2a63a8e 100644 --- a/src/app/pages/schedule-list/schedule-list.page.ts +++ b/src/app/pages/schedule-list/schedule-list.page.ts @@ -1,7 +1,7 @@ import { Component, ChangeDetectorRef, ViewChild, OnInit } from '@angular/core'; import { ConferenceData } from '../../providers/conference-data'; import { ActivatedRoute } from '@angular/router'; -import { Config, InfiniteScrollCustomEvent, LoadingController } from '@ionic/angular'; +import { Config, IonContent, InfiniteScrollCustomEvent, LoadingController } from '@ionic/angular'; import { InAppBrowser, DefaultWebViewOptions } from '@capacitor/inappbrowser'; import { LiveUpdateService } from '../../providers/live-update.service'; import { UserData } from '../../providers/user-data'; @@ -23,6 +23,8 @@ const slugify = str => export class ScheduleListPage implements OnInit { // Get a reference to the search bar @ViewChild('search') search : any; + @ViewChild(IonContent) content: IonContent; + showTitle = false; trackName: string; trackSlug: string; excludeTracks: any[] = []; @@ -51,6 +53,10 @@ export class ScheduleListPage implements OnInit { ) { } + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + updateSessions() { this.confData.getSessions(this.sessionQueryText, this.excludeTracks).subscribe((sessions: any[]) => { this.sessions = sessions; diff --git a/src/app/pages/session-detail/session-detail.html b/src/app/pages/session-detail/session-detail.html index 65579133..b8424b07 100644 --- a/src/app/pages/session-detail/session-detail.html +++ b/src/app/pages/session-detail/session-detail.html @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ - + @@ -17,21 +17,23 @@
-

{{session.name}}

+
+

{{session.name}}

-
- {{track | trackName : 'long'}} - - New track! - - En Español - Pre-registration required +
+ {{track | trackName : 'long'}} + + New track! + + En Español + Pre-registration required +
- {{session.day}} {{session.timeStart}} – {{session.timeEnd}} + {{session.day}} {{session.timeStart}} – {{session.timeEnd}}
@@ -39,28 +41,38 @@

{{session.name}}

-
- - - - - -

{{speaker.name}}

-

Speaker

-
-
-
+
+
+ +
+

{{keynoteData.name}}

+

{{keynoteData.bio}}

+
+
-
- -
+
+ + + + + +

{{speaker.name}}

+

Speaker

+
+
+
+ +
+ +
-
+
+
diff --git a/src/app/pages/session-detail/session-detail.scss b/src/app/pages/session-detail/session-detail.scss index e06ca4df..814687f7 100644 --- a/src/app/pages/session-detail/session-detail.scss +++ b/src/app/pages/session-detail/session-detail.scss @@ -1,5 +1,34 @@ -.session-detail-content { - padding: 20px 16px; +/* + * Session Detail — PyCon 2026 Theme + */ + +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + + &::after { + display: none; + } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-back-button, +ion-toolbar ion-button { + --color: #ffffff; + color: #ffffff; +} + +/* + * Hero section — gradient continuation from header + */ + +.session-hero { + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + padding: 0 20px 40px; } .session-title { @@ -7,34 +36,88 @@ font-weight: 700; line-height: 1.25; margin: 0 0 12px; + color: #ffffff; } .session-badges { display: flex; flex-wrap: wrap; gap: 6px; - margin-bottom: 16px; + + .prereg-badge { + background-color: rgba(255, 255, 255, 0.2); + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.5); + } + + .spanish-badge { + background-color: rgba(255, 255, 255, 0.2); + color: #ffffff; + } + + .new-badge { + background-color: rgba(255, 255, 255, 0.2); + color: #ffffff; + } +} + +/* + * Keynote speaker card + */ + +.keynote-speaker-card { + display: flex; + gap: 16px; + padding: 16px 20px; + margin: 0 0 8px; + align-items: flex-start; + + .keynote-photo { + width: 80px; + height: 80px; + border-radius: 12px; + object-fit: cover; + flex-shrink: 0; + } + + .keynote-info { + flex: 1; + + h3 { + margin: 0 0 4px; + font-size: 1rem; + font-weight: 700; + } + + p { + margin: 0; + font-size: 0.85rem; + line-height: 1.4; + color: var(--ion-color-medium); + } + } } +/* + * Meta card — overlaps the gradient + */ + .session-meta-card { - background: var(--ion-color-step-50, #f5f5f5); - border-radius: 12px; - padding: 14px 16px; - margin-bottom: 20px; + background: var(--ion-background-color, #ffffff); + border-radius: 16px; + padding: 16px 18px; + margin: -20px 16px 0; + position: relative; + z-index: 1; display: flex; flex-direction: column; gap: 10px; -} - -// When image or description directly follows meta-card (no speakers section), -// add extra top spacing for visual breathing room -.session-meta-card + .session-image-container, -.session-meta-card + .session-description { - margin-top: 4px; + box-shadow: 0 4px 16px rgba(16, 17, 54, 0.1); } :host-context(.dark-theme) .session-meta-card { background: var(--ion-color-step-100, #1a1a1a); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); } .meta-row { @@ -45,11 +128,23 @@ ion-icon { font-size: 1.2em; - color: var(--ion-color-step-500, #808080); + color: #3B3EA9; flex-shrink: 0; } } +:host-context(.dark-theme) .meta-row ion-icon { + color: #8b8fd4; +} + +/* + * Body content below the card + */ + +.session-body { + padding: 16px 16px 0; +} + .session-speakers-section { margin-bottom: 20px; diff --git a/src/app/pages/session-detail/session-detail.ts b/src/app/pages/session-detail/session-detail.ts index 2555364a..ee7a363e 100644 --- a/src/app/pages/session-detail/session-detail.ts +++ b/src/app/pages/session-detail/session-detail.ts @@ -17,8 +17,33 @@ export class SessionDetailPage { session: any; isFavorite = false; isOpenSpace = false; + isKeynote = false; + keynoteData: any = null; defaultHref = ''; + private keynoteSpeakers: Record = { + 'Lin Qiao': { + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/lin_qiao.original.jpg', + bio: 'Lin Qiao is the CEO and co-founder of global AI inference cloud and infrastructure platform Fireworks AI, enables teams like Cursor, Uber, DoorDash, and Shopify to build, tune, and scale highly optimized generative AI applications. Prior to founding Fireworks, Lin was the co-creator and head of Meta\'s PyTorch.', + }, + 'amanda casari': { + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/amcasari-headshot.original.png', + bio: 'amanda casari is an engineer and researcher who has worked in many technical and socio-technical disciplines for over 20 years, including developer relations, product management, data science, and underwater robotics.', + }, + 'Tim Schilling': { + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Tim_Schilling.original.jpg', + bio: 'A software engineer that loves Django and our community. On the Django Steering Council, a cofounder of Djangonaut Space and an admin of Django Commons.', + }, + 'Rachell Calhoun': { + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/rachell_calhoun.original.jpg', + bio: 'Co-founder of Djangonaut Space and a Django developer. Organized Django Girls workshops across multiple countries and continents for over 10 years.', + }, + 'Pablo Galindo Salgado': { + photo: 'https://pycon-assets.s3.amazonaws.com/2026/media/images/Pablo_Galindo_Salgado.original.jpg', + bio: 'CPython core developer and Theoretical Physicist. Currently serving on the Python Steering Council in his 6th term and release manager for Python 3.10 and 3.11.', + }, + }; + constructor( private dataProvider: ConferenceData, private userProvider: UserData, @@ -36,6 +61,18 @@ export class SessionDetailPage { ) this.session = foundSession; this.isOpenSpace = this.session?.tracks?.includes('open-space'); + this.isKeynote = this.session?.tracks?.includes('keynote') || this.session?.track === 'Keynote'; + + // Enrich keynote sessions with speaker photo/bio + if (this.isKeynote) { + const sessionName = this.session?.name || ''; + for (const [name, data] of Object.entries(this.keynoteSpeakers)) { + if (sessionName.toLowerCase().includes(name.toLowerCase())) { + this.keynoteData = { name, ...data }; + break; + } + } + } this.isFavorite = this.userProvider.hasFavorite( String(this.session.id) diff --git a/src/app/pages/session-types/session-types.page.html b/src/app/pages/session-types/session-types.page.html index 994094e7..650022d2 100644 --- a/src/app/pages/session-types/session-types.page.html +++ b/src/app/pages/session-types/session-types.page.html @@ -1,14 +1,14 @@ - + 1 - Session Types + Session Types - +
diff --git a/src/app/pages/session-types/session-types.page.scss b/src/app/pages/session-types/session-types.page.scss index 05dd133b..3666c9f8 100644 --- a/src/app/pages/session-types/session-types.page.scss +++ b/src/app/pages/session-types/session-types.page.scss @@ -1,5 +1,29 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .types-hero { - padding: 48px 24px 32px; + padding: 16px 24px 32px; background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); color: #fff; text-align: center; diff --git a/src/app/pages/session-types/session-types.page.ts b/src/app/pages/session-types/session-types.page.ts index da205de6..b4b9c1f0 100644 --- a/src/app/pages/session-types/session-types.page.ts +++ b/src/app/pages/session-types/session-types.page.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { LiveUpdateService } from '../../providers/live-update.service'; @Component({ @@ -7,7 +8,14 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./session-types.page.scss'], }) export class SessionTypesPage { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + constructor( public liveUpdateService: LiveUpdateService, ) {} + + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } } diff --git a/src/app/pages/social-media/social-media.page.html b/src/app/pages/social-media/social-media.page.html index 979c500e..d82c1c7a 100644 --- a/src/app/pages/social-media/social-media.page.html +++ b/src/app/pages/social-media/social-media.page.html @@ -1,14 +1,20 @@ - + 1 - Social Media + Social Media - + +
+ +

Social Media

+

Connect with the Python community online

+
+
diff --git a/src/app/pages/social-media/social-media.page.scss b/src/app/pages/social-media/social-media.page.scss index f767e181..b9dd72c1 100644 --- a/src/app/pages/social-media/social-media.page.scss +++ b/src/app/pages/social-media/social-media.page.scss @@ -1,3 +1,55 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #25C8EB; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + .hashtag-banner { display: flex; justify-content: center; diff --git a/src/app/pages/social-media/social-media.page.ts b/src/app/pages/social-media/social-media.page.ts index 90b21e55..4b16c37d 100644 --- a/src/app/pages/social-media/social-media.page.ts +++ b/src/app/pages/social-media/social-media.page.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { LiveUpdateService } from '../../providers/live-update.service'; @Component({ @@ -7,10 +8,17 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./social-media.page.scss'], }) export class SocialMediaPage { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + constructor( public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + openUrl(url: string) { window.open(url, '_system', 'location=yes'); } diff --git a/src/app/pages/speaker-detail/speaker-detail.html b/src/app/pages/speaker-detail/speaker-detail.html index 651feb9d..1e9b20dc 100644 --- a/src/app/pages/speaker-detail/speaker-detail.html +++ b/src/app/pages/speaker-detail/speaker-detail.html @@ -21,20 +21,17 @@

{{speaker?.name}}

Presentations

- - - - - - -

{{session.name}}

- {{session.track | trackName}} - {{session.day}} {{session.timeStart}} in {{session.location}} -
-
-
-
-
-
+ + + +

{{session.name}}

+

+ {{session.track | trackName}} + {{session.day}} {{session.timeStart}} in {{session.location}} +

+
+
+
diff --git a/src/app/pages/speaker-list/speaker-list.html b/src/app/pages/speaker-list/speaker-list.html index bef28d0d..d40ff626 100644 --- a/src/app/pages/speaker-list/speaker-list.html +++ b/src/app/pages/speaker-list/speaker-list.html @@ -1,63 +1,40 @@ - + - - + + 1 - Speakers - - - - - - + Speakers - - - - Speakers - - - - - - - + +
+ +

Speakers

+

PyCon US 2026 presenters

+
- - - - - - - - - - -

{{speaker.name}}

- -
-
-
+ + + - - - - -

{{session.name}}

- {{session.track | trackName}} - {{session.day}} {{session.timeStart}} in {{session.location}} -
-
+ + + + + + +

{{speaker.name}}

+

+ {{speaker.sessions[0].track | trackName}} + {{speaker.sessions[0].name}} +

+
+
+
-
-
-
-
-
-
diff --git a/src/app/pages/speaker-list/speaker-list.scss b/src/app/pages/speaker-list/speaker-list.scss index ad31b524..8a2d9f48 100644 --- a/src/app/pages/speaker-list/speaker-list.scss +++ b/src/app/pages/speaker-list/speaker-list.scss @@ -1,48 +1,83 @@ -.speaker-card { - display: flex; - flex-direction: column; - // add a border around the card to make it stand out - border: 1px solid var(--ion-color-step-150, #d7d8da); +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } } -/* Due to the fact the cards are inside of columns the margins don't overlap - * properly so we want to remove the extra margin between cards - */ -ion-col:not(:last-of-type) .speaker-card { - margin-bottom: 0; +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; } -.speaker-card .speaker-item { - --min-height: 85px; +ion-toolbar ion-menu-button { + --color: #ffffff; } -.speaker-card .speaker-item h2 { - font-size: 18px; - font-weight: 500; - letter-spacing: 0.02em; -} +ion-title { + opacity: 0; + transition: opacity 0.25s ease; -.speaker-card .speaker-item p { - font-size: 13px; - letter-spacing: 0.02em; + &.title-visible { + opacity: 1; + } } -.speaker-card ion-card-header { - padding: 0; -} +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #25C8EB; + margin-bottom: 10px; + } -.speaker-card ion-card-content { - flex: 1 1 auto; + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } - padding: 0; + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } } -.ios ion-list { - margin-bottom: 10px; +.search-toolbar { + --background: var(--ion-background-color, #fff); + --border-color: transparent; + margin-top: 8px; } -.md ion-list { - border-top: 1px solid var(--ion-color-step-150, #d7d8da); +.speaker-list { + ion-avatar { + width: 44px; + height: 44px; + } + + h2 { + font-size: 0.95rem; + font-weight: 600; + } + + p { + font-size: 0.8rem; + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; + margin-top: 2px; + } - padding: 0; + .track-badge { + font-size: 0.6rem; + padding: 1px 6px; + } } diff --git a/src/app/pages/speaker-list/speaker-list.ts b/src/app/pages/speaker-list/speaker-list.ts index 6eee53e7..705679cc 100644 --- a/src/app/pages/speaker-list/speaker-list.ts +++ b/src/app/pages/speaker-list/speaker-list.ts @@ -16,6 +16,7 @@ export class SpeakerListPage implements OnInit { speakerQueryText = ''; ios: boolean; showSearchbar: boolean; + showTitle = false; page: number = 0; scrolling: boolean = false; @@ -27,6 +28,10 @@ export class SpeakerListPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + updateSpeakers() { this.confData.getSpeakers("").subscribe((speakers: any[]) => { this.speakers = speakers; diff --git a/src/app/pages/sponsors/sponsors.page.html b/src/app/pages/sponsors/sponsors.page.html index bbe6a09b..c8546d66 100644 --- a/src/app/pages/sponsors/sponsors.page.html +++ b/src/app/pages/sponsors/sponsors.page.html @@ -3,11 +3,11 @@ - Sponsors + Sponsors
- +

Our Sponsors

diff --git a/src/app/pages/sponsors/sponsors.page.scss b/src/app/pages/sponsors/sponsors.page.scss index 69e04292..3669235f 100644 --- a/src/app/pages/sponsors/sponsors.page.scss +++ b/src/app/pages/sponsors/sponsors.page.scss @@ -13,6 +13,15 @@ ion-toolbar ion-menu-button { --color: #ffffff; } +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .sponsors-hero { display: flex; flex-direction: column; diff --git a/src/app/pages/sponsors/sponsors.page.ts b/src/app/pages/sponsors/sponsors.page.ts index cbb2ae4d..1d6aa330 100644 --- a/src/app/pages/sponsors/sponsors.page.ts +++ b/src/app/pages/sponsors/sponsors.page.ts @@ -1,6 +1,6 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; import { KeyValue } from '@angular/common'; -import { LoadingController } from '@ionic/angular'; +import { IonContent, LoadingController } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; @@ -12,6 +12,8 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./sponsors.page.scss'], }) export class SponsorsPage implements OnInit { + @ViewChild(IonContent) content: IonContent; + showTitle = false; sponsors: any; constructor( @@ -21,6 +23,10 @@ export class SponsorsPage implements OnInit { public liveUpdateService: LiveUpdateService, ) { } + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + levelOrder(a: KeyValue, b: KeyValue): number { return a.value[0].level_order < b.value[0].level_order ? -1 : (b.value[0].level_order < a.value[0].level_order ? 1 : 0); } diff --git a/src/app/pages/sprints/sprints.page.html b/src/app/pages/sprints/sprints.page.html index dbe6a9a5..76ec05b8 100644 --- a/src/app/pages/sprints/sprints.page.html +++ b/src/app/pages/sprints/sprints.page.html @@ -1,33 +1,29 @@ - + 1 - Development Sprints + Sprints - + +
+ +

Development Sprints

+

Contribute to open source after the conference

+
+ - - -

- Sprints are a key part of PyCon US! They take place after the main conference - and give attendees the chance to contribute to open source projects alongside - core developers and maintainers. -

-
-
- diff --git a/src/app/pages/sprints/sprints.page.scss b/src/app/pages/sprints/sprints.page.scss index 46bb26f4..3714622b 100644 --- a/src/app/pages/sprints/sprints.page.scss +++ b/src/app/pages/sprints/sprints.page.scss @@ -1,3 +1,59 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + +.page-hero { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px 32px; + background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); + color: #fff; + text-align: center; + + .page-hero-icon { + font-size: 40px; + color: #D47454; + margin-bottom: 10px; + } + + h1 { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + } + + p { + margin: 6px 0 0; + font-size: 0.9rem; + opacity: 0.8; + } +} + +.search-toolbar { + margin-top: 12px; +} + .sprint-card { cursor: pointer; } diff --git a/src/app/pages/sprints/sprints.page.ts b/src/app/pages/sprints/sprints.page.ts index 7b2b9ccd..f26776f7 100644 --- a/src/app/pages/sprints/sprints.page.ts +++ b/src/app/pages/sprints/sprints.page.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { UserData } from '../../providers/user-data'; @@ -11,6 +12,8 @@ import { environment } from '../../../environments/environment'; styleUrls: ['./sprints.page.scss'], }) export class SprintsPage implements OnInit { + @ViewChild(IonContent) content: IonContent; + showTitle = false; allSprints: any[] = []; sprints: any[] = []; searchText: string = ''; @@ -23,6 +26,10 @@ export class SprintsPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + loadSprints() { this.confData.getSprints().subscribe((sprints: any[]) => { if (sprints.length > 0 || this.allSprints.length === 0) { diff --git a/src/app/pages/tabs-page/tabs-page-routing.module.ts b/src/app/pages/tabs-page/tabs-page-routing.module.ts index ed31814a..c1f4b0ca 100644 --- a/src/app/pages/tabs-page/tabs-page-routing.module.ts +++ b/src/app/pages/tabs-page/tabs-page-routing.module.ts @@ -187,6 +187,15 @@ const routes: Routes = [ } ] }, + { + path: 'keynote-speakers', + children: [ + { + path: '', + loadChildren: () => import('../keynote-speakers/keynote-speakers.module').then(m => m.KeynoteSpeakersPageModule) + } + ] + }, { path: 'job-listings', children: [ diff --git a/src/app/pages/venues-hours/venues-hours.page.html b/src/app/pages/venues-hours/venues-hours.page.html index dcd8231e..5e552b1f 100644 --- a/src/app/pages/venues-hours/venues-hours.page.html +++ b/src/app/pages/venues-hours/venues-hours.page.html @@ -1,14 +1,14 @@ - + 1 - Venues & Hours + Venues & Hours - +
diff --git a/src/app/pages/venues-hours/venues-hours.page.scss b/src/app/pages/venues-hours/venues-hours.page.scss index d2f805ea..815e8970 100644 --- a/src/app/pages/venues-hours/venues-hours.page.scss +++ b/src/app/pages/venues-hours/venues-hours.page.scss @@ -1,5 +1,29 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .venues-hero { - padding: 48px 24px 32px; + padding: 16px 24px 32px; background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); color: #fff; text-align: center; diff --git a/src/app/pages/venues-hours/venues-hours.page.ts b/src/app/pages/venues-hours/venues-hours.page.ts index a6e54ea4..99ecda23 100644 --- a/src/app/pages/venues-hours/venues-hours.page.ts +++ b/src/app/pages/venues-hours/venues-hours.page.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { IonContent } from '@ionic/angular'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; @@ -8,7 +9,9 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./venues-hours.page.scss'], }) export class VenuesHoursPage implements OnInit { + @ViewChild(IonContent) ionContent: IonContent; content: any = ''; + showTitle = false; constructor( private confData: ConferenceData, @@ -16,6 +19,10 @@ export class VenuesHoursPage implements OnInit { public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + openUrl(url: string) { window.open(url, '_system', 'location=yes'); } diff --git a/src/app/pages/wifi/wifi.page.html b/src/app/pages/wifi/wifi.page.html index 17d84333..316c9956 100644 --- a/src/app/pages/wifi/wifi.page.html +++ b/src/app/pages/wifi/wifi.page.html @@ -1,14 +1,14 @@ - + 1 - Conference Wi-Fi + Wi-Fi - +

Connect to Wi-Fi

diff --git a/src/app/pages/wifi/wifi.page.scss b/src/app/pages/wifi/wifi.page.scss index 0201c1da..875d01d2 100644 --- a/src/app/pages/wifi/wifi.page.scss +++ b/src/app/pages/wifi/wifi.page.scss @@ -1,8 +1,32 @@ +ion-header { + background: linear-gradient(180deg, #3B3EA9 0%, #3B3EA9 100%); + &::after { display: none; } +} + +ion-toolbar { + --background: transparent; + --border-color: transparent; + --color: #ffffff; +} + +ion-toolbar ion-menu-button { + --color: #ffffff; +} + +ion-title { + opacity: 0; + transition: opacity 0.25s ease; + + &.title-visible { + opacity: 1; + } +} + .wifi-hero { display: flex; flex-direction: column; align-items: center; - padding: 48px 16px 32px; + padding: 16px 16px 32px; background: linear-gradient(180deg, #3B3EA9 23.5%, #101136 53.29%); color: #fff; diff --git a/src/app/pages/wifi/wifi.page.ts b/src/app/pages/wifi/wifi.page.ts index 8f8cce41..10aa7470 100644 --- a/src/app/pages/wifi/wifi.page.ts +++ b/src/app/pages/wifi/wifi.page.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { Platform, ToastController } from '@ionic/angular'; +import { Component, ViewChild } from '@angular/core'; +import { IonContent, Platform, ToastController } from '@ionic/angular'; import { LiveUpdateService } from '../../providers/live-update.service'; @Component({ @@ -8,12 +8,19 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./wifi.page.scss'], }) export class WifiPage { + @ViewChild(IonContent) content: IonContent; + showTitle = false; + constructor( private platform: Platform, private toastCtrl: ToastController, public liveUpdateService: LiveUpdateService, ) {} + onScroll(event: any) { + this.showTitle = event.detail.scrollTop > 100; + } + async copyPassword() { await navigator.clipboard.writeText('pyconLB2026'); const toast = await this.toastCtrl.create({