-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
91 changed files
with
3,466 additions
and
1,429 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
*, *:after, *:before { | ||
box-sizing: border-box | ||
} | ||
html[data-reduced-motion=true] * { | ||
animation: none!important; | ||
transition: none!important; | ||
} | ||
/* Base styles */ | ||
body { | ||
font-family: system-ui, sans-serif; | ||
font-size: 13px; | ||
margin: 0; | ||
|
||
height: 100vh; | ||
width: 100%; | ||
overflow-x: hidden; | ||
|
||
/* Make it feel more like something native */ | ||
user-select: none; | ||
-webkit-user-select: none; | ||
cursor: default; | ||
} | ||
button { | ||
font-family: system-ui, sans-serif; | ||
font-size: 13px; | ||
} | ||
ul { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
li { | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
} |
199 changes: 199 additions & 0 deletions
199
packages/special-pages/pages/duckplayer/app/components/App.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { h, Fragment } from "preact"; | ||
import cn from "classnames"; | ||
import styles from "./App.module.css"; | ||
import { Background } from "./Background.jsx"; | ||
import { InfoBar, InfoBarContainer } from "./InfoBar.jsx"; | ||
import { PlayerContainer, PlayerInternal } from "./PlayerContainer.jsx"; | ||
import { Player, PlayerError } from "./Player.jsx"; | ||
import { | ||
useLayout, | ||
useOpenInfoHandler, | ||
useOpenOnYoutubeHandler, | ||
useOpenSettingsHandler, usePlatformName, useSettings | ||
} from "../providers/SettingsProvider.jsx"; | ||
import { SwitchBarMobile } from "./SwitchBarMobile.jsx"; | ||
import { Button, Icon } from "./Button.jsx"; | ||
import info from "../img/info.data.svg"; | ||
import cog from "../img/cog.data.svg"; | ||
import { BottomNavBar, FloatingBar, TopBar } from "./FloatingBar.jsx"; | ||
import { Wordmark } from "./Wordmark.jsx"; | ||
import { SwitchProvider } from "../providers/SwitchProvider.jsx"; | ||
import { useOrientation } from "../providers/OrientationProvider.jsx"; | ||
import { createAppFeaturesFrom } from "../features/app.js"; | ||
import { useTypedTranslation } from "../types.js"; | ||
import { HideInFocusMode } from "./FocusMode.jsx"; | ||
|
||
|
||
/** | ||
* @param {object} props | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
*/ | ||
export function App({ embed }) { | ||
const layout = useLayout(); | ||
const orientation = useOrientation(); | ||
const settings = useSettings(); | ||
const features = createAppFeaturesFrom(settings) | ||
return ( | ||
<> | ||
<Background /> | ||
{features.focusMode()} | ||
<main class={styles.app}> | ||
{layout === 'desktop' && <DesktopLayout embed={embed} />} | ||
{layout === 'mobile' && <MobileLayout embed={embed} orientation={orientation} />} | ||
</main> | ||
</> | ||
) | ||
} | ||
|
||
/** | ||
* @param {object} props | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
*/ | ||
function DesktopLayout({embed}) { | ||
return ( | ||
<div class={styles.desktop}> | ||
<PlayerContainer> | ||
{embed === null && <PlayerError layout={'desktop'} kind={'invalid-id'} />} | ||
{embed !== null && <Player src={embed.toEmbedUrl()} layout={'desktop'} />} | ||
<HideInFocusMode style={"slide"}> | ||
<InfoBarContainer> | ||
<InfoBar embed={embed}/> | ||
</InfoBarContainer> | ||
</HideInFocusMode> | ||
</PlayerContainer> | ||
</div> | ||
) | ||
} | ||
|
||
/** | ||
* @param {object} props | ||
* @param {ReturnType<useOrientation>} props.orientation | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
*/ | ||
function MobileLayout({orientation, embed}) { | ||
const platformName = usePlatformName(); | ||
const insetPlayer = orientation === "portrait"; | ||
const classes = cn({ | ||
[styles.portrait]: orientation === "portrait", | ||
[styles.landscape]: orientation === "landscape" | ||
}); | ||
return ( | ||
<div class={classes}> | ||
{orientation === "portrait" && ( | ||
<div class={styles.header}> | ||
<HideInFocusMode> | ||
<TopBar> | ||
<Wordmark /> | ||
</TopBar> | ||
</HideInFocusMode> | ||
</div> | ||
)} | ||
<div class={styles.wrapper}> | ||
<div class={styles.main}> | ||
<PlayerContainer inset={insetPlayer}> | ||
<PlayerInternal inset={insetPlayer}> | ||
{embed === null && <PlayerError layout={'mobile'} kind={'invalid-id'}/>} | ||
{embed !== null && <Player src={embed.toEmbedUrl()} layout={'mobile'}/>} | ||
{orientation === "portrait" && ( | ||
<SwitchProvider> | ||
<SwitchBarMobile platformName={platformName} /> | ||
</SwitchProvider> | ||
)} | ||
</PlayerInternal> | ||
</PlayerContainer> | ||
</div> | ||
{orientation === "landscape" && <LandscapeControls embed={embed} platformName={platformName}/>} | ||
{orientation === "portrait" && <PortraitControls embed={embed} />} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
/** | ||
* How the controls are rendered in Portrait mode. | ||
* @param {object} props | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
*/ | ||
function PortraitControls({embed}) { | ||
return ( | ||
<div className={styles.controls}> | ||
<HideInFocusMode> | ||
<BottomNavBar> | ||
<FloatingBar inset> | ||
<MobileFooter embed={embed}/> | ||
</FloatingBar> | ||
</BottomNavBar> | ||
</HideInFocusMode> | ||
</div> | ||
) | ||
} | ||
|
||
/** | ||
* How the controls are rendered in Landscape mode | ||
* @param {object} props | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
* @param {ImportMeta['platform']} props.platformName - The name of the platform. | ||
*/ | ||
function LandscapeControls({embed, platformName}) { | ||
return ( | ||
<div className={styles.rhs}> | ||
<div className={styles.header}> | ||
<HideInFocusMode> | ||
<TopBar> | ||
<Wordmark /> | ||
</TopBar> | ||
</HideInFocusMode> | ||
</div> | ||
<div className={styles.controls}> | ||
<HideInFocusMode> | ||
<FloatingBar> | ||
<MobileFooter embed={embed}/> | ||
</FloatingBar> | ||
</HideInFocusMode> | ||
</div> | ||
<div className={styles.switch}> | ||
<SwitchProvider> | ||
<SwitchBarMobile platformName={platformName}/> | ||
</SwitchProvider> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
/** | ||
* @param {object} props | ||
* @param {import("../embed-settings.js").EmbedSettings|null} props.embed | ||
*/ | ||
function MobileFooter({embed}) { | ||
const openSettings = useOpenSettingsHandler(); | ||
const openInfo = useOpenInfoHandler(); | ||
const openOnYoutube = useOpenOnYoutubeHandler(); | ||
const {t} = useTypedTranslation(); | ||
return ( | ||
<> | ||
<Button | ||
icon={true} | ||
buttonProps={{ | ||
"aria-label": t('openInfoButton'), | ||
onClick: openInfo | ||
}} | ||
><Icon src={info}/></Button> | ||
<Button | ||
icon={true} | ||
buttonProps={{ | ||
"aria-label": t('openSettingsButton'), | ||
onClick: openSettings | ||
}} | ||
><Icon src={cog}/></Button> | ||
<Button fill={true} | ||
buttonProps={{ | ||
onClick: () => { | ||
if (embed) openOnYoutube(embed) | ||
} | ||
}} | ||
>{t('watchOnYoutube')}</Button> | ||
</> | ||
) | ||
} | ||
|
||
|
141 changes: 141 additions & 0 deletions
141
packages/special-pages/pages/duckplayer/app/components/App.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
:root { | ||
/* Set video to take up 80vw width */ | ||
--video-width: 80vw; | ||
} | ||
|
||
@media screen and (max-width: 1080px) { | ||
:root { | ||
--video-width: 85vw; | ||
} | ||
} | ||
|
||
@media screen and (max-width: 740px) { | ||
:root { | ||
--video-width: 90vw; | ||
} | ||
} | ||
|
||
:root [data-layout="desktop"] { | ||
--frame-height: min( | ||
calc(var(--video-width) * calc(9 / 16)), | ||
80vh | ||
) | ||
} | ||
:root [data-layout="mobile"][data-orientation="portrait"] { | ||
--video-width: calc(100vw - 32px) | ||
} | ||
:root [data-layout="mobile"][data-orientation="landscape"] { | ||
--video-width: calc(calc(100vw - 32px) * 0.6) /* 60% of the container */ | ||
} | ||
@media screen and (max-width: 700px) { | ||
:root [data-layout="mobile"][data-orientation="landscape"] { | ||
--video-width: calc(calc(100vw - 32px) * 0.5) /* 60% of the container */ | ||
} | ||
} | ||
|
||
:root [data-layout="mobile"] { | ||
--frame-height: min( | ||
calc(var(--video-width) * calc(9 / 16)), | ||
calc(100vh - 32px) | ||
) | ||
} | ||
|
||
.app { | ||
margin: 0 auto; | ||
position: relative; | ||
z-index: 1; | ||
height: 100%; | ||
width: 100%; | ||
max-width: 3840px; | ||
color: rgba(255, 255, 255, 0.85); | ||
} | ||
|
||
.portrait { | ||
height: 100%; | ||
display: grid; | ||
align-self: center; | ||
grid-template-areas: | ||
'header' | ||
'main'; | ||
grid-template-rows: max-content 1fr; | ||
} | ||
|
||
.landscape { | ||
height: 100%; | ||
display: grid; | ||
align-self: center; | ||
align-items: center; | ||
align-content: center; | ||
} | ||
|
||
.wrapper {} | ||
|
||
.portrait .wrapper { | ||
grid-area: main; | ||
display: grid; | ||
grid-template-areas: | ||
'main' | ||
'controls'; | ||
grid-template-rows: auto max-content; | ||
} | ||
|
||
.landscape .wrapper { | ||
display: grid; | ||
grid-template-columns: 60% 1fr; | ||
grid-column-gap: 8px; | ||
background: rgba(0, 0, 0, 0.3); | ||
border-radius: 16px; | ||
padding: 8px; | ||
@media screen and (max-width: 700px) { | ||
grid-template-columns: 50% 1fr; | ||
} | ||
} | ||
|
||
.desktop { | ||
height: 100%; | ||
width: var(--video-width); | ||
margin: 0 auto; | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: center; | ||
} | ||
|
||
.rhs { | ||
display: grid; | ||
height: 100%; | ||
grid-template-areas: 'header' 'controls' 'switch'; | ||
grid-template-rows: max-content max-content auto; | ||
grid-template-columns: 1fr; | ||
grid-row-gap: 12px; | ||
} | ||
|
||
/* When the RHS has a checked checkbox, we can center the other content */ | ||
.rhs:has([data-state=completed] [aria-checked="true"]) { | ||
align-content: center; | ||
} | ||
|
||
.header { | ||
grid-area: header; | ||
padding-top: 48px; | ||
@media screen and (max-height: 500px) { | ||
padding-top: 32px; | ||
} | ||
} | ||
|
||
.main { | ||
align-self: center; | ||
} | ||
|
||
.controls { | ||
grid-area: controls | ||
} | ||
.switch { | ||
grid-area: switch | ||
} | ||
|
||
.landscape .header { | ||
padding-top: 8px; | ||
} | ||
.landscape .switch { | ||
align-self: end; | ||
} |
Oops, something went wrong.