1
Fork 0
This commit is contained in:
Korbs 2024-04-14 23:56:13 -04:00
commit 23277af203
No known key found for this signature in database
109 changed files with 7171 additions and 0 deletions

View file

@ -0,0 +1,82 @@
---
import { Xmark } from "@iconoir/vue"
const {
Title,
Description,
Closable,
CloseOnclick
} = Astro.props
---
<div class="dialog-backdrop"></div>
<div id={'dialog-' + Title} class="dialog">
<div class="dialog-header">
<div class="dialog-header-title">
<h2>{Title}</h2>
<p>{Description}</p>
</div>
<div class="dialog-header-action">
{Closable ? <button onclick={CloseOnclick}><Xmark/></button> : null}
</div>
</div>
<div class="dialog-content">
<slot/>
</div>
</div>
<style lang="scss">
.dialog-backdrop {
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 6;
backdrop-filter: blur(10px) brightness(0.5) contrast(0.9);
}
.dialog {
display: none;
position: fixed;
z-index: 7;
width: 100%;
top: 25%;
left: 50%;
transform: translate(-50%, -50%);
background: rgb(24 24 24);
border: 2px rgba(255,255,255,0.05) solid;
margin: auto;
max-width: 600px;
border-radius: 10px;
flex-direction: column;
padding-bottom: 24px;
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
.dialog-header-title {
h2 {
font-size: 24px;
margin: 0px;
}
p {
font-size: 14px;
margin: 0px;
color: gray;
}
}
.dialog-header-action button {
background: transparent;
color: white;
border: none;
aspect-ratio: 1;
cursor: pointer;
}
}
.dialog-content {
padding: 0px 24px;
}
}
</style>

View file

@ -0,0 +1,67 @@
---
// Properties
const {
ID,
Title,
Creator,
Views,
UploadDate,
Length
} = Astro.props
// Configuration
import {
DEFAULT_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '../../config.json'
// i18n
import i18next, { t } from "i18next";
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString()
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
const LengthFormat = new Date(Length * 1000).toISOString().slice(14, 19).split(':').map(Number).join(':')
// Format Views
const ViewsConversion = Intl.NumberFormat('en', { notation: 'compact'})
const ViewsFormat = ViewsConversion.format(Views)
---
<a href={'/' + i18next.language + '/watch?v=' + ID} class="music-item">
<img src={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + ID + '/maxresdefault.jpg'}/>
<p id="title">{Title}</p>
<p id="artist">{Creator}</p>
<p id="date">{DateFormat}</p>
<p id="duration">{LengthFormat}</p>
</a>
<style lang="scss">
.music-item {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
#duration, #date {
color: gray;
}
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
img {
width: 60px;
height: 60px;
object-fit: cover;
object-position: center;
border-radius: 10px;
}
}
</style>

View file

@ -0,0 +1,54 @@
---
import { Search } from '@iconoir/vue'
import { t } from 'i18next'
---
<div class="search-bar">
<input type="search" placeholder={t("HEADER.SEARCH")} aria-label="Search for a video"/>
<button onclick="Search()"><Search/></button>
</div>
<script is:inline>
function Search() {
var SearchQuery = document.querySelector('input[type="search"]').value
location.href = `/search?q=${SearchQuery}`
}
</script>
<style lang="scss">
.search-bar {
display: flex;
width: 100%;
input[type="search"] {
background: transparent;
color: white;
border: none;
border-radius: 10px 0px 0px 10px;
padding: 24px;
width: 100%;
transition: 0.3s width;
&:focus {
outline: none;
transition: 0.3s width;
}
}
}
.search-bar button {
display: none;
border-radius: 1rem;
aspect-ratio: 1;
background: rgba(255,255,255,0.1);
color: white;
margin: 8px 0px 0px -31px;
height: 24px;
border: none;
cursor: pointer;
}
.search-bar button svg {
width: 14px;
}
.search-bar[data-astro-cid-otpdt6jm] {
display: flex;
}
</style>

View file

@ -0,0 +1,82 @@
---
// Properties
const {
ID,
Title,
Creator,
Views,
UploadDate,
Length
} = Astro.props
// Configuration
import {
DEFAULT_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '../../config.json'
// i18n
import i18next, { t } from "i18next";
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString()
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
const LengthFormat = new Date(Length * 1000).toISOString().slice(14, 19).split(':').map(Number).join(':')
// Format Views
const ViewsConversion = Intl.NumberFormat('en', { notation: 'compact'})
const ViewsFormat = ViewsConversion.format(Views)
---
<a href={'/' + i18next.language + '/watch?v=' + ID} class="video-item">
<div class="video-item-thumbnail">
<img src={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + ID + '/maxresdefault.jpg'}/>
<p id="vi-length">{LengthFormat}</p>
</div>
<div class="video-item-details">
<p id="vi-title">{Title}</p>
<p id="vi-author">{t("WATCH.BY")} {Creator}</p>
<p id="vi-viewCount">{ViewsFormat} {t("WATCH.VIEWS")} - {DateFormat}</p>
</div>
</a>
<style lang="scss">
.video-item {
display: flex;
flex-direction: column;
color: white;
text-decoration: none;
.video-item-thumbnail {
img {
width: 100%;
border-radius: 6px;
aspect-ratio: 16/9;
object-fit: contain;
}
p#vi-length {
margin: -41px 0px 0px 0px;
background: rgba(0,0,0,0.75);
padding: 12px 6px 6px 16px;
width: max-content;
border-radius: 50px 0px 6px 0px;
position: relative;
float: right;
}
}
.video-item-details {
display: flex;
flex-direction: column;
p {
margin: 0px;
&#vi-title {
font-weight: bold;
}
&#vi-author {
color: darkgrey;
}
}
}
}
</style>

View file

@ -0,0 +1,19 @@
---
// Configuration
import {
DEFAULT_PLAYER
} from '../../../config.json'
---
{
() => {
if (DEFAULT_PLAYER === "Browser") {
return (null)
}
if (DEFAULT_PLAYER === "Zorn") {
return (
<script is:inline src="/scripts/zorn.js"></script>
)
}
}
}

View file

@ -0,0 +1,82 @@
---
const {
Title,
Description,
EmbedId,
EmbedVideo,
EmbedImage,
EmbedTitle,
} = Astro.props
// Configuration
import {
STATISTICS,
ANALYLICS_TOOL,
ANALYLICS_ID,
SCRIPT_SRC,
DOMAIN,
SERVER_DOMAIN
} from '../../../config.json'
import { Donate, Download, ShareIos, ThumbsUp } from '@iconoir/vue'
const SWV = Astro.url.href.split("embed/").pop();
if (Astro.url.href.match('watch')) {
var IsVideo = true
} else if (Astro.url.href.match('embed')) {
var IsVideo = false
} else {
var IsVideo = false
}
---
<head>
<!-- Metadata -->
<title>{Title}</title>
<meta name="description" content={Description}>
<!-- Properties -->
<meta charset="UTF-8">
<meta name="theme-color" content="#111">
<meta name="viewport" content="width=device-1200px, initial-scale=1.0, shrink-to-fit=yes, viewport-fit=cover">
<meta http-equiv="content-language" content="en-us">
<link rel="manifest" href="/manifest.json" />
<script is:inline src="/service-worker.js"></script>
<!-- Favicon -->
<link rel="apple-touch-icon" type="image/png" sizes="256x256" href="/images/logo/Favicon.png">
<link rel="icon" type="image/png" sizes="256x256" href="/images/logo/Favicon.png">
<!-- Analytics -->
{STATISTICS ?
() => {
if (ANALYLICS_TOOL === "Plausible") {
return (
<script defer data-domain={DOMAIN} src={SCRIPT_SRC}></script>
)
}
if (ANALYLICS_TOOL === "Umami") {
return (
<script async src={SCRIPT_SRC} data-website-id={ANALYLICS_ID}></script>
)
}
}
:
null
}
<!-- Video Embed -->
{IsVideo ?
<meta property="og:type" content="video.other">
<meta property="og:video:url" content={SERVER_DOMAIN + '/embed/' + EmbedId}>
<meta property="og:video:type" content="text/html">
<meta property="twitter:player" content={SERVER_DOMAIN + '/embed/' + EmbedId}>
<meta property="og:video" content={EmbedVideo} />
<meta property="og:title" content={EmbedTitle}>
<meta property="og:image" content={EmbedImage}/>
<meta property="og:site_name" content={'MinPluto - Privacy YouTube Frontend'}>
:
null
}
</head>

View file

@ -0,0 +1,32 @@
---
// Components
import Search from '@components/Search.astro'
// Icons
import {
Message,
Youtube
} from '@iconoir/vue'
// i18n
import { t, changeLanguage } from "i18next"
import { LanguageSelector } from "astro-i18next/components"
---
<div class="header">
<div class="header-start">
<img class="mo" style="height: 24px; margin-right: 24px;" src="/images/logo/MinPluto - Logo.png"/>
<Search/>
</div>
<div class="header-end">
<LanguageSelector
showFlag={false}
languageMapping={{
en: "English",
jp: "Japanese",
ru: "Russian"
}}
/>
<a href="https://community.minpluto.org/c/feedback/2" target="_blank"><Message/> {t("HEADER.FEEDBACK")}</a>
</div>
</div>

View file

@ -0,0 +1,10 @@
---
import { GraphUp, HomeSimple, InfoCircle, ViewGrid } from "@iconoir/vue";
---
<div class="mobile-navigation">
<a href="/"><HomeSimple/></a>
<a href="/category/trending"><GraphUp/></a>
<a href="/m/about"><InfoCircle/></a>
</div>

View file

@ -0,0 +1,170 @@
---
// Configuration
import {
SIDEBAR_DISCOVER,
SIDEBAR_CATEGORIES
} from '@root/config.json'
import {
version
} from '@root/package.json'
// Icons
import {
HomeSimple,
GraphUp,
Movie,
MusicDoubleNote,
Gamepad,
AppleImac2021Side,
EmojiTalkingHappy,
PizzaSlice,
Treadmill,
PeaceHand,
InfoCircle,
HelpCircle,
Server
} from '@iconoir/vue'
// i18n
import i18next, { t } from "i18next"
---
<div class="sidebar">
<div class="sidebar-top">
<a href={'/' + i18next.language} class="sidebar-top-start">
<img src="/images/logo/MinPluto - Logo.png"/>
</a>
<div class="sidebar-top-end">
<a href={'/' + i18next.language + '/'}><HomeSimple/> {t("SIDEBAR.HOME")}</a>
<hr/>
{SIDEBAR_CATEGORIES ?
<h2>{t("SIDEBAR.CATEGORY")}</h2>
<a href={'/' + i18next.language + '/category/trending'}><GraphUp/> {t("SIDEBAR.CATEGORY_LIST.TRENDING")}</a>
<a href={'/' + i18next.language + '/category/movies'}><Movie/> {t("SIDEBAR.CATEGORY_LIST.MOVIES")}</a>
<a href={'/' + i18next.language + '/category/music'}><MusicDoubleNote/> {t("SIDEBAR.CATEGORY_LIST.MUSIC")}</a>
<a href={'/' + i18next.language + '/category/gaming'}><Gamepad/> {t("SIDEBAR.CATEGORY_LIST.GAMES")}</a>
:
null
}
{SIDEBAR_DISCOVER ?
<hr/>
<h2>{t("SIDEBAR.DISCOVER")}</h2>
<a href={'/' + i18next.language + '/discover/tech'}><AppleImac2021Side/> {t("SIDEBAR.DISCOVER_LIST.TECH")}</a>
<a id="sidebar-disable" href="#"><EmojiTalkingHappy/> {t("SIDEBAR.DISCOVER_LIST.COMEDY")}</a>
<a id="sidebar-disable" href="#"><PizzaSlice/> {t("SIDEBAR.DISCOVER_LIST.FOOD")}</a>
<a id="sidebar-disable" href="#"><Gamepad/> {t("SIDEBAR.DISCOVER_LIST.GAMES")}</a>
<a id="sidebar-disable" href="#"><Treadmill/> {t("SIDEBAR.DISCOVER_LIST.FITNESS")}</a>
:
null
}
</div>
</div>
<div class="sidebar-bottom">
<div class="sidebar-bottom-start">
<a style="pointer-events: none;"><PeaceHand/> {t("SIDEBAR.FOOTER.ALPHA")}</a>
<a><HelpCircle/> Help</a>
<a><Server/> Instance</a>
</div>
<div class="sidebar-bottom-center">
<p id="version">v{version}</p>
</div>
<div class="sidebar-bottom-end">
<a href="https://space.sudovanilla.com/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://sudovanilla.com/code/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
</div>
<style lang="scss">
.sidebar {
position: fixed;
top: 0px;
left: 0px;
width: 224px;
background: linear-gradient(315deg, #0e0e0e, #161616);
color: white;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0px 12px;
hr {
width: 32%;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
}
h2 {
font-size: 16px;
padding-left: 24px;
}
.sidebar-top {
.sidebar-top-start {
display: flex;
align-items: center;
gap: 24px;
padding: 32px 24px;
img {
height: 24px;
}
}
.sidebar-top-end {
display: flex;
flex-direction: column;
cursor: default;
}
}
.sidebar-bottom {
padding: 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
.sidebar-bottom-center {
background: #232323;
padding: 0px 24px;
margin: 0px -24px 12px -24px;
text-align: center;
}
a {
margin: 0px 4px;
color: white;
text-decoration: none;
font-size: 14px;
&:hover {
text-decoration: underline;
}
}
}
.sidebar-top-end a, .sidebar-bottom-start a {
color: white;
text-decoration: none;
margin-bottom: 6px;
border-radius: 6px;
padding: 8px 24px;
background: transparent;
border: 2px transparent solid;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
&:hover {
background: rgb(255 255 255 / 05%);
border: 2px rgba(255, 255, 255, 0.05) solid;
}
svg {
width: 18px;
}
&#sidebar-disable {
filter: brightness(0.4);
cursor: not-allowed;
&:hover {
background: transparent;
border: 2px transparent solid;
}
}
}
}
</style>

39
src/data/discover.json Normal file
View file

@ -0,0 +1,39 @@
{
"Tech": [
{
"Name": "Linus Tech Tips",
"Id": "UCXuqSBlHAE6Xw-yeJA0Tunw",
"Link": "UCXuqSBlHAE6Xw-yeJA0Tunw",
"Logo": "/ggpht/Vy6KL7EM_apxPSxF0pPy5w_c87YDTOlBQo3MADDF0Wl51kwxmt9wmRotnt2xQXwlrcyO0Xe56w",
"Banner": "/ggpht/oaPQn9KofKbSwSKTW8eGEQTM8KwZ5wZMbo69PPxhgqu3NTNljx7hfJB1XmR4ZTa0VPdpnpIzlQ"
},
{
"Name": "Mac Address",
"Id": "UC0KfjyvabuE2J-RBC6ko2Lw",
"Link": "UC0KfjyvabuE2J-RBC6ko2Lw",
"Logo": "/ggpht/ytc/AIdro_k44TPOZxZNdlBdnnEjlvFs8EP832dq1-bX0ZxG",
"Banner": "/ggpht/u5LNefuGhw400tFMOtaFY0JFUM-RUChETq3TRCa1v_vRhnXtULjELNgyTEgB2_PGUMnNdxr4qjY"
},
{
"Name": "MKBHD",
"Id": "/UCBJycsmduvYEL83R_U4JriQ",
"Link": "/UCBJycsmduvYEL83R_U4JriQ",
"Logo": "/ggpht/lkH37D712tiyphnu0Id0D5MwwQ7IRuwgQLVD05iMXlDWO-kDHut3uI4MgIEAQ9StK0qOST7fiA",
"Banner": "/ggpht/KhLiJiH9xE8AiPUz9fLD9rOQYNH_EVhVuaKyAu2hp8alAhtkV4IQ9-Dfsoge8E11Im0nxDgjsQ"
},
{
"Name": "The Linux Experiment",
"Id": "UC5UAwBUum7CPN5buc-_N1Fw",
"Link": "UC5UAwBUum7CPN5buc-_N1Fw",
"Logo": "/ggpht/ytc/AIdro_kHP15da41K6sBItk2eRhwK31vp_UO6a4DEzH0EFw",
"Banner": "/ggpht/Lc1dH-nj6nvU63AH85SPgoLd2oba4zA41zNbZSWg81PN_zl1mgC_eiL3DbJJPb7b_nS7FarUTYE"
},
{
"Name": "NetworkChuck",
"Id": "UC9x0AN7BWHpCDHSm9NiJFJQ",
"Link": "UC9x0AN7BWHpCDHSm9NiJFJQ",
"Logo": "/ggpht/ytc/AIdro_lUWCdtX-DVvLxWMK-gY-p24JNKmrCtKX_s404uMg",
"Banner": "/ggpht/Lc1dH-6TsuYML3OSXSVVFkRGfPfq1qhEiG0tkKj-UV6XPfifmDDAuV8QTF4wna5maptTBMhikcjaAWcg"
}
]
}

1
src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

View file

@ -0,0 +1,92 @@
---
// Properties
const {
FetchData,
CategoryName,
CategoryDescription,
GradientHero
} = Astro.props
// Use on top of Default Layout
import Base from '@layouts/Default.astro'
import Video from '@components/VideoItem.astro'
// Configuration
import {
DEFAULT_DATA_PROXY,
DEFAULT_VIDEO_PROXY
} from '../../config.json'
// Fetch
const fetchFrom = DEFAULT_DATA_PROXY + '/api/v1/trending' + FetchData
const response = await fetch(fetchFrom)
const data = await response.json()
const heroItem = data.slice(0,1)
---
<Base Title={CategoryName + " - MinPluto"} Description={CategoryDescription}>
<div style={'background: linear-gradient(180deg, ' + GradientHero + ', transparent);'} class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>{CategoryName}</h2>
<p>{CategoryDescription}</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="video-grid">
{data.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Base>
<style define:vars={{GradientHero}} lang="scss">
.category-hero {
margin-top: -80px;
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
h2 {
font-size: 44px;
margin: 0px;
}
.c-hero-video {
-webkit-mask-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
aspect-ratio: 16/9;
video {
pointer-events: none;
object-fit: none;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-image: linear-gradient(270deg, black, transparent);
}
}
}
}
</style>

24
src/layouts/Default.astro Normal file
View file

@ -0,0 +1,24 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
import Header from '@components/global/Header.astro'
import MobileNav from '@components/global/MobileNav.astro'
import Sidebar from '@components/global/Sidebar.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
---
<Head Title={Title} Description={Description}/>
<MobileNav/>
<Sidebar/>
<div class="content">
<Header/>
<slot/>
</div>
<Footer/>

29
src/layouts/Embed.astro Normal file
View file

@ -0,0 +1,29 @@
---
// Properties
const {
Title,
Description,
EmbedId,
EmbedVideo,
EmbedImage,
EmbedTitle
} = Astro.props
// Components
import Head from '@components/global/Head.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/embed.scss'
---
<Head
Title={Title}
Description={Description}
EmbedId={EmbedId}
EmbedVideo={EmbedVideo}
EmbedImage={EmbedImage}
EmbedTitle={EmbedTitle}
/>
<slot/>
<Footer/>

View file

@ -0,0 +1,35 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
import Header from '@components/global/Header.astro'
import MobileNav from '@components/global/MobileNav.astro'
import Sidebar from '@components/global/Sidebar.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
---
<Head Title={Title} Description={Description}/>
<MobileNav/>
<Sidebar/>
<div class="markdown">
<slot/>
</div>
<Footer/>
<style lang="scss">
.markdown {
background: transparent;
color: white;
max-width: 700px;
margin: auto;
margin-top: 32px;
margin-bottom: 32px;
padding: 6px 24px;
}
</style>

20
src/pages/404.astro Normal file
View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>Error 404</h2>
<p>Page not found.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#269753" FetchData="?type=gaming" CategoryName={t("SIDEBAR.CATEGORY_LIST.GAMES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.GAMES_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/gaming"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,10 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#ff4f4f" FetchData="?type=movies" CategoryName={t("SIDEBAR.CATEGORY_LIST.MOVIES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.MOVIES_DESCRIPTION")}>
</Category>
<style is:inline>a[href="/category/movies"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,112 @@
---
import { t, changeLanguage } from "i18next";
import Default from "@layouts/Default.astro";
changeLanguage("en");
// Configuration
import {
DEFAULT_DATA_PROXY,
DEFAULT_VIDEO_PROXY
} from '../../../config.json'
// Components
import MusicItem from '@components/MusicItem.astro'
// Fetch
const fetchFrom = DEFAULT_DATA_PROXY + '/api/v1/trending?type=music'
const response = await fetch(fetchFrom)
const data = await response.json()
const heroItem = data.slice(0,1)
---
<Default FetchData="?type=movies" Title={t("SIDEBAR.CATEGORY_LIST.MUSIC")} Description={t("SIDEBAR.CATEGORY_LIST.MUSIC_DESCRIPTION")}>
<div class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>Music</h2>
<p>Listen to the latest hits</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="or-mu">
<span></span>
<p id="title">{t("MUSIC.TITLE")}</p>
<p id="artist">{t("MUSIC.ARTIST")}</p>
<p id="date">{t("MUSIC.UPLOADED")}</p>
<p id="duration">{t("MUSIC.DURATION")}</p>
</div>
<div class="music-list">
{data.map((data) =>
<MusicItem
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Default>
<style is:inline>a[href="/en /category/music"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.category-hero {
margin-top: -80px;
background: linear-gradient(180deg, #502969, transparent);
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
.c-hero-video {
-webkit-mask-box-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-box-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
video {
object-fit: cover;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-box-image: linear-gradient(270deg, black, transparent);
}
}
}
}
.or-mu {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
max-width: 1000px;
margin: auto;
border-bottom: 1px #252525 solid;
margin-bottom: 24px;
position: sticky;
top: 58px;
background: black;
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#ff650b" FetchData="" CategoryName={t("SIDEBAR.CATEGORY_LIST.TRENDING")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.TRENDING_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/trending"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,139 @@
---
import Base from "@layouts/Default.astro";
// i18n
import i18next, { t, changeLanguage } from "i18next";
changeLanguage("en");
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../config.json";
import { BrightStar, Donate, Download, ShareIos, ThumbsUp } from "@iconoir/vue";
// Components
import Video from '@components/VideoItem.astro'
// Fetch
const SWV = Astro.url.href.split("channel/").pop();
const channel = await fetch(DEFAULT_DATA_PROXY + "/api/v1/channels/" + SWV).then((response) => response.json());
const DescriptionFormat = channel.descriptionHtml.replaceAll("\n", " <br/> ");
---
<Base Title="MinPluto" Description="">
<div class="channel-backdrop">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel">
<div class="channel-header">
<div class="channel-banner">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel-meta">
<div>
<img src={channel.authorThumbnails[1].url}/>
<h2>{channel.author}</h2>
<p>{channel.subCountText}</p>
</div>
<div>
<!-- {channel.isFamilyFriendly ? <p id="family-friendly"><BrightStar/> {t("CHANNEL.FAMILY_FRIENDLY")}</p> : null} -->
</div>
</div>
</div>
<div class="channel-content">
<!-- <div class="channel-tabs">
<a href="#">{t("CHANNEL.HOME")}</a>
<a href="#">{t("CHANNEL.VIDEOS")}</a>
<a href="#">{t("CHANNEL.COMMUNITY")}</a>
</div> -->
<div class="channel-tab-content">
<h2>{t("CHANNEL.ABOUT")} {channel.author}</h2>
<p><Fragment set:html={DescriptionFormat}/></p>
<hr/>
<h2>{t("CHANNEL.LATEST")}</h2>
<div class="video-grid">
{channel.latestVideos.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
<!-- <a>View All Latest Videos</a> -->
</div>
</div>
</div>
</div>
</Base>
<style lang="scss">
.channel-backdrop img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: max-content;
filter: blur(30px) contrast(0.8);
z-index: -1;
transform: scale(1.4);
opacity: 0.3;
display: none;
}
.channel {
max-width: 1000px;
margin: auto;
.channel-header {
.channel-banner {
margin: 12px 0px;
img {
width: 100%;
border-radius: 10px;
}
}
.channel-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
#family-friendly {
color: white;
background: #3b003b;
border: 1px #a500a5 solid;
border-radius: 3rem;
display: flex;
align-items: center;
padding: 6px 12px;
gap: 12px;
pointer-events: none;
}
div:nth-child(1) {
display: flex;
flex-direction: row;
gap: 12px;
img {
border-radius: 3rem;
width: 64px;
height: 64px;
}
}
}
}
.channel-content {
.channel-tabs {
padding-top: 24px;
padding-bottom: 12px;
cursor: default;
a {
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 24px;
&:hover {
background: rgba(41, 41, 41, 0.5);
}
}
}
}
}
</style>

View file

@ -0,0 +1,58 @@
---
import { changeLanguage } from "i18next";
import Discover from "@layouts/Discover.astro";
// Properties
const { FetchData, CategoryName, CategoryDescription } = Astro.props;
// Use on top of Default Layout
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
// Discover Data
import Discover from "../../data/discover.json";
changeLanguage("en");
---
<Base>
<div class="video-grid">
{Discover.Tech.map((channel) =>
<a href={'/channel/' + channel.Link} style={"background: url('" + DEFAULT_DATA_PROXY + channel.Banner} class="discovery-channel">
<div class="dc-c">
<img src={DEFAULT_DATA_PROXY + channel.Logo}/>
<p>{channel.Name}</p>
</div>
</a>
)}
</div>
<hr/>
<div style="max-width: 1000px; margin: auto; text-align: center;">
<p>This is a curated list for all MinPluto users.</p>
<p>Is there a channel missing here that you think should be added? You can either <a href="https://sudovanilla.com/code/MinPluto/MinPluto/src/branch/main/src/data/discover.json">edit our list here</a> or you can <a href="https://community.minpluto.org/">submit a request</a>.</p>
</div>
</Base>
<style is:inline>a[href="/discover/tech"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.discovery-channel {
text-decoration: none;
border-radius: 10px;
font-weight: bold;
background-position: center !important;
background-size: cover !important;
.dc-c {
border-radius: 10px;
display: flex;
flex-direction: row;
align-items: center;
text-align: center;
backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
img {
width: 24%;
align-self: center;
border-radius: 10rem;
padding: 30px;
}
}
}
</style>

View file

@ -0,0 +1,93 @@
---
import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../config.json";
// Fetch
const SWV = Astro.url.href.split("embed/").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
changeLanguage("en");
---
<Embed
Title={video.title}
EmbedId={video.videoId}
EmbedVideo={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
EmbedImage={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
EmbedTitle={video.title}
>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
</Embed>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

20
src/pages/en/404.astro Normal file
View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>Error 404</h2>
<p>Page not found.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#269753" FetchData="?type=gaming" CategoryName={t("SIDEBAR.CATEGORY_LIST.GAMES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.GAMES_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/gaming"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,10 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#ff4f4f" FetchData="?type=movies" CategoryName={t("SIDEBAR.CATEGORY_LIST.MOVIES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.MOVIES_DESCRIPTION")}>
</Category>
<style is:inline>a[href="/category/movies"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,106 @@
---
import { t, changeLanguage } from "i18next";
import Default from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_VIDEO_PROXY } from "../../../../config.json";
// Components
import MusicItem from "@components/MusicItem.astro";
changeLanguage("en");
// Fetch
const fetchFrom = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const response = await fetch(fetchFrom);
const data = await response.json();
const heroItem = data.slice(0, 1);
---
<Default FetchData="?type=movies" Title={t("SIDEBAR.CATEGORY_LIST.MUSIC")} Description={t("SIDEBAR.CATEGORY_LIST.MUSIC_DESCRIPTION")}>
<div class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>Music</h2>
<p>Listen to the latest hits</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="or-mu">
<span></span>
<p id="title">{t("MUSIC.TITLE")}</p>
<p id="artist">{t("MUSIC.ARTIST")}</p>
<p id="date">{t("MUSIC.UPLOADED")}</p>
<p id="duration">{t("MUSIC.DURATION")}</p>
</div>
<div class="music-list">
{data.map((data) =>
<MusicItem
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Default>
<style is:inline>a[href="/en /category/music"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.category-hero {
margin-top: -80px;
background: linear-gradient(180deg, #502969, transparent);
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
.c-hero-video {
-webkit-mask-box-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-box-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
video {
object-fit: cover;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-box-image: linear-gradient(270deg, black, transparent);
}
}
}
}
.or-mu {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
max-width: 1000px;
margin: auto;
border-bottom: 1px #252525 solid;
margin-bottom: 24px;
position: sticky;
top: 58px;
background: black;
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("en");
---
<Category GradientHero="#ff650b" FetchData="" CategoryName={t("SIDEBAR.CATEGORY_LIST.TRENDING")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.TRENDING_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/trending"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,137 @@
---
import Base from "@layouts/Default.astro";
// i18n
import i18next, { t, changeLanguage } from "i18next";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
import { BrightStar, Donate, Download, ShareIos, ThumbsUp } from "@iconoir/vue";
// Components
import Video from "@components/VideoItem.astro";
changeLanguage("en");
// Fetch
const SWV = Astro.url.href.split("channel/").pop();
const channel = await fetch(DEFAULT_DATA_PROXY + "/api/v1/channels/" + SWV).then((response) => response.json());
const DescriptionFormat = channel.descriptionHtml.replaceAll("\n", " <br/> ");
---
<Base Title="MinPluto" Description="">
<div class="channel-backdrop">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel">
<div class="channel-header">
<div class="channel-banner">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel-meta">
<div>
<img src={channel.authorThumbnails[1].url}/>
<h2>{channel.author}</h2>
<p>{channel.subCountText}</p>
</div>
<div>
<!-- {channel.isFamilyFriendly ? <p id="family-friendly"><BrightStar/> {t("CHANNEL.FAMILY_FRIENDLY")}</p> : null} -->
</div>
</div>
</div>
<div class="channel-content">
<!-- <div class="channel-tabs">
<a href="#">{t("CHANNEL.HOME")}</a>
<a href="#">{t("CHANNEL.VIDEOS")}</a>
<a href="#">{t("CHANNEL.COMMUNITY")}</a>
</div> -->
<div class="channel-tab-content">
<h2>{t("CHANNEL.ABOUT")} {channel.author}</h2>
<p><Fragment set:html={DescriptionFormat}/></p>
<hr/>
<h2>{t("CHANNEL.LATEST")}</h2>
<div class="video-grid">
{channel.latestVideos.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
<!-- <a>View All Latest Videos</a> -->
</div>
</div>
</div>
</div>
</Base>
<style lang="scss">
.channel-backdrop img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: max-content;
filter: blur(30px) contrast(0.8);
z-index: -1;
transform: scale(1.4);
opacity: 0.3;
display: none;
}
.channel {
max-width: 1000px;
margin: auto;
.channel-header {
.channel-banner {
margin: 12px 0px;
img {
width: 100%;
border-radius: 10px;
}
}
.channel-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
#family-friendly {
color: white;
background: #3b003b;
border: 1px #a500a5 solid;
border-radius: 3rem;
display: flex;
align-items: center;
padding: 6px 12px;
gap: 12px;
pointer-events: none;
}
div:nth-child(1) {
display: flex;
flex-direction: row;
gap: 12px;
img {
border-radius: 3rem;
width: 64px;
height: 64px;
}
}
}
}
.channel-content {
.channel-tabs {
padding-top: 24px;
padding-bottom: 12px;
cursor: default;
a {
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 24px;
&:hover {
background: rgba(41, 41, 41, 0.5);
}
}
}
}
}
</style>

View file

@ -0,0 +1,57 @@
---
import { changeLanguage } from "i18next";
import Discover from "@layouts/Discover.astro";
// Properties
const { FetchData, CategoryName, CategoryDescription } = Astro.props;
// Use on top of Default Layout
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../../config.json";
// Discover Data
import Discover from "../../../data/discover.json";
changeLanguage("en");
---
<Base>
<div class="video-grid">
{Discover.Tech.map((channel) =>
<a href={'/channel/' + channel.Link} style={"background: url('" + DEFAULT_DATA_PROXY + channel.Banner} class="discovery-channel">
<div class="dc-c">
<img src={DEFAULT_DATA_PROXY + channel.Logo}/>
<p>{channel.Name}</p>
</div>
</a>
)}
</div>
<hr/>
<div style="max-width: 1000px; margin: auto; text-align: center;">
<p>This is a curated list for all MinPluto users.</p>
<p>Is there a channel missing here that you think should be added? You can either <a href="https://sudovanilla.com/code/MinPluto/MinPluto/src/branch/main/src/data/discover.json">edit our list here</a> or you can <a href="https://community.minpluto.org/">submit a request</a>.</p>
</div>
</Base>
<style is:inline>a[href="/discover/tech"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.discovery-channel {
text-decoration: none;
border-radius: 10px;
font-weight: bold;
background-position: center !important;
background-size: cover !important;
.dc-c {
border-radius: 10px;
display: flex;
flex-direction: row;
align-items: center;
text-align: center;
backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
img {
width: 24%;
align-self: center;
border-radius: 10rem;
padding: 30px;
}
}
}
</style>

View file

@ -0,0 +1,93 @@
---
import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
changeLanguage("en");
// Fetch
const SWV = Astro.url.href.split("embed/").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
---
<Embed
Title={video.title}
EmbedId={video.videoId}
EmbedVideo={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
EmbedImage={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
EmbedTitle={video.title}
>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
</Embed>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

143
src/pages/en/index.astro Normal file
View file

@ -0,0 +1,143 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("en");
// Fetch
const TrendingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending";
const TrendingResponse = await fetch(TrendingFetch);
const TrendingData = await TrendingResponse.json();
const TrendingSplit = TrendingData.slice(0, 1);
const MoviesFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=movies";
const MoviesResponse = await fetch(MoviesFetch);
const MoviesData = await MoviesResponse.json();
const MoviesSplit = MoviesData.slice(0, 1);
const MusicFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const MusicResponse = await fetch(MusicFetch);
const MusicData = await MusicResponse.json();
const MusicSplit = MusicData.slice(0, 1);
const GamingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=gaming";
const GamingResponse = await fetch(GamingFetch);
const GamingData = await GamingResponse.json();
const GamingSplit = GamingData.slice(0, 1);
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<div class="category-select-grid">
{TrendingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/trending'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<GraphUp viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.TRENDING")}</p>
</div>
</a>
)}
{MoviesSplit.map((data) =>
<a href={'/' + i18next.language + '/category/movies'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Movie viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MOVIES")}</p>
</div>
</a>
)}
{MusicSplit.map((data) =>
<a href={'/' + i18next.language + '/category/music'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<MusicDoubleNote viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MUSIC")}</p>
</div>
</a>
)}
{GamingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/gaming'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Gamepad viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.GAMES")}</p>
</div>
</a>
)}
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.goin-card {
height: 124px;
text-decoration: none;
border-radius: 10px;
background-size: cover !important;
background-position: center !important;
}
.goin-card-content {
display: flex;
align-items: center;
gap: 12px;
padding: 0px 12px;
height: 100%;
place-content: center;
border-radius: 10px;
backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
}
.goin-card-content p {
font-size: 24px;
font-weight: bold;
position: absolute;
left: 0px;
bottom: 0px;
margin: 24px;
}
.goin-card-content svg {
position: absolute;
right: 0px;
bottom: 0px;
width: 50px;
height: 120px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
opacity: 0.3;
}
.goin-card img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.category-select-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
</style>

View file

@ -0,0 +1,75 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { SERVER_ADMIN, ANALYLICS_TOOL, DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, DEFAULT_PLAYER, SERVER_LOCATION, MODIFIED, CUSTOM_SOURCE_CODE, STATISTICS } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<div class="video-grid">
<div class="in">
<h2>Operated By</h2>
<p>{SERVER_ADMIN}</p>
</div>
<div class="in">
<h2>Analytics Software</h2>
<p>
{
STATISTICS
?
<p>{ANALYLICS_TOOL}</p>
:
<p>None</p>
}
</p>
</div>
<div class="in">
<h2>Location</h2>
<p>{SERVER_LOCATION}</p>
</div>
<div class="in">
<h2>Modified</h2>
<p>{MODIFIED ? <p>Yes</p> : <p>No</p>}</p>
</div>
<div class="in">
<h2>Video Proxy</h2>
<a href={DEFAULT_VIDEO_PROXY}>{DEFAULT_VIDEO_PROXY}</a>
</div>
<div class="in">
<h2>Image Proxy</h2>
<a href={DEFAULT_IMAGE_PROXY}>{DEFAULT_IMAGE_PROXY}</a>
</div>
</div>
{MODIFIED ? <p>Modified Source Code <a href={CUSTOM_SOURCE_CODE}>{CUSTOM_SOURCE_CODE}</a></p> : null}
<img src="/images/backgrounds/1.webp"/>
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}

View file

@ -0,0 +1,66 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { version } from "@root/package.json";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="ms">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<h2>MinPluto</h2>
<p>{t("HOME.P1")}</p>
<hr/>
<p style="font-size: 14px;"><i>{t("HOME.P2")}</i></p>
<hr/>
<div style="display: flex; justify-content: space-evenly;">
<a href="https://community.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://sudovanilla.com/code/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
<p id="version">v{version}</p>
<img src="/images/backgrounds/1.webp"/>
</Base>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.ms {
margin-top: 20%;
text-align: center;
padding: 0px 24px;
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
p#version {
position: fixed;
bottom: 88px;
left: 0px;
width: 100%;
background: #232323;
color: white;
padding: 24px 0px;
text-align: center;
}
</style>

View file

@ -0,0 +1,40 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { HomeSimple, GraphUp, Movie, MusicDoubleNote, Gamepad, AppleImac2021Side, EmojiTalkingHappy, PizzaSlice, Treadmill, PeaceHand } from "@iconoir/vue";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div style="padding: 0px 24px;">
<h2>Categories</h2>
<div id="c" style="display: flex;flex-direction: column;gap: 24px;">
<a href="/category/trending"><GraphUp/> {t("CATEGORY_LIST.TRENDING")}</a>
<a href="/category/movies"><Movie/> {t("CATEGORY_LIST.MOVIES")}</a>
<a href="/category/music"><MusicDoubleNote/> {t("CATEGORY_LIST.MUSIC")}</a>
<a href="/category/gaming"><Gamepad/> {t("CATEGORY_LIST.GAMES")}</a>
</div>
</div>
</Base>
<style lang="scss">
#c a {
color: white;
text-decoration: none;
background: linear-gradient(45deg, #636363, #181818);
padding: 48px 24px;
font-size: 24px;
display: flex;
align-items: center;
gap: 24px;
border-radius: 10px;
svg {
position: absolute;
right: 60px;
width: 100px;
height: 70px;
opacity: 0.4;
}
}
</style>

View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>No Internet Connection</h2>
<p>It appears that you are offline, try connecting your device to the internet, and try again.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

35
src/pages/en/search.astro Normal file
View file

@ -0,0 +1,35 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import Video from "@components/VideoItem.astro";
changeLanguage("en");
// Fetch
const SBO = Astro.url.href.split("search?query=").pop();
const response = await fetch(DEFAULT_DATA_PROXY + "/api/v1/search?q=" + SBO)
.catch((error) => {
console.log(error);
});
const data = await response.json();
---
<Base Title='MinPluto Search'>
<div class="page-title">
<h2>Search</h2>
</div>
<div class="video-grid">
{data.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Base>

207
src/pages/en/watch.astro Normal file
View file

@ -0,0 +1,207 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../config.json";
import { Donate, Download, ShareIos, ThumbsUp, MediaVideo } from "@iconoir/vue";
// Components
import Dialog from "@components/Dialog.astro";
import Video from "@components/VideoItem.astro";
changeLanguage("en");
// Fetch
const SWV = Astro.url.href.split("watch?v=").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
const comments = await fetch(DEFAULT_DATA_PROXY + "/api/v1/comments/" + SWV).then((response) => response.json());
const Description = video.description;
const UploadDate = video.published;
const Views = video.viewCount;
const VideoSeconds = video.lengthSeconds;
let DescriptionFormat = Description.replaceAll("\n", " <br/> ");
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString();
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
new Date(VideoSeconds * 1000)
.toISOString()
.slice(14, 19)
.split(":")
.map(Number)
.join(":");
// Format Views
const ViewsConversion = Intl.NumberFormat("en", { notation: "compact" });
let ViewsFormat = ViewsConversion.format(Views);
---
<Base Title={video.title}>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
<div class="video-rea">
<div class="rea-details">
<p style="font-weight: bold; font-size: 24px;">{video.title}</p>
<div class="rea-details-start">
<a style="text-decoration: none;" href={video.authorUrl} class="rea-channel">
<img src={video.authorThumbnails[1].url} />
<div
style="display: flex; flex-direction: column; align-items: left;"
>
<h2 style="margin: 0px; font-weight: bold; font-size: 18px;">{video.author}</h2>
<p style="margin: 0px;" id="subs">
{video.subCountText} Subscribers
</p>
</div>
</a>
<div style="display: flex; flex-direction: row; align-items: left;">
<button onclick="DownloadDialogShow()"><Download /> Download</button>
<button onclick={'location.href = "/embed/' + video.videoId + '/"'}><MediaVideo /> Embed</button>
<button onclick="ShareDialogShow()"><ShareIos /> Share</button>
</div>
</div>
<div class="rea-details-end">
<p id="views">{ViewsFormat} Views - {DateFormat}</p>
<Fragment set:html={DescriptionFormat} />
</div>
</div>
<div style="display: flex; gap: 24px;">
<div class="rea-comments">
<h2>{t("WATCH.COMMENTS")}</h2>
{
comments.comments.map((comment) => (
<div class="comment">
<img
src={
DEFAULT_IMAGE_PROXY + "/" + comment.authorThumbnails[1].url
}
/>
<div>
<p>
<a href={comment.authorUrl}>{comment.author}</a> - 2 Months
Ago
</p>
<p>{comment.contentHtml}</p>
<p>
<ThumbsUp /> {comment.likeCount}
</p>
</div>
</div>
))
}
</div>
<div class="rea-recommendations">
<h2>{t("WATCH.RELATED")}</h2>
{
video.recommendedVideos.map((data) => (
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
))
}
</div>
</div>
</div>
<Dialog
Title="Download"
Description="Choose a download method"
Closable
CloseOnclick="DownloadDialogHide()"
>
<p>Video</p>
<div class="dialog-downloads-list">
<a href={video.formatStreams[1].url} download={video.title + '.mp4'} target="_blank">Download</a>
</div>
<p>Audio Only</p>
<div class="dialog-downloads-list">
<a href={video.adaptiveFormats[1].url} download={video.title + '.mp3'} target="_blank">Download</a>
</div>
</Dialog>
<Dialog
Title="Share"
Description="Choose a share method"
Closable
CloseOnclick="ShareDialogHide()"
>
<div class="dialog-downloads-list">
<a href={'https://mastodonshare.com/?url=' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Mastodon</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Misskey</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Firefish</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Elk</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Lemmy</a>
</div>
</Dialog>
</Base>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

156
src/pages/index.astro Normal file
View file

@ -0,0 +1,156 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from '@components/Dialog.astro'
changeLanguage("en");
// Configuration
import {
DEFAULT_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '../../config.json'
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
// Fetch
const TrendingFetch = DEFAULT_DATA_PROXY + '/api/v1/trending'
const TrendingResponse = await fetch(TrendingFetch)
const TrendingData = await TrendingResponse.json()
const TrendingSplit = TrendingData.slice(0, 1)
const MoviesFetch = DEFAULT_DATA_PROXY + '/api/v1/trending?type=movies'
const MoviesResponse = await fetch(MoviesFetch)
const MoviesData = await MoviesResponse.json()
const MoviesSplit = MoviesData.slice(0, 1)
const MusicFetch = DEFAULT_DATA_PROXY + '/api/v1/trending?type=music'
const MusicResponse = await fetch(MusicFetch)
const MusicData = await MusicResponse.json()
const MusicSplit = MusicData.slice(0, 1)
const GamingFetch = DEFAULT_DATA_PROXY + '/api/v1/trending?type=gaming'
const GamingResponse = await fetch(GamingFetch)
const GamingData = await GamingResponse.json()
const GamingSplit = GamingData.slice(0, 1)
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<div class="category-select-grid">
{TrendingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/trending'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<GraphUp viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.TRENDING")}</p>
</div>
</a>
)}
{MoviesSplit.map((data) =>
<a href={'/' + i18next.language + '/category/movies'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Movie viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MOVIES")}</p>
</div>
</a>
)}
{MusicSplit.map((data) =>
<a href={'/' + i18next.language + '/category/music'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<MusicDoubleNote viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MUSIC")}</p>
</div>
</a>
)}
{GamingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/gaming'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Gamepad viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.GAMES")}</p>
</div>
</a>
)}
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.goin-card {
height: 124px;
text-decoration: none;
border-radius: 10px;
background-size: cover !important;
background-position: center !important;
}
.goin-card-content {
display: flex;
align-items: center;
gap: 12px;
padding: 0px 12px;
height: 100%;
place-content: center;
border-radius: 10px;
backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
}
.goin-card-content p {
font-size: 24px;
font-weight: bold;
position: absolute;
left: 0px;
bottom: 0px;
margin: 24px;
}
.goin-card-content svg {
position: absolute;
right: 0px;
bottom: 0px;
width: 50px;
height: 120px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
opacity: 0.3;
}
.goin-card img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.category-select-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
</style>

88
src/pages/instance.astro Normal file
View file

@ -0,0 +1,88 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from '@components/Dialog.astro'
changeLanguage("en");
// Configuration
import {
SERVER_ADMIN,
ANALYLICS_TOOL,
DEFAULT_VIDEO_PROXY,
DEFAULT_DATA_PROXY,
DEFAULT_IMAGE_PROXY,
DEFAULT_PLAYER,
SERVER_LOCATION,
MODIFIED,
CUSTOM_SOURCE_CODE,
STATISTICS
} from '../../config.json'
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<div class="video-grid">
<div class="in">
<h2>Operated By</h2>
<p>{SERVER_ADMIN}</p>
</div>
<div class="in">
<h2>Analytics Software</h2>
<p>
{
STATISTICS
?
<p>{ANALYLICS_TOOL}</p>
:
<p>None</p>
}
</p>
</div>
<div class="in">
<h2>Location</h2>
<p>{SERVER_LOCATION}</p>
</div>
<div class="in">
<h2>Modified</h2>
<p>{MODIFIED ? <p>Yes</p> : <p>No</p>}</p>
</div>
<div class="in">
<h2>Video Proxy</h2>
<a href={DEFAULT_VIDEO_PROXY}>{DEFAULT_VIDEO_PROXY}</a>
</div>
<div class="in">
<h2>Image Proxy</h2>
<a href={DEFAULT_IMAGE_PROXY}>{DEFAULT_IMAGE_PROXY}</a>
</div>
</div>
{MODIFIED ? <p>Modified Source Code <a href={CUSTOM_SOURCE_CODE}>{CUSTOM_SOURCE_CODE}</a></p> : null}
<img src="/images/backgrounds/1.webp"/>
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}

20
src/pages/jp/404.astro Normal file
View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("jp");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>Error 404</h2>
<p>Page not found.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("jp");
---
<Category GradientHero="#269753" FetchData="?type=gaming" CategoryName={t("SIDEBAR.CATEGORY_LIST.GAMES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.GAMES_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/gaming"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,10 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("jp");
---
<Category GradientHero="#ff4f4f" FetchData="?type=movies" CategoryName={t("SIDEBAR.CATEGORY_LIST.MOVIES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.MOVIES_DESCRIPTION")}>
</Category>
<style is:inline>a[href="/category/movies"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,106 @@
---
import { t, changeLanguage } from "i18next";
import Default from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_VIDEO_PROXY } from "../../../../config.json";
// Components
import MusicItem from "@components/MusicItem.astro";
changeLanguage("jp");
// Fetch
const fetchFrom = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const response = await fetch(fetchFrom);
const data = await response.json();
const heroItem = data.slice(0, 1);
---
<Default FetchData="?type=movies" Title={t("SIDEBAR.CATEGORY_LIST.MUSIC")} Description={t("SIDEBAR.CATEGORY_LIST.MUSIC_DESCRIPTION")}>
<div class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>Music</h2>
<p>Listen to the latest hits</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="or-mu">
<span></span>
<p id="title">{t("MUSIC.TITLE")}</p>
<p id="artist">{t("MUSIC.ARTIST")}</p>
<p id="date">{t("MUSIC.UPLOADED")}</p>
<p id="duration">{t("MUSIC.DURATION")}</p>
</div>
<div class="music-list">
{data.map((data) =>
<MusicItem
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Default>
<style is:inline>a[href="/en /category/music"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.category-hero {
margin-top: -80px;
background: linear-gradient(180deg, #502969, transparent);
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
.c-hero-video {
-webkit-mask-box-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-box-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
video {
object-fit: cover;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-box-image: linear-gradient(270deg, black, transparent);
}
}
}
}
.or-mu {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
max-width: 1000px;
margin: auto;
border-bottom: 1px #252525 solid;
margin-bottom: 24px;
position: sticky;
top: 58px;
background: black;
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("jp");
---
<Category GradientHero="#ff650b" FetchData="" CategoryName={t("SIDEBAR.CATEGORY_LIST.TRENDING")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.TRENDING_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/trending"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,137 @@
---
import Base from "@layouts/Default.astro";
// i18n
import i18next, { t, changeLanguage } from "i18next";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
import { BrightStar, Donate, Download, ShareIos, ThumbsUp } from "@iconoir/vue";
// Components
import Video from "@components/VideoItem.astro";
changeLanguage("jp");
// Fetch
const SWV = Astro.url.href.split("channel/").pop();
const channel = await fetch(DEFAULT_DATA_PROXY + "/api/v1/channels/" + SWV).then((response) => response.json());
const DescriptionFormat = channel.descriptionHtml.replaceAll("\n", " <br/> ");
---
<Base Title="MinPluto" Description="">
<div class="channel-backdrop">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel">
<div class="channel-header">
<div class="channel-banner">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel-meta">
<div>
<img src={channel.authorThumbnails[1].url}/>
<h2>{channel.author}</h2>
<p>{channel.subCountText}</p>
</div>
<div>
<!-- {channel.isFamilyFriendly ? <p id="family-friendly"><BrightStar/> {t("CHANNEL.FAMILY_FRIENDLY")}</p> : null} -->
</div>
</div>
</div>
<div class="channel-content">
<!-- <div class="channel-tabs">
<a href="#">{t("CHANNEL.HOME")}</a>
<a href="#">{t("CHANNEL.VIDEOS")}</a>
<a href="#">{t("CHANNEL.COMMUNITY")}</a>
</div> -->
<div class="channel-tab-content">
<h2>{t("CHANNEL.ABOUT")} {channel.author}</h2>
<p><Fragment set:html={DescriptionFormat}/></p>
<hr/>
<h2>{t("CHANNEL.LATEST")}</h2>
<div class="video-grid">
{channel.latestVideos.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
<!-- <a>View All Latest Videos</a> -->
</div>
</div>
</div>
</div>
</Base>
<style lang="scss">
.channel-backdrop img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: max-content;
filter: blur(30px) contrast(0.8);
z-index: -1;
transform: scale(1.4);
opacity: 0.3;
display: none;
}
.channel {
max-width: 1000px;
margin: auto;
.channel-header {
.channel-banner {
margin: 12px 0px;
img {
width: 100%;
border-radius: 10px;
}
}
.channel-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
#family-friendly {
color: white;
background: #3b003b;
border: 1px #a500a5 solid;
border-radius: 3rem;
display: flex;
align-items: center;
padding: 6px 12px;
gap: 12px;
pointer-events: none;
}
div:nth-child(1) {
display: flex;
flex-direction: row;
gap: 12px;
img {
border-radius: 3rem;
width: 64px;
height: 64px;
}
}
}
}
.channel-content {
.channel-tabs {
padding-top: 24px;
padding-bottom: 12px;
cursor: default;
a {
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 24px;
&:hover {
background: rgba(41, 41, 41, 0.5);
}
}
}
}
}
</style>

View file

@ -0,0 +1,57 @@
---
import { changeLanguage } from "i18next";
import Discover from "@layouts/Discover.astro";
// Properties
const { FetchData, CategoryName, CategoryDescription } = Astro.props;
// Use on top of Default Layout
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../../config.json";
// Discover Data
import Discover from "../../../data/discover.json";
changeLanguage("jp");
---
<Base>
<div class="video-grid">
{Discover.Tech.map((channel) =>
<a href={'/channel/' + channel.Link} style={"background: url('" + DEFAULT_DATA_PROXY + channel.Banner} class="discovery-channel">
<div class="dc-c">
<img src={DEFAULT_DATA_PROXY + channel.Logo}/>
<p>{channel.Name}</p>
</div>
</a>
)}
</div>
<hr/>
<div style="max-width: 1000px; margin: auto; text-align: center;">
<p>This is a curated list for all MinPluto users.</p>
<p>Is there a channel missing here that you think should be added? You can either <a href="https://sudovanilla.com/code/MinPluto/MinPluto/src/branch/main/src/data/discover.json">edit our list here</a> or you can <a href="https://community.minpluto.org/">submit a request</a>.</p>
</div>
</Base>
<style is:inline>a[href="/discover/tech"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.discovery-channel {
text-decoration: none;
border-radius: 10px;
font-weight: bold;
background-position: center !important;
background-size: cover !important;
.dc-c {
border-radius: 10px;
display: flex;
flex-direction: row;
align-items: center;
text-align: center;
backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
img {
width: 24%;
align-self: center;
border-radius: 10rem;
padding: 30px;
}
}
}
</style>

View file

@ -0,0 +1,93 @@
---
import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
changeLanguage("jp");
// Fetch
const SWV = Astro.url.href.split("embed/").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
---
<Embed
Title={video.title}
EmbedId={video.videoId}
EmbedVideo={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
EmbedImage={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
EmbedTitle={video.title}
>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
</Embed>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

143
src/pages/jp/index.astro Normal file
View file

@ -0,0 +1,143 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("jp");
// Fetch
const TrendingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending";
const TrendingResponse = await fetch(TrendingFetch);
const TrendingData = await TrendingResponse.json();
const TrendingSplit = TrendingData.slice(0, 1);
const MoviesFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=movies";
const MoviesResponse = await fetch(MoviesFetch);
const MoviesData = await MoviesResponse.json();
const MoviesSplit = MoviesData.slice(0, 1);
const MusicFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const MusicResponse = await fetch(MusicFetch);
const MusicData = await MusicResponse.json();
const MusicSplit = MusicData.slice(0, 1);
const GamingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=gaming";
const GamingResponse = await fetch(GamingFetch);
const GamingData = await GamingResponse.json();
const GamingSplit = GamingData.slice(0, 1);
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<div class="category-select-grid">
{TrendingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/trending'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<GraphUp viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.TRENDING")}</p>
</div>
</a>
)}
{MoviesSplit.map((data) =>
<a href={'/' + i18next.language + '/category/movies'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Movie viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MOVIES")}</p>
</div>
</a>
)}
{MusicSplit.map((data) =>
<a href={'/' + i18next.language + '/category/music'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<MusicDoubleNote viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MUSIC")}</p>
</div>
</a>
)}
{GamingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/gaming'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Gamepad viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.GAMES")}</p>
</div>
</a>
)}
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.goin-card {
height: 124px;
text-decoration: none;
border-radius: 10px;
background-size: cover !important;
background-position: center !important;
}
.goin-card-content {
display: flex;
align-items: center;
gap: 12px;
padding: 0px 12px;
height: 100%;
place-content: center;
border-radius: 10px;
backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
}
.goin-card-content p {
font-size: 24px;
font-weight: bold;
position: absolute;
left: 0px;
bottom: 0px;
margin: 24px;
}
.goin-card-content svg {
position: absolute;
right: 0px;
bottom: 0px;
width: 50px;
height: 120px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
opacity: 0.3;
}
.goin-card img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.category-select-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
</style>

View file

@ -0,0 +1,75 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { SERVER_ADMIN, ANALYLICS_TOOL, DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, DEFAULT_PLAYER, SERVER_LOCATION, MODIFIED, CUSTOM_SOURCE_CODE, STATISTICS } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("jp");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<div class="video-grid">
<div class="in">
<h2>Operated By</h2>
<p>{SERVER_ADMIN}</p>
</div>
<div class="in">
<h2>Analytics Software</h2>
<p>
{
STATISTICS
?
<p>{ANALYLICS_TOOL}</p>
:
<p>None</p>
}
</p>
</div>
<div class="in">
<h2>Location</h2>
<p>{SERVER_LOCATION}</p>
</div>
<div class="in">
<h2>Modified</h2>
<p>{MODIFIED ? <p>Yes</p> : <p>No</p>}</p>
</div>
<div class="in">
<h2>Video Proxy</h2>
<a href={DEFAULT_VIDEO_PROXY}>{DEFAULT_VIDEO_PROXY}</a>
</div>
<div class="in">
<h2>Image Proxy</h2>
<a href={DEFAULT_IMAGE_PROXY}>{DEFAULT_IMAGE_PROXY}</a>
</div>
</div>
{MODIFIED ? <p>Modified Source Code <a href={CUSTOM_SOURCE_CODE}>{CUSTOM_SOURCE_CODE}</a></p> : null}
<img src="/images/backgrounds/1.webp"/>
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}

View file

@ -0,0 +1,66 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { version } from "@root/package.json";
changeLanguage("jp");
---
<Base Title="MinPluto" Description="">
<div class="ms">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<h2>MinPluto</h2>
<p>{t("HOME.P1")}</p>
<hr/>
<p style="font-size: 14px;"><i>{t("HOME.P2")}</i></p>
<hr/>
<div style="display: flex; justify-content: space-evenly;">
<a href="https://community.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://sudovanilla.com/code/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
<p id="version">v{version}</p>
<img src="/images/backgrounds/1.webp"/>
</Base>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.ms {
margin-top: 20%;
text-align: center;
padding: 0px 24px;
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
p#version {
position: fixed;
bottom: 88px;
left: 0px;
width: 100%;
background: #232323;
color: white;
padding: 24px 0px;
text-align: center;
}
</style>

View file

@ -0,0 +1,40 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { HomeSimple, GraphUp, Movie, MusicDoubleNote, Gamepad, AppleImac2021Side, EmojiTalkingHappy, PizzaSlice, Treadmill, PeaceHand } from "@iconoir/vue";
changeLanguage("jp");
---
<Base Title="MinPluto" Description="">
<div style="padding: 0px 24px;">
<h2>Categories</h2>
<div id="c" style="display: flex;flex-direction: column;gap: 24px;">
<a href="/category/trending"><GraphUp/> {t("CATEGORY_LIST.TRENDING")}</a>
<a href="/category/movies"><Movie/> {t("CATEGORY_LIST.MOVIES")}</a>
<a href="/category/music"><MusicDoubleNote/> {t("CATEGORY_LIST.MUSIC")}</a>
<a href="/category/gaming"><Gamepad/> {t("CATEGORY_LIST.GAMES")}</a>
</div>
</div>
</Base>
<style lang="scss">
#c a {
color: white;
text-decoration: none;
background: linear-gradient(45deg, #636363, #181818);
padding: 48px 24px;
font-size: 24px;
display: flex;
align-items: center;
gap: 24px;
border-radius: 10px;
svg {
position: absolute;
right: 60px;
width: 100px;
height: 70px;
opacity: 0.4;
}
}
</style>

View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("jp");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>No Internet Connection</h2>
<p>It appears that you are offline, try connecting your device to the internet, and try again.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

35
src/pages/jp/search.astro Normal file
View file

@ -0,0 +1,35 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import Video from "@components/VideoItem.astro";
changeLanguage("jp");
// Fetch
const SBO = Astro.url.href.split("search?query=").pop();
const response = await fetch(DEFAULT_DATA_PROXY + "/api/v1/search?q=" + SBO)
.catch((error) => {
console.log(error);
});
const data = await response.json();
---
<Base Title='MinPluto Search'>
<div class="page-title">
<h2>Search</h2>
</div>
<div class="video-grid">
{data.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Base>

207
src/pages/jp/watch.astro Normal file
View file

@ -0,0 +1,207 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../config.json";
import { Donate, Download, ShareIos, ThumbsUp, MediaVideo } from "@iconoir/vue";
// Components
import Dialog from "@components/Dialog.astro";
import Video from "@components/VideoItem.astro";
changeLanguage("jp");
// Fetch
const SWV = Astro.url.href.split("watch?v=").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
const comments = await fetch(DEFAULT_DATA_PROXY + "/api/v1/comments/" + SWV).then((response) => response.json());
const Description = video.description;
const UploadDate = video.published;
const Views = video.viewCount;
const VideoSeconds = video.lengthSeconds;
let DescriptionFormat = Description.replaceAll("\n", " <br/> ");
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString();
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
new Date(VideoSeconds * 1000)
.toISOString()
.slice(14, 19)
.split(":")
.map(Number)
.join(":");
// Format Views
const ViewsConversion = Intl.NumberFormat("en", { notation: "compact" });
let ViewsFormat = ViewsConversion.format(Views);
---
<Base Title={video.title}>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
<div class="video-rea">
<div class="rea-details">
<p style="font-weight: bold; font-size: 24px;">{video.title}</p>
<div class="rea-details-start">
<a style="text-decoration: none;" href={video.authorUrl} class="rea-channel">
<img src={video.authorThumbnails[1].url} />
<div
style="display: flex; flex-direction: column; align-items: left;"
>
<h2 style="margin: 0px; font-weight: bold; font-size: 18px;">{video.author}</h2>
<p style="margin: 0px;" id="subs">
{video.subCountText} Subscribers
</p>
</div>
</a>
<div style="display: flex; flex-direction: row; align-items: left;">
<button onclick="DownloadDialogShow()"><Download /> Download</button>
<button onclick={'location.href = "/embed/' + video.videoId + '/"'}><MediaVideo /> Embed</button>
<button onclick="ShareDialogShow()"><ShareIos /> Share</button>
</div>
</div>
<div class="rea-details-end">
<p id="views">{ViewsFormat} Views - {DateFormat}</p>
<Fragment set:html={DescriptionFormat} />
</div>
</div>
<div style="display: flex; gap: 24px;">
<div class="rea-comments">
<h2>{t("WATCH.COMMENTS")}</h2>
{
comments.comments.map((comment) => (
<div class="comment">
<img
src={
DEFAULT_IMAGE_PROXY + "/" + comment.authorThumbnails[1].url
}
/>
<div>
<p>
<a href={comment.authorUrl}>{comment.author}</a> - 2 Months
Ago
</p>
<p>{comment.contentHtml}</p>
<p>
<ThumbsUp /> {comment.likeCount}
</p>
</div>
</div>
))
}
</div>
<div class="rea-recommendations">
<h2>{t("WATCH.RELATED")}</h2>
{
video.recommendedVideos.map((data) => (
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
))
}
</div>
</div>
</div>
<Dialog
Title="Download"
Description="Choose a download method"
Closable
CloseOnclick="DownloadDialogHide()"
>
<p>Video</p>
<div class="dialog-downloads-list">
<a href={video.formatStreams[1].url} download={video.title + '.mp4'} target="_blank">Download</a>
</div>
<p>Audio Only</p>
<div class="dialog-downloads-list">
<a href={video.adaptiveFormats[1].url} download={video.title + '.mp3'} target="_blank">Download</a>
</div>
</Dialog>
<Dialog
Title="Share"
Description="Choose a share method"
Closable
CloseOnclick="ShareDialogHide()"
>
<div class="dialog-downloads-list">
<a href={'https://mastodonshare.com/?url=' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Mastodon</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Misskey</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Firefish</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Elk</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Lemmy</a>
</div>
</Dialog>
</Base>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

66
src/pages/m/about.astro Normal file
View file

@ -0,0 +1,66 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { version } from "@root/package.json";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="ms">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<h2>MinPluto</h2>
<p>{t("HOME.P1")}</p>
<hr/>
<p style="font-size: 14px;"><i>{t("HOME.P2")}</i></p>
<hr/>
<div style="display: flex; justify-content: space-evenly;">
<a href="https://community.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://sudovanilla.com/code/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
<p id="version">v{version}</p>
<img src="/images/backgrounds/1.webp"/>
</Base>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.ms {
margin-top: 20%;
text-align: center;
padding: 0px 24px;
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
p#version {
position: fixed;
bottom: 88px;
left: 0px;
width: 100%;
background: #232323;
color: white;
padding: 24px 0px;
text-align: center;
}
</style>

View file

@ -0,0 +1,40 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { HomeSimple, GraphUp, Movie, MusicDoubleNote, Gamepad, AppleImac2021Side, EmojiTalkingHappy, PizzaSlice, Treadmill, PeaceHand } from "@iconoir/vue";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div style="padding: 0px 24px;">
<h2>Categories</h2>
<div id="c" style="display: flex;flex-direction: column;gap: 24px;">
<a href="/category/trending"><GraphUp/> {t("CATEGORY_LIST.TRENDING")}</a>
<a href="/category/movies"><Movie/> {t("CATEGORY_LIST.MOVIES")}</a>
<a href="/category/music"><MusicDoubleNote/> {t("CATEGORY_LIST.MUSIC")}</a>
<a href="/category/gaming"><Gamepad/> {t("CATEGORY_LIST.GAMES")}</a>
</div>
</div>
</Base>
<style lang="scss">
#c a {
color: white;
text-decoration: none;
background: linear-gradient(45deg, #636363, #181818);
padding: 48px 24px;
font-size: 24px;
display: flex;
align-items: center;
gap: 24px;
border-radius: 10px;
svg {
position: absolute;
right: 60px;
width: 100px;
height: 70px;
opacity: 0.4;
}
}
</style>

20
src/pages/offline.astro Normal file
View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("en");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>No Internet Connection</h2>
<p>It appears that you are offline, try connecting your device to the internet, and try again.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

56
src/pages/privacy.md Normal file
View file

@ -0,0 +1,56 @@
---
layout: "@layouts/Markdown.astro"
---
# Privacy Policy
## 3rd Party Services are Proxied
Any service in MinPluto that is 3rd party is proxied by this instance or another instance such as images, videos, fonts, scripts, and more. No personal information information is collected and no information is sent to any 3rd party.
## Information Collection and Use
This instance of MinPluto uses Plausible for analytics, no personal informatin is collected about you. Data that is collected can not be used to identify you, along with devices or any other factors.
All data collected with Plausible is anonymous.
## We Don't Know What You're Watching
When you watch a video on a MinPluto instance with analytics enabled, the analytics software doesn't show what video are you watching. The query in the URL, which is the video ID, is not saved to analytics. What we see is only "/watch", not /watch?=dQw4w9WgXcQ".
## Liability
MinPluto and SudoVanilla take no responsibility for the use of our tool, or external instances provided by third parties. It is strongly recommended that you abide by the valid official regulations in your country. Furthermore, we refuse liability for any inappropriate use of MinPluto, such as illegal downloading.
MinPluto is licenced under AGPL v3, this software is included with a copy.
<hr/>
Copyright (C) 2024 SudoVanilla
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
<hr/>
## Links to Other Sites
Channels and videos on MinPluto may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by SudoVanilla, MinPluto, or the MinPluto instance. Therefore, I strongly advise you to review the privacy policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
## Changes to This Privacy Policy
The developers of MinPluto, SudoVanilla, may update our privacy policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new privacy policy on this page.
This also goes for the instance itself.
This policy is effective as of March 7th, 2024.
## Contact Us
If you have any questions or suggestions about MinPluto's privacy policy, do not hesitate to contact me at hello@minpluto.org or to [submit an issue](https://sudovanilla.com/code/MinPluto/MinPluto/issues).

20
src/pages/ru/404.astro Normal file
View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("ru");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>Error 404</h2>
<p>Page not found.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("ru");
---
<Category GradientHero="#269753" FetchData="?type=gaming" CategoryName={t("SIDEBAR.CATEGORY_LIST.GAMES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.GAMES_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/gaming"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,10 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("ru");
---
<Category GradientHero="#ff4f4f" FetchData="?type=movies" CategoryName={t("SIDEBAR.CATEGORY_LIST.MOVIES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.MOVIES_DESCRIPTION")}>
</Category>
<style is:inline>a[href="/category/movies"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,106 @@
---
import { t, changeLanguage } from "i18next";
import Default from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_VIDEO_PROXY } from "../../../../config.json";
// Components
import MusicItem from "@components/MusicItem.astro";
changeLanguage("ru");
// Fetch
const fetchFrom = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const response = await fetch(fetchFrom);
const data = await response.json();
const heroItem = data.slice(0, 1);
---
<Default FetchData="?type=movies" Title={t("SIDEBAR.CATEGORY_LIST.MUSIC")} Description={t("SIDEBAR.CATEGORY_LIST.MUSIC_DESCRIPTION")}>
<div class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>Music</h2>
<p>Listen to the latest hits</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="or-mu">
<span></span>
<p id="title">{t("MUSIC.TITLE")}</p>
<p id="artist">{t("MUSIC.ARTIST")}</p>
<p id="date">{t("MUSIC.UPLOADED")}</p>
<p id="duration">{t("MUSIC.DURATION")}</p>
</div>
<div class="music-list">
{data.map((data) =>
<MusicItem
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Default>
<style is:inline>a[href="/en /category/music"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.category-hero {
margin-top: -80px;
background: linear-gradient(180deg, #502969, transparent);
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
.c-hero-video {
-webkit-mask-box-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-box-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
video {
object-fit: cover;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-box-image: linear-gradient(270deg, black, transparent);
}
}
}
}
.or-mu {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
max-width: 1000px;
margin: auto;
border-bottom: 1px #252525 solid;
margin-bottom: 24px;
position: sticky;
top: 58px;
background: black;
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
}
</style>

View file

@ -0,0 +1,9 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
changeLanguage("ru");
---
<Category GradientHero="#ff650b" FetchData="" CategoryName={t("SIDEBAR.CATEGORY_LIST.TRENDING")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.TRENDING_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/trending"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -0,0 +1,137 @@
---
import Base from "@layouts/Default.astro";
// i18n
import i18next, { t, changeLanguage } from "i18next";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
import { BrightStar, Donate, Download, ShareIos, ThumbsUp } from "@iconoir/vue";
// Components
import Video from "@components/VideoItem.astro";
changeLanguage("ru");
// Fetch
const SWV = Astro.url.href.split("channel/").pop();
const channel = await fetch(DEFAULT_DATA_PROXY + "/api/v1/channels/" + SWV).then((response) => response.json());
const DescriptionFormat = channel.descriptionHtml.replaceAll("\n", " <br/> ");
---
<Base Title="MinPluto" Description="">
<div class="channel-backdrop">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel">
<div class="channel-header">
<div class="channel-banner">
<img src={channel.authorBanners[1].url}/>
</div>
<div class="channel-meta">
<div>
<img src={channel.authorThumbnails[1].url}/>
<h2>{channel.author}</h2>
<p>{channel.subCountText}</p>
</div>
<div>
<!-- {channel.isFamilyFriendly ? <p id="family-friendly"><BrightStar/> {t("CHANNEL.FAMILY_FRIENDLY")}</p> : null} -->
</div>
</div>
</div>
<div class="channel-content">
<!-- <div class="channel-tabs">
<a href="#">{t("CHANNEL.HOME")}</a>
<a href="#">{t("CHANNEL.VIDEOS")}</a>
<a href="#">{t("CHANNEL.COMMUNITY")}</a>
</div> -->
<div class="channel-tab-content">
<h2>{t("CHANNEL.ABOUT")} {channel.author}</h2>
<p><Fragment set:html={DescriptionFormat}/></p>
<hr/>
<h2>{t("CHANNEL.LATEST")}</h2>
<div class="video-grid">
{channel.latestVideos.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
<!-- <a>View All Latest Videos</a> -->
</div>
</div>
</div>
</div>
</Base>
<style lang="scss">
.channel-backdrop img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: max-content;
filter: blur(30px) contrast(0.8);
z-index: -1;
transform: scale(1.4);
opacity: 0.3;
display: none;
}
.channel {
max-width: 1000px;
margin: auto;
.channel-header {
.channel-banner {
margin: 12px 0px;
img {
width: 100%;
border-radius: 10px;
}
}
.channel-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
#family-friendly {
color: white;
background: #3b003b;
border: 1px #a500a5 solid;
border-radius: 3rem;
display: flex;
align-items: center;
padding: 6px 12px;
gap: 12px;
pointer-events: none;
}
div:nth-child(1) {
display: flex;
flex-direction: row;
gap: 12px;
img {
border-radius: 3rem;
width: 64px;
height: 64px;
}
}
}
}
.channel-content {
.channel-tabs {
padding-top: 24px;
padding-bottom: 12px;
cursor: default;
a {
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 24px;
&:hover {
background: rgba(41, 41, 41, 0.5);
}
}
}
}
}
</style>

View file

@ -0,0 +1,57 @@
---
import { changeLanguage } from "i18next";
import Discover from "@layouts/Discover.astro";
// Properties
const { FetchData, CategoryName, CategoryDescription } = Astro.props;
// Use on top of Default Layout
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../../config.json";
// Discover Data
import Discover from "../../../data/discover.json";
changeLanguage("ru");
---
<Base>
<div class="video-grid">
{Discover.Tech.map((channel) =>
<a href={'/channel/' + channel.Link} style={"background: url('" + DEFAULT_DATA_PROXY + channel.Banner} class="discovery-channel">
<div class="dc-c">
<img src={DEFAULT_DATA_PROXY + channel.Logo}/>
<p>{channel.Name}</p>
</div>
</a>
)}
</div>
<hr/>
<div style="max-width: 1000px; margin: auto; text-align: center;">
<p>This is a curated list for all MinPluto users.</p>
<p>Is there a channel missing here that you think should be added? You can either <a href="https://sudovanilla.com/code/MinPluto/MinPluto/src/branch/main/src/data/discover.json">edit our list here</a> or you can <a href="https://community.minpluto.org/">submit a request</a>.</p>
</div>
</Base>
<style is:inline>a[href="/discover/tech"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.discovery-channel {
text-decoration: none;
border-radius: 10px;
font-weight: bold;
background-position: center !important;
background-size: cover !important;
.dc-c {
border-radius: 10px;
display: flex;
flex-direction: row;
align-items: center;
text-align: center;
backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
img {
width: 24%;
align-self: center;
border-radius: 10rem;
padding: 30px;
}
}
}
</style>

View file

@ -0,0 +1,93 @@
---
import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../../config.json";
changeLanguage("ru");
// Fetch
const SWV = Astro.url.href.split("embed/").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
---
<Embed
Title={video.title}
EmbedId={video.videoId}
EmbedVideo={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
EmbedImage={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
EmbedTitle={video.title}
>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
</Embed>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

143
src/pages/ru/index.astro Normal file
View file

@ -0,0 +1,143 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("ru");
// Fetch
const TrendingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending";
const TrendingResponse = await fetch(TrendingFetch);
const TrendingData = await TrendingResponse.json();
const TrendingSplit = TrendingData.slice(0, 1);
const MoviesFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=movies";
const MoviesResponse = await fetch(MoviesFetch);
const MoviesData = await MoviesResponse.json();
const MoviesSplit = MoviesData.slice(0, 1);
const MusicFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=music";
const MusicResponse = await fetch(MusicFetch);
const MusicData = await MusicResponse.json();
const MusicSplit = MusicData.slice(0, 1);
const GamingFetch = DEFAULT_DATA_PROXY + "/api/v1/trending?type=gaming";
const GamingResponse = await fetch(GamingFetch);
const GamingData = await GamingResponse.json();
const GamingSplit = GamingData.slice(0, 1);
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<div class="category-select-grid">
{TrendingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/trending'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<GraphUp viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.TRENDING")}</p>
</div>
</a>
)}
{MoviesSplit.map((data) =>
<a href={'/' + i18next.language + '/category/movies'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Movie viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MOVIES")}</p>
</div>
</a>
)}
{MusicSplit.map((data) =>
<a href={'/' + i18next.language + '/category/music'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<MusicDoubleNote viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.MUSIC")}</p>
</div>
</a>
)}
{GamingSplit.map((data) =>
<a href={'/' + i18next.language + '/category/gaming'} style={'background: url("' + DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/' + data.videoThumbnails[1].url + '")'} class="goin-card">
<div class="goin-card-content">
<Gamepad viewBox="0 0 10 24"/>
<p>{t("SIDEBAR.CATEGORY_LIST.GAMES")}</p>
</div>
</a>
)}
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.goin-card {
height: 124px;
text-decoration: none;
border-radius: 10px;
background-size: cover !important;
background-position: center !important;
}
.goin-card-content {
display: flex;
align-items: center;
gap: 12px;
padding: 0px 12px;
height: 100%;
place-content: center;
border-radius: 10px;
backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
}
.goin-card-content p {
font-size: 24px;
font-weight: bold;
position: absolute;
left: 0px;
bottom: 0px;
margin: 24px;
}
.goin-card-content svg {
position: absolute;
right: 0px;
bottom: 0px;
width: 50px;
height: 120px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
opacity: 0.3;
}
.goin-card img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.category-select-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
</style>

View file

@ -0,0 +1,75 @@
---
import i18next, { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import Dialog from "@components/Dialog.astro";
// Configuration
import { SERVER_ADMIN, ANALYLICS_TOOL, DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, DEFAULT_PLAYER, SERVER_LOCATION, MODIFIED, CUSTOM_SOURCE_CODE, STATISTICS } from "../../../config.json";
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
changeLanguage("ru");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<div class="video-grid">
<div class="in">
<h2>Operated By</h2>
<p>{SERVER_ADMIN}</p>
</div>
<div class="in">
<h2>Analytics Software</h2>
<p>
{
STATISTICS
?
<p>{ANALYLICS_TOOL}</p>
:
<p>None</p>
}
</p>
</div>
<div class="in">
<h2>Location</h2>
<p>{SERVER_LOCATION}</p>
</div>
<div class="in">
<h2>Modified</h2>
<p>{MODIFIED ? <p>Yes</p> : <p>No</p>}</p>
</div>
<div class="in">
<h2>Video Proxy</h2>
<a href={DEFAULT_VIDEO_PROXY}>{DEFAULT_VIDEO_PROXY}</a>
</div>
<div class="in">
<h2>Image Proxy</h2>
<a href={DEFAULT_IMAGE_PROXY}>{DEFAULT_IMAGE_PROXY}</a>
</div>
</div>
{MODIFIED ? <p>Modified Source Code <a href={CUSTOM_SOURCE_CODE}>{CUSTOM_SOURCE_CODE}</a></p> : null}
<img src="/images/backgrounds/1.webp"/>
</div>
</Base>
<style is:inline>a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}

View file

@ -0,0 +1,66 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { version } from "@root/package.json";
changeLanguage("ru");
---
<Base Title="MinPluto" Description="">
<div class="ms">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<h2>MinPluto</h2>
<p>{t("HOME.P1")}</p>
<hr/>
<p style="font-size: 14px;"><i>{t("HOME.P2")}</i></p>
<hr/>
<div style="display: flex; justify-content: space-evenly;">
<a href="https://community.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://sudovanilla.com/code/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
<p id="version">v{version}</p>
<img src="/images/backgrounds/1.webp"/>
</Base>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.ms {
margin-top: 20%;
text-align: center;
padding: 0px 24px;
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
p#version {
position: fixed;
bottom: 88px;
left: 0px;
width: 100%;
background: #232323;
color: white;
padding: 24px 0px;
text-align: center;
}
</style>

View file

@ -0,0 +1,40 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { HomeSimple, GraphUp, Movie, MusicDoubleNote, Gamepad, AppleImac2021Side, EmojiTalkingHappy, PizzaSlice, Treadmill, PeaceHand } from "@iconoir/vue";
changeLanguage("ru");
---
<Base Title="MinPluto" Description="">
<div style="padding: 0px 24px;">
<h2>Categories</h2>
<div id="c" style="display: flex;flex-direction: column;gap: 24px;">
<a href="/category/trending"><GraphUp/> {t("CATEGORY_LIST.TRENDING")}</a>
<a href="/category/movies"><Movie/> {t("CATEGORY_LIST.MOVIES")}</a>
<a href="/category/music"><MusicDoubleNote/> {t("CATEGORY_LIST.MUSIC")}</a>
<a href="/category/gaming"><Gamepad/> {t("CATEGORY_LIST.GAMES")}</a>
</div>
</div>
</Base>
<style lang="scss">
#c a {
color: white;
text-decoration: none;
background: linear-gradient(45deg, #636363, #181818);
padding: 48px 24px;
font-size: 24px;
display: flex;
align-items: center;
gap: 24px;
border-radius: 10px;
svg {
position: absolute;
right: 60px;
width: 100px;
height: 70px;
opacity: 0.4;
}
}
</style>

View file

@ -0,0 +1,20 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
changeLanguage("ru");
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>No Internet Connection</h2>
<p>It appears that you are offline, try connecting your device to the internet, and try again.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

35
src/pages/ru/search.astro Normal file
View file

@ -0,0 +1,35 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../../config.json";
import Video from "@components/VideoItem.astro";
changeLanguage("ru");
// Fetch
const SBO = Astro.url.href.split("search?query=").pop();
const response = await fetch(DEFAULT_DATA_PROXY + "/api/v1/search?q=" + SBO)
.catch((error) => {
console.log(error);
});
const data = await response.json();
---
<Base Title='MinPluto Search'>
<div class="page-title">
<h2>Search</h2>
</div>
<div class="video-grid">
{data.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Base>

207
src/pages/ru/watch.astro Normal file
View file

@ -0,0 +1,207 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../../config.json";
import { Donate, Download, ShareIos, ThumbsUp, MediaVideo } from "@iconoir/vue";
// Components
import Dialog from "@components/Dialog.astro";
import Video from "@components/VideoItem.astro";
changeLanguage("ru");
// Fetch
const SWV = Astro.url.href.split("watch?v=").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
const comments = await fetch(DEFAULT_DATA_PROXY + "/api/v1/comments/" + SWV).then((response) => response.json());
const Description = video.description;
const UploadDate = video.published;
const Views = video.viewCount;
const VideoSeconds = video.lengthSeconds;
let DescriptionFormat = Description.replaceAll("\n", " <br/> ");
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString();
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
new Date(VideoSeconds * 1000)
.toISOString()
.slice(14, 19)
.split(":")
.map(Number)
.join(":");
// Format Views
const ViewsConversion = Intl.NumberFormat("en", { notation: "compact" });
let ViewsFormat = ViewsConversion.format(Views);
---
<Base Title={video.title}>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
<div class="video-rea">
<div class="rea-details">
<p style="font-weight: bold; font-size: 24px;">{video.title}</p>
<div class="rea-details-start">
<a style="text-decoration: none;" href={video.authorUrl} class="rea-channel">
<img src={video.authorThumbnails[1].url} />
<div
style="display: flex; flex-direction: column; align-items: left;"
>
<h2 style="margin: 0px; font-weight: bold; font-size: 18px;">{video.author}</h2>
<p style="margin: 0px;" id="subs">
{video.subCountText} Subscribers
</p>
</div>
</a>
<div style="display: flex; flex-direction: row; align-items: left;">
<button onclick="DownloadDialogShow()"><Download /> Download</button>
<button onclick={'location.href = "/embed/' + video.videoId + '/"'}><MediaVideo /> Embed</button>
<button onclick="ShareDialogShow()"><ShareIos /> Share</button>
</div>
</div>
<div class="rea-details-end">
<p id="views">{ViewsFormat} Views - {DateFormat}</p>
<Fragment set:html={DescriptionFormat} />
</div>
</div>
<div style="display: flex; gap: 24px;">
<div class="rea-comments">
<h2>{t("WATCH.COMMENTS")}</h2>
{
comments.comments.map((comment) => (
<div class="comment">
<img
src={
DEFAULT_IMAGE_PROXY + "/" + comment.authorThumbnails[1].url
}
/>
<div>
<p>
<a href={comment.authorUrl}>{comment.author}</a> - 2 Months
Ago
</p>
<p>{comment.contentHtml}</p>
<p>
<ThumbsUp /> {comment.likeCount}
</p>
</div>
</div>
))
}
</div>
<div class="rea-recommendations">
<h2>{t("WATCH.RELATED")}</h2>
{
video.recommendedVideos.map((data) => (
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
))
}
</div>
</div>
</div>
<Dialog
Title="Download"
Description="Choose a download method"
Closable
CloseOnclick="DownloadDialogHide()"
>
<p>Video</p>
<div class="dialog-downloads-list">
<a href={video.formatStreams[1].url} download={video.title + '.mp4'} target="_blank">Download</a>
</div>
<p>Audio Only</p>
<div class="dialog-downloads-list">
<a href={video.adaptiveFormats[1].url} download={video.title + '.mp3'} target="_blank">Download</a>
</div>
</Dialog>
<Dialog
Title="Share"
Description="Choose a share method"
Closable
CloseOnclick="ShareDialogHide()"
>
<div class="dialog-downloads-list">
<a href={'https://mastodonshare.com/?url=' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Mastodon</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Misskey</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Firefish</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Elk</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Lemmy</a>
</div>
</Dialog>
</Base>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

37
src/pages/search.astro Normal file
View file

@ -0,0 +1,37 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
// Configuration
import { DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY } from "../../config.json";
changeLanguage("en");
import Video from '@components/VideoItem.astro'
// Fetch
const SBO = Astro.url.href.split("search?query=").pop();
const response = await fetch(DEFAULT_DATA_PROXY + "/api/v1/search?q=" + SBO)
.catch((error) => {
console.log(error)
});
const data = await response.json();
---
<Base Title='MinPluto Search'>
<div class="page-title">
<h2>Search</h2>
</div>
<div class="video-grid">
{data.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Base>

209
src/pages/watch.astro Normal file
View file

@ -0,0 +1,209 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_VIDEO_PROXY, DEFAULT_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from "../../config.json";
import { Donate, Download, ShareIos, ThumbsUp, MediaVideo } from "@iconoir/vue";
// Components
import Dialog from '@components/Dialog.astro'
import Video from '@components/VideoItem.astro'
// Fetch
const SWV = Astro.url.href.split("watch?v=").pop();
const video = await fetch(DEFAULT_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
const comments = await fetch(DEFAULT_DATA_PROXY + "/api/v1/comments/" + SWV).then((response) => response.json());
changeLanguage("en");
const Description = video.description;
const UploadDate = video.published;
const Views = video.viewCount;
const VideoSeconds = video.lengthSeconds;
let DescriptionFormat = Description.replaceAll("\n", " <br/> ");
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString();
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
new Date(VideoSeconds * 1000)
.toISOString()
.slice(14, 19)
.split(":")
.map(Number)
.join(":");
// Format Views
const ViewsConversion = Intl.NumberFormat("en", { notation: "compact" });
let ViewsFormat = ViewsConversion.format(Views);
---
<Base Title={video.title}>
<div class="video-container">
<video
class="zorn-player"
autoplay
poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
video-title={video.title}
src={DEFAULT_VIDEO_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
>
</video>
</div>
<div class="video-rea">
<div class="rea-details">
<p style="font-weight: bold; font-size: 24px;">{video.title}</p>
<div class="rea-details-start">
<a style="text-decoration: none;" href={video.authorUrl} class="rea-channel">
<img src={DEFAULT_IMAGE_PROXY + "/" + video.authorThumbnails[1].url} />
<div
style="display: flex; flex-direction: column; align-items: left;"
>
<h2 style="margin: 0px; font-weight: bold; font-size: 18px;">{video.author}</h2>
<p style="margin: 0px;" id="subs">
{video.subCountText} Subscribers
</p>
</div>
</a>
<div style="display: flex; flex-direction: row; align-items: left;">
<button onclick="DownloadDialogShow()"><Download /> Download</button>
<button onclick={'location.href = "/embed/' + video.videoId + '/"'}><MediaVideo /> Embed</button>
<button onclick="ShareDialogShow()"><ShareIos /> Share</button>
</div>
</div>
<div class="rea-details-end">
<p id="views">{ViewsFormat} Views - {DateFormat}</p>
<Fragment set:html={DescriptionFormat} />
</div>
</div>
<div style="display: flex; gap: 24px;">
<div class="rea-comments">
<h2>{t("WATCH.COMMENTS")}</h2>
{
comments.comments.map((comment) => (
<div class="comment">
<img
src={
DEFAULT_IMAGE_PROXY + "/" + comment.authorThumbnails[1].url
}
/>
<div>
<p>
<a href={comment.authorUrl}>{comment.author}</a> - 2 Months
Ago
</p>
<p>{comment.contentHtml}</p>
<p>
<ThumbsUp /> {comment.likeCount}
</p>
</div>
</div>
))
}
</div>
<div class="rea-recommendations">
<h2>{t("WATCH.RELATED")}</h2>
{
video.recommendedVideos.map((data) => (
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
))
}
</div>
</div>
</div>
<Dialog
Title="Download"
Description="Choose a download method"
Closable
CloseOnclick="DownloadDialogHide()"
>
<p>Video</p>
<div class="dialog-downloads-list">
<a href={video.formatStreams[1].url} download={video.title + '.mp4'} target="_blank">Download</a>
</div>
<p>Audio Only</p>
<div class="dialog-downloads-list">
<a href={video.adaptiveFormats[1].url} download={video.title + '.mp3'} target="_blank">Download</a>
</div>
</Dialog>
<Dialog
Title="Share"
Description="Choose a share method"
Closable
CloseOnclick="ShareDialogHide()"
>
<div class="dialog-downloads-list">
<a href={'https://mastodonshare.com/?url=' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Mastodon</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Misskey</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Firefish</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Elk</a>
<a href={'https://share.minpluto.org/?url' + SERVER_DOMAIN + '/watch?q=' + video.videoId} target="_blank">Lemmy</a>
</div>
</Dialog>
</Base>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View file

@ -0,0 +1,61 @@
{
"GLOBAL": {
"MINPLUTO": "MinPluto"
},
"SIDEBAR": {
"HOME": "Home",
"CATEGORY": "Category",
"CATEGORY_LIST": {
"TRENDING": "Trending",
"TRENDING_DESCRIPTION": "What is everyone watching",
"MOVIES": "Movies",
"MOVIES_DESCRIPTION": "Movies and TV Show Trailers",
"MUSIC": "Music",
"MUSIC_DESCRIPTION": "Popuar Hits",
"GAMES": "Games",
"GAMES_DESCRIPTION": "Gameplay and more"
},
"DISCOVER": "Discover",
"DISCOVER_LIST": {
"TECH": "Tech",
"COMEDY": "Comedy",
"FOOD": "Food",
"GAMES": "Games",
"FITNESS": "Fitness"
},
"FOOTER": {
"ALPHA": "You're in Alpha",
"STATUS": "Status",
"FORUM": "Forum",
"SOURCE_CODE": "Source Code"
}
},
"HEADER": {
"SEARCH": "Search",
"FEEDBACK": "Feedback"
},
"HOME": {
"P1": "Select a category or search something to get started.",
"P2": "Currently, MinPluto is in alpha. Features are currently barebones or may not work as expected."
},
"WATCH": {
"BY": "By",
"VIEWS": "Views",
"RELATED": "Related Videos",
"COMMENTS": "Comments"
},
"CHANNELS": {
"FAMILY_FRIENDLY": "Family Friendly",
"HOME": "Home",
"VIDEOS": "Videos",
"COMMUNITY": "Community",
"LATEST": "Latest Videos",
"ABOUT": "About"
},
"MUSIC": {
"TITLE": "Title",
"ARTIST": "Artist",
"UPLOADED": "Uploaded",
"DURATION": "Duration"
}
}

View file

@ -0,0 +1,61 @@
{
"GLOBAL": {
"MINPLUTO": "ミン・プルート"
},
"SIDEBAR": {
"HOME": "ホーム",
"CATEGORY": "カテゴリー",
"CATEGORY_LIST": {
"TRENDING": "トレンド",
"TRENDING_DESCRIPTION": "みんなは何を見ているの",
"MOVIES": "映画",
"MOVIES_DESCRIPTION": "映画とテレビ番組の予告編",
"MUSIC": "ミュージック",
"MUSIC_DESCRIPTION": "人気の曲",
"GAMES": "ゲーム",
"GAMES_DESCRIPTION": "ゲームプレイその他"
},
"DISCOVER": "ディスカバー",
"DISCOVER_LIST": {
"TECH": "テクノロジー",
"COMEDY": "コメディ",
"FOOD": "食べ物",
"GAMES": "ゲーム",
"FITNESS": "フィットネス"
},
"FOOTER": {
"ALPHA": "アルファ版にいます",
"STATUS": "ステータス",
"FORUM": "フォーラム",
"SOURCE_CODE": "ソースコード"
}
},
"HEADER": {
"SEARCH": "サーチ",
"FEEDBACK": "フィードバック"
},
"HOME": {
"P1": "カテゴリを選択するか、何かを検索して始めてください。",
"P2": "現在、ミン・プルート はベータ版です。 現在、機能は最低限のものであるか、期待どおりに動作しない可能性があります。"
},
"WATCH": {
"BY": "による",
"VIEWS": "ビュー",
"RELATED": "関連ビデオ",
"COMMENTS": "コメント"
},
"CHANNEL": {
"FAMILY_FRIENDLY": "ファミリー向け",
"HOME": "ホーム",
"VIDEOS": "ビデオ",
"COMMUNITY": "コミュニティ",
"LATEST": "最新のビデオ",
"ABOUT": "について"
},
"MUSIC": {
"TITLE": "タイトル",
"ARTIST": "アーティスト",
"UPLOADED": "アップロード済み",
"DURATION": "間隔"
}
}

View file

@ -0,0 +1,55 @@
{
"GLOBAL": {
"MINPLUTO": "MinPluto"
},
"SIDEBAR": {
"HOME": "Главная",
"CATEGORY": "Категория",
"CATEGORY_LIST": {
"TRENDING": "В тренде",
"TRENDING_DESCRIPTION": "То, что смотрят все",
"MOVIES": "Фильмы",
"MOVIES_DESCRIPTION": "Трейлеры фильмов и ТВ шоу",
"MUSIC": "Музыка",
"MUSIC_DESCRIPTION": "Популярные хиты",
"GAMES": "Игры",
"GAMES_DESCRIPTION": "Геймплей и прочее"
},
"DISCOVER": "Рекомендации",
"DISCOVER_LIST": {
"TECH": "Технологии",
"COMEDY": "Комедия",
"FOOD": "Еда",
"GAMES": "Игры",
"FITNESS": "Фитнес"
},
"FOOTER": {
"ALPHA": "Вы в Альфа",
"STATUS": "Статус",
"FORUM": "Форум",
"SOURCE_CODE": "Исходный код"
}
},
"HEADER": {
"SEARCH": "Поиск",
"FEEDBACK": "Отзывы"
},
"HOME": {
"P1": "Выберите категорию или начните поиск.",
"P2": "В настоящий момент, MinPluto в альфе. Некоторые функции сырые или работают непредсказуемо"
},
"WATCH": {
"BY": "От",
"VIEWS": "Просмотры",
"RELATED": "Похожие видео",
"COMMENTS": "Комментарии"
},
"CHANNELS": {
"FAMILY_FRIENDLY": "Для детей",
"HOME": "Главная",
"VIDEOS": "Видео",
"COMMUNITY": "Сообщество",
"LATEST": "Новые видео",
"ABOUT": "Про"
}
}

33
src/public/manifest.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "MinPluto",
"short_name": "MinPluto",
"description": "An open source frontend alternative to YouTube.",
"start_url": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000",
"categories": [
"entertainment",
"music"
],
"dir": "ltr",
"orientation": "any",
"display_override": [
"window-controls-overlay"
],
"icons": [
{
"src": "/images/pwa/MinPluto - PWA Logo.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/images/pwa/Mobile.png",
"type": "image/png",
"sizes": "527x1004",
"form_factor": "narrow"
}
]
}

9
src/public/robots.txt Normal file
View file

@ -0,0 +1,9 @@
User-agent: Applebot
User-agent: Googlebot
User-agent: bingbot
User-agent: Yandex
User-agent: Yeti
User-agent: Baiduspider
User-agent: 360Spider
User-agent: *
Disallow: /

778
src/public/scripts/zorn.js Normal file
View file

@ -0,0 +1,778 @@
(() => {
// src/assets/icons/play-solid.svg
var play_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5"><path d="M6.90588 4.53682C6.50592 4.2998 6 4.58808 6 5.05299V18.947C6 19.4119 6.50592 19.7002 6.90588 19.4632L18.629 12.5162C19.0211 12.2838 19.0211 11.7162 18.629 11.4838L6.90588 4.53682Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/pause-solid.svg
var pause_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M6 18.4V5.6C6 5.26863 6.26863 5 6.6 5H9.4C9.73137 5 10 5.26863 10 5.6V18.4C10 18.7314 9.73137 19 9.4 19H6.6C6.26863 19 6 18.7314 6 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path><path d="M14 18.4V5.6C14 5.26863 14.2686 5 14.6 5H17.4C17.7314 5 18 5.26863 18 5.6V18.4C18 18.7314 17.7314 19 17.4 19H14.6C14.2686 19 14 18.7314 14 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/maximize.svg
var maximize_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M7 4H4V7" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M17 4H20V7" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M7 20H4V17" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M17 20H20V17" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/closed-captions-tag.svg
var closed_captions_tag_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M1 15V9C1 5.68629 3.68629 3 7 3H17C20.3137 3 23 5.68629 23 9V15C23 18.3137 20.3137 21 17 21H7C3.68629 21 1 18.3137 1 15Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M10.5 10L10.3284 9.82843C9.79799 9.29799 9.07857 9 8.32843 9V9C6.76633 9 5.5 10.2663 5.5 11.8284V12.1716C5.5 13.7337 6.76633 15 8.32843 15V15C9.07857 15 9.79799 14.702 10.3284 14.1716L10.5 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M18.5 10L18.3284 9.82843C17.798 9.29799 17.0786 9 16.3284 9V9C14.7663 9 13.5 10.2663 13.5 11.8284V12.1716C13.5 13.7337 14.7663 15 16.3284 15V15C17.0786 15 17.798 14.702 18.3284 14.1716L18.5 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/backward15-seconds.svg
var backward15_seconds_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" stroke-width="1.5" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M3 13C3 17.9706 7.02944 22 12 22C16.9706 22 21 17.9706 21 13C21 8.02944 16.9706 4 12 4" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M9 9L9 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9L13 9C12.4477 9 12 9.44772 12 10L12 11.5C12 12.0523 12.4477 12.5 13 12.5L14 12.5C14.5523 12.5 15 12.9477 15 13.5L15 15C15 15.5523 14.5523 16 14 16L12 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4L4.5 4M4.5 4L6.5 2M4.5 4L6.5 6" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/forward15-seconds.svg
var forward15_seconds_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" stroke-width="1.5" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M21 13C21 17.9706 16.9706 22 12 22C7.02944 22 3 17.9706 3 13C3 8.02944 7.02944 4 12 4" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4H19.5M19.5 4L17.5 2M19.5 4L17.5 6" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M9 9L9 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9L13 9C12.4477 9 12 9.44772 12 10L12 11.5C12 12.0523 12.4477 12.5 13 12.5L14 12.5C14.5523 12.5 15 12.9477 15 13.5L15 15C15 15.5523 14.5523 16 14 16L12 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/sound-high.svg
var sound_high_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M1 13.8571V10.1429C1 9.03829 1.89543 8.14286 3 8.14286H5.9C6.09569 8.14286 6.28708 8.08544 6.45046 7.97772L12.4495 4.02228C13.1144 3.5839 14 4.06075 14 4.85714V19.1429C14 19.9392 13.1144 20.4161 12.4495 19.9777L6.45046 16.0223C6.28708 15.9146 6.09569 15.8571 5.9 15.8571H3C1.89543 15.8571 1 14.9617 1 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M17.5 7.5C17.5 7.5 19 9 19 11.5C19 14 17.5 15.5 17.5 15.5" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M20.5 4.5C20.5 4.5 23 7 23 11.5C23 16 20.5 18.5 20.5 18.5" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/sound-min.svg
var sound_min_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M3.5 13.8571V10.1429C3.5 9.03829 4.39543 8.14286 5.5 8.14286H8.4C8.59569 8.14286 8.78708 8.08544 8.95046 7.97772L14.9495 4.02228C15.6144 3.5839 16.5 4.06075 16.5 4.85714V19.1429C16.5 19.9392 15.6144 20.4161 14.9495 19.9777L8.95046 16.0223C8.78708 15.9146 8.59569 15.8571 8.4 15.8571H5.5C4.39543 15.8571 3.5 14.9617 3.5 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M20.5 15L20.5 9" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/sound-off.svg
var sound_off_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M18 14L20.0005 12M22 10L20.0005 12M20.0005 12L18 10M20.0005 12L22 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M2 13.8571V10.1429C2 9.03829 2.89543 8.14286 4 8.14286H6.9C7.09569 8.14286 7.28708 8.08544 7.45046 7.97772L13.4495 4.02228C14.1144 3.5839 15 4.06075 15 4.85714V19.1429C15 19.9392 14.1144 20.4161 13.4495 19.9777L7.45046 16.0223C7.28708 15.9146 7.09569 15.8571 6.9 15.8571H4C2.89543 15.8571 2 14.9617 2 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/refresh-double.svg
var refresh_double_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M21.1679 8C19.6247 4.46819 16.1006 2 11.9999 2C6.81459 2 2.55104 5.94668 2.04932 11" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M17 8H21.4C21.7314 8 22 7.73137 22 7.4V3" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M2.88146 16C4.42458 19.5318 7.94874 22 12.0494 22C17.2347 22 21.4983 18.0533 22 13" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M7.04932 16H2.64932C2.31795 16 2.04932 16.2686 2.04932 16.6V21" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/get.js
var ZornVideoPlayer = document.querySelector(".zorn-player");
var VideoContainer = document.querySelector(".video-container");
var VideoControls = document.querySelector(".zorn-player-controls");
var PlayIcon = play_solid_default;
var PauseIcon = pause_solid_default;
var FullcreenIcon = maximize_default;
var CaptionsIcon = closed_captions_tag_default;
var Backward15Icon = backward15_seconds_default;
var Forward15Icon = forward15_seconds_default;
var VolumeHighIcon = sound_high_default;
var VolumeMinIcon = sound_min_default;
var VolumeOffIcon = sound_off_default;
var RefreshIcon = refresh_double_default;
// src/themes/default.js
function Title() {
let VideoTitle = ZornVideoPlayer.getAttribute("video-title");
document.querySelector(".zorn-title").innerHTML = VideoTitle;
if (ZornVideoPlayer.hasAttribute("video-title")) {
document.querySelector(".zorn-title").style.display = "inherit";
} else {
document.querySelector(".zorn-title").style.display = "none";
}
}
var Controls = `
<div oncontextmenu="return false" class="zorn-player-controls">
<div class="row-2">
<div class="video-progress">
<progress id="progress-bar" value="0" min="0"></progress>
<input class="seek" id="seek" value="0" min="0" type="range" step="1">
<div class="seek-tooltip" id="seek-tooltip">00:00</div>
</div>
</div>
<div class="row-1">
<div class="row-1-start">
<div class="volume-controls">
<button data-title="Mute (m)" class="volume-button" id="volume-button">${VolumeHighIcon}</button>
<input class="volume" id="volume" value="1" type="range" max="1" min="0" step="0.01"/>
</div>
<div class="time">
<time id="time-elapsed">00:00</time>
<span> / </span>
<time id="duration">00:00</time>
</div>
</div>
<div class="row-1-center">
<button id="skip-back">${Backward15Icon}</button>
<button id="play-pause">${PlayIcon}</button>
<button id="skip-forth">${Forward15Icon}</button>
</div>
<div class="row-1-end">
<button id="subtitles">${CaptionsIcon}</button>
<button id="fullscreen">${FullcreenIcon}</button>
</div>
</div>
</div>
<style>
:root {
--zorn-progress-bar-bg: rgba(100, 100, 100, 0.5);
--zorn-progress-bar: rgba(255, 0, 0, 0.5);
--zorn-thumb: red;
--zorn-rounded-corners: 4px;
}
.zorn-context-menu {
background: linear-gradient(45deg, #0a141c 0%, rgba(10, 20, 28, 1) 100%);
border-radius: 6px;
border: 1px rgba(255, 255, 255, 0.08) solid;
}
.zorn-context-menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.zorn-context-menu ul li {
padding: 8px 32px 8px 16px;
margin: 4px;
border-radius: 4px;
font-family: arial;
}
.zorn-context-menu ul li:hover {
background: rgba(255, 255, 255, 0.1);
cursor: pointer;
}
.zorn-context-menu ul li i {
font-size: 14px;
margin-right: 12px;
width: 12px;
}
.video-container {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
font-family: Arial, Helvetica, sans-serif;
color: white;
}
.video-container .zorn-player-dialogs #buffering {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 0px 24px;
border-radius: 6px;
}
.video-container .zorn-player-dialogs #buffering h2 {
font-size: 52px;
}
.video-container .zorn-player-dialogs #invalid-src {
display: none;
position: absolute;
top: 50%;
left: 24px;
transform: translate(0px, -50%);
padding: 0px 24px;
border-radius: 6px;
}
.video-container .zorn-player-dialogs #invalid-src h2 {
font-size: 52px;
}
.video-container .zorn-player {
display: inline-flex;
}
.video-container .zorn-title {
position: absolute;
top: 0px;
background: rgb(0 0 0 / 50%);
width: auto;
margin: 12px;
padding: 12px 24px;
border-radius: 10px;
font-size: 18px;
}
.video-container .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-title {
position: absolute;
top: 0px;
width: 100%;
background-image: linear-gradient(to top, rgba(12, 12, 12, 0), rgba(12, 12, 12, 0.75));
padding: 12px 24px;
font-size: 18px;
font-weight: bold;
}
.video-container .subtitles-menu {
display: none;
position: absolute;
right: 60px;
bottom: 70px;
background: #000 9;
list-style: none;
padding: 6px;
border-radius: 6px;
background: rgb(0 0 0 / 50%);
}
.video-container .subtitles-menu button {
background-color: transparent;
color: white;
border: none;
border-radius: 4px;
width: 100%;
text-align: left;
padding: 6px 12px;
cursor: pointer;
}
.video-container .subtitles-menu button:hover {
background: #fff 29;
}
.video-container .subtitles-menu .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-controls {
display: inline-flex;
right: 0;
left: 0;
padding: 10px;
position: absolute;
bottom: -1px;
transition: all 0.2s ease;
background-image: linear-gradient(to bottom, rgba(12, 12, 12, 0), rgba(12, 12, 12, 0.75));
flex-direction: inherit;
}
.video-container .zorn-player-controls .row-1 {
display: flex;
justify-content: space-between;
width: 100%;
}
.video-container .zorn-player-controls .row-1-start {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls .row-1-center {
display: flex;
justify-content: center;
}
.video-container .zorn-player-controls .row-1-end {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls button {
aspect-ratio: 1;
height: 32px;
width: 32px;
color: white;
background-color: transparent;
border: none;
margin: 0px 6px;
}
.volume-controls {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls .volume-controls:hover > #volume {
opacity: 1;
transition: 0.3s opacity, 0.3s width;
margin: 0px;
width: 72px;
}
.video-container .zorn-player-controls #volume {
opacity: 0;
transition: 0.3s opacity, 0.3s width;
margin: 0px 12px 0px -6px;
width: 0px;
}
.video-container .zorn-player-controls #volume-button svg {
aspect-ratio: 1;
height: 16px;
width: 16px;
fill: white;
padding: 3px 0px 0px 0px;
}
.video-container .zorn-player-controls .video-progress {
position: relative;
height: 6.4px;
margin: 24px 0px;
width: 100%;
}
.video-container .zorn-player-controls progress {
border-radius: 1rem;
width: 100%;
height: 8.4px;
position: absolute;
top: 0;
}
.video-container .zorn-player-controls progress::-webkit-progress-bar {
border-radius: 1rem;
background: var(--zorn-progress-bar-bg);
}
.video-container .zorn-player-controls progress::-webkit-progress-value {
background: var(--zorn-progress-bar);
border-radius: 1rem;
}
.video-container .zorn-player-controls progress::-moz-progress-bar {
border-radius: 1rem;
background: var(--zorn-progress-bar-bg);
}
.video-container .zorn-player-controls .seek {
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
margin: 0;
}
.video-container .zorn-player-controls .seek:hover + .seek-tooltip {
display: block;
}
.video-container .zorn-player-controls .seek-tooltip {
display: none;
position: relative;
top: -32px;
margin-left: -30px;
font-size: 12px;
content: attr(data-title);
font-weight: bold;
color: #fff;
background-color: rgba(0, 0, 0, .5);
border-radius: 4px;
padding: 6px;
width: fit-content;
}
.video-container .zorn-player-controls input[type=range] {
height: 8.4px;
background: transparent;
cursor: pointer;
opacity: 0;
}
.video-container .zorn-player-controls input[type=range]:focus {
outline: none;
}
.video-container .zorn-player-controls input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
}
.video-container .zorn-player-controls input[type=range]:focus::-moz-range-track {
outline: none;
}
.video-container .zorn-player-controls input[type=range]::-webkit-slider-runnable-track {
width: 100%;
cursor: pointer;
border-radius: 1.3px;
-webkit-appearance: none;
transition: all 0.4s ease;
}
.video-container .zorn-player-controls input[type=range]::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 10px;
background: var(--zorn-thumb);
cursor: pointer;
-webkit-appearance: none;
margin-left: -1px;
}
.video-container .zorn-player-controls input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
cursor: pointer;
border: 1px solid transparent;
background: transparent;
border-radius: 0;
}
.video-container .zorn-player-controls input[type=range].volume {
height: 5px;
background-color: #fff;
}
.video-container .zorn-player-controls input[type=range].volume::-webkit-slider-runnable-track {
background-color: transparent;
}
.video-container .zorn-player-controls input[type=range].volume::-webkit-slider-thumb {
margin-left: 0;
height: 14px;
width: 14px;
background: #fff;
}
.video-container .zorn-player-controls input[type=range].volume::-moz-range-thumb {
border: 1px solid #fff;
background: #fff;
}
.video-container .zorn-player-controls input[type="range"]::-moz-range-thumb {
height: 12px;
width: 12px;
border-radius: 10px;
border: none;
background: var(--zorn-thumb);
cursor: pointer;
}
.video-container .zorn-player-controls .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-controls #progress-bar {
background: var(--zorn-progress-bar-bg);
border: none;
border-radius: 10px;
}
div#buffering {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
animation: 1s spin linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>
`;
ZornVideoPlayer.insertAdjacentHTML("afterend", Controls);
if (ZornVideoPlayer.getAttribute("layout") === "default") {
Title();
}
// src/events.js
function Events() {
ZornVideoPlayer.addEventListener("error", function(event) {
document.querySelector("#invalid-src").style.display = "inherit";
document.querySelector(".zorn-player-controls").style.display = "none";
videoContainer.style.backgroundColor = "#101010";
setTimeout(() => {
ZornVideoPlayer.style.opacity = "0.10";
document.querySelector("#buffering").style.display = "none";
}, 168);
}, true);
ZornVideoPlayer.onwaiting = (event) => {
document.querySelector("#buffering").style.display = "inherit";
ZornVideoPlayer.style.transition = "5s opacity";
ZornVideoPlayer.style.opacity = "0.25";
};
ZornVideoPlayer.oncanplaythrough = (event) => {
document.querySelector("#buffering").style.display = "none";
ZornVideoPlayer.style.transition = "0.3s opacity";
ZornVideoPlayer.style.opacity = "1";
};
}
// src/functions/PlayPause.js
function PlayPause() {
const Button_PlayPause = document.querySelector(".zorn-player-controls #play-pause");
Button_PlayPause.addEventListener("click", Toggle_PlayPause);
ZornVideoPlayer.addEventListener("click", Toggle_PlayPause);
ZornVideoPlayer.addEventListener("play", Update_PlayPauseButton);
ZornVideoPlayer.addEventListener("pause", Update_PlayPauseButton);
function Toggle_PlayPause() {
if (ZornVideoPlayer.paused || ZornVideoPlayer.ended) {
ZornVideoPlayer.play();
} else {
ZornVideoPlayer.pause();
}
}
function Update_PlayPauseButton() {
if (ZornVideoPlayer.paused) {
Button_PlayPause.setAttribute("data-title", "Play (K)");
Button_PlayPause.innerHTML = `${PlayIcon}`;
} else {
Button_PlayPause.setAttribute("data-title", "Pause (K)");
Button_PlayPause.innerHTML = `${PauseIcon}`;
}
}
}
// src/functions/SkipAround.js
function SkipAround() {
const Button_SkipBack = document.querySelector(".zorn-player-controls #skip-back");
const Button_SkipForth = document.querySelector(".zorn-player-controls #skip-forth");
Button_SkipBack.addEventListener("click", Toggle_SkipBack);
Button_SkipForth.addEventListener("click", Toggle_SkipForth);
function Toggle_SkipBack() {
Skip(-10);
}
function Toggle_SkipForth() {
Skip(10);
}
function Skip(value) {
ZornVideoPlayer.currentTime += value;
}
}
// src/functions/Fullscreen.js
function Fullscreen() {
const Button_Fullscreen = document.getElementById("fullscreen");
function Toggle_Fullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
VideoContainer.webkitRequestFullscreen();
} else {
VideoContainer.requestFullscreen();
}
}
Button_Fullscreen.onclick = Toggle_Fullscreen;
function Update_FullscreenButton() {
if (document.fullscreenElement) {
Button_Fullscreen.setAttribute("data-title", "Exit full screen (f)");
} else {
Button_Fullscreen.setAttribute("data-title", "Full screen (f)");
}
}
ZornVideoPlayer.addEventListener("dblclick", () => {
Toggle_Fullscreen();
});
}
// src/functions/Subtitles.js
function Subtitles() {
var subtitles = document.querySelector('.zorn-player-controls #subtitles')
var subtitleMenuButtons = []
var createMenuItem = function(id, lang, label) {
var listItem = document.createElement('li')
var button = listItem.appendChild(document.createElement('button'))
button.setAttribute('id', id)
button.className = 'subtitles-button'
if (lang.length > 0) button.setAttribute('lang', lang)
button.value = label
button.setAttribute('data-state', 'inactive')
button.appendChild(document.createTextNode(label))
button.addEventListener('click', function(e) {
subtitleMenuButtons.map(function(v, i, a) {
subtitleMenuButtons[i].setAttribute('data-state', 'inactive')
})
var lang = this.getAttribute('lang')
for (var i = 0; i < ZornVideoPlayer.textTracks.length; i++) {
if (ZornVideoPlayer.textTracks[i].language == lang) {
ZornVideoPlayer.textTracks[i].mode = 'showing'
this.setAttribute('data-state', 'active')
}
else {
ZornVideoPlayer.textTracks[i].mode = 'hidden'
}
}
subtitlesMenu.style.display = 'none'
})
subtitleMenuButtons.push(button)
return listItem
}
var subtitlesMenu
if (ZornVideoPlayer.textTracks) {
var df = document.createDocumentFragment()
var subtitlesMenu = df.appendChild(document.createElement('ul'))
subtitlesMenu.className = 'subtitles-menu'
subtitlesMenu.appendChild(createMenuItem('subtitles-off', '', 'Off'))
for (var i = 0; i < ZornVideoPlayer.textTracks.length; i++) {
subtitlesMenu.appendChild(createMenuItem('subtitles-' + ZornVideoPlayer.textTracks[i].language, ZornVideoPlayer.textTracks[i].language, ZornVideoPlayer.textTracks[i].label))
}
VideoContainer.appendChild(subtitlesMenu)
}
subtitles.addEventListener('click', function(e) {
if (subtitlesMenu) {
subtitlesMenu.style.display = (subtitlesMenu.style.display == 'block' ? 'none' : 'block')
}
})
}
// src/functions/Volume.js
function Volume() {
const Button_Volume = document.getElementById("volume-button");
const volume2 = document.getElementById("volume");
function Update_Volme() {
if (ZornVideoPlayer.muted) {
ZornVideoPlayer.muted = false;
}
ZornVideoPlayer.volume = volume2.value;
}
volume2.addEventListener("input", Update_Volme);
function Update_Volume_Icon() {
Button_Volume.setAttribute("data-title", "Mute (M)");
if (ZornVideoPlayer.muted || ZornVideoPlayer.volume === 0) {
Button_Volume.innerHTML = `${VolumeOffIcon}`;
Button_Volume.setAttribute("data-title", "Unmute (M)");
} else if (ZornVideoPlayer.volume > 0 && ZornVideoPlayer.volume <= 0.5) {
Button_Volume.innerHTML = `${VolumeMinIcon}`;
} else {
Button_Volume.innerHTML = `${VolumeHighIcon}`;
}
}
ZornVideoPlayer.addEventListener("volumechange", Update_Volume_Icon);
function Toggle_Mute() {
ZornVideoPlayer.muted = !ZornVideoPlayer.muted;
if (ZornVideoPlayer.muted) {
volume2.setAttribute("data-volume", volume2.value);
volume2.value = 0;
} else {
volume2.value = volume2.dataset.volume;
}
}
Button_Volume.addEventListener("click", Toggle_Mute);
}
// src/functions/Seek.js
function Seek() {
const timeElapsed = document.getElementById("time-elapsed");
const duration = document.getElementById("duration");
function formatTime(timeInSeconds) {
const result = new Date(timeInSeconds * 1e3).toISOString().substr(11, 8);
return {
minutes: result.substr(3, 2),
seconds: result.substr(6, 2)
};
}
;
function initializeVideo() {
const videoDuration = Math.round(ZornVideoPlayer.duration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
ZornVideoPlayer.addEventListener("loadedmetadata", initializeVideo);
function updateTimeElapsed() {
const time = formatTime(Math.round(ZornVideoPlayer.currentTime));
timeElapsed.innerText = `${time.minutes}:${time.seconds}`;
timeElapsed.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
ZornVideoPlayer.addEventListener("timeupdate", updateTimeElapsed);
const progressBar = document.getElementById("progress-bar");
const seek = document.getElementById("seek");
function initializeVideo() {
const videoDuration = Math.round(ZornVideoPlayer.duration);
seek.setAttribute("max", videoDuration);
progressBar.setAttribute("max", videoDuration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
function updateProgress() {
seek.value = Math.floor(ZornVideoPlayer.currentTime);
progressBar.value = Math.floor(ZornVideoPlayer.currentTime);
}
ZornVideoPlayer.addEventListener("timeupdate", updateProgress);
const seekTooltip = document.getElementById("seek-tooltip");
function updateSeekTooltip(event) {
const skipTo = Math.round(event.offsetX / event.target.clientWidth * parseInt(event.target.getAttribute("max"), 10));
seek.setAttribute("data-seek", skipTo);
const t = formatTime(skipTo);
seekTooltip.textContent = `${t.minutes}:${t.seconds}`;
const rect = ZornVideoPlayer.getBoundingClientRect();
seekTooltip.style.left = `${event.pageX - rect.left}px`;
}
seek.addEventListener("mousemove", updateSeekTooltip);
function skipAhead(event) {
const skipTo = event.target.dataset.seek ? event.target.dataset.seek : event.target.value;
ZornVideoPlayer.currentTime = skipTo;
progressBar.value = skipTo;
seek.value = skipTo;
}
seek.addEventListener("input", skipAhead);
initializeVideo();
}
// src/dialogs/Buffering.js
var BufferDialog = `
<div id="buffering" class="zorn-dialog">
${RefreshIcon}
</div>
`;
ZornVideoPlayer.insertAdjacentHTML("afterend", BufferDialog);
// src/functions/AutoToggleControls.js
function AutoToggleControls() {
function Hide_Controls2() {
if (ZornVideoPlayer.paused) {
return;
} else {
document.querySelector(".zorn-player-controls").classList.add("hide");
}
}
function Show_Controls2() {
document.querySelector(".zorn-player-controls").classList.remove("hide");
}
ZornVideoPlayer.addEventListener("mouseenter", Show_Controls2);
ZornVideoPlayer.addEventListener("mouseleave", Hide_Controls2);
document.querySelector(".zorn-player-controls").addEventListener("mouseenter", Show_Controls2);
document.querySelector(".zorn-player-controls").addEventListener("mouseleave", Hide_Controls2);
var mouseTimer = null, cursorVisible = true;
function Hide_Cursor() {
mouseTimer = null;
VideoContainer.style.cursor = "none";
cursorVisible = false;
Hide_Controls2();
}
document.onmousemove = function() {
if (mouseTimer) {
window.clearTimeout(mouseTimer);
Show_Controls2();
}
if (!cursorVisible) {
VideoContainer.style.cursor = "default";
cursorVisible = true;
}
mouseTimer = window.setTimeout(Hide_Cursor, 3200);
};
}
// src/functions/KeyboardShortcuts.js
function KeyboardShortcuts(events) {
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-fullscreen")) {
var Fullscreen_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-fullscreen");
} else {
var Fullscreen_KeyboardShortcut = "f";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-mute")) {
var Mute_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-mute");
} else {
var Mute_KeyboardShortcut = "m";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-playpause")) {
var PlayPause_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-playpause");
} else {
var PlayPause_KeyboardShortcut = "k";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-skipback")) {
var SkipBack_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-skipback");
} else {
var SkipBack_KeyboardShortcut = "j";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-skipforth")) {
var SkipForth_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-skipforth");
} else {
var SkipForth_KeyboardShortcut = "l";
}
function keyboardShortcuts(event) {
const { key } = event;
if (key === PlayPause_KeyboardShortcut) {
if (ZornVideoPlayer.paused || ZornVideoPlayer.ended) {
ZornVideoPlayer.play();
} else {
ZornVideoPlayer.pause();
}
if (ZornVideoPlayer.paused) {
Show_Controls();
} else {
setTimeout(() => {
Hide_Controls();
}, 1200);
}
} else if (key === Mute_KeyboardShortcut) {
ZornVideoPlayer.muted = !ZornVideoPlayer.muted;
if (ZornVideoPlayer.muted) {
volume.setAttribute("data-volume", volume.value);
volume.value = 0;
} else {
volume.value = volume.dataset.volume;
}
} else if (key === Fullscreen_KeyboardShortcut) {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
VideoContainer.webkitRequestFullscreen();
} else {
VideoContainer.requestFullscreen();
}
} else if (key === SkipBack_KeyboardShortcut) {
ZornVideoPlayer.currentTime += -10;
} else if (key === SkipForth_KeyboardShortcut) {
ZornVideoPlayer.currentTime += 10;
}
}
document.addEventListener("keyup", keyboardShortcuts);
}
// src/index.js
Events();
KeyboardShortcuts();
PlayPause();
AutoToggleControls();
SkipAround();
Fullscreen();
Subtitles();
Volume();
Seek();
Buffering();
})();

View file

@ -0,0 +1,43 @@
// Service Worker is used to cache the offline page
// if the end-user were to ever go offline. An offline page
// should show up if their network condition is offline.
// This is best practice for the Progress Web App and
// provides a better user experience.
// Read more on this topic:
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Offline_and_background_operation#offline_operation
'use strict'
var cacheVersion = 1
var currentCache = {
offline: 'offline-cache' + cacheVersion
}
const offlineUrl = 'offline'
this.addEventListener('install', event => {
event.waitUntil(
caches.open(currentCache.offline).then(function (cache) {
return cache.addAll([
offlineUrl
])
})
)
})
this.addEventListener('fetch', event => {
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(event.request.url).catch(error => {
return caches.match(offlineUrl)
})
)
}
else {
event.respondWith(caches.match(event.request)
.then(function (response) {
return response || fetch(event.request)
})
)
}
})

10
src/styles/embed.scss Normal file
View file

@ -0,0 +1,10 @@
body {background: black}
video {border-radius: 0px !important}
.video-container {
max-width: 100% !important;
width: 100% !important;
position: fixed !important;
top: 0px !important;
left: 0px !important;
height: 100% !important;
}

110
src/styles/index.scss Normal file
View file

@ -0,0 +1,110 @@
body {
background-color: black;
color: white;
font-family: Arial, Helvetica, sans-serif;
position: absolute;
top: 0px;
right: 0px;
margin: auto;
width: calc(100% - 248px);
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
.header {
backdrop-filter: blur(10px) brightness(0.4);
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1000px;
margin: auto;
position: sticky;
top: 12px;
z-index: 5;
border-radius: 10px;
margin-top: 12px;
.header-start {
width: 75%;
display: flex;
align-items: center;
}
.header-end {
display: flex;
align-items: center;
gap: 12px;
}
a, select {
background: transparent;
color: white;
text-decoration: none;
display: flex;
align-items: center;
border-radius: 1rem;
padding: 20px;
font-size: 14px;
border: 1px transparent solid;
gap: 6px;
svg {
width: 20px;
}
&:hover {
color: gray;
}
}
}
span#gradient-header {
background: linear-gradient(180deg, black, transparent);
height: 50px;
display: block;
margin: auto;
max-width: 1000px;
position: sticky;
top: 0px;
}
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
.music-list {
display: grid;
grid-gap: 25px;
max-width: 1000px;
margin: auto;
padding: 0px 24px;
}
.page-title {
max-width: 1000px;
margin: auto;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 58px;
background: black;
z-index: 1;
padding: 0px 24px;
}
.rea-recommendations {
min-width: 200px;
max-width: 360px;
}

58
src/styles/mobile.scss Normal file
View file

@ -0,0 +1,58 @@
.mobile-navigation, .mo {
display: none;
}
.mobile-navigation {
position: fixed;
bottom: 24px;
left: 50%;
width: fit-content;
background: rgba(10, 10, 10, 0.8);
backdrop-filter:blur(4px) contrast(0.5);
padding: 12px;
gap: 24px;
justify-content: space-around;
z-index: 50;
transform: translate(-50%);
border-radius: 10rem;
a {
background: transparent;
border-radius: 3rem;
aspect-ratio: 1;
color: white;
padding: 6px;
display: flex;
align-items: center;
svg {
width: 32px;
height: 28px;
}
}
}
@media only screen and (max-width: 875px) {
body {
width: 100%;
padding-bottom: 124px;
}
.sidebar {
display: none !important;
}
.mobile-navigation, .mo {
display: flex;
}
.header, .page-title {
background: rgb(0 0 0 / 80%);
backdrop-filter: blur(10px) contrast(0.8);
}
}
@media only screen and (max-width: 800px) {
.rea-details-start {
flex-direction: column;
gap: 32px;
align-items: start !important;
}
body > div.content > div.video-rea > div:nth-child(2) {
flex-direction: column;
}
}

0
src/styles/sidebar.scss Normal file
View file

97
src/styles/video.scss Normal file
View file

@ -0,0 +1,97 @@
.video-rea {
max-width: 1000px;
margin: auto;
padding: 0px 24px;
.rea-details-start {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 0px;
button {
background: rgb(45 45 45);
border: none;
color: white;
display: flex;
align-items: center;
gap: 12px;
border-radius: 3rem;
padding: 6px 16px;
margin-left: 6px;
cursor: pointer;
}
.rea-channel {
display: flex;
gap: 12px;
align-items: center;
img {
border-radius: 3rem;
}
.rea-channel div {
text-align: left;
font-size: 14px;
h2, p {
margin: 0px;
}
}
}
}
.comment {
display: flex;
align-items: start;
gap: 12px;
margin-bottom: 24px;
img {
border-radius: 3rem;
width: 48px;
height: 48px;
}
div {
display: flex;
flex-direction: column;
gap: 12px;
a {
color: white;
}
p {
margin: 0px;
}
}
}
}
.video-item {
display: flex;
flex-direction: column;
color: white;
text-decoration: none;
.video-item-thumbnail {
img {
width: 100%;
border-radius: 6px;
aspect-ratio: 16/9;
object-fit: contain;
}
p#vi-length {
margin: -42px 6px 20px 0px;
background: rgb(0 0 0 / 75%);
padding: 6px;
width: max-content;
border-radius: 4px;
position: relative;
float: right;
}
}
.video-item-details {
display: flex;
flex-direction: column;
p {
margin: 0px;
&#vi-title {
font-weight: bold;
}
&#vi-author {
color: darkgrey;
}
}
}
}