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
39 changes: 38 additions & 1 deletion src/lib/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
| 'instagram'
| 'threads'
| 'thumbtack'
| 'youtube';
| 'youtube'
| 'headphones'
| 'video';
</script>

<script lang="ts">
Expand Down Expand Up @@ -369,6 +371,41 @@
/></svg
>
{/if}
{#if name === 'headphones'}
<svg
style="width: var(--icon_size, 16px);"
viewBox="0 0 24 24"
aria-hidden={aria_hidden}
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<title>{title}</title>
<path
d="M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 18 0v7a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3"
/>
</svg>
{/if}
{#if name === 'video'}
<svg
style="width: var(--icon_size, 16px);"
viewBox="0 0 24 24"
aria-hidden={aria_hidden}
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<title>{title}</title>
<path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" />
<rect x="2" y="6" width="14" height="12" rx="2" />
</svg>
{/if}
{#if name === 'facebook'}
<svg xmlns="http://www.w3.org/2000/svg" style="width: var(--icon_size, 16px);" viewBox="0 0 24 24"
><path
Expand Down
113 changes: 98 additions & 15 deletions src/lib/ShowCard.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';

import { goto } from '$app/navigation';
import { player } from '$state/player';
import { format_show_type } from '$utilities/format_show_type';
import get_show_path from '$utilities/slug';
import { get_youtube_id } from '$utilities/youtube';
import { format } from 'date-fns';
import FacePile from './FacePile.svelte';
import Icon from './Icon.svelte';
import Badge from './badges/Badge.svelte';
import Badges from './badges/Badges.svelte';
import YoutubeEmbed from './videos/YoutubeEmbed.svelte';
import type { ShowCard } from '$/server/shows/shows_queries';

interface Props {
Expand All @@ -18,24 +20,21 @@
show_date?: any;
}

let {
show,
display = 'card',
heading = 'h4',
show_date = new Date(show.date)
}: Props = $props();
let { show, display = 'card', heading = 'h4', show_date = new Date(show.date) }: Props = $props();

let has_youtube = $derived(Boolean(get_youtube_id(show.youtube_url)));

function format_date(date: Date, baseDate: Date = new Date()) {
const timeFormatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const time_formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const diff = date.getTime() - baseDate.getTime();
const days = (diff / (1000 * 60 * 60 * 24)) * -1;
switch (true) {
case days < 1:
return timeFormatter.format(-Math.round(days * 24), 'hour');
return time_formatter.format(-Math.round(days * 24), 'hour');
case days < 12:
return timeFormatter.format(-Math.floor(days), 'day');
return time_formatter.format(-Math.floor(days), 'day');
case days < 30:
return timeFormatter.format(-Math.floor(days / 7), 'week');
return time_formatter.format(-Math.floor(days / 7), 'week');
default:
return format(date, 'MMMM do, yyyy');
}
Expand All @@ -59,9 +58,30 @@
{ name: 'Scott Tolinski', github: 'stolinski' }
];
}

// Video cards have space outside the main link (next to the video), make it clickable too.
// Keyboard and screen reader users already have the link.
function handle_card_click(e: MouseEvent) {
if (!(e.target instanceof Element)) return;
if (e.target.closest('a, button, .youtube-embed-wrap')) return;
// Don't navigate away when someone is selecting text
if (window.getSelection()?.toString()) return;
const path = get_show_path(show);
if (e.metaKey || e.ctrlKey || e.shiftKey) {
window.open(path, '_blank', 'noopener');
return;
}
goto(path);
}
</script>

<article class={display}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<article
class={display}
class:has-youtube={has_youtube}
onclick={has_youtube ? handle_card_click : undefined}
>
<a
href={get_show_path(show)}
aria-label="Show #{show.number} posted {format_date(show_date)}, {show.title}"
Expand All @@ -76,9 +96,11 @@
<Icon name="play" />
</button>
{/if}
<span style:--transition-name="show-date-{show.number}" class="show-number fst-900 grit"
>{show.number}</span
>
{#if !has_youtube}
<span style:--transition-name="show-date-{show.number}" class="show-number fst-900 grit"
>{show.number}</span
>
{/if}

<div class="details">
<p class="date" style:--transition-name="show-date-{show.number}">
Expand Down Expand Up @@ -143,6 +165,15 @@
</div>
</div>
</a>

{#if has_youtube}
<div class="video-column">
<span style:--transition-name="show-date-{show.number}" class="show-number-small fst-900 grit"
>#{show.number}</span
>
<YoutubeEmbed {show} />
</div>
{/if}
</article>

<style lang="postcss">
Expand All @@ -156,18 +187,30 @@
position: relative;
overflow: hidden;
align-items: start;

&.has-youtube {
gap: 0.75rem;
cursor: pointer;
@media (--above-med) {
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
}

& a {
color: var(--fg);
display: flex;
gap: 10px;
height: 100%;
min-width: 0;
}

.details {
display: grid;
flex-grow: 1;
grid-template-rows: auto auto 1fr auto auto;
gap: 1rem;
min-width: 0;
& > * {
margin: 0;
position: relative;
Expand Down Expand Up @@ -201,6 +244,11 @@
margin-bottom: 20px;
padding: 20px 0;
margin-inline: auto;

&.has-youtube {
padding: 20px;
width: 100%;
}
}

.description {
Expand All @@ -219,6 +267,15 @@
need a new row to become 100% height -- the show title */
grid-template-rows: auto 1fr auto auto;
}
&.has-youtube .details {
/* the video sits below on mobile, so no row needs to stretch */
grid-template-rows: unset;
gap: 0.75rem;
}
/* leave room for the absolutely positioned show number */
&.has-youtube .date {
padding-right: calc(var(--font-size-lg) * 3.5);
}
.description {
display: none;
mask-image: none;
Expand All @@ -239,6 +296,32 @@
}
}

.video-column {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0;
width: 100%;
min-width: 0;

@media (--above-med) {
gap: 0.5rem;
}
}

.show-number-small {
font-size: var(--font-size-lg);
color: var(--primary);
line-height: 1;
text-align: right;

@media (--below-med) {
position: absolute;
top: 10px;
right: 10px;
}
}

.h3 {
view-transition-name: var(--transition-name);
margin: 0;
Expand Down
21 changes: 20 additions & 1 deletion src/lib/player/Player.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { player } from '$state/player';
import { player_window_status } from '$state/player_window_status';
import { get_youtube_id } from '$utilities/youtube';
import AlbumArt from './AlbumArt.svelte';
import get_show_path from '$utilities/slug';
import Icon from '../Icon.svelte';
Expand All @@ -20,10 +21,27 @@
});

let mix_max_verb = $derived($player_window_status === 'MINI' ? 'Maximize' : 'Minimize');
let has_video = $derived(Boolean(get_youtube_id($player.current_show?.youtube_url)));
let is_video = $derived($player.media_kind === 'VIDEO');
let media_kind_label = $derived(is_video ? 'Switch to Audio' : 'Switch to Video');
let player_height = $state(0);

// Lets the YouTube mini-player sit on top of the player in both its full and minimized sizes
$effect(() => {
document.documentElement.style.setProperty('--player-height', `${player_height}px`);
});
</script>

<section class="player {$player_window_status} {$player.status}">
<section class="player {$player_window_status} {$player.status}" bind:offsetHeight={player_height}>
<div class="window-controls">
{#if has_video}
<button
onclick={() => player.set_media_kind(is_video ? 'AUDIO' : 'VIDEO')}
aria-label={media_kind_label}
title={media_kind_label}
><Icon name={is_video ? 'headphones' : 'video'} title={media_kind_label} /></button
>
{/if}
{#if $player.current_show}<ShareButton show={$player.current_show} />{/if}
<button
class="minimize"
Expand Down Expand Up @@ -58,6 +76,7 @@
<audio
ontimeupdate={player.ontimeupdate}
onplay={player.onplay}
ondurationchange={player.ondurationchange}
onended={player.onended}
onpause={player.onpause}
slot="media"
Expand Down
Loading
Loading