@@ -47,7 +47,42 @@ const schedule = await getSchedule()
4747 <div id =" schedule-list-view" class =" schedule-view hidden" data-view =" list" >
4848 {
4949 schedule ? (
50- <ScheduleList schedule = { schedule } lang = { lang } />
50+ <>
51+ <div class = " max-w-3xl mx-auto mb-6 flex justify-end" >
52+ <button
53+ type = " button"
54+ class = " favorites-filter-btn inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-white/10 text-sm text-pycon-gray-25 hover:border-pycon-orange/50 hover:text-pycon-orange transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pycon-yellow has-[:checked]:bg-pycon-orange has-[:checked]:text-pycon-black has-[:checked]:font-semibold has-[:checked]:border-pycon-orange"
55+ aria-pressed = " false"
56+ data-filter-favorites
57+ >
58+ <svg
59+ class = " w-4 h-4"
60+ viewBox = " 0 0 24 24"
61+ fill = " none"
62+ stroke = " currentColor"
63+ stroke-width = " 2"
64+ aria-hidden = " true"
65+ >
66+ <path
67+ stroke-linecap = " round"
68+ stroke-linejoin = " round"
69+ d = " M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
70+ />
71+ </svg >
72+ <span >
73+ { t .list .favoritesOnly } (<span data-favorites-count >0</span >)
74+ </span >
75+ </button >
76+ </div >
77+ <ScheduleList schedule = { schedule } lang = { lang } />
78+ <p
79+ class = " favorites-empty max-w-3xl mx-auto mt-4 text-center text-pycon-gray-25/80 text-sm"
80+ data-favorites-empty
81+ hidden
82+ >
83+ { t .list .noFavorites }
84+ </p >
85+ </>
5186 ) : (
5287 <div class = " max-w-2xl mx-auto text-center py-12 px-6 bg-pycon-black/40 rounded-2xl border border-white/5" >
5388 <h2 class = " text-2xl font-bold text-white mb-4" >{ t .list .emptyTitle } </h2 >
@@ -103,6 +138,19 @@ const schedule = await getSchedule()
103138 .schedule-view.hidden {
104139 display: none;
105140 }
141+ #schedule-list-view.filter-favorites-only .schedule-list li:not(.is-favorite) {
142+ display: none;
143+ }
144+ .favorites-filter-btn[aria-pressed='true'] {
145+ background-color: var(--color-pycon-orange, #fd6837);
146+ color: #03004b;
147+ font-weight: 600;
148+ border-color: var(--color-pycon-orange, #fd6837);
149+ }
150+ .favorites-filter-btn[aria-pressed='true'] svg {
151+ fill: currentColor;
152+ stroke: currentColor;
153+ }
106154</style >
107155
108156<script is:inline src =" https://pretalx.com/pycones-2026/widgets/schedule.js" ></script >
@@ -137,12 +185,14 @@ const schedule = await getSchedule()
137185
138186 try {
139187 const saved = localStorage.getItem(STORAGE_KEY)
140- if (saved === 'list' || saved === 'grid') {
141- const target = document.querySelector(`input[name="schedule-view"][value="${saved}"]`)
142- if (target) {
143- target.checked = true
144- applyView(saved)
145- }
188+ // Only restore the saved view if it matches the HTML default. Legacy
189+ // values (e.g. 'list' from when list was the default) are ignored so
190+ // the page always opens with the current default and there's no jump
191+ // from a stale saved value.
192+ const defaultChecked = document.querySelector('input[name="schedule-view"]:checked')
193+ const defaultValue = defaultChecked ? defaultChecked.value : null
194+ if ((saved === 'list' || saved === 'grid') && saved === defaultValue) {
195+ applyView(saved)
146196 }
147197 } catch (e) {}
148198 })()
@@ -177,10 +227,31 @@ const schedule = await getSchedule()
177227 document.querySelectorAll('.favorite-btn').forEach((btn) => {
178228 const code = btn.getAttribute('data-session-code')
179229 if (!code) return
180- btn.setAttribute('aria-pressed', favs.has(code) ? 'true' : 'false')
230+ const isFav = favs.has(code)
231+ btn.setAttribute('aria-pressed', isFav ? 'true' : 'false')
232+ const li = btn.closest('li')
233+ if (li) li.classList.toggle('is-favorite', isFav)
234+ })
235+ updateFavoritesCount()
236+ updateEmptyState()
237+ }
238+
239+ function updateFavoritesCount() {
240+ const count = readFavs().length
241+ document.querySelectorAll('[data-favorites-count]').forEach((el) => {
242+ el.textContent = String(count)
181243 })
182244 }
183245
246+ function updateEmptyState() {
247+ const empty = document.querySelector('[data-favorites-empty]')
248+ const listView = document.getElementById('schedule-list-view')
249+ if (!empty || !listView) return
250+ const filterActive = listView.classList.contains('filter-favorites-only')
251+ const count = readFavs().length
252+ empty.hidden = !(filterActive && count === 0)
253+ }
254+
184255 // Click handler: toggle, persist, update visual state and announce
185256 document.addEventListener('click', (e) => {
186257 const btn = e.target.closest && e.target.closest('.favorite-btn')
@@ -222,6 +293,18 @@ const schedule = await getSchedule()
222293 if (e.key === FAV_KEY) syncFavorites()
223294 })
224295
296+ // Filter button: show only favourited sessions
297+ const filterBtn = document.querySelector('[data-filter-favorites]')
298+ if (filterBtn) {
299+ filterBtn.addEventListener('click', () => {
300+ const pressed = filterBtn.getAttribute('aria-pressed') === 'true'
301+ filterBtn.setAttribute('aria-pressed', pressed ? 'false' : 'true')
302+ const listView = document.getElementById('schedule-list-view')
303+ if (listView) listView.classList.toggle('filter-favorites-only', !pressed)
304+ updateEmptyState()
305+ })
306+ }
307+
225308 // Initial render
226309 syncFavorites()
227310 })()
0 commit comments