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
31 changes: 11 additions & 20 deletions src/components/MeetupsSection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const statusConfig = {
<div class="meetup-cover">
{event.coverUrl && <img class="meetup-cover-image" src={event.coverUrl} alt="" loading="lazy" decoding="async" />}
<span class={`meetup-status-badge status-${event.status}`}>{cfg.label}</span>
<span class="meetup-photo-badge">{event.format.replaceAll('_', ' ')}</span>
</div>
</a>
<div class="meetup-body">
Expand Down Expand Up @@ -138,10 +137,7 @@ const statusConfig = {
const statusBadge = document.createElement('span');
statusBadge.className = `meetup-status-badge status-${status}`;
statusBadge.textContent = config.label;
const typeBadge = document.createElement('span');
typeBadge.className = 'meetup-photo-badge';
typeBadge.textContent = event.format.replaceAll('_', ' ');
cover.append(statusBadge, typeBadge);
cover.append(statusBadge);
coverLink.append(cover);

const body = document.createElement('div');
Expand Down Expand Up @@ -260,20 +256,20 @@ const statusConfig = {
align-items: flex-end;
justify-content: space-between;
padding: var(--s3);
transition: opacity 200ms ease;
}
.meetup-cover-image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
.meetup-status-badge,
.meetup-photo-badge {
position: relative;
.meetup-status-badge { position: relative; }
@media (hover: hover) and (pointer: fine) {
.meetup-card:hover { transform: none; }
.meetup-card:hover .meetup-cover-image { opacity: 0.92; }
}
.meetup-card:hover .meetup-cover { opacity: 0.9; }

.meetup-status-badge {
font-size: var(--text-xs);
Expand All @@ -286,15 +282,6 @@ const statusConfig = {
.status-live { background: var(--pink); color: #fff; }
.status-past { background: var(--bg-white); color: var(--text-muted); }

.meetup-photo-badge {
font-size: var(--text-xs);
font-weight: 700;
background: rgba(0,0,0,0.6);
color: #fff;
border-radius: var(--r-sm);
padding: 2px var(--s2);
}

.meetup-body {
padding: var(--s5);
display: flex;
Expand Down Expand Up @@ -348,5 +335,9 @@ const statusConfig = {
flex: 1;
}

.meetup-cta { align-self: flex-start; margin-top: var(--s2); }
.meetup-cta { align-self: flex-end; margin-top: auto; }

@media (prefers-reduced-motion: reduce) {
.meetup-cover-image { transition: none; }
}
</style>
138 changes: 138 additions & 0 deletions src/layouts/Base.astro
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,144 @@ const site = siteEntries[0]!.data;
box-shadow: var(--shadow-solid);
}

/* Runtime event cards are created after Astro's scoped styles run. Keep this
compatibility layer narrow so only the live homepage/list card surfaces
opt into the same geometry as their server-rendered counterparts. */
[data-home-events-grid] > .meetup-card {
width: calc((min(var(--max-w), 100vw) - 2 * var(--pad) - 2 * var(--s5)) / 3);
flex-shrink: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
[data-home-events-grid] > .meetup-card .meetup-cover-link,
#events-list-view .event-card .event-cover-link { display: block; }
[data-home-events-grid] > .meetup-card .meetup-cover,
#events-list-view .event-card .event-cover {
width: 100%;
aspect-ratio: 16 / 9;
position: relative;
display: flex;
align-items: flex-end;
justify-content: space-between;
padding: var(--s3);
background-color: var(--border-light);
}
[data-home-events-grid] > .meetup-card .meetup-cover-image,
#events-list-view .event-card .event-cover-image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
[data-home-events-grid] > .meetup-card .meetup-status-badge,
#events-list-view .event-card .event-status-badge { position: relative; }
[data-home-events-grid] > .meetup-card .meetup-status-badge,
#events-list-view .event-card .event-status-badge {
padding: 2px var(--s2);
border: 2px solid var(--black);
border-radius: var(--r-sm);
font-size: var(--text-xs);
font-weight: 700;
}
[data-home-events-grid] > .meetup-card .status-upcoming,
#events-list-view .event-card .status-upcoming { background: var(--yellow); color: var(--black); }
[data-home-events-grid] > .meetup-card .status-live,
#events-list-view .event-card .status-live { background: var(--pink); color: #fff; }
[data-home-events-grid] > .meetup-card .status-past,
#events-list-view .event-card .status-past { background: var(--bg-white); color: var(--text-muted); }
[data-home-events-grid] > .meetup-card .meetup-body,
#events-list-view .event-card .event-body {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: var(--s3);
padding: var(--s5);
border-top: 2px solid var(--black);
}
[data-home-events-grid] > .meetup-card .meetup-meta,
#events-list-view .event-card .event-meta {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
gap: var(--s2);
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-muted);
letter-spacing: 0.04em;
text-transform: uppercase;
}
[data-home-events-grid] > .meetup-card .meetup-location,
#events-list-view .event-card .event-location {
min-width: 0;
max-width: 100%;
flex: 1 1 0;
overflow: hidden;
color: var(--pink);
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-home-events-grid] > .meetup-card .meetup-meta time,
#events-list-view .event-card .event-meta time {
flex: 0 0 auto;
white-space: nowrap;
}
[data-home-events-grid] > .meetup-card .meetup-name,
#events-list-view .event-card .event-name {
min-width: 0;
overflow: hidden;
font-size: var(--text-xl);
line-height: var(--leading-snug);
text-overflow: ellipsis;
white-space: nowrap;
}
[data-home-events-grid] > .meetup-card .meetup-desc,
#events-list-view .event-card .event-desc {
display: -webkit-box;
flex: 1;
overflow: hidden;
color: var(--text-mid);
font-size: var(--text-sm);
line-height: var(--leading-body);
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
[data-home-events-grid] > .meetup-card .meetup-cta,
#events-list-view .event-card .event-cta {
align-self: flex-end;
margin-top: auto;
}
#events-list-view .events-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--s5);
}
#events-list-view[hidden],
#events-calendar-view[hidden],
#event-detail-view[hidden] { display: none !important; }

@media (hover: hover) and (pointer: fine) {
[data-home-events-grid] > .meetup-card.card:hover,
#events-list-view .event-card.card:hover { transform: none; }
[data-home-events-grid] > .meetup-card:hover .meetup-cover-image,
#events-list-view .event-card:hover .event-cover-image { opacity: 0.92; }
}

@media (max-width: 900px) {
[data-home-events-grid] > .meetup-card {
width: calc((100vw - 2 * var(--pad) - var(--s5)) / 2);
}
}
@media (max-width: 640px) {
[data-home-events-grid] > .meetup-card { width: 85vw; }
#events-list-view .events-grid { grid-template-columns: 1fr; }
}

/* ─── Divider ────────────────────────────────────────────── */
.divider { border: none; border-top: 2px solid var(--black); }

Expand Down
8 changes: 8 additions & 0 deletions src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ const publicEventSchema = z
venue_name: z.string().trim().min(1).max(500).nullable(),
venue_address: z.string().trim().min(1).max(1_000).nullable(),
online_url: publicHttpUrlSchema.nullable(),
stream_url: publicHttpUrlSchema.nullable().optional(),
embed_stream: z.boolean().optional().default(false),
registration_url: publicWebsiteUrlSchema.nullable(),
organizer_name: z.string().trim().min(1).max(300),
organizer_website: publicHttpUrlSchema.nullable(),
Expand Down Expand Up @@ -140,6 +142,8 @@ export interface WebsiteEvent {
venueName: string | null;
venueAddress: string | null;
onlineUrl: string | null;
streamUrl: string | null;
embedStream: boolean;
registrationUrl: string | null;
organizerName: string;
organizerWebsite: string | null;
Expand Down Expand Up @@ -243,6 +247,8 @@ async function fetchMeetupFallback(): Promise<WebsiteEvent[]> {
venueName: meetup.data.location.name,
venueAddress: meetup.data.location.label ?? null,
onlineUrl: meetup.data.stream_url ?? null,
streamUrl: meetup.data.stream_url ?? null,
embedStream: meetup.data.embed_stream,
registrationUrl: meetup.data.registration_url ?? null,
organizerName: "DevCongress",
organizerWebsite: "https://devcongress.org",
Expand All @@ -268,6 +274,8 @@ function mapPublicEvent(event: PublicEvent): WebsiteEvent {
venueName: event.venue_name,
venueAddress: event.venue_address,
onlineUrl: event.online_url,
streamUrl: event.stream_url ?? null,
embedStream: event.embed_stream,
registrationUrl: resolveOptionalWebsiteUrl(event.registration_url),
organizerName: event.organizer_name,
organizerWebsite: event.organizer_website,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/live-public-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ function normalizePublicEvent(value: unknown): WebsiteEvent | null {
const venueName = nullableString(value.venue_name, 500);
const venueAddress = nullableString(value.venue_address, 1_000);
const onlineUrl = safePublicUrl(value.online_url);
const streamUrl = safePublicUrl(value.stream_url);
const embedStream = value.embed_stream === true;
const registrationUrl = safePublicUrl(value.registration_url);
const organizerWebsite = safePublicUrl(value.organizer_website);
const coverUrl = safePublicUrl(value.cover_url);
Expand All @@ -95,6 +97,8 @@ function normalizePublicEvent(value: unknown): WebsiteEvent | null {
venueName,
venueAddress,
onlineUrl,
streamUrl,
embedStream,
registrationUrl,
organizerName,
organizerWebsite,
Expand Down
Loading
Loading