Skip to content
Merged
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
4 changes: 3 additions & 1 deletion apps/site/.cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
"wordmark",
"Wordmark",
"WORDMARK",
"writeups"
"writeups",
"Tous",
"Jours"
]
}
8 changes: 1 addition & 7 deletions apps/site/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,14 +381,11 @@
}
},
"src/lib/events-loader.ts": {
"@typescript-eslint/consistent-type-definitions": {
"count": 1
},
"@typescript-eslint/no-unnecessary-condition": {
"count": 15
},
"@typescript-eslint/no-unnecessary-type-assertion": {
"count": 2
"count": 1
},
"@typescript-eslint/no-unsafe-argument": {
"count": 1
Expand Down Expand Up @@ -465,9 +462,6 @@
}
},
"src/pages/llms-full.txt.ts": {
"complexity": {
"count": 1
},
"max-lines-per-function": {
"count": 1
},
Expand Down
2 changes: 1 addition & 1 deletion apps/site/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default defineConfig({
{
name: 'ui-contracts',
testMatch:
/(campaign-events|body-links|color-contracts|event-metadata|footer-colophon|header-current|not-found-search|print-layout|typography-contracts)\.spec\.ts$/,
/(campaign-events|body-links|color-contracts|event-metadata|footer-colophon|header-current|not-found-search|print-layout|program-scroll|typography-contracts)\.spec\.ts$/,
use: { ...devices['Desktop Chrome'] },
},
{
Expand Down
Binary file added apps/site/public/programs/transit-mapper.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions apps/site/src/components/programs/ProgramEventFeature.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
import {
programEventFeature,
programEventPayload,
selectProgramEvent,
type ProgramEvent,
type ProgramParticipation,
} from '../../lib/program-events';

interface Props {
program: ProgramParticipation;
events: ProgramEvent[];
headingId: string;
}
const { program, events, headingId } = Astro.props;
const now = new Date();
const panel = programEventFeature(
program,
selectProgramEvent(events, program.calendarSeriesUid, now),
now,
);
---

<aside
class="program-feature-aside program-feature-event"
aria-labelledby={headingId}
data-program-event-panel
data-program-event-feature
>
<h3 id={headingId} class="text-body-lg" data-program-event-heading>{panel.heading}</h3>
<div class="program-feature-event-content">
<p class="program-feature-event-topic" data-program-event-topic>{panel.headline}</p>
<div class="program-feature-event-footer">
<div>
<p
class="program-feature-event-date"
data-program-event-date-group
hidden={!panel.dateLabel}
>
<time datetime={panel.dateIso} data-program-event-date>
<span data-program-event-feature-date>{panel.dateLabel}</span>
</time>
</p>
<p class="text-on-surface-variant">
<span data-program-event-time>{panel.timeLabel}</span>
<span data-program-event-separator hidden={!panel.timeLabel || !panel.venue}> · </span>
<span data-program-event-venue-name
>{panel.venue?.name}{panel.venue?.tentative ? ' (tentative)' : ''}</span
>
</p>
</div>
<div data-program-event-action-group hidden={!panel.action}>
<a
class="program-feature-event-action"
href={panel.action?.href}
data-program-event-action
data-print-url={panel.action?.href}
>
<span data-program-event-action-label>{panel.action?.label}</span>
<span aria-hidden="true">→</span>
<span data-print-link aria-hidden="true"
><span data-program-event-print-url>{panel.printUrl}</span></span
>
</a>
</div>
</div>
</div>
<script
is:inline
type="application/json"
data-program-events
set:html={programEventPayload(program, events)}
/>
</aside>

<script>
import '../../scripts/program-events';
</script>
95 changes: 95 additions & 0 deletions apps/site/src/components/programs/ProgramEventPanel.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
import { Icon } from 'astro-icon/components';
import {
programEventPanel,
programEventPayload,
selectProgramEvent,
type ProgramEvent,
type ProgramParticipation,
} from '../../lib/program-events';

interface Props {
program: ProgramParticipation;
events: ProgramEvent[];
}

const { program, events } = Astro.props;
const now = new Date();
const panel = programEventPanel(
program,
selectProgramEvent(events, program.calendarSeriesUid, now),
now,
);
const payload = programEventPayload(program, events);
const venueAddress = [panel.venue?.address, panel.venue?.tentative ? '(tentative)' : undefined]
.filter(Boolean)
.join(' ');
---

<aside class="min-w-0 self-start lg:sticky lg:top-24 lg:order-last" data-program-event-panel>
<h2 class="mb-4 text-body-lg font-medium" data-program-event-heading>{panel.heading}</h2>
<div class="flex flex-col bg-surface-container">
<span class="h-2 bg-primary" aria-hidden="true"></span>
<div class="p-6 md:p-8">
<p class="flex items-start gap-3 font-semibold">
<Icon name="mdi:calendar-blank-outline" class="size-5 shrink-0" aria-hidden="true" />
<span>
<span data-program-event-date-group hidden={!panel.eventHref}>
<time datetime={panel.dateIso} data-program-event-date>
<a
href={panel.eventHref}
class="underline underline-offset-4"
data-program-event-link
>
{panel.dateLabel}
</a>
</time>
<span class="block" data-program-event-time>{panel.timeLabel}</span>
</span>
<span data-program-event-schedule hidden={!!panel.eventHref}>{panel.schedule}</span>
</span>
</p>
<div data-program-event-venue hidden={!panel.venue}>
<p class="mt-3 flex items-start gap-3">
<Icon name="mdi:map-marker-outline" class="mt-0.5 size-5 shrink-0" aria-hidden="true" />
<span>
<span data-program-event-venue-link-group hidden={!panel.venue?.mapUrl}>
<a
href={panel.venue?.mapUrl}
class="font-semibold underline underline-offset-4"
data-program-event-venue-link
>
{panel.venue?.name}
</a>
</span>
<span data-program-event-venue-name hidden={!!panel.venue?.mapUrl}
>{panel.venue?.name}</span
>
<span class="block text-on-surface-variant" data-program-event-venue-address
>{venueAddress}</span
>
</span>
</p>
</div>
</div>
<div data-program-event-action-group hidden={!panel.action}>
<a
href={panel.action?.href}
class="group flex items-center justify-between gap-4 bg-slab px-6 py-4 font-semibold text-on-slab no-underline transition-colors hover:bg-primary hover:text-on-primary focus-visible:bg-primary focus-visible:text-on-primary md:px-8"
data-program-event-action
>
<span data-program-event-action-label>{panel.action?.label}</span>
<Icon
name={panel.action?.external ? 'mdi:open-in-new' : 'mdi:arrow-right'}
class="arrow size-6 shrink-0 transition-transform"
aria-hidden="true"
/>
</a>
</div>
</div>
<script is:inline type="application/json" data-program-events set:html={payload} />
</aside>

<script>
import '../../scripts/program-events';
</script>
57 changes: 57 additions & 0 deletions apps/site/src/components/programs/ProgramFeature.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
import { Icon } from 'astro-icon/components';
import type { CollectionEntry } from 'astro:content';
import ProgramEventFeature from './ProgramEventFeature.astro';
import { paths } from '../../lib/paths';
import type { ProgramEvent } from '../../lib/program-events';

interface Props {
program: CollectionEntry<'programs'>;
events?: ProgramEvent[];
next: { title: string; href: string };
}

const { program, events = [], next } = Astro.props;
const { title, icon, summary, image, calendarSeriesUid } = program.data;
---

<section id={program.id} class="program-feature" aria-labelledby={`${program.id}-name`}>
<div class="program-feature-copy">
<header class="program-feature-identity">
<span class="program-feature-icon" aria-hidden="true"><Icon name={icon} /></span>
<h2 id={`${program.id}-name`} class="text-display-sm">{title}</h2>
</header>
<div class="program-feature-body">
<p class="text-body-lg text-on-surface-variant">{summary}</p>
<a class="body-link program-feature-link" href={paths.program(program.id)}>
About {title}
<span aria-hidden="true">→</span>
</a>
</div>
</div>
{
image ? (
<figure class="program-feature-aside program-feature-media">
<img
src={image.src}
alt={image.alt}
width={image.width}
height={image.height}
loading="lazy"
decoding="async"
class:list={{ 'program-feature-tool-image': image.kind === 'screenshot' }}
/>
</figure>
) : calendarSeriesUid ? (
<ProgramEventFeature
program={program.data}
events={events}
headingId={`${program.id}-event-heading`}
/>
) : null
}
<a class="program-feature-onward" href={next.href} data-screen-action>
<span>{next.title}</span>
<Icon name="mdi:arrow-down" class="size-6" aria-hidden="true" />
</a>
</section>
32 changes: 18 additions & 14 deletions apps/site/src/components/site/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const normalizePath = (value: string) => {
};

const path = normalizePath(Astro.url.pathname);
const tabletNavigation = new Set(['/programs', '/events']);
---

{
Expand All @@ -18,9 +19,9 @@ const path = normalizePath(Astro.url.pathname);
<div data-header-sentinel aria-hidden="true" class="h-px"></div>
<header
data-site-header
class="sticky top-0 z-(--z-header) bg-surface/80 border-b border-transparent data-[stuck]:backdrop-blur-md data-[stuck]:backdrop-saturate-150 data-[stuck]:border-outline-variant/15 transition-[border-color] duration-200 ease-out [view-transition-name:site-header]"
class="sticky top-0 h-(--lvbt-header-h) z-(--z-header) bg-surface/80 border-b border-transparent data-[stuck]:backdrop-blur-md data-[stuck]:backdrop-saturate-150 data-[stuck]:border-outline-variant/15 transition-[border-color] duration-200 ease-out [view-transition-name:site-header]"
>
<div class="container-page py-4 flex items-center justify-between gap-6">
<div class="container-page h-full flex items-center justify-between gap-3 lg:gap-6">
{
/* Wordmark sits at text-xl across breakpoints: at 18px (text-lg)
the primary "for Better Transit" reads 3.64:1 on cream, which
Expand All @@ -30,17 +31,20 @@ const path = normalizePath(Astro.url.pathname);
a11y on every paper-canvas page. text-xl (20px) clears it on
every viewport. */
}
<a href="/" class="font-extrabold tracking-tight text-xl no-underline">
<a
href="/"
class="shrink-0 whitespace-nowrap font-extrabold tracking-tight text-xl no-underline"
>
<span class="block leading-none">Las Vegans</span>
<span class="block leading-none text-primary">for Better Transit</span>
</a>
<nav aria-label="Primary" class="hidden md:block">
<ul class="flex items-center gap-7 text-sm font-semibold">
<nav aria-label="Primary" class="hidden md:block shrink-0">
<ul class="flex items-center gap-5 lg:gap-7 whitespace-nowrap text-sm font-semibold">
{
navMain.map((item) => {
const active = path === normalizePath(item.href);
return (
<li>
<li class={tabletNavigation.has(item.href) ? undefined : 'hidden lg:block'}>
{item.href === '/join' ? (
<>
<a
Expand Down Expand Up @@ -83,7 +87,7 @@ const path = normalizePath(Astro.url.pathname);
server. Without JavaScript a public page shows "Sign in", which
sends a signed-in member straight to their account. */
}
<div class="hidden md:flex items-center gap-6">
<div class="hidden lg:flex shrink-0 items-center gap-6 whitespace-nowrap">
<div class="flex items-center gap-5 text-sm font-semibold">
<a
href="/sign-in/"
Expand Down Expand Up @@ -129,8 +133,8 @@ const path = normalizePath(Astro.url.pathname);
</div>
</div>
{/* Mobile controls: Join CTA (hidden when nav open) + hamburger toggle */}
<div class="md:hidden flex items-center gap-3">
<div data-auth="signed-out">
<div class="lg:hidden flex shrink-0 items-center gap-3 whitespace-nowrap">
<div data-auth="signed-out" class="hidden min-[375px]:block">
<a
href="/join"
data-mobile-join
Expand All @@ -153,11 +157,11 @@ const path = normalizePath(Astro.url.pathname);
aria-label="Open menu"
aria-expanded="false"
aria-controls="mobile-nav-overlay"
class="flex flex-col items-center justify-center gap-[5px] p-2 -mr-2 w-10 h-10"
class="flex flex-col items-center justify-center gap-1 p-2 -mr-2 w-10 h-10"
>
<span class="block w-5 h-[1.5px] rounded-[1px] bg-current" aria-hidden="true"></span>
<span class="block w-5 h-[1.5px] rounded-[1px] bg-current" aria-hidden="true"></span>
<span class="block w-5 h-[1.5px] rounded-[1px] bg-current" aria-hidden="true"></span>
<span class="block w-5 h-0.5 bg-current" aria-hidden="true"></span>
<span class="block w-5 h-0.5 bg-current" aria-hidden="true"></span>
<span class="block w-5 h-0.5 bg-current" aria-hidden="true"></span>
</button>
</div>
</div>
Expand All @@ -169,7 +173,7 @@ const path = normalizePath(Astro.url.pathname);
data-nav-overlay
aria-label="Mobile navigation"
inert
class="md:hidden fixed left-0 right-0 bottom-0 bg-surface z-(--z-mobile-nav) flex flex-col overflow-y-auto"
class="lg:hidden fixed left-0 right-0 bottom-0 bg-surface z-(--z-mobile-nav) flex flex-col overflow-y-auto"
style="top: var(--lvbt-header-h)"
>
<ul class="flex-1 list-none m-0 px-6 py-2">
Expand Down
Loading
Loading