initial commit

This commit is contained in:
suroh 2023-10-04 13:38:35 +02:00
commit e20d5bbb46
54 changed files with 6251 additions and 0 deletions

53
.eslintrc.cjs Normal file
View File

@ -0,0 +1,53 @@
module.exports = {
root: true,
'env': {
'browser': true,
'node': true,
'es2019': true
},
'extends': [
'eslint:recommended',
'plugin:lit/recommended',
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
'rules': {
'no-var': 'error',
'semi': [
'error',
'never',
],
'quotes': [
'error',
'single',
],
'object-curly-spacing': [
'warn',
'always',
],
'array-bracket-spacing': [
'warn',
'always',
],
'space-in-parens': [
'warn',
'never',
],
'array-bracket-newline': [
'warn',
'consistent',
],
'object-curly-newline': [
'warn',
{
'consistent': true,
},
],
'space-before-blocks': [
'warn',
'always',
],
},
};

125
.gitignore vendored Normal file
View File

@ -0,0 +1,125 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# sqliteDb files
**/db/*.db
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# MM.Media
public/fonts/*.woff*
public/images
public/media

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta base="/" />
<title>Meredith Monk</title>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Meredith Monk Listening Rooms" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<script type="module" src="/src/App.js"></script>
</head>
<body>
<mm-app></mm-app>
</body>
</html>

2765
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "mm.spa",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@lit-labs/task": "^3.0.2",
"@thepassle/app-tools": "^0.9.8",
"@web/rollup-plugin-polyfills-loader": "^2.0.1",
"lit": "^2.7.6",
"urlpattern-polyfill": "^9.0.0",
"wavesurfer.js": "^7.3.2"
},
"devDependencies": {
"eslint": "^8.49.0",
"eslint-plugin-lit": "^1.9.1",
"sass": "^1.67.0",
"vite": "^4.4.5"
}
}

View File

@ -0,0 +1,42 @@
[
{
"title": "Totally Wired with Kimberly Haas",
"details": "1980s",
"media": "01_Meredith Monk_Totally Wired with Kimberly Haas_1980s"
},
{
"title": "Speaking of Music",
"details": "1984, Other Minds radio program Part 1 of 2",
"media": "02_Speaking of Music, Meredith Monk. 1984 Other Minds radio program_Part 1 of 2"
},
{
"title": "Speaking of Music",
"details": "1984, Other Minds radio program Part 2 of 2",
"media": "03_Speaking of Music_ Meredith Monk, 1984. Other Minds radio program_Part 2 of 2"
},
{
"title": "Interview with Irene Ferchl Bayn Rundfunk Vielkurjieunal",
"details": "1986",
"media": "04 Meredith Monk Interview with Irene Ferchl_Bayn Rundfunk Vielkurjieunal_1986"
},
{
"title": "Terry Gross Interview",
"details": "1988",
"media": "05 Terry Gross interview_1987"
},
{
"title": "WNYC Interview with David Garland",
"details": "2008, Sacred Sundays",
"media": "06 WNYC Interview w_ David Garland_SacredSundays_2008"
},
{
"title": "Mark Frosty McNeil and Meredith Monk in Conversation",
"details": "2019, Red Bull Radio, Fireside Chat",
"media": "07_Red Bull Radio_Fireside Chat_Mark frosty McNeil and MM in conversation_4.11.19"
},
{
"title": "Late Junction",
"details": "2019, BBC",
"media": "08_Late Junction_2019 - Meredith Monk v2 BBC"
}
]

View File

@ -0,0 +1,32 @@
[
{
"title": "Meredith Monk: Inner Voice",
"detail": "2009, 82:00",
"media": "MeredithMonk_Inner Voice.mp4"
},
{
"title": "Meredith Monk: A Documentary",
"detail": "by Sidsel Mundal, Norwegian Television Documentary, 1994",
"media": "1994 documentary by Sidsel Mundal_for Norwegian Television.m4v"
},
{
"title": "Meredith Monk: A Documentary",
"detail": "by Mariusz Grzegorzek, Polish Television Documentary, 1995",
"media": ""
},
{
"title": "Souls messenger",
"detail": "Poland, 2011",
"media": ""
},
{
"title": "Meredith Monk",
"detail": "by Ingo Berhman, ECM Documentary - ECM50 | 1981, 2019",
"media": ""
},
{
"title": "4 American Composers",
"detail": "Peter Greenaway Documentary",
"media": ""
}
]

1
public/data/films.json Normal file
View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,128 @@
[
{
"title": "On Behalf of Nature - Album",
"details": "2016",
"media": "on_behalf_of_nature",
"album": true
},
{
"title": "Piano Songs - Album",
"details": "2014",
"media": "piano_songs",
"album": true
},
{
"title": "Songs of Ascension",
"details": "2011",
"media": "songs_of_ascension",
"album": true
},
{
"title": "impermanance",
"details": "2008",
"media": "impermanance",
"album": true
},
{
"title": "mercy",
"details": "2003",
"media": "mercy",
"album": true
},
{
"title": "Volcano Songs",
"details": "1997",
"media": "volcano_songs",
"album": true
},
{
"title": "ATLAS: an opera in three parts",
"details": "2-CD album, 1993",
"media": "atlas",
"album": true
},
{
"title": "Facing North",
"details": "1992",
"media": "facing_north",
"album": true
},
{
"title": "Book of Days",
"details": "1990",
"media": "book_of_days",
"album": true
},
{
"title": "Do You Be",
"details": "1987",
"media": "do_you_be",
"album": true
},
{
"title": "Turtle Dreams - Album",
"details": "1983",
"media": "turtule_dreams",
"album": true
},
{
"title": "Dolmen Music - Album",
"details": "1981",
"media": "dolmen_music",
"album": true
},
{
"title": "Songs from the Hill/Tablet - Album",
"details": "1977",
"media": "songs_from_the_hill",
"album": true
},
{
"title": "Our Lady of Late - Album",
"details": "1973",
"media": "our_lady_of_late",
"album": true
},
{
"title": "KEY: an album of invisible theater - Album",
"details": "1971",
"media": "key",
"album": true
},
{
"title": "MEMORY GAME",
"details": "2020",
"media": "memory_game",
"album": true
},
{
"title": "MONK MIX: Remixes and Interpretations of Music by Meredith Monk",
"details": "2-CD Album, 2012",
"media": "monk_mix",
"album": true
},
{
"title": "Beginnings",
"details": "Album, 2009",
"media": "beginnings",
"album": true
},
{
"title": "Radio Songs",
"details": "LP/Album, 2014",
"media": "radio_songs",
"album": true
},
{
"title": "Monk and the Abbiss",
"details": "1996",
"media": "monk_and_the_abbiss",
"album": true
},
{
"title": "Biography (from Education of the Girlchild)",
"details": "recording, 1978",
"media": "biography",
"album": true
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,54 @@
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-Light.woff2') format('woff2'),
url('Theinhardt-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-LightIta.woff2') format('woff2'),
url('Theinhardt-LightIta.woff') format('woff');
font-weight: 300;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-Medium.woff2') format('woff2'),
url('Theinhardt-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-RegularIta.woff2') format('woff2'),
url('Theinhardt-RegularIta.woff') format('woff');
font-weight: normal;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-Regular.woff2') format('woff2'),
url('Theinhardt-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Theinhardt';
src: url('Theinhardt-MediumIta.woff2') format('woff2'),
url('Theinhardt-MediumIta.woff') format('woff');
font-weight: 500;
font-style: italic;
font-display: swap;
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

34
src/App.js Normal file
View File

@ -0,0 +1,34 @@
import { LitElement } from 'lit'
import Router from './api/Router.js'
// global reset
import './assets/styles/main.scss'
export class App extends LitElement {
static properties = {}
constructor() {
super()
}
firstUpdated() {
Router.addEventListener('route-changed', () => {
if ('startViewTransition' in document) {
return document.startViewTransition(() => {
this.requestUpdate()
})
}
else {
this.requestUpdate()
}
})
}
render() {
return Router.render()
}
}
customElements.define('mm-app', App)

105
src/api/Router.js Normal file
View File

@ -0,0 +1,105 @@
// // Conditional ESM module loading (Node.js and browser)
// // @ts-ignore: Property 'UrlPattern' does not exist
if (!globalThis.URLPattern) {
await import('urlpattern-polyfill')
}
import { Router } from '@thepassle/app-tools/router.js'
import { lazy } from '@thepassle/app-tools/router/plugins/lazy.js'
import { html } from 'lit'
// home view
import '../views/home.js'
const baseURL = import.meta.env.BASE_URL
export default new Router({
fallback: '/404',
routes: [
{
path: resolveRouterPath(),
title: 'home',
short: 'home',
icon: 'home',
render: () => html`<mm-home></mm-home>`
},
{
path: resolveRouterPath('documentaries_interviews'),
title: 'Video Documentaries & Interviews',
short: 'docs & interviews',
icon: 'film',
plugins: [
lazy(() => import('../views/videos.js'))
],
render: () => html`<mm-videos></mm-videos>`
},
{
path: resolveRouterPath('audio_interviews'),
title: 'Audio Interviews',
short: 'interviews',
icon: 'headphones',
plugins: [
lazy(() => import('../views/audio.js'))
],
render: () => html`<mm-audio></mm-audio>`
},
{
path: resolveRouterPath('recordings_live'),
title: 'Recordings of Live Works',
short: 'live works',
icon: 'film',
plugins: [
lazy(() => import('../views/videos.js'))
],
render: () => html`<mm-videos></mm-videos>`
},
{
path: resolveRouterPath('music_sound'),
title: 'Music & Sound',
short: 'music & sound',
icon: 'headphones',
plugins: [
lazy(() => import('../views/audio.js'))
],
render: () => html`<mm-audio></mm-audio>`
},
{
path: resolveRouterPath('films'),
title: 'Films',
short: 'films',
icon: 'film',
plugins: [
lazy(() => import('../views/videos.js'))
],
render: () => html`<mm-videos></mm-videos>`
},
{
path: resolveRouterPath('images'),
title: 'Images',
short: 'images',
icon: 'camera',
plugins: [
lazy(() => import('../views/images.js'))
],
render: () => html`<mm-images></mm-images>`
},
{
path: '/404',
title: 'Not found',
hide: true,
plugins: [
lazy(() => import('../views/fourohfour.js'))
],
render: () => html`<mm-404></mm-404>`
}
]
})
export function resolveRouterPath(unresolvedPath) {
let resolvedPath = baseURL
if(unresolvedPath) {
resolvedPath = resolvedPath + unresolvedPath
}
return resolvedPath
}

24
src/api/utils.js Normal file
View File

@ -0,0 +1,24 @@
function pad(d) {
return (d < 10) ? '0' + d.toString() : d.toString();
}
export const formatSeconds = (seconds) => {
let hrs = 0, min = 0, sec = 0
if (seconds) {
const fraction = seconds / 60
min = fraction
if (min > 60) {
hrs = min / 60
min = 60 * (hrs % 1)
}
sec = parseInt(60 * (fraction % 1))
}
hrs = parseInt(hrs)
min = pad(parseInt(min))
sec = pad(sec)
return `${hrs}:${min}:${sec}`
}

View File

@ -0,0 +1,35 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-08e30ed9-75c0-8079-8003-292ade8ea3ef" version="1.1" viewBox="219.5 605.5 79 78" height="78">
<g id="shape-08e30ed9-75c0-8079-8003-292ade8ea3ef" style="fill:currentcolor" clip-path="url(#rumext-id-1405-clip2_9_654)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-1405-clip2_9_654" class="svg-def" transform="matrix(3.905449, 0.000000, 0.000000, 3.860753, 165.254782, 119.487755)">
<rect width="24" height="24" fill="white" transform="translate(12 124)">
</rect>
</clipPath>
</defs>
<g id="shape-08e30ed9-75c0-8079-8003-292ade8ee7b4">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-292ade8ee7b4">
<path rx="0" ry="0" style="fill:currentcolor;" d="M288.702,621.158L288.702,621.158ZC293.167,626.679,295.960,633.331,296.761,640.350C297.562,647.372,296.339,654.472,293.232,660.831C290.125,667.193,285.262,672.556,279.203,676.301C273.144,680.046,266.138,682.023,258.992,681.999L258.978,681.999C257.537,682.007,256.098,681.930,254.666,681.779C249.717,681.250,244.924,679.753,240.565,677.378C236.206,675.004,232.367,671.796,229.271,667.942L229.270,667.942C222.994,660.151,220.107,650.214,221.242,640.319C222.377,630.424,227.443,621.378,235.324,615.174C243.205,608.969,253.257,606.116,263.267,607.240C273.277,608.359,282.426,613.367,288.702,621.158ZZM263.114,609.236L263.114,609.236ZL263.114,609.236ZC261.731,609.089,260.354,609.024,258.978,609.031C252.051,609.039,245.274,611.023,239.463,614.753C233.650,618.479,229.051,623.787,226.220,630.041C223.390,636.292,222.448,643.218,223.509,649.986C224.570,656.758,227.588,663.078,232.199,668.190C236.811,673.301,242.819,676.988,249.500,678.803C256.182,680.613,263.251,680.482,269.857,678.413C276.463,676.347,282.323,672.436,286.733,667.151C291.141,661.870,293.911,655.437,294.708,648.631C295.254,643.994,294.870,639.296,293.577,634.806C292.285,630.316,290.109,626.119,287.175,622.459C284.240,618.799,280.605,615.749,276.476,613.479C272.347,611.213,267.806,609.769,263.114,609.236ZZ">
</path>
</g>
<g id="strokes-08e30ed9-75c0-8079-8003-292ade8ee7b4" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke-width:3;stroke:currentcolor;" d="M288.702,621.158L288.702,621.158ZC293.167,626.679,295.960,633.331,296.761,640.350C297.562,647.372,296.339,654.472,293.232,660.831C290.125,667.193,285.262,672.556,279.203,676.301C273.144,680.046,266.138,682.023,258.992,681.999L258.978,681.999C257.537,682.007,256.098,681.930,254.666,681.779C249.717,681.250,244.924,679.753,240.565,677.378C236.206,675.004,232.367,671.796,229.271,667.942L229.270,667.942C222.994,660.151,220.107,650.214,221.242,640.319C222.377,630.424,227.443,621.378,235.324,615.174C243.205,608.969,253.257,606.116,263.267,607.240C273.277,608.359,282.426,613.367,288.702,621.158ZZM263.114,609.236L263.114,609.236ZL263.114,609.236ZC261.731,609.089,260.354,609.024,258.978,609.031C252.051,609.039,245.274,611.023,239.463,614.753C233.650,618.479,229.051,623.787,226.220,630.041C223.390,636.292,222.448,643.218,223.509,649.986C224.570,656.758,227.588,663.078,232.199,668.190C236.811,673.301,242.819,676.988,249.500,678.803C256.182,680.613,263.251,680.482,269.857,678.413C276.463,676.347,282.323,672.436,286.733,667.151C291.141,661.870,293.911,655.437,294.708,648.631C295.254,643.994,294.870,639.296,293.577,634.806C292.285,630.316,290.109,626.119,287.175,622.459C284.240,618.799,280.605,615.749,276.476,613.479C272.347,611.213,267.806,609.769,263.114,609.236ZZ">
</path>
</g>
</g>
</g>
<g id="shape-08e30ed9-75c0-8079-8003-292ade8f32ac">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-292ade8f32ac">
<path rx="0" ry="0" style="fill:currentcolor" d="M253.561,636.524C255.167,635.466,257.054,634.898,258.986,634.898C261.575,634.898,264.058,635.914,265.890,637.724C267.720,639.535,268.749,641.990,268.749,644.550C268.749,646.457,268.177,648.326,267.104,649.913C266.031,651.500,264.506,652.735,262.722,653.469C260.938,654.198,258.975,654.387,257.081,654.017C255.187,653.646,253.447,652.723,252.082,651.376C250.716,650.025,249.786,648.307,249.409,646.434C249.033,644.562,249.226,642.620,249.965,640.855C250.704,639.091,251.956,637.585,253.561,636.524ZZM254.646,650.971C255.931,651.820,257.441,652.272,258.986,652.272C261.057,652.272,263.044,651.457,264.509,650.009C265.974,648.561,266.796,646.596,266.796,644.550C266.796,643.021,266.338,641.531,265.480,640.261C264.622,638.991,263.402,638.002,261.975,637.415C260.547,636.833,258.977,636.678,257.462,636.975C255.947,637.277,254.555,638.010,253.462,639.091C252.370,640.168,251.626,641.547,251.325,643.044C251.024,644.542,251.178,646.094,251.769,647.504C252.360,648.917,253.362,650.121,254.646,650.971ZZ">
</path>
</g>
<g id="strokes-08e30ed9-75c0-8079-8003-292ade8f32ac" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke-width:3;stroke:currentcolor" d="M253.561,636.524C255.167,635.466,257.054,634.898,258.986,634.898C261.575,634.898,264.058,635.914,265.890,637.724C267.720,639.535,268.749,641.990,268.749,644.550C268.749,646.457,268.177,648.326,267.104,649.913C266.031,651.500,264.506,652.735,262.722,653.469C260.938,654.198,258.975,654.387,257.081,654.017C255.187,653.646,253.447,652.723,252.082,651.376C250.716,650.025,249.786,648.307,249.409,646.434C249.033,644.562,249.226,642.620,249.965,640.855C250.704,639.091,251.956,637.585,253.561,636.524ZZM254.646,650.971C255.931,651.820,257.441,652.272,258.986,652.272C261.057,652.272,263.044,651.457,264.509,650.009C265.974,648.561,266.796,646.596,266.796,644.550C266.796,643.021,266.338,641.531,265.480,640.261C264.622,638.991,263.402,638.002,261.975,637.415C260.547,636.833,258.977,636.678,257.462,636.975C255.947,637.277,254.555,638.010,253.462,639.091C252.370,640.168,251.626,641.547,251.325,643.044C251.024,644.542,251.178,646.094,251.769,647.504C252.360,648.917,253.362,650.121,254.646,650.971ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="20" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-3275de8a-06b0-809d-8003-27fb8ec0beab" version="1.1" viewBox="245 423 20 34.808" height="34.808">
<g id="shape-3275de8a-06b0-809d-8003-27fb8ec0beab">
<g class="fills" id="fills-3275de8a-06b0-809d-8003-27fb8ec0beab">
<path rx="0" ry="0" style="fill:currentcolor" d="M245.000,440.404C245.000,441.073,245.256,441.643,245.787,442.154L261.126,457.159C261.539,457.592,262.089,457.808,262.719,457.808C263.997,457.808,265.000,456.825,265.000,455.527C265.000,454.898,264.744,454.327,264.312,453.895L250.487,440.404L264.312,426.914C264.744,426.461,265.000,425.891,265.000,425.262C265.000,423.983,263.997,423.000,262.719,423.000C262.089,423.000,261.539,423.216,261.126,423.649L245.787,438.654C245.256,439.165,245.020,439.735,245.000,440.404ZZ">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 969 B

View File

@ -0,0 +1,11 @@
<!-- Shape: Mnochrome=camera.fill -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="28" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-08e30ed9-75c0-8079-8003-293d62258e73" version="1.1" viewBox="2029 1889 28 27.034" height="27.034">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e73" width="29" height="28" rx="0" ry="0">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e76">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-293d62258e76">
<path rx="0" ry="0" style="fill:currentcolor" d="M2032.553,1912.508L2053.236,1912.508C2055.589,1912.508,2056.789,1911.331,2056.789,1909.000L2056.789,1897.154C2056.789,1894.823,2055.589,1893.658,2053.236,1893.658L2050.000,1893.658C2049.106,1893.658,2048.835,1893.477,2048.325,1892.911L2047.420,1891.893C2046.855,1891.270,2046.277,1890.931,2045.112,1890.931L2040.598,1890.931C2039.432,1890.931,2038.855,1891.270,2038.289,1891.893L2037.384,1892.911C2036.875,1893.466,2036.592,1893.658,2035.710,1893.658L2032.553,1893.658C2030.188,1893.658,2029.000,1894.823,2029.000,1897.154L2029.000,1909.000C2029.000,1911.331,2030.188,1912.508,2032.553,1912.508ZZM2042.894,1909.170C2039.466,1909.170,2036.717,1906.432,2036.717,1902.981C2036.717,1899.542,2039.466,1896.803,2042.894,1896.803C2046.323,1896.803,2049.061,1899.542,2049.061,1902.981C2049.061,1906.432,2046.311,1909.170,2042.894,1909.170ZZM2042.894,1907.450C2045.361,1907.450,2047.352,1905.470,2047.352,1902.981C2047.352,1900.503,2045.361,1898.512,2042.894,1898.512C2040.428,1898.512,2038.425,1900.503,2038.425,1902.981C2038.425,1905.470,2040.439,1907.450,2042.894,1907.450ZZM2049.819,1898.817C2049.819,1898.048,2050.498,1897.369,2051.290,1897.369C2052.071,1897.369,2052.738,1898.048,2052.738,1898.817C2052.738,1899.621,2052.071,1900.266,2051.290,1900.277C2050.498,1900.277,2049.819,1899.632,2049.819,1898.817ZZ">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

19
src/assets/icons/film.svg Normal file
View File

@ -0,0 +1,19 @@
<!-- Shape: Property 1=video.fill -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-08e30ed9-75c0-8079-8003-293d62258e64" version="1.1" viewBox="1797 1888 29 28" height="28">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e64" style="fill:currentcolor" rx="0" ry="0">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e67" style="fill:currentcolor" clip-path="url(#rumext-id-388-clip0_2201_1398)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-388-clip0_2201_1398" class="svg-def" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 1797.000000, 1888.000000)">
<rect width="28.7695" height="19.043" fill="white" transform="translate(0 4)">
</rect>
</clipPath>
</defs>
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e69">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-293d62258e69">
<path rx="0" ry="0" style="fill:currentcolor" d="M1800.762,1911.031L1813.348,1911.031C1815.703,1911.031,1817.109,1909.672,1817.109,1907.316L1817.109,1895.703C1817.109,1893.359,1815.820,1892.000,1813.465,1892.000L1800.762,1892.000C1798.512,1892.000,1797.000,1893.359,1797.000,1895.703L1797.000,1907.316C1797.000,1909.672,1798.406,1911.031,1800.762,1911.031ZZM1818.703,1904.973L1822.969,1908.664C1823.367,1909.016,1823.836,1909.238,1824.258,1909.238C1825.172,1909.238,1825.769,1908.570,1825.769,1907.609L1825.769,1895.422C1825.769,1894.461,1825.172,1893.793,1824.258,1893.793C1823.836,1893.793,1823.367,1894.016,1822.969,1894.367L1818.703,1898.047L1818.703,1904.973ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,17 @@
<!-- Shape: Property 1=arrow.down.backward.and.arrow.up.forward.square -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="34" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-280c9aed5d3f" version="1.1" viewBox="2164 1264 34 34" height="34">
<g id="shape-b4e55d81-e415-8079-8003-280c9aed5d3f" width="28" height="28" rx="0" ry="0">
<g id="shape-b4e55d81-e415-8079-8003-280c9aedfba7">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-280c9aedfba7">
<path rx="0" ry="0" style="fill:currentcolor" d="M2172.111,1293.869L2189.372,1293.869C2192.360,1293.869,2193.840,1292.389,2193.840,1289.457L2193.840,1272.083C2193.840,1269.151,2192.360,1267.671,2189.372,1267.671L2172.111,1267.671C2169.137,1267.671,2167.643,1269.137,2167.643,1272.083L2167.643,1289.457C2167.643,1292.403,2169.137,1293.869,2172.111,1293.869ZZM2172.140,1291.578C2170.717,1291.578,2169.934,1290.823,2169.934,1289.343L2169.934,1272.196C2169.934,1270.717,2170.717,1269.962,2172.140,1269.962L2189.343,1269.962C2190.752,1269.962,2191.549,1270.717,2191.549,1272.196L2191.549,1289.343C2191.549,1290.823,2190.752,1291.578,2189.343,1291.578L2172.140,1291.578ZZ">
</path>
</g>
</g>
<g id="shape-b4e55d81-e415-8079-8003-280c9aee5cac">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-280c9aee5cac">
<path rx="0" ry="0" style="fill:currentcolor" d="M2186.896,1280.734C2187.451,1280.734,2187.906,1280.293,2187.906,1279.738L2187.906,1274.786C2187.906,1273.932,2187.465,1273.605,2186.725,1273.605L2181.802,1273.605C2181.247,1273.605,2180.791,1274.061,2180.791,1274.616C2180.791,1275.170,2181.247,1275.612,2181.802,1275.612L2182.499,1275.612L2184.676,1275.469L2181.460,1278.529C2181.047,1278.927,2181.033,1279.624,2181.446,1280.037C2181.873,1280.464,2182.542,1280.478,2182.968,1280.023L2186.042,1276.793L2185.900,1279.041L2185.900,1279.738C2185.900,1280.293,2186.341,1280.734,2186.896,1280.734ZZM2179.710,1287.920C2180.265,1287.920,2180.706,1287.465,2180.706,1286.910C2180.706,1286.355,2180.265,1285.914,2179.710,1285.914L2179.013,1285.914L2176.835,1286.056L2180.037,1282.997C2180.450,1282.598,2180.464,1281.901,2180.051,1281.489C2179.639,1281.062,2178.956,1281.047,2178.543,1281.503L2175.469,1284.733L2175.612,1282.485L2175.612,1281.787C2175.612,1281.232,2175.156,1280.791,2174.601,1280.791C2174.046,1280.791,2173.605,1281.232,2173.605,1281.787L2173.605,1286.739C2173.605,1287.593,2174.032,1287.920,2174.772,1287.920L2179.710,1287.920ZZ">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,19 @@
<!-- Shape: Monochrome=headphones -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-08e30ed9-75c0-8079-8003-293d62258e6c" version="1.1" viewBox="1460 1886 28 28">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e6c" style="fill:currentcolor" width="28" height="28" rx="0" ry="0">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e6e" style="fill:currentcolor" clip-path="url(#rumext-id-555-clip0_2124_24753)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-555-clip0_2124_24753" class="svg-def" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 1460.000000, 1886.000000)">
<rect width="23.8594" height="24.8555" fill="white" transform="translate(2 2)">
</rect>
</clipPath>
</defs>
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e70">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-293d62258e70">
<path rx="0" ry="0" style="fill:currentcolor" d="M1462.000,1900.832C1462.000,1904.359,1463.020,1908.250,1464.742,1911.273C1465.035,1911.777,1465.586,1911.918,1466.113,1911.625C1466.605,1911.355,1466.746,1910.805,1466.441,1910.254C1464.871,1907.395,1463.992,1903.984,1463.992,1900.832C1463.992,1894.328,1467.965,1889.992,1473.930,1889.992C1479.883,1889.992,1483.867,1894.328,1483.867,1900.832C1483.867,1903.984,1482.977,1907.395,1481.406,1910.254C1481.102,1910.805,1481.242,1911.355,1481.734,1911.625C1482.262,1911.918,1482.824,1911.777,1483.106,1911.273C1484.828,1908.250,1485.859,1904.359,1485.859,1900.832C1485.859,1893.121,1481.102,1888.000,1473.930,1888.000C1466.746,1888.000,1462.000,1893.121,1462.000,1900.832ZZM1465.668,1910.981C1466.055,1912.328,1467.203,1912.949,1468.563,1912.563C1469.910,1912.176,1470.543,1911.004,1470.144,1909.656L1468.469,1903.938C1468.082,1902.602,1466.934,1901.969,1465.574,1902.356C1464.227,1902.754,1463.594,1903.914,1463.992,1905.273L1465.668,1910.981ZZM1482.180,1910.981L1483.856,1905.273C1484.254,1903.902,1483.633,1902.754,1482.273,1902.356C1480.914,1901.969,1479.777,1902.602,1479.379,1903.938L1477.703,1909.656C1477.305,1911.016,1477.938,1912.176,1479.285,1912.563C1480.656,1912.949,1481.793,1912.328,1482.180,1910.981ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-08e30ed9-75c0-8079-8003-293d62258e4c" version="1.1" viewBox="264 1889.368 30 26.264">
<g id="shape-08e30ed9-75c0-8079-8003-293d62258e4c">
<g class="fills" id="fills-08e30ed9-75c0-8079-8003-293d62258e4c">
<path rx="0" ry="0" style="fill:currentcolor" d="M264.000,1901.757C264.000,1902.395,264.479,1902.936,265.266,1902.936C265.647,1902.936,265.978,1902.728,266.274,1902.481L278.490,1892.244C278.821,1891.948,279.203,1891.948,279.534,1892.244L291.739,1902.481C292.034,1902.728,292.366,1902.936,292.747,1902.936C293.460,1902.936,294.000,1902.494,294.000,1901.793C294.000,1901.376,293.841,1901.043,293.521,1900.785L290.067,1897.873L290.067,1892.797C290.067,1892.244,289.711,1891.888,289.170,1891.888L287.511,1891.888C286.958,1891.888,286.589,1892.244,286.589,1892.797L286.589,1894.961L280.739,1890.031C279.682,1889.147,278.354,1889.147,277.286,1890.031L264.479,1900.785C264.159,1901.056,264.000,1901.413,264.000,1901.757ZZM267.982,1912.805C267.982,1914.576,269.088,1915.632,270.895,1915.632L287.105,1915.632C288.924,1915.632,290.030,1914.576,290.030,1912.805L290.030,1903.440L279.756,1894.825C279.289,1894.419,278.711,1894.432,278.244,1894.825L267.982,1903.428L267.982,1912.805ZZM282.214,1913.224L275.773,1913.224L275.773,1905.247C275.773,1904.669,276.155,1904.301,276.732,1904.301L281.267,1904.301C281.845,1904.301,282.214,1904.669,282.214,1905.247L282.214,1913.224ZZ">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,23 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="18.6" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-f2a697a0-1165-8029-8003-2a7165bc4c8d" version="1.1" viewBox="1131.5 705.5 18.6 20.468" height="20.468">
<g id="shape-f2a697a0-1165-8029-8003-2a7165bc4c8d" style="fill:currentcolor" clip-path="url(#rumext-id-329-clip3_9_654)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-329-clip3_9_654" class="svg-def" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 1116.799974, 531.762000)">
<rect width="24" height="24" fill="currentcolor" transform="translate(12 172)">
</rect>
</clipPath>
</defs>
<g id="shape-f2a697a0-1165-8029-8003-2a7165bccc80">
<g class="fills" id="fills-f2a697a0-1165-8029-8003-2a7165bccc80">
<path rx="0" ry="0" style="fill:currentcolor" d="M1149.600,706.000L1149.600,720.759C1149.591,721.415,1149.363,722.050,1148.953,722.562C1148.542,723.075,1147.971,723.437,1147.331,723.588C1146.692,723.740,1146.020,723.674,1145.422,723.400C1144.824,723.126,1144.335,722.661,1144.032,722.077C1143.729,721.494,1143.630,720.826,1143.750,720.179C1143.870,719.533,1144.203,718.945,1144.695,718.509C1145.188,718.073,1145.811,717.815,1146.468,717.774C1147.124,717.733,1147.775,717.913,1148.317,718.284L1149.100,718.821L1149.100,717.872L1149.100,707.252L1149.100,706.623L1148.487,706.765L1138.287,709.125L1137.900,709.214L1137.900,709.612L1137.900,722.512L1137.900,722.513C1137.901,723.172,1137.681,723.813,1137.276,724.333C1136.870,724.853,1136.302,725.222,1135.662,725.381C1135.022,725.540,1134.348,725.480,1133.746,725.211C1133.144,724.942,1132.649,724.479,1132.341,723.896C1132.034,723.313,1131.930,722.643,1132.047,721.994C1132.164,721.346,1132.495,720.755,1132.987,720.316C1133.479,719.877,1134.104,719.616,1134.762,719.574C1135.420,719.532,1136.073,719.712,1136.617,720.084L1137.400,720.621L1137.400,719.672L1137.400,708.810L1149.600,706.000ZZM1134.683,724.903L1134.683,724.903ZL1134.962,724.962C1135.291,724.954,1135.614,724.881,1135.914,724.748C1136.215,724.615,1136.486,724.424,1136.712,724.186C1136.938,723.948,1137.116,723.667,1137.234,723.361C1137.351,723.056,1137.407,722.732,1137.400,722.406C1137.399,721.913,1137.252,721.432,1136.979,721.023C1136.704,720.612,1136.313,720.292,1135.857,720.102C1135.400,719.913,1134.897,719.864,1134.412,719.960C1133.927,720.056,1133.482,720.295,1133.132,720.644C1132.783,720.994,1132.544,721.439,1132.448,721.924C1132.352,722.409,1132.401,722.912,1132.590,723.369C1132.779,723.826,1133.100,724.216,1133.511,724.491C1133.862,724.725,1134.265,724.866,1134.683,724.903ZZM1146.378,723.097L1146.378,723.097ZL1148.600,720.762L1149.099,720.787C1149.099,720.787,1149.099,720.787,1149.099,720.787C1149.124,720.301,1149.003,719.818,1148.752,719.401C1148.502,718.983,1148.132,718.650,1147.691,718.444C1147.250,718.237,1146.757,718.167,1146.276,718.242C1145.794,718.317,1145.346,718.533,1144.989,718.864C1144.631,719.195,1144.380,719.625,1144.268,720.099C1144.156,720.573,1144.188,721.069,1144.359,721.525C1144.531,721.981,1144.834,722.376,1145.231,722.658C1145.571,722.900,1145.966,723.051,1146.378,723.097ZZ">
</path>
</g>
<g id="strokes-f2a697a0-1165-8029-8003-2a7165bccc80" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke:currentcolor" d="M1149.600,706.000L1149.600,720.759C1149.591,721.415,1149.363,722.050,1148.953,722.562C1148.542,723.075,1147.971,723.437,1147.331,723.588C1146.692,723.740,1146.020,723.674,1145.422,723.400C1144.824,723.126,1144.335,722.661,1144.032,722.077C1143.729,721.494,1143.630,720.826,1143.750,720.179C1143.870,719.533,1144.203,718.945,1144.695,718.509C1145.188,718.073,1145.811,717.815,1146.468,717.774C1147.124,717.733,1147.775,717.913,1148.317,718.284L1149.100,718.821L1149.100,717.872L1149.100,707.252L1149.100,706.623L1148.487,706.765L1138.287,709.125L1137.900,709.214L1137.900,709.612L1137.900,722.512L1137.900,722.513C1137.901,723.172,1137.681,723.813,1137.276,724.333C1136.870,724.853,1136.302,725.222,1135.662,725.381C1135.022,725.540,1134.348,725.480,1133.746,725.211C1133.144,724.942,1132.649,724.479,1132.341,723.896C1132.034,723.313,1131.930,722.643,1132.047,721.994C1132.164,721.346,1132.495,720.755,1132.987,720.316C1133.479,719.877,1134.104,719.616,1134.762,719.574C1135.420,719.532,1136.073,719.712,1136.617,720.084L1137.400,720.621L1137.400,719.672L1137.400,708.810L1149.600,706.000ZZM1134.683,724.903L1134.683,724.903ZL1134.962,724.962C1135.291,724.954,1135.614,724.881,1135.914,724.748C1136.215,724.615,1136.486,724.424,1136.712,724.186C1136.938,723.948,1137.116,723.667,1137.234,723.361C1137.351,723.056,1137.407,722.732,1137.400,722.406C1137.399,721.913,1137.252,721.432,1136.979,721.023C1136.704,720.612,1136.313,720.292,1135.857,720.102C1135.400,719.913,1134.897,719.864,1134.412,719.960C1133.927,720.056,1133.482,720.295,1133.132,720.644C1132.783,720.994,1132.544,721.439,1132.448,721.924C1132.352,722.409,1132.401,722.912,1132.590,723.369C1132.779,723.826,1133.100,724.216,1133.511,724.491C1133.862,724.725,1134.265,724.866,1134.683,724.903ZZM1146.378,723.097L1146.378,723.097ZL1148.600,720.762L1149.099,720.787C1149.099,720.787,1149.099,720.787,1149.099,720.787C1149.124,720.301,1149.003,719.818,1148.752,719.401C1148.502,718.983,1148.132,718.650,1147.691,718.444C1147.250,718.237,1146.757,718.167,1146.276,718.242C1145.794,718.317,1145.346,718.533,1144.989,718.864C1144.631,719.195,1144.380,719.625,1144.268,720.099C1144.156,720.573,1144.188,721.069,1144.359,721.525C1144.531,721.981,1144.834,722.376,1145.231,722.658C1145.571,722.900,1145.966,723.051,1146.378,723.097ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="48.947" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-88ca4951-8a7d-80f2-8003-2e6a3ecede70" version="1.1" viewBox="1921.058 3266.8 18.017 24.057" height="24.057">
<g id="shape-88ca4951-8a7d-80f2-8003-2e6a3ecede70">
<g class="fills" id="fills-88ca4951-8a7d-80f2-8003-2e6a3ecede70">
<path rx="0" ry="0" style="fill:currentcolor" d="M1923.022,3290.857L1926.389,3290.857C1927.674,3290.857,1928.353,3290.178,1928.353,3288.878L1928.353,3268.764C1928.353,3267.420,1927.674,3266.800,1926.389,3266.800L1923.022,3266.800C1921.737,3266.800,1921.058,3267.479,1921.058,3268.764L1921.058,3288.878C1921.058,3290.178,1921.737,3290.857,1923.022,3290.857ZZM1933.758,3290.857L1937.111,3290.857C1938.410,3290.857,1939.075,3290.178,1939.075,3288.878L1939.075,3268.764C1939.075,3267.420,1938.410,3266.800,1937.111,3266.800L1933.758,3266.800C1932.459,3266.800,1931.779,3267.479,1931.779,3268.764L1931.779,3288.878C1931.779,3290.178,1932.459,3290.857,1933.758,3290.857ZZ">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="43" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-28125ae0f790" version="1.1" viewBox="223 1753 43 43" height="43">
<g id="shape-b4e55d81-e415-8079-8003-28125ae0f790">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-28125ae0f790">
<path rx="0" ry="0" style="fill:currentcolor" d="M244.500,1796.000C247.437,1796.000,250.198,1795.439,252.784,1794.314C255.384,1793.190,257.674,1791.638,259.655,1789.656C261.637,1787.674,263.190,1785.390,264.314,1782.806C265.438,1780.206,266.000,1777.437,266.000,1774.500C266.000,1771.563,265.438,1768.802,264.314,1766.216C263.190,1763.618,261.637,1761.326,259.655,1759.346C257.674,1757.364,255.384,1755.810,252.784,1754.686C250.184,1753.563,247.416,1753.000,244.479,1753.000C241.542,1753.000,238.774,1753.563,236.174,1754.686C233.588,1755.810,231.305,1757.364,229.323,1759.346C227.356,1761.326,225.810,1763.618,224.686,1766.216C223.562,1768.802,223.000,1771.563,223.000,1774.500C223.000,1777.437,223.562,1780.206,224.686,1782.806C225.810,1785.390,227.363,1787.674,229.345,1789.656C231.326,1791.638,233.610,1793.190,236.195,1794.314C238.795,1795.439,241.563,1796.000,244.500,1796.000ZL244.500,1796.000ZM244.500,1792.417C242.013,1792.417,239.687,1791.954,237.523,1791.026C235.359,1790.098,233.455,1788.820,231.811,1787.190C230.181,1785.545,228.902,1783.642,227.974,1781.478C227.061,1779.313,226.604,1776.988,226.604,1774.500C226.604,1772.014,227.061,1769.687,227.974,1767.522C228.902,1765.360,230.181,1763.455,231.811,1761.810C233.441,1760.166,235.338,1758.889,237.502,1757.974C239.666,1757.048,241.992,1756.583,244.479,1756.583C246.966,1756.583,249.292,1757.048,251.456,1757.974C253.634,1758.889,255.538,1760.166,257.168,1761.810C258.812,1763.455,260.098,1765.360,261.026,1767.522C261.953,1769.687,262.417,1772.014,262.417,1774.500C262.417,1776.988,261.953,1779.313,261.026,1781.478C260.112,1783.642,258.833,1785.545,257.189,1787.190C255.545,1788.820,253.641,1790.098,251.477,1791.026C249.313,1791.954,246.987,1792.417,244.500,1792.417ZL244.500,1792.417ZM240.558,1782.741L252.573,1775.660C253.009,1775.393,253.219,1775.014,253.205,1774.522C253.205,1774.029,252.995,1773.656,252.573,1773.405L240.558,1766.280C240.109,1766.013,239.645,1765.970,239.167,1766.153C238.689,1766.335,238.450,1766.681,238.450,1767.187L238.450,1781.835C238.450,1782.355,238.675,1782.714,239.125,1782.910C239.589,1783.093,240.067,1783.038,240.558,1782.741ZL240.558,1782.741Z">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

23
src/assets/icons/play.svg Normal file
View File

@ -0,0 +1,23 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="60.407" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-291b04b22fb6" version="1.1" viewBox="1606.762 1456.5 60.407 81" height="81">
<g id="shape-b4e55d81-e415-8079-8003-291b04b22fb6" style="fill:currentcolor" clip-path="url(#rumext-id-15137-clip4_302_1179)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-15137-clip4_302_1179" class="svg-def" transform="matrix(4.168817, 0.000000, 0.000000, 4.168817, 189.863929, 1444.889380)">
<rect width="24" height="24" fill="white" transform="translate(334.5 0.5)">
</rect>
</clipPath>
</defs>
<g id="shape-b4e55d81-e415-8079-8003-291b04b27892">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-291b04b27892">
<path rx="0" ry="0" style="fill:currentcolor" d="M1610.459,1536.553L1610.459,1536.553ZL1610.367,1536.619C1609.179,1537.534,1607.262,1536.734,1607.262,1534.936L1607.262,1459.064C1607.262,1457.266,1609.179,1456.466,1610.367,1457.381L1610.413,1457.417L1610.463,1457.450L1665.700,1495.178L1665.700,1495.178L1665.721,1495.191C1666.984,1496.033,1666.984,1497.758,1665.721,1498.601L1665.721,1498.600L1665.696,1498.617L1610.459,1536.553ZZ">
</path>
</g>
<g id="strokes-b4e55d81-e415-8079-8003-291b04b27892" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke-width:1;stroke:currentcolor;" d="M1610.459,1536.553L1610.459,1536.553ZL1610.367,1536.619C1609.179,1537.534,1607.262,1536.734,1607.262,1534.936L1607.262,1459.064C1607.262,1457.266,1609.179,1456.466,1610.367,1457.381L1610.413,1457.417L1610.463,1457.450L1665.700,1495.178L1665.700,1495.178L1665.721,1495.191C1666.984,1496.033,1666.984,1497.758,1665.721,1498.601L1665.721,1498.600L1665.696,1498.617L1610.459,1536.553ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="46.667" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-28248f46222a" version="1.1" viewBox="1468.595 1470.333 46.667 51.102" height="51.102">
<g id="shape-b4e55d81-e415-8079-8003-28248f46222a">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-28248f46222a">
<path rx="0" ry="0" style="fill:currentcolor" d="M1491.928,1521.436C1504.693,1521.436,1515.262,1510.867,1515.262,1498.102C1515.262,1486.847,1507.049,1477.308,1496.343,1475.204L1496.343,1471.955C1496.343,1470.331,1495.222,1469.896,1493.987,1470.789L1486.690,1475.890C1485.637,1476.622,1485.615,1477.720,1486.690,1478.475L1493.964,1483.599C1495.222,1484.514,1496.343,1484.079,1496.343,1482.432L1496.343,1479.161C1504.990,1481.128,1511.350,1488.838,1511.350,1498.102C1511.350,1508.900,1502.726,1517.547,1491.928,1517.547C1481.131,1517.547,1472.461,1508.900,1472.484,1498.102C1472.507,1491.606,1475.664,1485.864,1480.536,1482.387C1481.474,1481.678,1481.771,1480.602,1481.200,1479.642C1480.628,1478.704,1479.369,1478.452,1478.363,1479.230C1472.484,1483.508,1468.595,1490.416,1468.595,1498.102C1468.595,1510.867,1479.186,1521.436,1491.928,1521.436ZZM1485.134,1507.482C1486.026,1507.482,1486.598,1506.864,1486.598,1505.903L1486.598,1491.468C1486.598,1490.302,1486.003,1489.684,1484.974,1489.684C1484.334,1489.684,1483.876,1489.890,1483.075,1490.439L1479.918,1492.566C1479.392,1492.955,1479.141,1493.367,1479.141,1493.893C1479.141,1494.648,1479.735,1495.289,1480.468,1495.289C1480.925,1495.289,1481.154,1495.197,1481.588,1494.854L1483.739,1493.275L1483.739,1505.903C1483.739,1506.841,1484.288,1507.482,1485.134,1507.482ZZM1496.755,1507.756C1500.552,1507.756,1503.069,1505.308,1503.069,1501.625C1503.069,1498.262,1500.804,1495.861,1497.533,1495.861C1496.183,1495.861,1494.719,1496.478,1494.033,1497.508L1494.422,1492.566L1501.285,1492.566C1501.948,1492.566,1502.520,1492.017,1502.520,1491.240C1502.520,1490.462,1501.948,1489.959,1501.285,1489.959L1493.964,1489.959C1492.775,1489.959,1492.111,1490.645,1491.997,1491.857L1491.494,1498.446C1491.402,1499.589,1491.997,1500.138,1493.003,1500.138C1493.758,1500.138,1494.079,1500.001,1494.742,1499.498C1495.611,1498.697,1496.320,1498.377,1497.213,1498.377C1498.997,1498.377,1500.187,1499.681,1500.187,1501.671C1500.187,1503.684,1498.791,1505.148,1496.892,1505.148C1495.543,1505.148,1494.376,1504.370,1493.781,1503.181C1493.438,1502.586,1493.026,1502.266,1492.454,1502.266C1491.700,1502.266,1491.173,1502.815,1491.173,1503.593C1491.173,1503.913,1491.242,1504.210,1491.356,1504.508C1491.974,1506.155,1493.987,1507.756,1496.755,1507.756ZZ">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,9 @@
<!-- Shape: svg-path -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="46.667" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-28248f46f412" version="1.1" viewBox="1749.928 1470.333 46.667 51.097" height="51.097">
<g id="shape-b4e55d81-e415-8079-8003-28248f46f412">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-28248f46f412">
<path rx="0" ry="0" style="fill:currentcolor" d="M1773.260,1521.430C1786.027,1521.430,1796.595,1510.861,1796.595,1498.096C1796.595,1490.409,1792.707,1483.501,1786.827,1479.223C1785.822,1478.445,1784.563,1478.697,1783.991,1479.635C1783.419,1480.595,1783.716,1481.671,1784.655,1482.380C1789.526,1485.857,1792.684,1491.599,1792.707,1498.096C1792.729,1508.894,1784.059,1517.541,1773.260,1517.541C1762.464,1517.541,1753.839,1508.894,1753.839,1498.096C1753.839,1488.831,1760.198,1481.122,1768.846,1479.154L1768.846,1482.448C1768.846,1484.073,1769.968,1484.507,1771.225,1483.615L1778.522,1478.491C1779.553,1477.782,1779.576,1476.683,1778.522,1475.929L1771.248,1470.804C1769.968,1469.889,1768.846,1470.324,1768.846,1471.971L1768.846,1475.196C1758.141,1477.278,1749.928,1486.841,1749.928,1498.096C1749.928,1510.861,1760.520,1521.430,1773.260,1521.430ZZM1766.467,1507.475C1767.359,1507.475,1767.930,1506.858,1767.930,1505.897L1767.930,1491.462C1767.930,1490.295,1767.337,1489.677,1766.307,1489.677C1765.667,1489.677,1765.208,1489.883,1764.408,1490.432L1761.252,1492.560C1760.725,1492.949,1760.473,1493.361,1760.473,1493.887C1760.473,1494.642,1761.068,1495.282,1761.800,1495.282C1762.257,1495.282,1762.487,1495.191,1762.920,1494.848L1765.071,1493.269L1765.071,1505.897C1765.071,1506.835,1765.620,1507.475,1766.467,1507.475ZZM1778.089,1507.750C1781.885,1507.750,1784.403,1505.302,1784.403,1501.619C1784.403,1498.256,1782.137,1495.854,1778.866,1495.854C1777.517,1495.854,1776.051,1496.472,1775.367,1497.501L1775.755,1492.560L1782.619,1492.560C1783.280,1492.560,1783.852,1492.011,1783.852,1491.233C1783.852,1490.455,1783.280,1489.952,1782.619,1489.952L1775.298,1489.952C1774.108,1489.952,1773.444,1490.638,1773.331,1491.851L1772.827,1498.439C1772.736,1499.583,1773.331,1500.132,1774.336,1500.132C1775.091,1500.132,1775.411,1499.995,1776.075,1499.492C1776.946,1498.691,1777.654,1498.371,1778.546,1498.371C1780.330,1498.371,1781.520,1499.674,1781.520,1501.665C1781.520,1503.678,1780.125,1505.142,1778.226,1505.142C1776.875,1505.142,1775.710,1504.364,1775.115,1503.175C1774.771,1502.580,1774.359,1502.259,1773.788,1502.259C1773.032,1502.259,1772.507,1502.809,1772.507,1503.586C1772.507,1503.907,1772.576,1504.204,1772.689,1504.502C1773.308,1506.149,1775.320,1507.750,1778.089,1507.750ZZ">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,23 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="29.477" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-291b04b298aa" version="1.1" viewBox="1894.762 1480.5 29.477 31.014" height="31.014">
<g id="shape-b4e55d81-e415-8079-8003-291b04b298aa" style="fill:currentcolor" clip-path="url(currentcolor-id-15145-clip5_302_1179)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-15145-clip5_302_1179" class="svg-def" transform="matrix(2.308802, 0.000000, 0.000000, 2.308802, 989.442280, 1467.147186)">
<rect width="16" height="16" fill="white" transform="translate(390.5 4.5)">
</rect>
</clipPath>
</defs>
<g id="shape-b4e55d81-e415-8079-8003-291b04b2db18">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-291b04b2db18">
<path rx="0" ry="0" style="fill:currentcolor" d="M1916.336,1495.735L1916.544,1495.969L1916.336,1496.203C1916.313,1496.228,1916.286,1496.253,1916.249,1496.278L1916.249,1496.278L1916.235,1496.287L1895.841,1510.294L1895.813,1510.311L1895.790,1510.330C1895.721,1510.383,1895.668,1510.396,1895.626,1510.399C1895.583,1510.401,1895.525,1510.391,1895.467,1510.360C1895.347,1510.297,1895.262,1510.174,1895.262,1510.014L1895.262,1482.000C1895.262,1481.841,1895.347,1481.718,1895.467,1481.654C1895.525,1481.623,1895.583,1481.613,1895.626,1481.616C1895.668,1481.618,1895.721,1481.632,1895.790,1481.684L1895.816,1481.704L1895.843,1481.722L1916.237,1495.652L1916.237,1495.652L1916.249,1495.660C1916.286,1495.684,1916.313,1495.710,1916.336,1495.735ZZM1918.350,1494.965L1918.350,1493.951L1918.350,1481.385C1918.350,1481.176,1918.525,1481.000,1918.735,1481.000L1923.353,1481.000C1923.563,1481.000,1923.738,1481.176,1923.738,1481.385L1923.738,1510.630C1923.738,1510.839,1923.563,1511.014,1923.353,1511.014L1918.735,1511.014C1918.525,1511.014,1918.350,1510.839,1918.350,1510.630L1918.350,1497.987L1918.350,1496.973L1918.350,1494.965ZZ">
</path>
</g>
<g id="strokes-b4e55d81-e415-8079-8003-291b04b2db18" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke-width:1;stroke:currentcolor" d="M1916.336,1495.735L1916.544,1495.969L1916.336,1496.203C1916.313,1496.228,1916.286,1496.253,1916.249,1496.278L1916.249,1496.278L1916.235,1496.287L1895.841,1510.294L1895.813,1510.311L1895.790,1510.330C1895.721,1510.383,1895.668,1510.396,1895.626,1510.399C1895.583,1510.401,1895.525,1510.391,1895.467,1510.360C1895.347,1510.297,1895.262,1510.174,1895.262,1510.014L1895.262,1482.000C1895.262,1481.841,1895.347,1481.718,1895.467,1481.654C1895.525,1481.623,1895.583,1481.613,1895.626,1481.616C1895.668,1481.618,1895.721,1481.632,1895.790,1481.684L1895.816,1481.704L1895.843,1481.722L1916.237,1495.652L1916.237,1495.652L1916.249,1495.660C1916.286,1495.684,1916.313,1495.710,1916.336,1495.735ZZM1918.350,1494.965L1918.350,1493.951L1918.350,1481.385C1918.350,1481.176,1918.525,1481.000,1918.735,1481.000L1923.353,1481.000C1923.563,1481.000,1923.738,1481.176,1923.738,1481.385L1923.738,1510.630C1923.738,1510.839,1923.563,1511.014,1923.353,1511.014L1918.735,1511.014C1918.525,1511.014,1918.350,1510.839,1918.350,1510.630L1918.350,1497.987L1918.350,1496.973L1918.350,1494.965ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,23 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="29.477" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-291b04b1b0a9" version="1.1" viewBox="1338.762 1480.5 29.477 31.014" height="31.014">
<g id="shape-b4e55d81-e415-8079-8003-291b04b1b0a9" style="fill:currentcolor" clip-path="url(currentcolor-id-15149-clip3_302_1179)" rx="0" ry="0">
<defs>
<clipPath id="rumext-id-15149-clip3_302_1179" class="svg-def" transform="matrix(2.308802, 0.000000, 0.000000, 2.308802, 673.557720, 1467.147186)">
<rect width="16" height="16" fill="white" transform="translate(286.5 4.5)">
</rect>
</clipPath>
</defs>
<g id="shape-b4e55d81-e415-8079-8003-291b04b22fb5">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-291b04b22fb5">
<path rx="0" ry="0" style="fill:currentcolor" d="M1344.650,1497.985L1344.650,1510.630C1344.650,1510.839,1344.475,1511.014,1344.265,1511.014L1339.647,1511.014C1339.437,1511.014,1339.262,1510.839,1339.262,1510.630L1339.262,1481.385C1339.262,1481.176,1339.437,1481.000,1339.647,1481.000L1344.265,1481.000C1344.475,1481.000,1344.650,1481.176,1344.650,1481.385L1344.650,1493.953L1344.650,1494.965L1344.650,1496.973L1344.650,1497.985ZZM1346.664,1496.203L1346.456,1495.969L1346.664,1495.735C1346.687,1495.710,1346.714,1495.684,1346.751,1495.660L1346.751,1495.660L1346.763,1495.652L1367.157,1481.722L1367.184,1481.704L1367.210,1481.684C1367.279,1481.632,1367.332,1481.618,1367.374,1481.616C1367.417,1481.613,1367.475,1481.623,1367.533,1481.654C1367.653,1481.718,1367.738,1481.841,1367.738,1482.000L1367.738,1510.014C1367.738,1510.174,1367.653,1510.297,1367.533,1510.360C1367.475,1510.391,1367.417,1510.401,1367.374,1510.399C1367.332,1510.396,1367.279,1510.383,1367.210,1510.330L1367.187,1510.311L1367.159,1510.294L1346.765,1496.287L1346.765,1496.287L1346.751,1496.278C1346.714,1496.254,1346.687,1496.228,1346.664,1496.203ZZ">
</path>
</g>
<g id="strokes-b4e55d81-e415-8079-8003-291b04b22fb5" class="strokes">
<g class="stroke-shape">
<path rx="0" ry="0" style="stroke-width:1;stroke:currentcolor" d="M1344.650,1497.985L1344.650,1510.630C1344.650,1510.839,1344.475,1511.014,1344.265,1511.014L1339.647,1511.014C1339.437,1511.014,1339.262,1510.839,1339.262,1510.630L1339.262,1481.385C1339.262,1481.176,1339.437,1481.000,1339.647,1481.000L1344.265,1481.000C1344.475,1481.000,1344.650,1481.176,1344.650,1481.385L1344.650,1493.953L1344.650,1494.965L1344.650,1496.973L1344.650,1497.985ZZM1346.664,1496.203L1346.456,1495.969L1346.664,1495.735C1346.687,1495.710,1346.714,1495.684,1346.751,1495.660L1346.751,1495.660L1346.763,1495.652L1367.157,1481.722L1367.184,1481.704L1367.210,1481.684C1367.279,1481.632,1367.332,1481.618,1367.374,1481.616C1367.417,1481.613,1367.475,1481.623,1367.533,1481.654C1367.653,1481.718,1367.738,1481.841,1367.738,1482.000L1367.738,1510.014C1367.738,1510.174,1367.653,1510.297,1367.533,1510.360C1367.475,1510.391,1367.417,1510.401,1367.374,1510.399C1367.332,1510.396,1367.279,1510.383,1367.210,1510.330L1367.187,1510.311L1367.159,1510.294L1346.765,1496.287L1346.765,1496.287L1346.751,1496.278C1346.714,1496.254,1346.687,1496.228,1346.664,1496.203ZZ">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,23 @@
<!-- Shape: svg-g -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" width="40" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact" id="screenshot-b4e55d81-e415-8079-8003-2824f34b3d55" version="1.1" viewBox="1357.5 1655 40 31" height="31">
<g id="shape-b4e55d81-e415-8079-8003-2824f34b3d55" style="fill:currentcolor" filter="url(#rumext-id-449-filter6_b_71_6738)" rx="0" ry="0">
<defs>
<filter y="1501.2438669438664" data-old-width="357.2409927942415" filterUnits="userSpaceOnUse" width="357.2409927942415" class="svg-def" data-old-x="1198.877101681342" id="rumext-id-449-filter6_b_71_6738" data-old-height="338.51387733887873" x="1198.877101681342" data-old-y="1501.2438669438664" height="338.51387733887873" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix">
</feFlood>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="40.7742">
</feGaussianBlur>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_71_6738">
</feComposite>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_71_6738" result="shape">
</feBlend>
</filter>
</defs>
<g id="shape-b4e55d81-e415-8079-8003-2824f34b3d56">
<g class="fills" id="fills-b4e55d81-e415-8079-8003-2824f34b3d56">
<path rx="0" ry="0" style="fill:currentcolor" d="M1376.363,1686.000C1377.644,1686.000,1378.589,1685.033,1378.589,1683.760L1378.589,1657.336C1378.589,1656.031,1377.644,1655.000,1376.315,1655.000C1375.450,1655.000,1374.826,1655.371,1373.881,1656.273L1366.691,1663.040C1366.579,1663.137,1366.451,1663.201,1366.291,1663.201L1361.423,1663.201C1358.877,1663.201,1357.500,1664.619,1357.500,1667.294L1357.500,1673.739C1357.500,1676.413,1358.877,1677.831,1361.423,1677.831L1366.291,1677.831C1366.451,1677.831,1366.563,1677.896,1366.691,1677.992L1373.881,1684.808C1374.746,1685.646,1375.466,1686.000,1376.363,1686.000ZZM1391.639,1682.842C1392.440,1683.325,1393.401,1683.148,1393.945,1682.343C1396.187,1679.152,1397.500,1674.883,1397.500,1670.484C1397.500,1666.085,1396.203,1661.799,1393.945,1658.625C1393.401,1657.820,1392.440,1657.626,1391.639,1658.126C1390.823,1658.641,1390.695,1659.689,1391.319,1660.607C1393.145,1663.298,1394.185,1666.810,1394.185,1670.484C1394.185,1674.141,1393.129,1677.654,1391.319,1680.361C1390.711,1681.279,1390.823,1682.310,1391.639,1682.842ZZM1384.738,1678.202C1385.474,1678.685,1386.435,1678.508,1386.964,1677.751C1388.357,1675.881,1389.189,1673.239,1389.189,1670.484C1389.189,1667.729,1388.357,1665.102,1386.964,1663.217C1386.435,1662.444,1385.474,1662.283,1384.738,1662.782C1383.889,1663.330,1383.713,1664.361,1384.402,1665.360C1385.346,1666.730,1385.891,1668.567,1385.891,1670.484C1385.891,1672.401,1385.330,1674.222,1384.402,1675.608C1383.729,1676.607,1383.889,1677.638,1384.738,1678.202ZZ">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,13 @@
@import url(/fonts/stylesheet.css);
h1, h2, h3, h4, h5 {
font-weight: 500;
}
p {
font-weight: 300;
}
.title {
font-weight: 500;
}

View File

@ -0,0 +1,27 @@
/*
Reset by Josh W Comeau
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
#root, #__next {
isolation: isolate;
}

View File

@ -0,0 +1,25 @@
@use 'reset.scss';
@use 'fonts.scss';
body {
font-family: 'Theinhardt', sans-serif;
line-height: 1;
font-size: 16px;
--neutral-900: hsl(0 0% 0%);
--neutral-700: hsl(220 10% 44%);
--neutral-400: hsl(220 10% 88%);
--neutral-200: hsl(230 30% 96%);
--neutral-150: hsl(228 33% 94%);
--neutral-100: hsl(0 100% 100%);
--green-500: hsl(147 100% 27%);
--green-400: hsl(147 100% 34%);
--green-200: hsl(147 100% 50%);
--neutral-gradient-400: linear-gradient(to bottom, hsl(228, 5%, 82%) 0%, var(--neutral-400) 100%);
--green-gradient-400: linear-gradient(to bottom, var(--green-400) 0%, var(--green-500) 100%);
--box-shadow: 0 0 6px 2px #00000010;
}

View File

@ -0,0 +1,79 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import MainCSS from '../assets/styles/main.scss'
import './SvgIcon.js'
class AudioCard extends LitElement {
static properties = {
idx: { type: Number },
icon: { type: String },
details: { type: Object },
selected: { type: Boolean }
}
constructor() {
super()
this.details = {}
this.icon = 'play-circle'
}
clickHandler() {
const event = new CustomEvent('select-audio', {
bubbles: true, composed: true,
detail: { ...this.details, idx: this.idx }
})
this.dispatchEvent(event)
}
render() {
return html`
<div class="card ${this.selected ? 'selected' : ''}" @click=${this.clickHandler}>
<mm-icon name=${this.icon}></mm-icon>
<main>
<p class="heading">${this.details?.title}</p>
<p class="info">${this.details?.details}</p>
</main>
</div>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
* {
margin: 0;
}
.heading {
font-weight: 500;
}
mm-icon {
color: var(--green-400, black);
font-size: 3em;
}
.card {
display: grid;
grid-auto-flow: column;
grid-template-columns: min-content 1fr;
gap: 1em;
align-items: center;
padding-inline: 0.75em;
padding-block: 0.5em;
background: var(--neutral-100, white);
min-height: 3.5em;
border-radius: 0.5em;
box-shadow: var(--box-shadow);
outline: 2px solid transparent;
transition: outline 0.5s ease;
}
.card.selected {
outline: 2px solid var(--green-400, black);
}
` ]
}
customElements.define('mm-acard', AudioCard)

View File

@ -0,0 +1,368 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import { Task } from '@lit-labs/task'
import WaveSurfer from 'wavesurfer.js'
import { formatSeconds } from '../api/utils'
import MainCSS from '../assets/styles/main.scss?inline'
// temp bg image
import Photo from '/images/Meredith Monk (1974) Photo Lauretta HArris.jpg?url'
// components
import './SvgIcon.js'
import './RangeSlider.js'
class AudioPlayer extends LitElement {
static properties = {
details: { type: Object },
data: { state: true },
playing: { state: true },
volume: { state: true },
track: { state: true },
audio: { state: true },
}
constructor() {
super()
this.details = {}
this.data = {}
this.audio = null
this.playing = false
this.audioEl = {}
this.track = 0
}
_getAudio = new Task(
this,
async () => {
if (!this.details.media) return
if (this.details.tracks) {
console.log(this.details.tracks[0])
if (!this.audio) {
this.initAudio()
}
this.audio.load(this.details.tracks[this.track])
return this.audio
} else {
try {
if (this.audio.media) {
this.audio.destroy()
}
const wsEl = this.initWs()
this.audio.load(`/media/${this.details.media}.mp3`, this.details.data)
return wsEl
} catch (err) {
console.error(err)
}
}
},
() => [ this.details ]
)
initAudio() {
this.audio = document.createElement('audio')
this.audio.addEventListener('loadstart', () => {
this.loading = true
})
this.audio.addEventListener('canplay', () => {
this.loading = false
})
this.audio.addEventListener('play', () => {
this.playing = true
})
this.audio.addEventListener('pause', () => {
this.playing = false
})
this.audio.addEventListener('loadedmetadata', () => {
this.data = {
...this.data,
position: 0,
duration: this.audio.duration
}
})
this.audio.addEventListener('ended', () => {
// auto play next track or end if last track
})
this.audio.addEventListener('timeupdate', () => {
this.data = {
...this.data,
position: this.audio.currentTime
}
})
this.audio.addEventListener('seeking', () => {
this.data = {
...this.data,
position: this.audio.currentTime
}
})
}
initWs() {
const div = document.createElement('div')
div.classList.add('waveform')
this.audio = WaveSurfer.create({
container: div,
waveColor: '#ffffff50',
progressColor: 'white',
cursorColor: 'transparent',
fillParent: true,
barWidth: 4,
barGap: 6,
barRadius: 5,
dragToSeek: true,
})
this.audio.setVolume(0.6)
this.volume = this.audio.getVolume()
this.audio.on('ready', (duration) => {
this.data = {
...this.data,
position: 0,
duration
}
})
this.audio.on('play', () => {
this.playing = true
})
this.audio.on('pause', () => {
this.playing = false
})
this.audio.on('interaction', (newTime) => {
this.data = {
...this.data,
position: newTime
}
})
this.audio.on('timeupdate', (currentTime) => {
this.data = {
...this.data,
position: currentTime
}
})
return div
}
togglePlay() {
this.audio.playPause()
}
skipForward() {
this.audio.skip(15)
}
skipBackward() {
this.audio.skip(-15)
}
nextTrack() {
this.dispatchEvent(new Event('next-track', { bubbles: true, composed: true }))
}
prevTrack() {
this.dispatchEvent(new CustomEvent('prev-track', { bubbles: true, composed: true }))
}
volHandler(evt) {
if (evt.detail) {
this.audio.setVolume(evt.detail.value)
}
}
progressCalc() {
const { position, duration } = this.data
let percentage = 0
if (position && duration) {
percentage = (position / duration) * 100
}
return `${percentage}%`
}
render() {
return html`
<img class="bg-img" src=${Photo} />
<header>
<h2>${this.details.title}</h2>
<p class="details">${this.details.details}</p>
</header>
<div class="info">
<div class="progress">
<div class="bar" style="--progress-bar: ${this.progressCalc()}"></div>
</div>
${this._getAudio.render({
complete: (div) => div
})}
<span class="time_pos">${formatSeconds(this.data.position)}</span> <span class="time_dur">${formatSeconds(this.data.duration)}</span>
</div>
<div class="controls">
<div>
<mm-icon name="skip-prev" @click=${this.prevTrack} ?disabled=${!this.data.duration || this.getAudio?.status == 1}></mm-icon>
<mm-icon name="skip-b15" @click=${this.skipBackward} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="${this.playing ? 'pause' : 'play'}" @click=${this.togglePlay} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class=${this.playing ? 'pause' : 'play'}></mm-icon>
<mm-icon name="skip-f15" @click=${this.skipForward} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="skip-next" @click=${this.nextTrack} ?disabled=${!this.data.duration || this.getAudio?.status == 1}></mm-icon>
</div>
<mm-range
value=${this.volume}
@input=${this.volHandler}
?disabled=${!this.data.duration || this.getAudio?.status == 1}
style="--track-height: 0.4em; --thumb-size: 1.2em; --track-color: #ffffff50"
></mm-range>
</div>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
position: absolute;
inset: 0;
padding: 1em;
display: grid;
grid-template-rows: 0.7fr 0.7fr 1fr;
gap: 1em;
opacity: 0;
animation: fadeIn 0.125s ease-in forwards;
overflow: hidden;
border-radius: 0.75em 0 0 0.75em;
isolation: isolate;
}
.bg-img {
position: absolute;
inset: 0;
object-fit: cover;
object-position: center 25%;
opacity: 0.2;
mix-blend-mode: overlay;
scale: 1.1;
width: 100%;
height: 100%;
}
header {
color: var(--netural-100, white);
font-size: 1.5em;
line-height: 1.2;
padding: 0.5em;
}
header > p {
font-size: 1.5em;
max-width: 25ch;
}
.info, .controls {
position: relative;
}
.progress {
--progress-bar: 0%;
position: absolute;
display: flex;
align-items: center;
inset: 0;
z-index: 3;
pointer-events: none;
}
.progress > .bar {
height: 0.25em;
width: var(--progress-bar);
background: #ffffff80;
border-radius: 100vmax;
}
.time_pos, .time_dur {
font-family: sans-serif;
font-size: 0.85em;
color: var(--neutral-100, white);
position: absolute;
bottom: 1em;
}
.time_dur {
right: 0;
}
.controls {
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 65%;
margin-inline: auto;
}
.controls > div {
display: flex;
align-items: center;
justify-content: space-evenly;
margin-block-end: 1em;
gap: 0.25em;
}
mm-icon {
color: white;
font-size: 2.5em;
}
mm-icon.skip15 {
font-size: 1.5em;
}
mm-icon:not(.skip15, .play, .pause) {
font-size: 1em;
}
mm-volctrl {
display: block;
width: 80%;
margin-inline: auto;
}
.waveform {
position: relative;
height: 15em;
display: flex;
flex-direction: column;
justify-content: center;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
` ]
}
customElements.define('mm-aplayer', AudioPlayer)

50
src/components/Button.js Normal file
View File

@ -0,0 +1,50 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import './SvgIcon.js'
import MainCSS from '../assets/styles/main.scss?inline'
class Button extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<button>
<mm-icon name="back-arrow"></mm-icon>
<span><slot></slot></span>
</button>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--primary-color: currentcolor;
--background-color: lightgrey;
}
button {
border: none;
display: flex;
align-items: center;
gap: 0.25em;
padding-block: 0.75em;
padding-inline-start: 1em;
padding-inline-end: 1.25em;
border-radius: 0.25em;
color: var(--primary-color);
background-color: var(--background-color);
}
span {
text-transform: capitalize;
font-weight: 500;
font-size: 1.1em;
}
` ]
}
customElements.define('mm-button', Button)

71
src/components/Footer.js Normal file
View File

@ -0,0 +1,71 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import Router from '../api/Router'
import MainCSS from '../assets/styles/main.scss?inline'
import './SvgIcon.js'
class Footer extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<footer>
<nav>
${Router.routes.map(r => !r.hide ? html`
<a href=${r.path} class=${r.path == Router.route.path ? 'selected' : ''}>
<mm-icon name=${r.icon}></mm-icon>
${r.short}
</a>` : '') }
</nav>
</footer>`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
background: var(--neutral-200, white);
box-shadow: 0 0.5em 10px 10px #00000020;
}
a {
display: flex;
align-items: center;
gap: 0.5em;
color: var(--green-400, blue);
text-decoration: none;
text-transform: capitalize;
}
a > svg {
height: 1em;
width: 1em;
margin-inline-end: 0.5em;
}
a.selected {
color: var(--neutral-900, black);
}
.unicon {
width: 1em;
height: 1em;
outline: thin solid red;
}
footer {
padding-inline: 0.5em;
padding-block-start: 0.5em;
padding-block-end: 0.75em;
}
nav {
display: flex;
justify-content: space-around;
}
` ]
}
customElements.define('mm-footer', Footer)

57
src/components/Header.js Normal file
View File

@ -0,0 +1,57 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import Router from '../api/Router.js'
import './Button.js'
import MainCSS from '../assets/styles/main.scss?inline'
class Header extends LitElement {
static properties = {}
constructor() {
super()
}
navHome() {
Router.navigate('/')
}
render() {
return html`
<header>
<div>
<mm-button @click=${this.navHome}>back</mm-button>
</div>
<h1>${Router.route.title || ''}</h1>
</header>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
header {
padding-inline: 0.5em;
padding-block-end: 0.5em;
}
header > div {
display: flex;
align-items: center;
}
mm-button {
--primary-color: var(--green-400);
--background-color: var(--neutral-400);
}
h1 {
text-align: center;
text-transform: capitalize;
margin-block: 0.25em;
font-size: 1.75em;
}
` ]
}
customElements.define('mm-header', Header)

View File

@ -0,0 +1,40 @@
import { LitElement, css, html } from 'lit'
class HorizontalScroller extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<div class="scroller">
<slot></slot>
</div>
`
}
static styles = css`
:host {
--col-width: 10em;
--gap: 1em;
}
.scroller {
display: grid;
gap: var(--gap);
grid-auto-flow: column;
grid-auto-columns: var(--col-width);
width: calc(100vw - 1em);
overflow: auto;
scroll-behaviour: smooth;
scroll-padding-inline: 0.5em;
padding-inline-start: 0.5em;
padding-inline-end: 0.5em;
padding-block-end: 0.75em;
}
`
}
customElements.define('mm-hscroller', HorizontalScroller)

View File

@ -0,0 +1,87 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import MainCSS from '../assets/styles/main.scss?inline'
class ImageViewer extends LitElement {
static properties = {
src: { type: String },
imgEl: { state: true },
captionWidth: { state: true },
loading: { state: true }
}
constructor() {
super()
this.src = ''
this.imgEl = new Image()
this.captionWidth = 'min-content'
this.loading = true
}
connectedCallback() {
super.connectedCallback()
this.imgEl.addEventListener('load', () => {
this.captionWidth = `${this.imgEl.offsetWidth}px`
this.loading = false
})
}
set src(val) {
if (val) {
this.imgEl.src = val
console.log(this.imgEl)
}
}
render() {
return html`
<figure class="${this.loading ? 'loading' : ''}">
<picture>
${this.imgEl}
</picture>
<figcaption style="--width: ${this.captionWidth}">
Image Description
</figcaption>
</figure>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
figure {
position: absolute;
inset: 0;
display: grid;
grid-template-rows: 1fr 4em;
justify-content: center;
transition: opacity 0.5s ease;
}
.loading {
opacity: 0;
}
picture {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
}
figcaption {
padding-block: 0.75em;
width: var(--width);
margin-inline: auto;
}
img {
position: absolute;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
` ]
}
customElements.define('mm-imageviewer', ImageViewer)

88
src/components/Loading.js Normal file
View File

@ -0,0 +1,88 @@
import { LitElement, html, css } from 'lit'
class Loading extends LitElement {
static properties = {
nobg: { type: Boolean }
}
constructor() {
super()
this.nobg = false
}
render() {
return html`
<div class=${this.nobg ? '' : 'pulse'}>
<span class="loading">Loading<span class="d1">.</span><span class="d2">.</span><span class="d3">.</span></span>
</div>
`
}
static styles = css`
:host {
--fade-duration: 0.125s;
--pulse-duration: 0.75s;
height: 100%;
width: 100%;
}
div {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
div.pulse {
opacity: 0;
background-color: #ffffff60;
animation:
fadeIn var(--fade-duration) ease-in forwards,
pulse var(--pulse-duration) ease-in-out alternate infinite;
}
.loading {
--dot-duration: 1s;
font-size: 2em;
font-style: italic;
font-weight: 200;
color: white;
}
.d1, .d2, .d3 {
opacity: 0;
animation: dot var(--dot-duration) alternate infinite;
}
.d2 {
animation-delay: calc(var(--dot-duration) * 0.3);
}
.d3 {
animation-delay: calc(calc(var(--dot-duration) * 0.3) * 2);
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
@keyframes dot {
to {
opacity: 1;
}
}
@keyframes pulse {
from {
background-color: #ffffff20;
}
to {
background-color: #ffffff00;
}
}
`
}
customElements.define('mm-loading', Loading)

View File

@ -0,0 +1,584 @@
import { LitElement, html, css,unsafeCSS } from 'lit'
import { Task } from '@lit-labs/task'
import { formatSeconds } from '../api/utils'
import WaveSurfer from 'wavesurfer.js'
import Photo from '/images/Meredith Monk (1974) Photo Lauretta Harris.jpg'
import MainCSS from '../assets/styles/main.scss?inline'
import './RangeSlider.js'
import './Loading.js'
class ModularPlayer extends LitElement {
static properties = {
// property
details: { type: Object },
// state
duration: { state: true },
position: { state: true },
playing: { state: true },
volume: { state: true },
track: { state: true },
loading: { state: true },
// audio element
audio: { state: true },
ws: { state: true }
}
constructor() {
super()
this.details = {}
this.audio = null
this.ws = null
this.track = 0
this.position = 0
this.duration = 0
this.loading = true
this.initAudio()
}
_getTrack = new Task(
this,
async () => {
if (this.details.tracks) {
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
} else {
this.audio.src = `/media${this.details.media}.mp3`
}
this.audio.load()
if (this.playing && this.audio.paused) {
this.audio.play()
}
},
() => [ this.track ]
)
_renderWave = new Task(
this,
{
task: async () => {
return await this.initWs(this.details.data)
},
autoRun: false
}
)
initAudio() {
this.audio = document.createElement('audio')
if (!localStorage.getItem('deviceVolume')) {
localStorage.setItem('deviceVolume', 0.6)
}
this.audio.addEventListener('loadstart', () => {
this.loading = true
this.audio.volume = localStorage.getItem('deviceVolume')
this.volume = this.audio.volume
})
this.audio.addEventListener('canplay', () => {
if (this.details.tracks) {
this.loading = false
}
})
this.audio.addEventListener('play', () => {
this.playing = true
})
this.audio.addEventListener('pause', () => {
this.playing = false
})
this.audio.addEventListener('loadedmetadata', () => {
this.position = 0
this.duration = this.audio.duration
if (this.duration > 0) {
this._renderWave.run()
}
})
this.audio.addEventListener('ended', () => {
this.audio.currentTime = 0
this.position = this.audio.currentTime
if (this.tracks) {
this.track += 1
if (this.track >= this.details.tracks.length) {
this.track = 0
}
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
this.audio.load()
}
})
this.audio.addEventListener('timeupdate', () => {
this.position = this.audio.currentTime
})
this.audio.addEventListener('seeking', () => {
this.position = this.audio.currentTime
})
}
initWs(peakData) {
return new Promise((go, no) => {
try {
const div = document.createElement('div')
div.classList.add('waveform')
this.ws = WaveSurfer.create({
container: div,
waveColor: '#ffffff50',
progressColor: 'white',
cursorColor: 'transparent',
fillParent: true,
normalize: true,
barWidth: 4,
barGap: 6,
barRadius: 5,
dragToSeek: true,
media: this.audio,
peaks: peakData
})
this.ws.on('interaction', (currentTime) => {
this.position = currentTime
})
this.ws.on('ready', () => {
setTimeout(() => go(div), 500)
})
} catch (err) {
no(new Error(err))
}
})
}
togglePlay() {
if (this.audio.paused) {
this.playing = true
this.audio.play()
} else {
this.playing = false
this.audio.pause()
}
}
seekTrack({ detail }) {
if (detail) {
const { value } = detail
this.audio.currentTime = value
this.position = value
}
}
skipForward() {
this.audio.currentTime = this.audio.currentTime + 15
}
skipBackward() {
this.audio.currentTime = this.audio.currentTime - 15
}
selectTrack(evt) {
const trackNumber = evt.target.dataset.trackNumber
this.track = parseInt(trackNumber)
}
nextTrack() {
if (this.details.tracks) {
if (this.track + 1 < this.details.tracks.length) {
this.track += 1
}
} else {
this.dispatchEvent(new Event('next-track', { bubbles: true, composed: true }))
}
}
prevTrack() {
if (this.details.tracks) {
if (this.track - 1 >= 0) {
this.track -= 1
}
} else {
this.dispatchEvent(new Event('prev-track', { bubbles: true, composed: true }))
}
}
volHandler(evt) {
if (evt.detail) {
this.audio.volume = evt.detail.value
localStorage.setItem('deviceVolume', evt.detail.value)
}
}
progressCalc() {
let percentage = 0
if (this.position && this.duration) {
percentage = (this.position / this.duration) * 100
}
return `${percentage}%`
}
render() {
return html`
<div class="player">
${this.details.tracks ? html`
<div class="tracklist">
<h2>${this.details.title}</h2>
<div class="list">
<ul>
${this.details.tracks.map((t, i) => html`
<li class="${this.track === i ? 'selected' : ''}" data-track-number=${i} @click=${this.selectTrack}>
<mm-icon name="notes" class=${this.playing ? 'playing' : ''}></mm-icon>
<span>${t}</span>
</li>`)}
</ul>
</div>
</div>
` : html`<img class="bg-img" src=${Photo} />`}
<header>
<h2>${this.details.tracks ? this.details?.tracks[this.track] : this.details.title}</h2>
${this.details.tracks ? '' : html`<p class="details">${this.details.details}</p>`}
</header>
<div class="info">
${this._getTrack.render({
complete: () => html`
<span class="time_pos">${formatSeconds(this.position)}</span>
${!this.details.tracks ?
this._renderWave.render({
initial: () => html`<mm-loading class="waveform" nobg></mm-loading>`,
pending: () => html`<mm-loading class="waveform" nobg></mm-loading>`,
complete: (ws) => html`
<div class="progress">
<div class="bar" style="--progress-bar: ${this.progressCalc()}">
<div class="blob"></div>
</div>
</div>
${ws}`
}) :
html`<mm-range
value=${this.position}
max=${this.duration}
step="0.1"
@input=${this.seekTrack}
></mm-range>`
}
<span class="time_dur">${formatSeconds(this.duration)}</span>
`
})}
${this.audio}
</div>
<div class="controls">
<div class="buttons">
<mm-icon name="skip-prev" @click=${this.prevTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
<mm-icon name="skip-b15" @click=${this.skipBackward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="${this.playing ? 'pause' : 'play'}" @click=${this.togglePlay} ?disabled=${!this.duration || this.getAudio?.status == 1} class=${this.playing ? 'pause' : 'play'}></mm-icon>
<mm-icon name="skip-f15" @click=${this.skipForward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="skip-next" @click=${this.nextTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
</div>
<div class="volume">
<mm-icon name="volume"></mm-icon>
<mm-range
value=${this.volume}
@input=${this.volHandler}
?disabled=${!this.duration || this.getAudio?.status == 1}
></mm-range>
</div>
</div>
</div>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--padding: 1rem;
--tracklist-background: lightgrey;
position: absolute;
inset: 0;
}
.player {
position: absolute;
inset: 0;
display: grid;
gap: 1em;
opacity: 0;
animation: fadeIn 0.125s ease-in forwards;
overflow: hidden;
border-radius: 0.75em 0 0 0.75em;
isolation: isolate;
}
.player:has(.tracklist) {
grid-template-rows: 0.7fr 0.5fr 0.3fr 1fr;
padding-block-end: 2em;
gap: 0;
& header {
margin-block-start: 1.5em;
margin-block-end: 2em;
}
}
.player:has(.waveform) {
grid-template-rows: 0.7fr 0.7fr 1fr;
& header {
padding-block-start: 1em;
padding-inline: 1em;
}
}
.bg-img {
position: absolute;
inset: 0;
object-fit: cover;
object-position: center 25%;
opacity: 0.2;
mix-blend-mode: overlay;
scale: 1.1;
width: 100%;
height: 100%;
}
header {
color: var(--netural-100, white);
font-size: 1.5em;
line-height: 1.2;
}
header > p {
font-size: 1.5em;
max-width: 25ch;
}
.tracklist + header {
text-align: center;
font-size: 1.25em;
}
.tracklist {
--tracklist-padding: calc(var(--padding) * 2);
position: relative;
background: var(--neutral-gradient-400);
height: 20em;
> h2 {
margin-inline: var(--tracklist-padding);
margin-block-start: 0.75em;
padding-block-start: 0.75em;
border-block-start: thin solid var(--green-400);
}
> .list {
position: absolute;
bottom: 0;
top: calc(var(--tracklist-padding) * 2.5);
right: var(--tracklist-padding);
left: var(--tracklist-padding);
overflow-x: hidden;
overflow-y: auto;
border-radius: 0.75em 0.75em 0 0;
background: var(--neutral-150);
box-shadow: var(--box-shadow);
}
}
.list ul {
list-style: none;
padding: 0;
margin-block: 0;
margin-inline: 0;
& li {
display: grid;
grid-template-columns: 1em auto;
gap: 0.5em;
padding-block: 0.5em;
padding-inline: 0.5em;
&:nth-child(2n-1) {
box-shadow: var(--box-shadow);
background: var(--neutral-100);
}
& > * {
pointer-events: none;
}
& > mm-icon {
font-size: 1em;
opacity: 0;
color: var(--neutral-700);
transition: opacity 0.25s ease, color 0.25s ease;
&.playing {
color: var(--green-400);
}
}
&.selected > mm-icon {
opacity: 1;
}
}
}
.info, .controls {
position: relative;
display: flex;
padding-inline: var(--padding);
}
mm-range {
--track-color: #ffffff50
}
.info:not(:has(.waveform)) {
display: flex;
gap: 1em;
align-items: center;
& > mm-range {
flex-grow: 1;
--track-height: 0.4em;
--thumb-size: 0;
}
& .time_dur {
margin-inline-start: auto;
}
}
.info:has(.waveform) {
& .time_pos,
& .time_dur {
position: absolute;
bottom: var(--padding);
}
& .time_dur {
right: var(--padding);
}
}
.time_pos,
.time_dur {
font-family: sans-serif;
font-size: 0.85em;
color: var(--neutral-100, white);
}
.controls {
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 65%;
margin-inline: auto;
& > div {
display: flex;
align-items: center;
justify-content: space-evenly;
margin-block-end: 1em;
gap: 0.25em;
}
& .volume {
display: flex;
width: 70%;
align-self: center;
gap: 0.75em;
& mm-icon {
font-size: 1.25em;
}
& mm-range {
--track-height: 0.25em;
--thumb-size: 0.8em;
flex-grow: 1;
}
}
}
.progress {
--progress-bar: 0%;
position: absolute;
display: flex;
align-items: center;
inset: 0;
z-index: 3;
pointer-events: none;
padding-inline: var(--padding);
> .bar {
--bar-height: 0.25em;
height: var(--bar-height);
width: var(--progress-bar);
background: #ffffff80;
border-radius: 100vmax;
position: relative;
> .blob {
position: absolute;
top: calc(var(--bar-height) * -0.5);
right: calc(var(--bar-height) * -1);
width: 0.5em;
height: 0.5em;
border-radius: 100vmax;
background: white;
}
}
}
mm-icon {
color: white;
font-size: 2.5em;
&.skip15 {
font-size: 1.5em;
}
&:not(.skip15, .play, .pause) {
font-size: 1em;
}
}
.waveform {
position: absolute;
inset: 0 var(--padding);
display: flex;
flex-direction: column;
justify-content: center;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
` ]
}
customElements.define('mm-audio-player', ModularPlayer)

67
src/components/NavCard.js Normal file
View File

@ -0,0 +1,67 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import Router from '../api/Router'
import './SvgIcon.js'
import MainCSS from '../assets/styles/main.scss?inline'
class NavCard extends LitElement {
static properties = {
route: { type: Object }
}
constructor() {
super()
this.route = {}
}
firstUpdated() {
this.shadowRoot.host.addEventListener('click', () => {
Router.navigate(this.route.path)
})
}
render() {
return html`
<div class="card">
<mm-icon name=${this.route?.icon}></mm-icon>
<p class="title">${this.route?.title}</p>
</div>
`
}
static styles = [css`${unsafeCSS(MainCSS)}`, css`
:host {
--highlight-color: black;
--shadow: 0 0 0px transparent;
height: 100%;
background: linear-gradient(to bottom, rgb(255 255 255 / 0.9) 0%, rgb(159 159 159 / 0.6) 100%);
border-radius: 0.75em;
padding: 1em;
padding-block-start: 1.25em;
box-shadow: 0px 0px 10px #00000060;
text-shadow: var(--shadow);
}
.card {
display: grid;
grid-template-columns: auto 1fr;
border-top: thin solid var(--highlight-color);
padding-block-start: 0.3em;
cursor: pointer;
font-size: 1.65em;
}
mm-icon {
--svg-shadow: var(--shadow);
margin-inline-end: 0.5em;
font-size: 0.8em;
}
.title {
max-width: 12ch;
}
`]
}
customElements.define('mm-ncard', NavCard)

View File

@ -0,0 +1,117 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import { live } from 'lit/directives/live.js'
import MainCSS from '../assets/styles/main.scss?inline'
class RangeSlider extends LitElement {
static properties = {
min: { type: Number },
max: { type: Number },
value: { type: Number },
step: { type: Number },
disabled: { type: Boolean },
thumb: { type: Boolean },
sliderEl: { state: true }
}
constructor() {
super()
this.min = 0
this.max = 1
this.value = 0
this.step = 0.01
this.disabled = false
this.thumb = true
this.sliderEl = null
}
firstUpdated() {
this.sliderEl = this.shadowRoot.querySelector('input')
this.sliderEl.style.setProperty('--progress', `${(this.sliderEl.value / this.sliderEl.max) * 100}%`)
}
draw({ target: sliderEl }) {
const progress = (sliderEl.value / sliderEl.max) * 100
sliderEl.style.setProperty('--progress', `${progress}%`)
const event = new CustomEvent('input', {
bubbles: true, composed: true,
detail: { value: sliderEl.value }
})
this.dispatchEvent(event)
}
willUpdate(changedProperties) {
if (changedProperties.has('value') && this.sliderEl) {
const progress = (this.value / this.max) * 100
this.sliderEl.style.setProperty('--progress', `${progress}%`)
}
}
render() {
return html`
<input type="range"
min=${live(this.min || 0)}
.max=${live(this.max || 1)}
.step=${live(this.step || 0.01)}
.value=${live(this.value || 0)}
@input=${this.draw}
></input>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--thumb-size: 1.5em;
--track-height: 0.5em;
--track-progress: white;
--track-color: grey;
--thumb-color: white;
--progress: 0%;
}
input {
display: block;
appearance: none;
background: none;
cursor: pointer;
width: 100%;
border-radius: 100vmax;
height: var(--track-height);
background: linear-gradient(to right, var(--track-progress), var(--progress), var(--track-color) var(--progress));
}
input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: var(--thumb-size);
aspect-ratio: 1;
background-color: var(--thumb-color);
border-radius: 100vmax;
border: none;
}
input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: var(--thumb-size);
aspect-ratio: 1;
background-color: var(--thumb-color);
border-radius: 100vmax;
border: none;
}
input::-moz-range-thumb {
appearance: none;
height: var(--thumb-size);
aspect-ratio: 1;
background-color: var(--thumb-color);
border-radius: 100vmax;
border: none;
}
` ]
}
customElements.define('mm-range', RangeSlider)

47
src/components/SvgIcon.js Normal file
View File

@ -0,0 +1,47 @@
import { LitElement, css, html } from 'lit'
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
class SvgIcon extends LitElement {
static properties = {
name: { type: String },
element: { state: true },
}
constructor() {
super()
this.name = ''
}
attributeChangedCallback(att, _, nv) {
if (att === 'name' && nv) {
this.loadSVG(nv)
}
}
async loadSVG(name) {
const { default: svg } = await import(`../assets/icons/${name}.svg?raw`)
this.element = html`${unsafeSVG(svg)}`
}
render() {
return this.element
}
static styles = css`
:host {
--svg-shadow: 0px 0px 0px transparent;
}
svg {
fill: currentcolor;
display: block;
height: 1em;
width: 1em;
padding: 0;
margin: 0;
filter: drop-shadow(var(--svg-shadow));
}
`
}
customElements.define('mm-icon', SvgIcon)

View File

@ -0,0 +1,98 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import MainCSS from '../assets/styles/main.scss?inline'
class VerticalCard extends LitElement {
static properties = {
details: { type: Object },
selected: { type: Boolean }
}
constructor() {
super()
this.details = {}
this.selected = false
}
select() {
const event = new CustomEvent('video-select', {
bubbles: true, composed: true,
detail: {
...this.details
}
})
this.dispatchEvent(event)
}
render() {
return html`
<div @click=${this.select} class=${this.selected ? 'selected' : ''}>
<picture>
<img src="/media/documentaries_interviews/thumbs/girlchild.png">
</picture>
<aside>
<p class="title">${this.details?.title}</p>
<p class="detail">${this.details?.detail}</p>
</aside>
</div>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--background-color: lightgrey;
--font-color: black;
--image-placeholder: lightgrey;
--color: black;
}
div {
height: 100%;
display: grid;
grid-template-rows: auto 0.2fr;
background: lightgrey;
line-height: 1;
overflow: hidden;
border-radius: var(--border-radius, 0.75em);
background: var(--background-color);
color: var(--font-color);
align-content: start;
}
.selected {
outline: 2px solid var(--color);
}
picture {
display: block;
background: var(--image-placeholder);
aspect-ratio: 1 / 1;
overflow: hidden;
}
picture > img {
height: 100%;
width: 100%;
object-fit: cover;
object-position: center;
}
aside {
padding: 0.5em;
font-size: 1em;
}
.title {
font-weight: 500;
}
.detail {
font-size: 0.7em;
line-height: 1.2;
margin-block-start: 0.125em;
}
` ]
}
customElements.define('mm-vcard', VerticalCard)

View File

@ -0,0 +1,235 @@
import { LitElement, css, html } from 'lit'
import './RangeSlider.js'
import './SvgIcon.js'
import { formatSeconds } from '../api/utils.js'
class VideoPlayer extends LitElement {
static properties = {
media: { type: String },
autoplay: { type: Boolean },
videoEl: { state: true },
playing: { state: true },
controls: { state: true },
ctrlTimer: { state: true },
position: { state: true },
duration: { state: true },
remain: { state: true }
}
constructor() {
super()
this.media = ''
this.hostHeight = 0
this.hostWidth = 0
this.playing = false
this.controls = true
this.ctrlTimer = {}
this.position = 0
this.duration = 0
this.remain = true
this.autoplay = false
this.videoEl = document.createElement('video')
this.videoEl.playsInline = true
this.videoEl.preload = 'metadata'
}
firstUpdated() {
this.videoEl.addEventListener('loadeddata', () => {
this.duration = this.videoEl.duration
})
this.videoEl.addEventListener('timeupdate', () => {
this.position = this.videoEl.currentTime
})
}
willUpdate(att) {
if (att.has('media')) {
this.videoEl.src = this.media
if (this.autoplay) {
this.togglePlay()
this.interact()
}
}
this.update()
}
togglePlay() {
if (this.playing) {
this.videoEl.pause()
} else {
this.videoEl.play()
}
this.playing = !this.playing
}
interact() {
if (this.playing) {
// show controls
this.controls = true
clearTimeout(this.ctrlTimer)
// timeout
this.ctrlTimer = setTimeout(() => {
if (this.playing) {
this.controls = false
}
}, 2000)
}
}
toggleFullscreen() {
const container = this.shadowRoot.querySelector('.videoContainer')
if (container.webkitSupportsFullscreen) {
container.webkitEnterFullscreen()
return
}
if (document.fullscreenElement !== null) {
document.exitFullscreen()
container.setAttribute('data-fullscreen', false)
} else {
container.requestFullscreen()
container.setAttribute('data-fullscreen', true)
}
}
toggleDuration() {
console.log(this.remain)
this.remain = !this.remain
}
seek({ detail }) {
if (detail) {
this.interact()
this.videoEl.currentTime = detail.value
this.position = detail.value
}
}
render() {
return html`
<div class="videoContainer">
${this.videoEl}
<div class="touchbox" @click=${this.interact}>
<div class="playPause ${this.controls ? '' : 'invisible'}" @click=${this.togglePlay}>
<mm-icon class="play ${this.playing ? '' : 'invisible'}" name="pause"></mm-icon>
<mm-icon class="pause ${this.playing ? 'invisible' : ''}" name="play"></mm-icon>
</div>
<div class="controls ${this.controls ? '' : 'invisible'}">
<mm-icon @click=${this.toggleFullscreen} class="fullscreen" name="fullscreen"></mm-icon>
<mm-range
value="${this.position}"
max="${this.duration}"
step="0.1"
@input=${this.seek}
></mm-range>
<div class="time">
<span class="pos">${formatSeconds(this.position)}</span>
<span class="dur" @click=${this.toggleDuration}>${ this.remain ? `-${formatSeconds(this.duration - this.position)}` : formatSeconds(this.duration)}</span>
</div>
</div>
</div>
</div>
`
}
static styles = css`
:host {
height: 100%;
width: 100%;
position: relative;
}
video {
position: absolute;
display: block;
inset: 0;
width: 100%;
height: 100%;
background: black;
}
.controls {
position: absolute;
padding-inline: 0.75em;
padding-block: 0.5em;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
transition: opacity 0.25s ease;
}
.time {
display: flex;
color: white;
font-size: 0.65em;
padding-inline: 0.125em;
padding-block: 1em 0.5em;
}
.dur {
margin-inline-start: auto;
}
mm-icon {
display: block;
color: white;
}
mm-icon.fullscreen {
font-size: 1.25em;
width: 1em;
margin-inline-start: auto;
margin-block-end: 1em;
}
mm-range {
--track-height: 0.3em;
--thumb-size: 0.8em;
--track-color: #ffffff50;
}
.touchbox {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
inset: 0;
opacity: 1;
transition: opacity 0.25s ease;
}
.playPause {
display: grid;
align-items: center;
justify-content: center;
font-size: 2.25em;
transition: opacity 0.25 ease;
}
.playPause > mm-icon {
inset: 0;
grid-column: 1 / span 1;
grid-row: 1 / span 1;
}
.invisible {
pointer-events: none;
opacity: 0;
}
`
}
customElements.define('mm-vplayer', VideoPlayer)

162
src/views/audio.js Normal file
View File

@ -0,0 +1,162 @@
import { LitElement, css, html } from 'lit'
import { Task } from '@lit-labs/task'
// components
import '../components/Header.js'
import '../components/Footer.js'
import '../components/Loading.js'
import '../components/AudioCard.js'
import '../components/ModularPlayer.js'
import Router from '../api/Router.js'
class AudioView extends LitElement {
static properties = {
path: { type: String },
tracks: { type: Array },
selected: { state: true }
}
constructor() {
super()
this.path = ''
this.tracks = []
this.selected = null
}
firstUpdated() {
this.addEventListener('select-audio', ({ detail }) => {
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
console.log(this.selected)
this._selectAudio.run()
})
this.addEventListener('next-track', () => {
this._skipTrack(1)
})
this.addEventListener('prev-track', () => {
this._skipTrack(-1)
})
}
_getAudio = new Task(
this,
async () => {
const res = await fetch(`/data${Router.route.path}.json`)
const json = await res.json()
this.tracks = json
// start track (0 is not currently available)
const idx = 1
this.selected = { ...this.tracks[idx], idx, media: `${Router.route.path}/${this.tracks[idx].media}` }
},
() => [ this.path ]
)
_selectAudio = new Task(
this,
{
task: async ([ selected ]) => {
const res = await fetch(`/media${selected.media}.json`)
return { ...selected, ...await res.json() }
},
args: () => [ this.selected ]
}
)
_skipTrack(direction) {
const idx = (this.selected.idx + direction) % this.tracks.length
const detail = { ...this.tracks[idx], idx }
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
this._selectAudio.run()
}
render() {
return html`
<mm-header></mm-header>
<main>
<nav class="tracks">
<div class="scroll-items">
${this._getAudio.render({
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
complete: () => html`${this.tracks.map((t, i) => html`<mm-acard idx=${i} icon=${t.album ? 'album' : 'play-circle'} ?selected=${t.title == this.selected.title && t.details == this.selected.details} .details=${t}></mm-acard>`)}`,
error: (err) => html`Error: ${err}`
})}
</div>
</nav>
<div class="player">
<div>
${this._selectAudio.render({
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
complete: (selected) => html`<mm-audio-player .details=${selected}></mm-audio-player>`
})}
</div>
</div>
</main>
<mm-footer></mm-footer>
`
}
static styles = css`
:host {
display: flex;
flex-direction: column;
height: 100vh;
}
main {
flex-grow: 1;
display: grid;
grid-template-columns: 0.7fr 1fr;
gap: 0.5em;
}
.tracks {
--border-radius: 0.75em;
--padding: 0.5em;
position: relative;
background: var(--neutral-400, lightgrey);
border-radius: 0 var(--border-radius) 0 0;
}
.scroll-items {
position: absolute;
inset: 0;
padding-block: 0.75em;
padding-inline-start: 0.5em;
padding-inline-end: 0.75em;
border-radius: 0 calc(var(--border-radius) * 0.5) 0 0;
display: grid;
grid-auto-flow: rows;
align-content: start;
gap: 0.75em;
overflow-x: hidden;
overflow-y: auto;
}
.player {
position: relative;
}
.player > div {
position: absolute;
inset: 0;
border-radius: 0.75em 0 0 0.75em;
background: var(--green-gradient-400, lightgrey);
margin-block-end: 1em;
}
mm-footer {
z-index: 1;
}
`
}
customElements.define('mm-audio', AudioView)

48
src/views/fourohfour.js Normal file
View File

@ -0,0 +1,48 @@
import { LitElement, html, css } from 'lit'
import '../components/Header.js'
import '../components/Footer.js'
class FourOhFour extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<mm-header></mm-header>
<main>
<h1>404</h1>
<p>The page you're looking for cannot be found</p>
</main>
<mm-footer></mm-footer>
`
}
static styles = css`
:host {
display: grid;
grid-template-rows: auto 1fr auto;
gap: 0.5em;
height: 100vh;
}
main {
display: flex;
flex-direction: column;
padding: 0em 0em 10em;
text-align: center;
justify-content: center;
}
main > * {
margin: 0.25em;
}
`
}
customElements.define('mm-404', FourOhFour)

107
src/views/home.js Normal file
View File

@ -0,0 +1,107 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import Router from '../api/Router.js'
import MainCSS from '../assets/styles/main.scss?inline'
// components
import '../components/NavCard.js'
class Home extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<header>
<div>
<p class="sub">Oude Kerk and Hartwig Art Foundation present</p>
<h1>Meredith Monk: Calling</h1>
</div>
</header>
<nav class="home-nav">
${Router.routes.map(r => (r.title != 'Not found' && r.title != 'home') ? html`<mm-ncard .route=${r}></mm-ncard>` : '')}
</nav>
<footer>
<div>
<p>Rooms for Listening & Watching</p>
</div>
</footer>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--margin: 0.75em;
display: block;
display: flex;
flex-direction: column;
height: calc(100vh - calc(var(--margin) * 2));
background-image: url('/images/home-bg.png');
background-size: cover;
background-position: center;
margin: var(--margin);
}
div {
background-color: white;
color: var(--green-400, black);
text-shadow: 0 0 5px rgba(0, 173, 78, 0.4);
padding-inline: 1em;
}
header {
display: flex;
}
header > div {
margin-inline: 4em;
border-radius: 0 0 0.75em 0.75em;
padding-block-start: 0.5em;
padding-block-end: 1em;
}
header h1 {
font-size: 2.25em;
}
.home-nav {
display: grid;
margin: 4em;
flex-grow: 1;
gap: 2.5em;
grid-template-columns: repeat(auto-fill, minmax(25vw, 1fr));
grid-auto-rows: 1fr;
}
mm-ncard {
--highlight-color: var(--green-400);
--shadow: 0px 0px 5px #00000099;
}
footer {
display: flex;
align-items: center;
}
footer > div {
margin-inline-end: 4em;
margin-inline-start: auto;
border-radius: 0.75em 0.75em 0 0;
padding-block-start: 1em;
padding-block-end: 0.5em;
}
footer p {
font-size: 1.5em;
font-weight: 500;
}
` ]
}
customElements.define('mm-home', Home)

69
src/views/images.js Normal file
View File

@ -0,0 +1,69 @@
import { LitElement, css, html } from "lit";
import '../components/Header.js'
import '../components/Footer.js'
import '../components/HorizontalScroller.js'
import '../components/VerticalCard.js'
import '../components/ImageViewer.js'
class ImageView extends LitElement {
static properties = {}
constructor() {
super()
}
render() {
return html`
<mm-header></mm-header>
<main>
<div class="image">
<mm-imageviewer src="https://picsum.photos/${parseInt((Math.random() * 400) + 300)}/${parseInt((Math.random() * 400) + 400)}"></mm-imageviewer>
</div>
<mm-hscroller>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
</mm-hscroller>
</main>
<mm-footer></mm-footer>
`
}
static styles = css`
:host {
display: grid;
grid-template-rows: min-content 1fr min-content;
gap: 0.25em;
height: 100vh;
}
mm-hscroller {
--col-width: 7.5em;
--gap: 0.75em;
}
main {
display: grid;
grid-template-rows: 1fr auto;
gap: 0.5em;
}
.image {
position: relative;
inline-margin: auto;
height: 100%;
}
`
}
customElements.define('mm-images', ImageView)

124
src/views/videos.js Normal file
View File

@ -0,0 +1,124 @@
import { LitElement, html, css, unsafeCSS } from 'lit'
import { Task } from '@lit-labs/task'
import MainCSS from '../assets/styles/main.scss?inline'
// components
import '../components/Header.js'
import '../components/Footer.js'
import '../components/VerticalCard.js'
import '../components/VideoPlayer.js'
import '../components/HorizontalScroller.js'
import Router from '../api/Router.js'
class VideoPage extends LitElement {
static properties = {
data: { type: Array },
fims: { type: Array, state: true },
selected: { state: true }
}
constructor() {
super()
this.data = []
this.selected = {}
}
firstUpdated() {
this.addEventListener('video-select', ({ detail }) => {
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}`, autoplay: true }
})
}
_getVideos = new Task(
this,
async () => {
const res = await fetch(`/data/${Router.route.path}.json`)
this.films = await res.json()
this.selected = { ...this.films[0], media: `${Router.route.path}/${this.films[0].media}` }
},
() => [ this.data ]
)
// TODO@mx this might not be necessary
_selectVideo = new Task(
this,
{
task: () => {
// console.log(this.selected)
},
args: () => [ this.selected ],
}
)
render() {
return html`
<mm-header></mm-header>
<main>
${this._selectVideo.render({
pending: () => html`Loading...`,
complete: () => html`
<aside>
<h2>${this.selected.title}</h2>
<p class="detail">${this.selected.detail}</p>
</aside>
<mm-vplayer media='/media/${this.selected.media}' ?autoplay=${this.selected.autoplay}></mm-vplayer>
`
})}
</main>
<mm-hscroller>
${this._getVideos.render({
pending: () => html`Loading...`,
complete: () => html`${this.films.map(t => html`
<mm-vcard
.details=${t}
?selected=${t.title == this.selected.title && t.detail == this.selected.detail}
></mm-vcard>`
)}`
})}
</mm-hscroller>
<mm-footer></mm-footer>`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
display: grid;
grid-template-rows: min-content 1fr min-content min-content;
gap: 0.25em;
height: 100vh;
}
mm-vcard {
--color: var(--green-400);
--background-color: var(--neutral-200);
}
main {
display: grid;
grid-template-columns: 0.4fr 1fr;
margin-inline: 0.5em;
margin-block-end: 0.5em;
gap: 0.5em;
}
aside {
font-size: 1.5em;
padding-inline-start: 0.5em;
> h2 {
max-width: 12ch;
margin-block-end: 0.2em;
}
> .detail {
line-height: 1.1;
}
}
` ]
}
customElements.define('mm-videos', VideoPage)

9
vite.config.js Normal file
View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vite'
export default defineConfig({
base: '/',
build: {
sourcemap: true,
target: [ 'esnext', 'edge100', 'firefox100', 'chrome100', 'safari18' ],
},
})