├── .eslintrc.cjs ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public └── mimi.jpg ├── src ├── App.jsx ├── App.scss ├── Controller.jsx ├── EntryDetailsDialog.jsx ├── EntryDetailsDialog.scss ├── Lyrics.jsx ├── NowPlayingSection.jsx ├── NowPlayingSection.scss ├── Player.jsx ├── SongListSection.jsx ├── SongListSection.scss ├── components │ ├── Button.jsx │ ├── Button.module.scss │ ├── Card.jsx │ ├── Card.module.scss │ ├── Checkbox.jsx │ ├── Checkbox.module.scss │ ├── Dialog.jsx │ ├── Dialog.module.scss │ ├── FAB.jsx │ ├── FAB.module.scss │ ├── Icon.jsx │ ├── Icon.module.scss │ ├── IconButton.jsx │ ├── IconButton.module.scss │ ├── LazyImg.jsx │ ├── LazyImg.module.scss │ ├── Menu.animations.scss │ ├── Menu.jsx │ ├── Menu.module.scss │ ├── MenuItem.jsx │ ├── MenuItem.module.scss │ ├── NowPlayingIndicatorIcon.jsx │ ├── NowPlayingIndicatorIcon.module.scss │ ├── Popper.jsx │ ├── Popper.module.scss │ ├── Radio.jsx │ ├── Radio.module.scss │ ├── RadioGroup.jsx │ ├── Ripple.jsx │ ├── Ripple.module.scss │ ├── SegmentedButtons.jsx │ ├── SegmentedButtons.module.scss │ ├── Select.jsx │ ├── Select.module.scss │ ├── Tab.jsx │ ├── Tabs.jsx │ ├── Tabs.module.scss │ ├── Tag.jsx │ ├── Tag.module.scss │ ├── TextField.jsx │ ├── TextField.module.scss │ ├── Toolbar.jsx │ ├── Toolbar.module.scss │ ├── TopAppBar.jsx │ ├── TopAppBar.module.scss │ ├── WaveProgressBar.jsx │ ├── WaveProgressBar.module.scss │ └── global │ │ └── elevation.scss ├── contexts │ ├── QueueContext.jsx │ ├── RadioGroupContext.tsx │ └── ThemeColorSetContext.jsx ├── data │ ├── covers │ │ ├── CIEL - 空中散歩.jpg │ │ ├── ESHIKARA,MIMI,可不 - ふわり (feat. MIMI,可不,初音ミク).jpg │ │ ├── I Need You - Miuna Usako.jpg │ │ ├── LSCM,MIMI - そして夜と灯る (feat. MIMI).jpg │ │ ├── LSCM,佳館杏ノ助,MIMI - てんぐ夜かぐら (feat. 佳館杏ノ助 & MIMI).jpg │ │ ├── MIMI - Cold Waltz - in G Minor.png │ │ ├── MIMI - Lilly.jpg │ │ ├── MIMI - LyriC.jpg │ │ ├── MIMI - NOREEN.png │ │ ├── MIMI - Pale.jpg │ │ ├── MIMI - SorrowChat.jpg │ │ ├── MIMI - What Call This Day ? (feat. にんじん).jpg │ │ ├── MIMI - parabola.png │ │ ├── MIMI - いいじゃない.jpg │ │ ├── MIMI - ぎゅって.jpg │ │ ├── MIMI - もーいいかい.jpg │ │ ├── MIMI - ゆめまぼろし.jpg │ │ ├── MIMI - よるつむぎ.jpg │ │ ├── MIMI - れじぇろ.jpg │ │ ├── MIMI - アンダー.jpg │ │ ├── MIMI - カラバコにアイ.jpg │ │ ├── MIMI - コウフク貯金 (feat. 初音ミク).jpg │ │ ├── MIMI - フィオーレ (feat. 初音ミク&可不).jpg │ │ ├── MIMI - フローレミ.jpg │ │ ├── MIMI - マシュマリー.jpg │ │ ├── MIMI - ミライリフレクト.png │ │ ├── MIMI - モーメント.jpg │ │ ├── MIMI - ラピスラズリ.jpg │ │ ├── MIMI - ルルージュ.jpg │ │ ├── MIMI - 何もない様な.jpg │ │ ├── MIMI - 哀の隙間.jpg │ │ ├── MIMI - 夜明け前に飛び乗って.jpg │ │ ├── MIMI - 妄想哀歌 (feat. 初音ミク&可不).jpg │ │ ├── MIMI - 心を刺す言葉だけ feat. 初音ミク & 可不.jpg │ │ ├── MIMI - 愛し愛 (feat. 初音ミク & 可不).jpg │ │ ├── MIMI - 星涙哀歌.jpg │ │ ├── MIMI - 水流音楽.jpg │ │ ├── MIMI - 水音とカーテン.jpg │ │ ├── MIMI - 淡さと微睡む.jpg │ │ ├── MIMI - 透明夏 (feat. 初音ミク).jpg │ │ ├── MIMI - 遠く夢の奥.jpg │ │ ├── MIMI - 静寂に咲く.jpg │ │ ├── MIMI feat. 初音ミク - GLACIES.jpg │ │ ├── MIMI feat. 可不 - オマジナイ (long ver.).jpg │ │ ├── MIMI, 可不 - サヨナラは言わないでさ.jpg │ │ ├── MIMI, 可不 - 愛するように (feat. 可不).jpg │ │ ├── MIMI,わん子 - みにまむ (feat. わん子).jpg │ │ ├── MIMI,わん子 - もでらーと (feat. わん子).jpg │ │ ├── MIMI,アカラカイ - Without Knowing (feat. アカラカイ).jpg │ │ ├── MIMI,ロクデナシ - 愛が灯る.jpg │ │ ├── MIMI,初音ミク - GLACIES (feat. 初音ミク).jpg │ │ ├── MIMI,初音ミク - コウフク貯金 (feat. 初音ミク).jpg │ │ ├── MIMI,初音ミク - ロココ (feat. 初音ミク).jpg │ │ ├── MIMI,初音ミク - 夜のあいろに (feat. 初音ミク).jpg │ │ ├── MIMI,初音ミク - 始点前夕暮れ (feat. 初音ミク).jpg │ │ ├── MIMI,初音ミク - 風鈴歌.jpg │ │ ├── MIMI,初音ミク,可不 - はぐ.jpg │ │ ├── MIMI,可不 - あのね (feat. 可不).jpg │ │ ├── MIMI,可不 - いっせーのーで.jpg │ │ ├── MIMI,可不 - くうになる (feat. 可不).jpg │ │ ├── MIMI,可不 - それで充分だよ (feat. 可不).jpg │ │ ├── MIMI,可不 - だきしめるまで。.jpg │ │ ├── MIMI,可不 - ハナタバ.jpg │ │ ├── MIMI,可不 - ヒミツ.jpg │ │ ├── MIMI,可不 - ポシェット (feat. 可不).jpg │ │ ├── MIMI,可不 - 今はいいんだよ。 (feat. 可不).jpg │ │ ├── MIMI,可不 - 息をするだけ (feat. 可不).jpg │ │ ├── MIMI,星界 - えすけーぷ.jpg │ │ ├── MIMI,森先化歩,花譜,夜河世界,ヰ世界情緒 - あわく心模様.jpg │ │ ├── MIMI,羽累 - Maple (feat. 羽累).jpg │ │ ├── MIMI,裏命 - ぽけっと・愛の歌.jpg │ │ ├── MORE MORE JUMP!, 鏡音レン - はぐ.jpg │ │ ├── May'n - 人生進行形.jpg │ │ ├── Miuna Usako - I Need You.jpg │ │ ├── NINA - VALIS - わたしマニュアル.jpg │ │ ├── Rain Drops - シャロウ.jpg │ │ ├── WI'P - ツキミチシルベ feat. おれんじ君, たこちゃん, MIMI.jpg │ │ ├── konoco - ひだまりの彩度.jpg │ │ ├── konoco,こばしり。 - Bullets.jpg │ │ ├── seiza, MIMI, 初音ミク- 流星症候群.jpg │ │ ├── shorts │ │ │ ├── 20160901-771271485478404097_video.png │ │ │ ├── 20161127-802798343961202688_video.png │ │ │ ├── 20161203-804963578797170688_video.png │ │ │ ├── 20161210-807505126428581889_video.png │ │ │ ├── 20161225-812826935415836672_video.png │ │ │ ├── 20170226-835787026842542080_video.png │ │ │ ├── 20170311-840497669558566912_video.png │ │ │ ├── 20170318-842998283240787972_video.png │ │ │ ├── 20170820-899271417874726912_video.png │ │ │ ├── 20180101-947863080720928768_video.png │ │ │ ├── 20180122-955340979845840896_video.png │ │ │ ├── 20180426-989434455256256513_video.png │ │ │ ├── 20180520-998147054219051013_video.png │ │ │ ├── 20180531-1002160900265005057_video.png │ │ │ ├── 20180624-1010836676455755776_video.png │ │ │ ├── 20180702-1013769075724468224_video.png │ │ │ ├── 20200411-1248891825693184000_video.png │ │ │ ├── 20200617-1273168511574171655_video.png │ │ │ ├── 20200705-1279704774439403521_video.png │ │ │ ├── 20220717-1548734689854509056_video.png │ │ │ ├── 20221024-1584533187396784129_video.png │ │ │ ├── 20230104-1610554437269098498_video.png │ │ │ ├── 20230311-1634663531298820096_video.png │ │ │ ├── 20230405-1643716964257505286_video.png │ │ │ ├── 20230408-1644838849959571456_video.png │ │ │ ├── 20230530-1663668305754980353_video.png │ │ │ ├── 20230714-1679850034739974144.png │ │ │ ├── 20230821-1693701649980850325_video.png │ │ │ ├── 20230831-1697179345960472839.png │ │ │ ├── 20231112-1723793803130392766.png │ │ │ ├── tiktok_7121730142648716545.png │ │ │ ├── tiktok_7217363999069883650.png │ │ │ ├── tiktok_7217728862703209730.png │ │ │ ├── tiktok_7218664751784660225.png │ │ │ ├── tiktok_7243687154054974722.png │ │ │ ├── tiktok_7243971426762411266.png │ │ │ ├── tiktok_7244460440527326466.png │ │ │ ├── tiktok_7249008212798655745.png │ │ │ ├── tiktok_7249388073945926913.png │ │ │ ├── tiktok_7249388698058411265.png │ │ │ ├── tiktok_7249487516645084418.png │ │ │ ├── tiktok_7249488478126296322.png │ │ │ ├── tiktok_7249671575778151682.png │ │ │ ├── tiktok_7251113652382731521.png │ │ │ ├── tiktok_7251896291632893186.png │ │ │ ├── tiktok_7253351896734289154.png │ │ │ ├── tiktok_7253366505151253761.png │ │ │ ├── tiktok_7253381975820586241.png │ │ │ ├── tiktok_7254148636915797249.png │ │ │ ├── tiktok_7254149470517038338.png │ │ │ ├── tiktok_7254150282668461314.png │ │ │ ├── tiktok_7254462703803108610.png │ │ │ ├── tiktok_7255219509097680130.png │ │ │ ├── tiktok_7255594178279738625.png │ │ │ ├── tiktok_7256023414228503810.png │ │ │ ├── tiktok_7256365983093280001.png │ │ │ └── tiktok_7261167927523691783.png │ │ ├── 『 わたしマニュアル (Original Arrange Ver.) 』/ MIMI feat. 可不.jpg │ │ ├── いゔどっと - 月日記.jpg │ │ ├── ひなの羽衣 - ルティナ.jpg │ │ ├── むト - silence.jpg │ │ ├── わたしトラベラー - NINA - VALIS.jpg │ │ ├── ロクデナシ - ただ声一つ.jpg │ │ ├── ロクデナシ - 知らないままで.jpg │ │ ├── 原因は自分にある - ダイヤモンドリリー.jpg │ │ ├── 夢音ちゃの×MIMI - 季節にまどろむ.png │ │ ├── 存流 - まってるよ.jpg │ │ ├── 涼海ネモ, MIMI - カタチのないもの.jpg │ │ ├── 花譜 feat. 可不(KAFU) - いっせーのーで.png │ │ └── 花譜 feat. 理芽 - まほう(MIMI Remix).jpg │ ├── lyrics │ │ ├── 20220717-1548734689854509056_video.json │ │ ├── 20230311-1634663531298820096_video.json │ │ ├── 20230530-1663668305754980353_video.json │ │ ├── ESHIKARA,MIMI,可不 - ふわり (feat. MIMI,可不,初音ミク).json │ │ ├── I Need You - Miuna Usako.json │ │ ├── LSCM,MIMI - そして夜と灯る (feat. MIMI).json │ │ ├── MIMI - Pale.json │ │ ├── MIMI - SorrowChat.json │ │ ├── MIMI - What Call This Day ? (feat. にんじん).json │ │ ├── MIMI - ぎゅって.json │ │ ├── MIMI - もーいいかい.json │ │ ├── MIMI - ゆめまぼろし.json │ │ ├── MIMI - よるつむぎ.json │ │ ├── MIMI - カラバコにアイ.json │ │ ├── MIMI - フィオーレ (feat. 初音ミク&可不).json │ │ ├── MIMI - フローレミ.json │ │ ├── MIMI - マシュマリー.json │ │ ├── MIMI - モーメント.json │ │ ├── MIMI - ルルージュ.json │ │ ├── MIMI - 何もない様な.json │ │ ├── MIMI - 哀の隙間.json │ │ ├── MIMI - 夜明け前に飛び乗って.json │ │ ├── MIMI - 妄想哀歌 (feat. 初音ミク&可不).json │ │ ├── MIMI - 水音とカーテン.json │ │ ├── MIMI - 透明夏 (feat. 初音ミク).json │ │ ├── MIMI - 静寂に咲く.json │ │ ├── MIMI feat. 初音ミク - GLACIES.json │ │ ├── MIMI, 可不 - サヨナラは言わないでさ.json │ │ ├── MIMI, 可不 - 愛するように (feat. 可不).json │ │ ├── MIMI,わん子 - みにまむ (feat. わん子).json │ │ ├── MIMI,わん子 - もでらーと (feat. わん子).json │ │ ├── MIMI,アカラカイ - Without Knowing (feat. アカラカイ).json │ │ ├── MIMI,ロクデナシ - 愛が灯る.json │ │ ├── MIMI,初音ミク - GLACIES (feat. 初音ミク).json │ │ ├── MIMI,初音ミク - コウフク貯金 (feat. 初音ミク).json │ │ ├── MIMI,初音ミク - ロココ (feat. 初音ミク).json │ │ ├── MIMI,初音ミク - 夜のあいろに (feat. 初音ミク).json │ │ ├── MIMI,初音ミク - 風鈴歌.json │ │ ├── MIMI,可不 - あのね (feat. 可不).json │ │ ├── MIMI,可不 - いっせーのーで.json │ │ ├── MIMI,可不 - くうになる (feat. 可不).json │ │ ├── MIMI,可不 - だきしめるまで。.json │ │ ├── MIMI,可不 - ハナタバ.json │ │ ├── MIMI,可不 - ヒミツ.json │ │ ├── MIMI,可不 - ポシェット (feat. 可不).json │ │ ├── MIMI,可不 - 今はいいんだよ。 (feat. 可不).json │ │ ├── MIMI,可不 - 息をするだけ (feat. 可不).json │ │ ├── MIMI,星界 - えすけーぷ.json │ │ ├── MIMI,森先化歩,花譜,夜河世界,ヰ世界情緒 - あわく心模様.json │ │ ├── MIMI,羽累 - Maple (feat. 羽累).json │ │ ├── MIMI,裏命 - ぽけっと・愛の歌.json │ │ ├── May'n - 人生進行形.json │ │ ├── Miuna Usako - I Need You.json │ │ ├── NINA - VALIS - わたしマニュアル.json │ │ ├── Rain Drops - シャロウ.json │ │ ├── konoco - ひだまりの彩度.json │ │ ├── konoco,こばしり。 - Bullets.json │ │ ├── seiza, MIMI, 初音ミク- 流星症候群.json │ │ ├── tiktok_7243687154054974722.json │ │ ├── tiktok_7243971426762411266.json │ │ ├── tiktok_7256023414228503810.json │ │ ├── tiktok_7256365983093280001.json │ │ ├── tiktok_7261167927523691783.json │ │ ├── いゔどっと - 月日記.json │ │ ├── ひなの羽衣 - ルティナ.json │ │ ├── むト - silence.json │ │ ├── ロクデナシ - ただ声一つ.json │ │ ├── ロクデナシ - 知らないままで.json │ │ └── 存流 - まってるよ.json │ ├── mirrored.json │ ├── shorts.json │ └── songs.json ├── hooks │ ├── useAsyncCachedFetch.js │ ├── useRefState.js │ ├── useRefStateStorage.js │ ├── useScrolled.js │ ├── useShuffleDeque.js │ └── useStateStorage.js ├── index.scss ├── main.jsx ├── utils.js └── utils │ ├── applyThemeTokensToElement.js │ ├── calcThemeTokens.js │ ├── colorToInt.js │ ├── getCloestScrollableParent.js │ ├── getInViewPosition.js │ ├── getRelativePosition.js │ └── isScrollable.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | settings: { react: { version: '18.2' } }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | blurhash-map.json -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MIMI Radio 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mimi-radio", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@material/material-color-utilities": "^0.2.6", 14 | "base-64": "^1.0.0", 15 | "camelcase": "^7.0.1", 16 | "classnames": "^2.3.2", 17 | "md5": "^2.3.0", 18 | "react": "^18.2.0", 19 | "react-blurhash": "^0.3.0", 20 | "react-click-away-listener": "^2.2.3", 21 | "react-dom": "^18.2.0", 22 | "react-flip-toolkit": "^7.1.0", 23 | "react-icons": "^4.8.0", 24 | "react-youtube": "^10.1.0", 25 | "utf8": "^3.0.0" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.0.28", 29 | "@types/react-dom": "^18.0.11", 30 | "@vitejs/plugin-react-swc": "^3.0.0", 31 | "eslint": "^8.38.0", 32 | "eslint-plugin-react": "^7.32.2", 33 | "eslint-plugin-react-hooks": "^4.6.0", 34 | "eslint-plugin-react-refresh": "^0.3.4", 35 | "sass": "^1.62.1", 36 | "sharp": "^0.32.4", 37 | "vite": "^4.3.2", 38 | "vite-plugin-blurhash-sharp-fix-fork": "^0.2.1", 39 | "vite-plugin-image-optimizer": "^1.1.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/mimi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/public/mimi.jpg -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useLayoutEffect, useState } from 'react' 2 | import IconButton from './components/IconButton.jsx' 3 | import TopAppBar from './components/TopAppBar.jsx' 4 | import Card from './components/Card.jsx' 5 | import { FaGithub } from 'react-icons/fa' 6 | import { SongListSection } from './SongListSection.jsx' 7 | import { NowPlayingSection } from './NowPlayingSection.jsx' 8 | import { EntryDetailsDialog } from './EntryDetailsDialog.jsx' 9 | import './App.scss' 10 | 11 | function App() { 12 | return ( 13 | <> 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | } 25 | > 26 | MIMI Radio 27 | {' '} 28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | ) 37 | } 38 | 39 | export default App 40 | 41 | function SongCount() { 42 | const [count, setCount] = useState(window.songCount ?? -1); 43 | 44 | useLayoutEffect(() => { 45 | const updateCount = () => { 46 | const count = window.songCount ?? -1; 47 | setCount(count); 48 | } 49 | window.addEventListener('song-count-change', updateCount); 50 | updateCount(); 51 | return () => { 52 | window.removeEventListener('song-count-change', updateCount); 53 | } 54 | }, []); 55 | 56 | 57 | return ( 58 | count >= 0 && {count} Songs 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | #root { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | > * { 7 | flex-shrink: 0; 8 | } 9 | } 10 | .main { 11 | flex: 1; 12 | width: 100%; 13 | display: flex; 14 | flex-direction: row; 15 | gap: 20px; 16 | padding-left: 25px; 17 | padding-right: 25px; 18 | padding-top: 10px; 19 | padding-bottom: 10px; 20 | overflow: hidden; 21 | > * { 22 | flex: 1; 23 | flex-shrink: 0; 24 | } 25 | } 26 | 27 | @media screen and (max-width: 950px) { 28 | .main { 29 | flex-direction: column; 30 | padding-left: 15px; 31 | padding-right: 15px; 32 | padding-bottom: 0; 33 | .list-section { 34 | height: 100%; 35 | flex: unset; 36 | overflow: hidden; 37 | .song-list::after { 38 | display: none; 39 | } 40 | } 41 | } 42 | } 43 | 44 | .topbar-song-count { 45 | opacity: .6; 46 | font-size: 80%; 47 | padding-left: 4px; 48 | } 49 | 50 | .mobile-topbar-icon-placeholder { 51 | display: none; 52 | pointer-events: none; 53 | @media screen and (max-width: 550px) { 54 | display: flex; 55 | } 56 | } 57 | 58 | ::selection { 59 | //background-color: var(--md-sys-color-primary); 60 | //color: var(--md-sys-color-on-primary); 61 | background-color: rgba(var(--md-sys-color-primary-rgb), 0.25); 62 | } -------------------------------------------------------------------------------- /src/EntryDetailsDialog.scss: -------------------------------------------------------------------------------- 1 | .details-dialog { 2 | .dialog { 3 | min-width: min(100% - 48px, 450px) !important; 4 | .tabs { 5 | margin-left: -24px; 6 | margin-right: -24px; 7 | .tab-content { 8 | padding-left: 24px; 9 | padding-right: 24px; 10 | } 11 | } 12 | } 13 | .details-dialog-header { 14 | display: flex; 15 | flex-direction: row; 16 | flex-wrap: nowrap; 17 | align-items: center; 18 | gap: 16px; 19 | .cover { 20 | aspect-ratio: 1 / 1; 21 | width: 48px; 22 | height: 48px; 23 | border-radius:10px; 24 | } 25 | 26 | &.big { 27 | margin-left: -24px; 28 | margin-right: -24px; 29 | margin-top: -24px; 30 | width: calc(100% + 48px); 31 | aspect-ratio: 1/1; 32 | position: relative; 33 | .cover { 34 | width: 100%; 35 | height: unset; 36 | aspect-ratio: 1/1; 37 | border-radius: 24px 24px 0 0; 38 | display: block; 39 | } 40 | .name { 41 | position: absolute; 42 | left: 0px; 43 | bottom: 0px; 44 | right: 0px; 45 | padding: 24px; 46 | font-size: 125%; 47 | background: linear-gradient(0deg, #0006 20%, #0000 100%); 48 | } 49 | } 50 | } 51 | 52 | .list-item { 53 | overflow: hidden; 54 | position: relative; 55 | display: flex; 56 | flex-direction: column; 57 | font-size: 18px; 58 | gap: 8px; 59 | padding-top: 10px; 60 | padding-bottom: 10px; 61 | margin-left: -24px; 62 | margin-right: -24px; 63 | padding-left: 24px; 64 | padding-right: 24px; 65 | .headline { 66 | display: flex; 67 | gap: 0.4em; 68 | font-size: 16px; 69 | align-items: center; 70 | opacity: .8; 71 | } 72 | .content { 73 | line-height: 1.5; 74 | &.smaller { 75 | font-size: 85%; 76 | } 77 | } 78 | } 79 | 80 | .list-row { 81 | display: flex; 82 | flex-direction: row; 83 | gap: 0px; 84 | > * { 85 | border-radius: 8px; 86 | flex: 1; 87 | } 88 | > *:first-child { 89 | border-top-left-radius: 0px; 90 | border-bottom-left-radius: 0px; 91 | } 92 | > *:last-child { 93 | border-top-right-radius: 0px; 94 | border-bottom-right-radius: 0px; 95 | } 96 | > *:not(:first-child) { 97 | margin-left: 0px; 98 | } 99 | > *:not(:last-child) { 100 | margin-right: 0px; 101 | } 102 | } 103 | 104 | button { 105 | outline: none !important; 106 | } 107 | 108 | .no-scale-animation { 109 | transform: none !important; 110 | } 111 | 112 | 113 | .lyrics { 114 | .lyrics-control { 115 | display: flex; 116 | flex-direction: row; 117 | gap: 12px; 118 | flex-wrap: nowrap; 119 | margin-bottom: 15px; 120 | align-items: center; 121 | justify-content: center; 122 | .translation-selector { 123 | flex: 1; 124 | } 125 | } 126 | .lyrics-content { 127 | font-size: 16px; 128 | line-height: 1.5; 129 | .line { 130 | display: flex; 131 | flex-direction: row; 132 | flex-wrap: nowrap; 133 | gap: 0.6em; 134 | .time-stamp { 135 | font-family: monospace; 136 | } 137 | &.interlude { 138 | opacity: .5; 139 | line-height: 1.8; 140 | .lyrics-text::before { 141 | content: '\266B'; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | 150 | ::view-transition-old(entry-details-dialog), 151 | ::view-transition-new(entry-details-dialog) { 152 | mix-blend-mode: plus-lighter; 153 | height: 100%; 154 | overflow: clip; 155 | } 156 | ::view-transition-old(entry-details-dialog-cover), 157 | ::view-transition-new(entry-details-dialog-cover) { 158 | mix-blend-mode: plus-lighter; 159 | height: 100%; 160 | overflow: clip; 161 | } -------------------------------------------------------------------------------- /src/NowPlayingSection.jsx: -------------------------------------------------------------------------------- 1 | import './NowPlayingSection.scss'; 2 | import { VideoPlayer } from './Player.jsx'; 3 | import { Lyrics } from './Lyrics.jsx'; 4 | import { Controller } from './Controller.jsx'; 5 | 6 | import { MdKeyboardArrowUp } from 'react-icons/md'; 7 | import { useRipple } from './components/Ripple'; 8 | 9 | import { QueueContext } from './contexts/QueueContext.jsx'; 10 | import classNames from 'classnames'; 11 | import { useContext } from 'react'; 12 | 13 | export function NowPlayingSection() { 14 | const queueManager = useContext(QueueContext); 15 | 16 | return ( 17 |
25 | 26 | 27 | 28 | 29 |
30 | ) 31 | } 32 | 33 | 34 | function ExpandButton() { 35 | const ref = useRipple(); 36 | 37 | return ( 38 |
{ 42 | document.body.classList.toggle('now-playing-expanded'); 43 | }} 44 | > 45 | 46 |
47 | ); 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import css from './Button.module.scss'; 4 | import { forwardRef, useRef } from 'react'; 5 | 6 | const Button = forwardRef(function Button(props, ref) { 7 | if (!ref) ref = useRef(null); 8 | useRipple(ref); 9 | 10 | return ( 11 | 38 | ) 39 | }); 40 | 41 | export default Button; -------------------------------------------------------------------------------- /src/components/Button.module.scss: -------------------------------------------------------------------------------- 1 | @import './global/elevation.scss'; 2 | 3 | .Button { 4 | height: 40px; 5 | padding-left: 24px; 6 | padding-right: 24px; 7 | border-radius: 20px; 8 | font-size: 14px; 9 | font-weight: 500; 10 | font-family: inherit; 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | gap: 8px; 15 | position: relative; 16 | overflow: hidden; 17 | cursor: pointer; 18 | user-select: none; 19 | transition: box-shadow 0.2s ease, background-color 0.2s ease, color 0.2s ease; 20 | box-sizing: border-box; 21 | border: none; 22 | background-color: transparent; 23 | .icon { 24 | font-size: 18px; 25 | width: 18px; 26 | height: 18px; 27 | margin-left: -8px; 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | } 32 | 33 | 34 | &::before { 35 | content: ''; 36 | position: absolute; 37 | display: block; 38 | inset: 0; 39 | opacity: 0; 40 | transition: opacity 0.2s ease, background-color 0.2s ease; 41 | pointer-events: none; 42 | } 43 | &:hover::before { 44 | opacity: 0.08; 45 | } 46 | &:active::before { 47 | opacity: 0.12; 48 | } 49 | &:focus-visible::before { 50 | opacity: 0.12; 51 | } 52 | 53 | 54 | &.elevated { 55 | background-color: var(--md-sys-color-surface-container-low); 56 | @include elevation(1); 57 | color: var(--md-sys-color-primary); 58 | --ripple-color: var(--md-sys-color-primary); 59 | &::before { 60 | background-color: var(--md-sys-color-primary); 61 | } 62 | 63 | &:hover { 64 | @include elevation(2); 65 | } 66 | &:active { 67 | @include elevation(1); 68 | } 69 | &:focus-visible { 70 | @include elevation(1); 71 | } 72 | &.disabled { 73 | background-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 74 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 75 | } 76 | } 77 | 78 | &.filled { 79 | background-color: var(--md-sys-color-primary); 80 | color: var(--md-sys-color-on-primary); 81 | --ripple-color: var(--md-sys-color-on-primary); 82 | &::before { 83 | background-color: var(--md-sys-color-on-primary); 84 | } 85 | 86 | &:hover { 87 | @include elevation(1); 88 | } 89 | &:active { 90 | @include elevation(0); 91 | } 92 | &:focus-visible { 93 | @include elevation(0); 94 | } 95 | &.disabled { 96 | background-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 97 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 98 | } 99 | } 100 | 101 | 102 | &.tonal { 103 | background-color: var(--md-sys-color-secondary-container); 104 | color: var(--md-sys-color-on-secondary-container); 105 | --ripple-color: var(--md-sys-color-on-secondary-container); 106 | &::before { 107 | background-color: var(--md-sys-color-on-secondary-container); 108 | } 109 | 110 | &:hover { 111 | @include elevation(1); 112 | } 113 | &:active { 114 | @include elevation(0); 115 | } 116 | &:focus-visible { 117 | @include elevation(0); 118 | } 119 | &.disabled { 120 | background-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 121 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 122 | } 123 | } 124 | 125 | 126 | &.outlined { 127 | border: 1px solid var(--md-sys-color-outline); 128 | color: var(--md-sys-color-primary); 129 | --ripple-color: var(--md-sys-color-primary); 130 | &::before { 131 | background-color: var(--md-sys-color-primary); 132 | } 133 | 134 | &.disabled { 135 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 136 | border-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 137 | } 138 | } 139 | 140 | 141 | &.text { 142 | background-color: transparent; 143 | color: var(--md-sys-color-primary); 144 | --ripple-color: var(--md-sys-color-primary); 145 | &::before { 146 | background-color: var(--md-sys-color-primary); 147 | } 148 | 149 | &.disabled { 150 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 151 | } 152 | } 153 | 154 | 155 | &.disabled { 156 | pointer-events: none; 157 | } 158 | } -------------------------------------------------------------------------------- /src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import css from './Card.module.scss'; 4 | 5 | function Card(props) { 6 | const container = useRipple(); 7 | 8 | return ( 9 |
28 | {props.children} 29 |
30 | ) 31 | } 32 | 33 | export default Card; -------------------------------------------------------------------------------- /src/components/Card.module.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | border-radius: 12px; 3 | overflow: hidden; 4 | position: relative; 5 | transition: box-shadow 0.2s ease; 6 | &::before { 7 | content: ''; 8 | position: absolute; 9 | display: block; 10 | inset: 0; 11 | background-color: var(--md-sys-color-on-surface); 12 | opacity: 0; 13 | transition: opacity 0.2s ease; 14 | pointer-events: none; 15 | } 16 | &.noRipple > *[class*='ripple-wrap'] { 17 | display: none !important; 18 | } 19 | &.noLayer::before { 20 | display: none; 21 | } 22 | &.filled { 23 | background-color: var(--md-sys-color-surface-container-highest); 24 | color: var(--md-sys-color-on-surface-variant); 25 | &:hover { 26 | &::before { 27 | opacity: 0.08; 28 | } 29 | &:not(:active) { 30 | //box-shadow: 0 1px 2px 1px rgba(var(--md-sys-color-shadow-rgb), 0.1), 0 3px 8px 3px rgba(var(--md-sys-color-shadow-rgb), 0.05); 31 | //level2 shadow, should be revised to level1 32 | } 33 | } 34 | &:active { 35 | &::before { 36 | opacity: 0.12; 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/components/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import css from './Checkbox.module.scss'; 3 | import { useEffect, useState } from 'react'; 4 | import { useRipple } from './Ripple.jsx'; 5 | 6 | function Checkbox(props) { 7 | const [checked, setChecked] = useState((props.checked !== undefined ? props.checked : props.defaultChecked) || false); 8 | 9 | const checkmarkContainerRef = useRipple(null, { center: true }); 10 | 11 | const onClick = e => { 12 | setChecked(!checked); 13 | props.onChange?.(!checked); 14 | } 15 | 16 | const currentChecked = props.checked !== undefined ? props.checked : checked; 17 | 18 | return ( 19 |
23 |
33 | 41 | 44 | 45 |
46 | { 47 | props.label && 48 | } 49 |
50 | 51 | ) 52 | } 53 | 54 | export default Checkbox; -------------------------------------------------------------------------------- /src/components/Checkbox.module.scss: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | gap: 0.2em; 6 | .input { 7 | width: 0; 8 | height: 0; 9 | opacity: 0; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | .checkmarkContainer { 14 | width: 40px; 15 | height: 40px; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | cursor: pointer; 20 | overflow: hidden; 21 | border-radius: 1000px; 22 | position: relative; 23 | --ripple-color: var(--md-sys-color-primary); 24 | 25 | &::before { 26 | content: ''; 27 | position: absolute; 28 | display: block; 29 | inset: 0; 30 | background-color: var(--md-sys-color-on-surface-variant); 31 | opacity: 0; 32 | transition: opacity 0.2s ease, background-color 0.2s ease; 33 | } 34 | &:hover::before { 35 | opacity: 0.08; 36 | } 37 | &:active::before { 38 | opacity: 0.12; 39 | } 40 | &.checked::before { 41 | background-color: var(--md-sys-color-primary); 42 | } 43 | 44 | .checkmark { 45 | border-width: 2px; 46 | border-style: solid; 47 | border-radius: 2px; 48 | border-color: var(--md-sys-color-on-surface-variant); 49 | background-color: transparent; 50 | 51 | fill: none; 52 | stroke: var(--md-sys-color-on-primary); 53 | stroke-miterlimit: 12; 54 | stroke-width: 12px; 55 | stroke-dasharray: 80.5px; 56 | stroke-dashoffset: 80.5px; 57 | transition: stroke-dashoffset 0.2s ease, border-color 0.2s ease, background-color 0.2s ease; 58 | width: 18px; 59 | height: 18px; 60 | 61 | 62 | &.checked { 63 | background-color: var(--md-sys-color-primary); 64 | stroke-dashoffset: 0px; 65 | border-color: transparent; 66 | } 67 | 68 | path { 69 | transform: scale(1.2); 70 | transform-origin: center; 71 | } 72 | } 73 | } 74 | 75 | .label { 76 | cursor: pointer; 77 | user-select: none; 78 | } 79 | } -------------------------------------------------------------------------------- /src/components/Dialog.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { forwardRef, useEffect, useRef } from 'react'; 3 | import { createPortal } from 'react-dom'; 4 | import Button from './Button.jsx'; 5 | import css from './Dialog.module.scss'; 6 | 7 | const Dialog = forwardRef(function Dialog(props, ref) { 8 | if (ref === null) ref = useRef(); 9 | 10 | const parentElement = props?.parentElement ?? document.body; 11 | 12 | const open = props.open ?? false; 13 | 14 | const hasButton = props.positiveButton || props.negativeButton || props.neutralButton; 15 | 16 | useEffect(() => { 17 | if ((props?.closeOnEsc ?? true) && open) { 18 | const listener = (e) => { 19 | if (e.key === 'Escape') { 20 | props.onClose?.(); 21 | } 22 | }; 23 | document.addEventListener('keydown', listener); 24 | return () => { 25 | document.removeEventListener('keydown', listener); 26 | } 27 | } 28 | }, [props.closeOnEsc, props.open]); 29 | 30 | return ( 31 | createPortal( 32 |
{ 42 | if (!props.closeOnClickOutside) return; 43 | if (e.target === ref.current) { 44 | props.onClose?.(); 45 | } 46 | }} 47 | style={{ 48 | 'zIndex': props.zIndex ?? 1000, 49 | }} 50 | > 51 |
60 | { 61 | props.icon &&
62 | {props.icon} 63 |
64 | } 65 | { 66 | props.title &&
67 | {props.title} 68 |
69 | } 70 |
71 | {props.children} 72 |
73 | { 74 | hasButton &&
75 | { 76 | props.neutralButton && 77 | 78 | } 79 |
80 | { 81 | props.negativeButton && 82 | 83 | } 84 | { 85 | props.positiveButton && 86 | 87 | } 88 |
89 | } 90 |
91 | { 92 | open && 99 | } 100 |
101 | , parentElement) 102 | ) 103 | }); 104 | 105 | export default Dialog; -------------------------------------------------------------------------------- /src/components/Dialog.module.scss: -------------------------------------------------------------------------------- 1 | @import './global/elevation.scss'; 2 | 3 | .dialogScrim { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | z-index: 1000; 8 | width: 100%; 9 | height: 100%; 10 | background-color: rgba(var(--md-sys-color-scrim-rgb), 0.2); 11 | backdrop-filter: blur(2px); 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | transition: opacity 0.2s ease, background-color 0.2s ease, backdrop-filter 0.2s ease; 16 | .dialog { 17 | background-color: var(--md-sys-color-surface-container-high); 18 | @include elevation(3); 19 | border-radius: 28px; 20 | padding: 24px; 21 | transition: transform 0.2s ease, opacity 0.2s ease; 22 | min-width: 280px; 23 | max-width: min(560px, calc(100% - 48px)); 24 | max-height: calc(100% - 48px); 25 | display: flex; 26 | flex-direction: column; 27 | 28 | .icon { 29 | text-align: center; 30 | display: flex; 31 | flex-direction: row; 32 | justify-content: center; 33 | font-size: 24px; 34 | color: var(--md-sys-color-secondary); 35 | fill: var(--md-sys-color-secondary); 36 | margin-bottom: 16px; 37 | } 38 | 39 | .title { 40 | font-size: 24px; 41 | font-weight: 400; 42 | line-height: 32px; 43 | color: var(--md-sys-color-on-surface); 44 | margin-bottom: 16px; 45 | letter-spacing: 0px; 46 | } 47 | 48 | .icon + .title { 49 | text-align: center; 50 | } 51 | 52 | .content { 53 | font-size: 14px; 54 | font-weight: 400; 55 | line-height: 20px; 56 | color: var(--md-sys-color-on-surface-variant); 57 | letter-spacing: 0.5px; 58 | overflow-y: auto; 59 | overflow-x: hidden; 60 | width: calc(100% + 48px); 61 | margin-left: -24px; 62 | margin-right: -24px; 63 | padding-left: 24px; 64 | padding-right: 24px; 65 | &::-webkit-scrollbar { 66 | width: 6px; 67 | height: 8px; 68 | background-color: transparent; 69 | } 70 | &::-webkit-scrollbar-thumb { 71 | background-color: var(--md-sys-color-surface-bright); 72 | } 73 | } 74 | 75 | .buttons { 76 | display: flex; 77 | flex-direction: row; 78 | flex-wrap: nowrap; 79 | gap: 8px; 80 | margin-top: 24px; 81 | .spacer { 82 | flex: 1; 83 | } 84 | } 85 | 86 | } 87 | 88 | 89 | &.hide { 90 | pointer-events: none; 91 | background-color: transparent; 92 | backdrop-filter: blur(0px); 93 | .dialog { 94 | transform: scale(0.9); 95 | opacity: 0; 96 | pointer-events: none; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/components/FAB.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import css from './FAB.module.scss'; 4 | import { forwardRef, useRef } from 'react'; 5 | 6 | const FAB = forwardRef(function FAB(props, ref) { 7 | if (!ref) ref = useRef(null); 8 | useRipple(ref); 9 | 10 | return ( 11 | 33 | ) 34 | }); 35 | 36 | export default FAB; -------------------------------------------------------------------------------- /src/components/FAB.module.scss: -------------------------------------------------------------------------------- 1 | .FAB { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | overflow: hidden; 6 | cursor: pointer; 7 | user-select: none; 8 | border: none; 9 | outline: none !important; 10 | position: relative; 11 | padding: 0; 12 | 13 | &::before { 14 | content: ''; 15 | position: absolute; 16 | display: block; 17 | inset: 0; 18 | opacity: 0; 19 | transition: opacity 0.2s ease, background-color 0.2s ease; 20 | pointer-events: none; 21 | } 22 | &:hover::before { 23 | opacity: 0.08; 24 | } 25 | &:active::before { 26 | opacity: 0.12; 27 | } 28 | 29 | &.sizeNormal { 30 | height: 56px; 31 | width: 56px; 32 | border-radius: 24px; 33 | font-size: 24px; 34 | } 35 | &.sizeSmall { 36 | height: 40px; 37 | width: 40px; 38 | border-radius: 12px; 39 | font-size: 24px; 40 | } 41 | &.sizeLarge { 42 | height: 96px; 43 | width: 96px; 44 | border-radius: 28px; 45 | font-size: 36px; 46 | } 47 | 48 | &.colorPrimary { 49 | background-color: var(--md-sys-color-primary-container); 50 | color: var(--md-sys-color-on-primary-container); 51 | &::before { 52 | background-color: var(--md-sys-color-on-primary-container); 53 | } 54 | } 55 | &.colorSurface, &.colorSurfaceLowered { 56 | background-color: var(--md-sys-color-surface-container-high); 57 | color: var(--md-sys-color-primary); 58 | &::before { 59 | background-color: var(--md-sys-color-primary); 60 | } 61 | &.colorSurfaceLowered { 62 | background-color: var(--md-sys-color-surface-container-low); 63 | } 64 | } 65 | &.colorSecondary { 66 | background-color: var(--md-sys-color-secondary-container); 67 | color: var(--md-sys-color-on-secondary-container); 68 | &::before { 69 | background-color: var(--md-sys-color-on-secondary-container); 70 | } 71 | } 72 | &.colorTertiary { 73 | background-color: var(--md-sys-color-tertiary-container); 74 | color: var(--md-sys-color-on-tertiary-container); 75 | &::before { 76 | background-color: var(--md-sys-color-on-tertiary-container); 77 | } 78 | } 79 | 80 | > svg { 81 | height: 1em; 82 | width: 1em; 83 | stroke: currentcolor; 84 | fill: currentcolor; 85 | stroke-width: 0; 86 | } 87 | } -------------------------------------------------------------------------------- /src/components/Icon.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import css from './Icon.module.scss'; 3 | 4 | function Icon(props) { 5 | return ( 6 | 7 | {props.children} 8 | 9 | ) 10 | } 11 | 12 | export default Icon; -------------------------------------------------------------------------------- /src/components/Icon.module.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 1em; 3 | height: 1em; 4 | svg { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/IconButton.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import css from './IconButton.module.scss'; 4 | import { forwardRef, useRef } from 'react'; 5 | 6 | 7 | const IconButton = forwardRef(function IconButton(props, ref) { 8 | if (!ref) ref = useRef(null); 9 | useRipple(ref); 10 | 11 | return ( 12 |
34 | {props.children} 35 |
36 | ) 37 | }); 38 | 39 | export default IconButton; -------------------------------------------------------------------------------- /src/components/IconButton.module.scss: -------------------------------------------------------------------------------- 1 | .iconButton { 2 | width: 40px; 3 | height: 40px; 4 | font-size: 24px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius: 1000px; 9 | position: relative; 10 | cursor: pointer; 11 | overflow: hidden; 12 | transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; 13 | 14 | &::before { 15 | content: ''; 16 | position: absolute; 17 | display: block; 18 | inset: 0; 19 | opacity: 0; 20 | transition: opacity 0.2s ease, background-color 0.2s ease; 21 | pointer-events: none; 22 | } 23 | &:hover::before { 24 | opacity: 0.08; 25 | } 26 | &:active::before { 27 | opacity: 0.12; 28 | } 29 | &:focus-visible::before { 30 | opacity: 0.12; 31 | } 32 | 33 | 34 | 35 | &.standard { 36 | color: var(--md-sys-color-on-surface-variant); 37 | &::before { 38 | background-color: var(--md-sys-color-on-surface-variant); 39 | } 40 | --ripple-color: var(--md-sys-color-on-surface-variant); 41 | &.selected { 42 | color: var(--md-sys-color-primary); 43 | } 44 | &.disabled { 45 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 46 | background-color: transparent; 47 | } 48 | } 49 | 50 | &.outlined { 51 | color: var(--md-sys-color-on-surface-variant); 52 | border: 1px solid var(--md-sys-color-outline); 53 | &::before { 54 | background-color: var(--md-sys-color-on-surface-variant); 55 | } 56 | --ripple-color: var(--md-sys-color-on-surface-variant); 57 | &.selected { 58 | color: var(--md-sys-color-inverse-on-surface); 59 | background-color: var(--md-sys-color-inverse-surface); 60 | &::before { 61 | background-color: var(--md-sys-color-inverse-on-surface); 62 | } 63 | --ripple-color: var(--md-sys-color-inverse-on-surface); 64 | } 65 | &.disabled { 66 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 67 | border-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 68 | } 69 | } 70 | 71 | 72 | &.filled { 73 | color: var(--md-sys-color-on-primary); 74 | background-color: var(--md-sys-color-primary); 75 | &::before { 76 | background-color: var(--md-sys-color-on-primary); 77 | } 78 | --ripple-color: var(--md-sys-color-on-primary); 79 | &.toggle { 80 | &:not(.selected) { 81 | color: var(--md-sys-color-primary); 82 | background-color: var(--md-sys-color-surface-container-highest); 83 | &::before { 84 | background-color: var(--md-sys-color-primary); 85 | } 86 | --ripple-color: var(--md-sys-color-primary); 87 | } 88 | &.selected { 89 | color: var(--md-sys-color-on-primary); 90 | background-color: var(--md-sys-color-primary); 91 | &::before { 92 | background-color: var(--md-sys-color-on-primary); 93 | } 94 | --ripple-color: var(--md-sys-color-on-primary); 95 | } 96 | } 97 | &.disabled { 98 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 99 | background-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 100 | } 101 | } 102 | 103 | 104 | &.tonal { 105 | color: var(--md-sys-color-on-secondary-container); 106 | background-color: var(--md-sys-color-secondary-container); 107 | &::before { 108 | background-color: var(--md-sys-color-on-secondary-container); 109 | } 110 | --ripple-color: var(--md-sys-color-on-secondary-container); 111 | &.toggle { 112 | &:not(.selected) { 113 | color: var(--md-sys-color-on-surface-variant); 114 | background-color: var(--md-sys-color-surface-container-highest); 115 | &::before { 116 | background-color: var(--md-sys-color-on-surface-variant); 117 | } 118 | --ripple-color: var(--md-sys-color-on-surface-variant); 119 | } 120 | &.selected { 121 | color: var(--md-sys-color-on-secondary-container); 122 | background-color: var(--md-sys-color-secondary-container); 123 | &::before { 124 | background-color: var(--md-sys-color-on-secondary-container); 125 | } 126 | --ripple-color: var(--md-sys-color-on-secondary-container); 127 | } 128 | } 129 | &.disabled { 130 | color: rgba(var(--md-sys-color-on-surface-rgb), 0.38); 131 | background-color: rgba(var(--md-sys-color-on-surface-rgb), 0.12); 132 | } 133 | } 134 | 135 | &.disabled { 136 | pointer-events: none; 137 | } 138 | } -------------------------------------------------------------------------------- /src/components/LazyImg.jsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useEffect, useRef, useState, useLayoutEffect } from 'react'; 2 | import blurHashMapRaw from '../../blurhash-map.json'; 3 | import camelcase from 'camelcase'; 4 | import { BlurhashCanvas } from "react-blurhash"; 5 | import classNames from 'classnames'; 6 | import css from './LazyImg.module.scss'; 7 | 8 | const blurHashMap = {}; 9 | for (const key in blurHashMapRaw) { 10 | blurHashMap[key.split('\\').pop()] = blurHashMapRaw[key].replace(/^"/, '').replace(/"$/, ''); 11 | } 12 | 13 | const getSlug = (src) => { 14 | src = decodeURIComponent(src); 15 | // the behaviors of vite-plugin-blurhash are different in dev and prod 16 | if (import.meta.env.PROD) { 17 | src = src.replace(/\//g, '.'); 18 | } else { 19 | src = src.split('/').pop(); 20 | } 21 | src = camelcase(src); 22 | return src; 23 | } 24 | 25 | 26 | const LazyImg = forwardRef(function LazyImg(props, ref) { 27 | if (ref === null) ref = useRef(); 28 | 29 | const containerRef = props.containerRef ?? useRef(); 30 | 31 | const blurHash = props.blurHash ?? true; 32 | const src = props.src; 33 | const rawSrc = props.rawSrc; 34 | 35 | useLayoutEffect(() => { 36 | const img = ref.current; 37 | if (img.complete) { 38 | containerRef.current.classList.add(css.loaded); 39 | } 40 | img.onload = () => { 41 | containerRef.current.classList.add(css.loaded); 42 | } 43 | }, []); 44 | 45 | 46 | // if (!blurHashMap[getSlug(rawSrc)]) console.log(blurHashMap, getSlug(rawSrc)); 47 | 48 | return
56 | {props.alt} 66 | { 67 | blurHash && blurHashMap[getSlug(rawSrc)] && 68 | 72 | } 73 |
74 | }); 75 | 76 | export default LazyImg; -------------------------------------------------------------------------------- /src/components/LazyImg.module.scss: -------------------------------------------------------------------------------- 1 | .lazyImg { 2 | position: relative; 3 | overflow: hidden; 4 | .img { 5 | width: 100%; 6 | height: 100%; 7 | object-fit: cover; 8 | pointer-events: none; 9 | } 10 | .blurHash { 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 100%; 15 | height: 100%; 16 | opacity: 1; 17 | transition: opacity 0.25s ease; 18 | pointer-events: none; 19 | } 20 | &.loaded { 21 | .blurHash { 22 | opacity: 0; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/components/Menu.animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes menu-unfold { 2 | 0% { 3 | height: 0; 4 | opacity: 0; 5 | margin-top: 0; 6 | margin-bottom: 0; 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import Popper from './Popper.jsx'; 3 | import css from './Menu.module.scss'; 4 | import './Menu.animations.scss'; 5 | import { forwardRef, useEffect, useRef } from 'react'; 6 | 7 | const Menu = forwardRef(function Menu(props, ref) { 8 | 9 | if (ref === null) ref = useRef(); 10 | return ( 11 | 20 |
{ 31 | e.stopPropagation(); 32 | props.onClose?.(); 33 | }} 34 | > 35 | {props.children} 36 |
37 |
38 | ) 39 | }); 40 | 41 | export default Menu; -------------------------------------------------------------------------------- /src/components/Menu.module.scss: -------------------------------------------------------------------------------- 1 | @import './global/elevation.scss'; 2 | 3 | .menu { 4 | display: block; 5 | background-color: var(--md-sys-color-surface-container); 6 | border-radius: 4px; 7 | min-width: 112px; 8 | max-width: 280px; 9 | padding-top: 8px; 10 | padding-bottom: 8px; 11 | //box-shadow: 0 1px 2px 1px rgba(var(--md-sys-color-shadow-rgb), 0.1), 0 3px 8px 3px rgba(var(--md-sys-color-shadow-rgb), 0.05); 12 | @include elevation(2); 13 | transition: opacity 0.2s ease, transform 0.2s ease; 14 | transform-origin: var(--anchor-position, center top); 15 | max-height: calc(50vh - 32px); 16 | overflow-y: auto; 17 | 18 | &.fullWidth { 19 | width: var(--anchor-element-width); 20 | max-width: unset; 21 | min-width: unset; 22 | } 23 | 24 | &.hide { 25 | pointer-events: none; 26 | opacity: 0; 27 | transform: scale(0.9); 28 | } 29 | 30 | &.animation:not(.hide) { 31 | :global{ 32 | :is(.menu-item, .menu-divider) { 33 | animation-name: menu-unfold; 34 | animation-duration: 0.25s; 35 | animation-timing-function: cubic-bezier(0.85, 0, 0.15, 1); 36 | animation-fill-mode: both; 37 | @for $i from 1 through 20 { 38 | &:nth-of-type(#{$i}) { 39 | animation-delay: 0.01s * $i; 40 | } 41 | } 42 | &:nth-of-type(n + 21) { 43 | animation-delay: 0.2s; 44 | } 45 | } 46 | } 47 | } 48 | 49 | &::-webkit-scrollbar { 50 | width: 6px; 51 | height: 8px; 52 | background-color: transparent; 53 | } 54 | &::-webkit-scrollbar-thumb { 55 | background-color: var(--md-sys-color-surface-variant); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import Checkbox from './Checkbox.jsx'; 3 | import css from './MenuItem.module.scss'; 4 | import { forwardRef, useEffect, useRef, useState } from 'react'; 5 | import { useRipple } from './Ripple.jsx'; 6 | 7 | const MenuItem = forwardRef(function MenuItem(props, ref) { 8 | if (!ref) ref = useRef(); 9 | 10 | useRipple(ref); 11 | 12 | const [checked, setChecked] = useState(props.defaultChecked ?? false); 13 | 14 | const onClick = e => { 15 | props.onClick?.(e); 16 | if (props.checkbox) { 17 | setChecked(!checked); 18 | props.onChange?.(!checked); 19 | } 20 | } 21 | 22 | const currentChecked = props.checked !== undefined ? props.checked : checked; 23 | 24 | 25 | return ( 26 |
35 | {props.icon &&
{props.icon}
} 36 |
{props.children}
37 | {props.checkbox && 38 | 42 | } 43 | 44 |
45 | ) 46 | }); 47 | 48 | export default MenuItem; 49 | 50 | export function MenuDivider(props) { 51 | return ( 52 |
58 | ) 59 | } -------------------------------------------------------------------------------- /src/components/MenuItem.module.scss: -------------------------------------------------------------------------------- 1 | .menuItem { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | overflow: hidden; 6 | position: relative; 7 | padding-left: 12px; 8 | padding-right: 12px; 9 | height: 48px; 10 | cursor: pointer; 11 | user-select: none; 12 | 13 | .icon { 14 | font-size: 24px; 15 | padding-right: 12px; 16 | color: var(--md-sys-color-on-surface-variant); 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | .content { 22 | flex: 1; 23 | padding-right: 28px; 24 | color: var(--md-sys-color-on-surface); 25 | } 26 | .checkbox { 27 | margin-right: -8px; 28 | } 29 | 30 | &::before { 31 | content: ''; 32 | position: absolute; 33 | display: block; 34 | inset: 0; 35 | background-color: var(--md-sys-color-on-surface); 36 | opacity: 0; 37 | transition: opacity 0.2s ease; 38 | pointer-events: none; 39 | } 40 | &:hover::before { 41 | opacity: 0.08; 42 | } 43 | &:active::before { 44 | opacity: 0.12; 45 | } 46 | } 47 | 48 | .divider { 49 | height: 1px; 50 | width: 100%; 51 | display: block; 52 | background-color: var(--md-sys-color-outline-variant); 53 | margin-top: 7.5px; 54 | margin-bottom: 7.5px; 55 | } -------------------------------------------------------------------------------- /src/components/NowPlayingIndicatorIcon.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import css from './NowPlayingIndicatorIcon.module.scss'; 3 | 4 | function NowPlayingIndicatorIcon(props) { 5 | return ( 6 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default NowPlayingIndicatorIcon; -------------------------------------------------------------------------------- /src/components/NowPlayingIndicatorIcon.module.scss: -------------------------------------------------------------------------------- 1 | .nowPlayingIndicatorIcon { 2 | width: 1em; 3 | height: 1em; 4 | position: relative; 5 | display: flex; 6 | justify-content: space-between; 7 | $bar-space: 0.2em; 8 | span { 9 | width: calc((1em - #{$bar-space}) / 3); 10 | height: 100%; 11 | background-color: currentColor; 12 | border-radius: 0.05em; 13 | animation: bounce 2.2s ease infinite alternate; 14 | transform-origin: bottom; 15 | &:nth-of-type(2) { 16 | animation-delay: -2.2s; 17 | } 18 | &:nth-of-type(3) { 19 | animation-delay: -3.7s; 20 | } 21 | 22 | } 23 | &.paused span { 24 | animation-play-state: paused; 25 | } 26 | } 27 | @keyframes bounce { 28 | 10% { 29 | transform: scaleY(0.3); 30 | } 31 | 30% { 32 | transform: scaleY(1); 33 | } 34 | 60% { 35 | transform: scaleY(0.5); 36 | } 37 | 80% { 38 | transform: scaleY(0.75); 39 | } 40 | 100% { 41 | transform: scaleY(0.6); 42 | } 43 | } -------------------------------------------------------------------------------- /src/components/Popper.module.scss: -------------------------------------------------------------------------------- 1 | .popper { 2 | position: absolute; 3 | z-index: var(--z-index, 10); 4 | &.noClick { 5 | pointer-events: none; 6 | } 7 | &.fixed { 8 | position: fixed; 9 | } 10 | } -------------------------------------------------------------------------------- /src/components/Radio.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import css from './Radio.module.scss'; 3 | import { useEffect, useState, useId, useContext } from 'react'; 4 | import { useRipple } from './Ripple.jsx'; 5 | import RadioGroupContext from '../contexts/RadioGroupContext.tsx'; 6 | 7 | 8 | function Radio(props) { 9 | if (useContext(RadioGroupContext) === null) { 10 | throw new Error('Radio must be a child of RadioGroup'); 11 | } 12 | 13 | const {currentValue, setValue, onChange} = useContext(RadioGroupContext); 14 | 15 | const circleContainerRef = useRipple(null, { center: true }); 16 | 17 | const id = props.value ? props.value : useId(); 18 | 19 | const onClick = e => { 20 | setValue(id); 21 | props?.onSelected?.(); 22 | } 23 | 24 | const currentSelected = currentValue === id; 25 | 26 | return ( 27 |
34 |
44 |
50 |
51 | { 52 | props.label && 53 | } 54 |
55 | 56 | ) 57 | } 58 | 59 | export default Radio; -------------------------------------------------------------------------------- /src/components/Radio.module.scss: -------------------------------------------------------------------------------- 1 | .radio { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | gap: 0.2em; 6 | --ripple-blur: 0; 7 | .circleContainer { 8 | width: 40px; 9 | height: 40px; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | cursor: pointer; 14 | overflow: hidden; 15 | border-radius: 1000px; 16 | position: relative; 17 | --ripple-color: var(--md-sys-color-primary); 18 | 19 | &::before { 20 | content: ''; 21 | position: absolute; 22 | display: block; 23 | inset: 0; 24 | background-color: var(--md-sys-color-on-surface-variant); 25 | opacity: 0; 26 | transition: opacity 0.2s ease, background-color 0.2s ease; 27 | } 28 | &:hover::before { 29 | opacity: 0.08; 30 | } 31 | &:active::before { 32 | opacity: 0.12; 33 | } 34 | &.checked::before { 35 | background-color: var(--md-sys-color-primary); 36 | } 37 | 38 | .circle { 39 | width: 20px; 40 | height: 20px; 41 | border-radius: 1000px; 42 | border: 2px solid var(--md-sys-color-on-surface-variant); 43 | box-sizing: border-box; 44 | position: relative; 45 | transition: border-color 0.2s ease; 46 | &.selected { 47 | border: 2px solid var(--md-sys-color-primary); 48 | } 49 | &::before { 50 | content: ''; 51 | display: block; 52 | position: absolute; 53 | width: 10px; 54 | height: 10px; 55 | top: 50%; 56 | left: 50%; 57 | transform: translate(-50%, -50%) scale(0.6); 58 | border-radius: 1000px; 59 | background-color: var(--md-sys-color-on-surface-variant); 60 | opacity: 0; 61 | transition: opacity 0.25s ease, transform 0.25s ease, background-color 0.2s ease; 62 | } 63 | &.selected::before { 64 | opacity: 1; 65 | transform: translate(-50%, -50%) scale(1); 66 | background-color: var(--md-sys-color-primary); 67 | } 68 | } 69 | } 70 | .label { 71 | cursor: pointer; 72 | user-select: none; 73 | } 74 | 75 | 76 | 77 | &.disabled { 78 | pointer-events: none; 79 | .circleContainer { 80 | .circle { 81 | border-color: var(--md-sys-color-on-surface); 82 | opacity: 0.38; 83 | &.checked { 84 | border-color: transparent; 85 | background-color: var(--md-sys-color-on-surface); 86 | } 87 | &:not(.checked) { 88 | &::before { 89 | background-color: var(--md-sys-color-on-surface); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/components/RadioGroup.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import RadioGroupContext from '../contexts/RadioGroupContext.tsx'; 3 | 4 | 5 | function RadioGroup(props) { 6 | const [value, setValue] = useState(props.value !== undefined ? props.value : props.defaultValue); 7 | 8 | useEffect(() => { 9 | props.onChange?.(value); 10 | }, [value]); 11 | 12 | return ( 13 | 17 | {props.children} 18 | 19 | ) 20 | } 21 | 22 | export default RadioGroup; -------------------------------------------------------------------------------- /src/components/Ripple.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import css from './Ripple.module.scss'; 3 | 4 | function addRipple(e, target, options = {}) { 5 | if (getComputedStyle(target).position !== 'absolute' && getComputedStyle(target).position !== 'relative') { 6 | target.style.position = 'relative'; 7 | } 8 | /*if (getComputedStyle(target).overflow !== 'hidden') { 9 | target.style.overflow = 'hidden'; 10 | }*/ 11 | 12 | if (e.type === 'mousedown' && e.sourceCapabilities?.firesTouchEvents) { 13 | return; 14 | } 15 | 16 | const rippleWrap = document.createElement('div'); 17 | rippleWrap.classList.add('ripple-wrap'); 18 | rippleWrap.classList.add(css.rippleWrap); 19 | const blur = Math.min(Math.max(target.offsetWidth, target.offsetHeight) / 15, 12); 20 | rippleWrap.style.setProperty('--ripple-blur', blur + 'px'); 21 | 22 | const ripple = document.createElement('span'); 23 | ripple.classList.add(css.ripple); 24 | 25 | const rect = target.getBoundingClientRect(); 26 | const diagonal = Math.sqrt(rect.width ** 2 + rect.height ** 2); 27 | const clientX = e.clientX ?? e.touches[0].clientX; 28 | const clientY = e.clientY ?? e.touches[0].clientY; 29 | let ox = clientX - rect.left; 30 | let oy = clientY - rect.top; 31 | if (options.center) { 32 | ox = rect.width / 2; 33 | oy = rect.height / 2; 34 | } 35 | const maxd = Math.ceil(2 * Math.sqrt(Math.max(ox ** 2 + oy ** 2, (rect.width - ox) ** 2 + oy ** 2, (rect.width - ox) ** 2 + (rect.height - oy) ** 2, ox ** 2 + (rect.height - oy) ** 2))); 36 | ripple.style.width = '0px'; 37 | ripple.style.height = '0px'; 38 | ripple.style.left = ox + 'px'; 39 | ripple.style.top = oy + 'px'; 40 | if (options.invert) { 41 | ripple.style.filter = 'invert(1)'; 42 | } 43 | rippleWrap.appendChild(ripple); 44 | target.appendChild(rippleWrap); 45 | 46 | let d = 0; 47 | let speed = 0.75 * (diagonal / 200); // 0.75px per 200px 48 | let opacity = 1; 49 | let status = 'hold'; 50 | 51 | const onMouseUp = (e) => { 52 | speed = 1.2 * (diagonal / 200); 53 | status = 'release'; 54 | document.removeEventListener('mouseup', onMouseUp); 55 | document.removeEventListener('mouseout', onMouseUp); 56 | } 57 | document.addEventListener('mouseup', onMouseUp); 58 | document.addEventListener('mouseout', onMouseUp); 59 | 60 | let animation = null; 61 | 62 | let lastTime = null; 63 | const frame = (time) => { 64 | const delta = time - (lastTime ?? time); 65 | lastTime = time; 66 | d += speed * delta; 67 | ripple.style.width = d + 'px'; 68 | ripple.style.height = d + 'px'; 69 | if ((status === 'release' && d >= maxd * 0.7) || (status === 'hold' && d >= maxd * 0.9)) { 70 | opacity -= 0.005 * delta; 71 | ripple.style.opacity = Math.max(opacity, 0); 72 | if (opacity <= 0) { 73 | cancelAnimationFrame(animation); 74 | document.removeEventListener('mouseup', onMouseUp); 75 | document.removeEventListener('mouseout', onMouseUp); 76 | rippleWrap.remove(); 77 | return; 78 | } 79 | } 80 | animation = requestAnimationFrame(frame); 81 | } 82 | animation = requestAnimationFrame(frame); 83 | } 84 | 85 | export function useRipple(ref = null, options = {}) { 86 | const containerRef = ref ?? useRef(null); 87 | 88 | const onMouseDown = (e) => { 89 | //console.log(e.target); 90 | if (e.button >= 3) return; 91 | if (e.target.closest('[ripple]') !== containerRef.current) return; 92 | if (!containerRef.current.hasAttribute('ripple-touching')) { 93 | addRipple(e, containerRef.current, options); 94 | } 95 | containerRef.current.removeAttribute('ripple-touching'); 96 | } 97 | const touchStart = (e) => { 98 | if (e.target.closest('[ripple]') !== containerRef.current) return; 99 | containerRef.current.setAttribute('ripple-touching', ''); 100 | addRipple(e, containerRef.current, options); 101 | } 102 | 103 | 104 | useEffect(() => { 105 | if (!containerRef.current) return; 106 | containerRef.current.addEventListener('mousedown', onMouseDown); 107 | containerRef.current.addEventListener('touchstart', touchStart); 108 | containerRef.current.setAttribute('ripple', ''); 109 | return () => { 110 | if (!containerRef.current) return; 111 | containerRef.current.removeEventListener('mousedown', onMouseDown); 112 | containerRef.current.removeEventListener('touchstart', touchStart); 113 | containerRef.current.removeAttribute('ripple'); 114 | } 115 | }, []); 116 | 117 | return containerRef; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/components/Ripple.module.scss: -------------------------------------------------------------------------------- 1 | .rippleWrap { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 0px; 6 | height: 0px; 7 | pointer-events: none; 8 | } 9 | .ripple { 10 | border-radius: 50%; 11 | position: absolute; 12 | background-color: var(--ripple-color, var(--md-sys-color-on-surface)); 13 | filter: opacity(0.1) blur(var(--ripple-blur, 0px)); 14 | pointer-events: none; 15 | transform: translate(-50%, -50%); 16 | z-index: 999; 17 | } 18 | *[ripple] { 19 | isolation: isolate; 20 | } -------------------------------------------------------------------------------- /src/components/SegmentedButtons.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer, forwardRef } from 'react'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import Icon from './Icon.jsx'; 4 | import css from './SegmentedButtons.module.scss'; 5 | import classNames from 'classnames'; 6 | import { MdCheck } from 'react-icons/md'; 7 | 8 | const SegmentedButtons = forwardRef( 9 | function SegmentedButtons(props, ref) { 10 | const [selected, dispatchSelected] = useReducer((state, action) => { 11 | switch (action.type) { 12 | case 'select': 13 | if (!props.multiple) { 14 | return action.value; 15 | } 16 | return props.buttons.filter((button) => button.value === action.value || state.includes(button.value)).map((button) => button.value); 17 | case 'deselect': 18 | if (!props.multiple) { 19 | return state; 20 | } 21 | if (!(props.canEmpty ?? true) && state.length === 1) { 22 | return state; 23 | } 24 | return state.filter((value) => value !== action.value); 25 | default: 26 | return state; 27 | } 28 | }, props.buttons.filter((button) => button.selected).map((button) => button.value)); 29 | 30 | useEffect(() => { 31 | if (props.setSelected) { 32 | props.setSelected(selected); 33 | } 34 | }, [selected]); 35 | 36 | 37 | return ( 38 |
44 | {props.buttons.map((button, index) => ( 45 | 53 | ))} 54 |
55 | ) 56 | } 57 | ); 58 | 59 | function SegmentedButton(props) { 60 | const container = useRipple(); 61 | 62 | return ( 63 | 86 | ) 87 | } 88 | 89 | export default SegmentedButtons; -------------------------------------------------------------------------------- /src/components/SegmentedButtons.module.scss: -------------------------------------------------------------------------------- 1 | .segmentedButtons { 2 | display: flex; 3 | width: fit-content; 4 | gap: 0; 5 | .segmentedButton { 6 | flex: 1; 7 | flex-shrink: 0; 8 | border-radius: 0; 9 | border: none; 10 | outline: none; 11 | background-color: transparent; 12 | color: var(--md-sys-color-on-surface); 13 | border: 1px solid var(--md-sys-color-outline); 14 | height: 40px; 15 | font-size: 1em; 16 | cursor: pointer; 17 | position: relative; 18 | overflow: hidden; 19 | transition: background-color 0.2s ease, color 0.2s ease; 20 | padding-left: 36px; 21 | padding-right: 36px; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | margin: 0; 26 | &:first-child { 27 | border-radius: 1000px 0 0 1000px; 28 | } 29 | &:last-child { 30 | border-radius: 0 1000px 1000px 0; 31 | } 32 | &:not(:last-child) { 33 | border-right: none; 34 | } 35 | &::before { 36 | content: ''; 37 | display: block; 38 | position: absolute; 39 | inset: 0; 40 | background-color: var(--md-sys-color-on-surface); 41 | opacity: 0; 42 | transition: opacity 0.2s ease, background-color 0.2s ease; 43 | } 44 | &:hover::before { 45 | opacity: 0.08; 46 | } 47 | /*&:focus::before { 48 | opacity: 0.12; 49 | }*/ 50 | &:active::before { 51 | opacity: 0.12; 52 | } 53 | &.selected { 54 | background-color: var(--md-sys-color-secondary-container); 55 | color: var(--md-sys-color-on-secondary-container); 56 | &::before { 57 | background-color: var(--md-sys-color-on-secondary-container); 58 | } 59 | } 60 | 61 | $icon-size: 1.25em; 62 | $icon-label-spacing: 0.2em; 63 | 64 | .icon { 65 | font-size: $icon-size; 66 | width: 0; 67 | transform: translateX(($icon-size + $icon-label-spacing) * -0.5); 68 | transition: opacity 0.2s ease; 69 | margin-bottom: -0.15em; 70 | } 71 | .iconCheck { 72 | opacity: 0; 73 | } 74 | &.selected { 75 | .iconCheck { 76 | opacity: 1; 77 | } 78 | .iconCustom { 79 | opacity: 0; 80 | } 81 | } 82 | .label { 83 | transform: none; 84 | transition: transform 0.2s ease; 85 | } 86 | &.selected .label, 87 | .iconCustom + .label { 88 | transform: translateX(($icon-size + $icon-label-spacing) * 0.5); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/components/Select.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { TextFieldLabel } from './TextField.jsx'; 3 | import Menu from './Menu.jsx'; 4 | import MenuItem, {MenuDivider} from './MenuItem.jsx'; 5 | import cssTf from './TextField.module.scss'; 6 | import css from './Select.module.scss'; 7 | import { forwardRef, useState, useEffect, useLayoutEffect, useRef } from 'react'; 8 | 9 | const Select = forwardRef(function Select(props, ref) { 10 | if (ref === null) ref = useRef(); 11 | 12 | const [value, setValue] = useState((props.value !== undefined ? props.value : props.defaultValue) ?? ''); 13 | const [displayValue, setDisplayValue] = useState( 14 | props.options.find(option => 15 | option.value === (props.value !== undefined ? props.value : props.defaultValue) 16 | )?.label?.trim() 17 | ?? '' 18 | ); 19 | 20 | const currentValue = props.value !== undefined ? props.value : value; 21 | 22 | const label = props.label?.trim() ?? ''; 23 | 24 | const placeholder = props.placeholder?.length ? props.placeholder : ' '; 25 | 26 | const [active, setActive] = useState(false); 27 | const [minZIndex, setMinZIndex] = useState(10); 28 | 29 | const textFieldRef = useRef(null); 30 | const menuRef = useRef(null); 31 | 32 | 33 | const handleClickAway = (e) => { 34 | if (open && !menuRef.current.contains(e.target) && !textFieldRef.current.contains(e.target)) { 35 | setActive(false); 36 | } 37 | } 38 | useEffect(() => { 39 | document.addEventListener('click', handleClickAway); 40 | return () => { 41 | document.removeEventListener('click', handleClickAway); 42 | } 43 | }, [open]); 44 | 45 | return ( 46 |
{ 69 | if (!active) { 70 | let min = 1000000000; 71 | let dom = textFieldRef.current; 72 | while (dom) { 73 | const zIndex = parseInt(getComputedStyle(dom).zIndex); 74 | if (zIndex && zIndex < min) { 75 | min = zIndex; 76 | } 77 | dom = dom.parentElement; 78 | } 79 | setMinZIndex(min + 1); 80 | } 81 | setActive(!active); 82 | }} 83 | > 84 | { 98 | setValue(ref.current.value); 99 | props?.onChange?.(ref.current.value); 100 | }} 101 | readOnly={true} 102 | disabled={props.disabled} 103 | /> 104 | 107 | { 108 | props.leadingIcon && 109 |
110 | {props.leadingIcon} 111 |
112 | } 113 | 120 | 121 | 122 | 134 | { 135 | props.options.map((option, index) => { 136 | if (option === '-') return ; 137 | return ( 138 | { 142 | setValue(option.value); 143 | setDisplayValue(option.label); 144 | props?.onChange?.(option.value); 145 | setActive(false); 146 | }} 147 | > 148 | {option.label} 149 | 150 | ) 151 | }) 152 | } 153 | 154 |
155 | ) 156 | }); 157 | 158 | export default Select; -------------------------------------------------------------------------------- /src/components/Select.module.scss: -------------------------------------------------------------------------------- 1 | .select { 2 | cursor: pointer; 3 | .input { 4 | pointer-events: none; 5 | user-select: none; 6 | } 7 | .dropdownIcon { 8 | position: absolute; 9 | pointer-events: none; 10 | right: 8px; 11 | fill: var(--md-sys-color-on-surface-variant); 12 | transform: rotate(0deg); 13 | transition: transform 0.2s ease; 14 | } 15 | &.outlined { 16 | .dropdownIcon { 17 | top: 22px; 18 | } 19 | &.noLabel { 20 | .dropdownIcon { 21 | top: 12px; 22 | } 23 | } 24 | } 25 | &.filled { 26 | .dropdownIcon { 27 | top: 20px; 28 | } 29 | &.noLabel { 30 | .dropdownIcon { 31 | top: 10px; 32 | } 33 | } 34 | } 35 | 36 | &.active .dropdownIcon { 37 | transform: rotate(180deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Tab.jsx: -------------------------------------------------------------------------------- 1 | const Tab = () => null; 2 | 3 | export default Tab; -------------------------------------------------------------------------------- /src/components/Tabs.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { forwardRef, useEffect, useRef, useState, useLayoutEffect } from 'react'; 3 | import { useRipple } from './Ripple.jsx'; 4 | import css from './Tabs.module.scss'; 5 | 6 | const Tabs = forwardRef(function Tabs(props, ref) { 7 | if (ref === null) ref = useRef(); 8 | 9 | const [currentTab, setCurrentTab] = useState(props?.currentTab ?? 0); 10 | 11 | useEffect(() => { 12 | if (props?.currentTab !== undefined) { 13 | setCurrentTab(props.currentTab); 14 | } 15 | }, [props.currentTab]); 16 | 17 | const tabsRef = useRef(null); 18 | const tabContentsRef = useRef(null); 19 | 20 | const calcHeight = () => { 21 | if (!(props?.dynamicHeight ?? true)) { 22 | tabContentsRef.current.style.height = ''; 23 | return; 24 | } 25 | if (tabContentsRef.current === null) return; 26 | const currentTabContentDom = tabContentsRef.current.children[currentTab]; 27 | const height = currentTabContentDom.getBoundingClientRect().height; 28 | tabContentsRef.current.style.height = height + 'px'; 29 | }; 30 | useLayoutEffect(() => { 31 | calcHeight(); 32 | window.addEventListener('resize', calcHeight); 33 | const resizeObserver = new ResizeObserver(calcHeight); 34 | tabContentsRef.current?.children[currentTab] && resizeObserver.observe(tabContentsRef.current.children[currentTab]); 35 | return () => { 36 | window.removeEventListener('resize', calcHeight); 37 | resizeObserver.disconnect(); 38 | } 39 | }, [currentTab, props.dynamicHeight]); 40 | 41 | useEffect(() => { 42 | if (!tabsRef.current) return; 43 | const currentTabDom = tabsRef.current.children[currentTab]; 44 | const innerDom = currentTabDom.querySelector(`.${css.tabInner}`); 45 | const width = innerDom.getBoundingClientRect().width; 46 | ref.current.style.setProperty('--current-tab-inner-width', width + 'px'); 47 | },[currentTab, props.children]); 48 | 49 | return ( 50 |
child.props.icon), 61 | [css.dynamicHeight]: props.dynamicHeight ?? true, 62 | } 63 | ) 64 | } 65 | style={{ 66 | '--current-tab': currentTab, 67 | '--tabs-count': props.children.length 68 | }} 69 | ref={ref} 70 | > 71 |
72 | { 73 | props.children.map((child, i) => { 74 | return ( 75 | { 88 | setCurrentTab(i); 89 | props.onTabChange?.(i); 90 | }} 91 | /> 92 | ) 93 | }) 94 | } 95 |
96 |
97 |
98 | { 99 | props.children.map((child, i) => { 100 | return ( 101 | 106 | {child.props.children} 107 | 108 | ) 109 | }) 110 | } 111 |
112 |
113 | ) 114 | }); 115 | 116 | 117 | export default Tabs; 118 | 119 | const TabItem = function (props) { 120 | const ref = useRipple(); 121 | return ( 122 |
{ 132 | props.onClick(); 133 | }} 134 | style={{ 135 | '--tab-id': props.tabId, 136 | }} 137 | > 138 |
139 | { 140 | props.icon && 141 |
142 | {props.icon} 143 |
144 | } 145 |
146 | {props.label} 147 |
148 |
149 |
150 | ); 151 | } 152 | 153 | const TabContentItem = function (props) { 154 | return ( 155 |
165 | {props.children} 166 |
167 | ) 168 | } -------------------------------------------------------------------------------- /src/components/Tabs.module.scss: -------------------------------------------------------------------------------- 1 | .tabs { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | .tabBar { 6 | display: flex; 7 | flex-direction: row; 8 | flex-wrap: nowrap; 9 | height: 48px; 10 | position: relative; 11 | .tab { 12 | flex: 1; 13 | height: 100%; 14 | overflow: hidden; 15 | position: relative; 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | flex-direction: column; 20 | cursor: pointer; 21 | user-select: none; 22 | transition: color .3s ease; 23 | //border-radius: 1000px; 24 | .tabInner { 25 | display: flex; 26 | flex-direction: column; 27 | justify-content: center; 28 | align-items: center; 29 | .tabLabel { 30 | text-align: center; 31 | } 32 | .tabIcon { 33 | font-size: 24px; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | } 39 | 40 | &::before { 41 | content: ''; 42 | position: absolute; 43 | display: block; 44 | inset: 0; 45 | opacity: 0; 46 | transition: opacity 0.2s ease, background-color 0.2s ease; 47 | pointer-events: none; 48 | background-color: var(--md-sys-color-on-surface); 49 | } 50 | &:hover::before { 51 | opacity: 0.08; 52 | } 53 | &:active::before { 54 | opacity: 0.12; 55 | } 56 | &:focus-visible::before { 57 | opacity: 0.12; 58 | } 59 | &.current { 60 | color: var(--md-sys-color-primary); 61 | --ripple-color: var(--md-sys-color-primary); 62 | &::before { 63 | background-color: var(--md-sys-color-primary); 64 | } 65 | 66 | } 67 | } 68 | .indicator { 69 | display: block; 70 | height: 3px; 71 | background-color: var(--md-sys-color-primary); 72 | position: absolute; 73 | bottom: 0; 74 | transform: translateX(-50%); 75 | width: var(--current-tab-inner-width); 76 | left: calc(100% / var(--tabs-count) * (var(--current-tab) + 0.5)); 77 | border-radius: 100px 100px 0 0; 78 | //transition: width .3s cubic-bezier(0.25, 2.38, 0.58, 1), left .3s ease; 79 | transition: width 0.3s cubic-bezier(0, 2.13, 0.28, 1.17), left 0.3s ease; 80 | } 81 | } 82 | .tabContents { 83 | position: relative; 84 | overflow: hidden; 85 | width: 100%; 86 | .tabContent { 87 | width: 100%; 88 | position: absolute; 89 | top: 0; 90 | width: 100%; 91 | left: calc((var(--tab-id, 0) - var(--current-tab, 0)) * 100%); 92 | transition: left .25s ease; 93 | 94 | &::-webkit-scrollbar { 95 | width: 6px; 96 | height: 8px; 97 | background-color: transparent; 98 | } 99 | &::-webkit-scrollbar-thumb { 100 | background-color: var(--md-sys-color-surface-variant); 101 | } 102 | } 103 | } 104 | 105 | &.secondary { 106 | .tabBar .indicator { 107 | width: calc(100% / var(--tabs-count)); 108 | border-radius: 0; 109 | height: 2px; 110 | transition: width 0.3s ease, left 0.3s ease; 111 | } 112 | } 113 | 114 | 115 | &.withIcon { 116 | .tabBar{ 117 | height: 64px; 118 | } 119 | } 120 | 121 | &.dynamicHeight { 122 | .tabContents { 123 | transition: height .25s ease; 124 | } 125 | } 126 | &:not(.dynamicHeight) { 127 | .tabContents { 128 | flex: 1; 129 | .tabContent { 130 | height: 100%; 131 | overflow-y: auto; 132 | } 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /src/components/Tag.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useRipple } from './Ripple.jsx'; 3 | import css from './Tag.module.scss'; 4 | 5 | function Tag(props) { 6 | return ( 7 |
12 | {props.children} 13 |
14 | ) 15 | } 16 | 17 | export default Tag; -------------------------------------------------------------------------------- /src/components/Tag.module.scss: -------------------------------------------------------------------------------- 1 | .tag { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | flex-direction: row; 6 | flex-wrap: nowrap; 7 | white-space: nowrap; 8 | gap: 0.25em; 9 | border-radius: 8px; 10 | text-align: center; 11 | font-size: 0.8em; 12 | padding-left: 0.5em; 13 | padding-right: 0.5em; 14 | line-height: 1.5; 15 | border: 1px solid var(--md-sys-color-outline); 16 | } -------------------------------------------------------------------------------- /src/components/TextField.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import css from './TextField.module.scss'; 3 | import { forwardRef, useState, useEffect, useLayoutEffect, useRef } from 'react'; 4 | 5 | const TextField = forwardRef(function TextField(props, ref) { 6 | if (ref === null) ref = useRef(); 7 | 8 | const [value, setValue] = useState((props.value !== undefined ? props.value : props.defaultValue) ?? ''); 9 | 10 | const currentValue = props.value !== undefined ? props.value : value; 11 | 12 | const label = props.label?.trim() ?? ''; 13 | 14 | const placeholder = props.placeholder?.length ? props.placeholder : ' '; 15 | return ( 16 |
31 | { 40 | setValue(ref.current.value); 41 | props?.onChange?.(ref.current.value); 42 | }} 43 | onFocus={props.onFocus} 44 | onBlur={props.onBlur} 45 | readOnly={props.readOnly} 46 | disabled={props.disabled} 47 | /> 48 | 51 | { 52 | props.leadingIcon && 53 |
54 | {props.leadingIcon} 55 |
56 | } 57 |
58 | ) 59 | }); 60 | 61 | export function TextFieldLabel({label}) { 62 | const labelTestRef = useRef(null); 63 | const [labelWidth, setLabelWidth] = useState(0); 64 | 65 | useLayoutEffect(() => { 66 | setLabelWidth(labelTestRef.current.offsetWidth); 67 | }, [label]); 68 | 69 | useEffect(() => { 70 | const resizeObserver = new ResizeObserver(() => { 71 | setLabelWidth(labelTestRef.current.offsetWidth); 72 | }); 73 | resizeObserver.observe(labelTestRef.current); 74 | return () => resizeObserver.disconnect(); 75 | }, []); 76 | 77 | return ( 78 | <> 79 | 87 | {label} 88 | 89 | {label} 90 | 91 | ); 92 | } 93 | 94 | export default TextField; -------------------------------------------------------------------------------- /src/components/Toolbar.jsx: -------------------------------------------------------------------------------- 1 | import css from './Toolbar.module.scss' 2 | 3 | function Toolbar(props) { 4 | return ( 5 |
6 |
7 | {props.title} 8 |
9 |
10 | ) 11 | } 12 | export default Toolbar; -------------------------------------------------------------------------------- /src/components/Toolbar.module.scss: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | width: 100%; 3 | height: 72px; 4 | //background-color: var(--md-sys-color-surface-variant); 5 | //color: var(--md-sys-color-on-surface-variant); 6 | font-size: 22px; 7 | display: flex; 8 | align-items: center; 9 | padding-left: 24px; 10 | padding-right: 24px; 11 | } -------------------------------------------------------------------------------- /src/components/TopAppBar.jsx: -------------------------------------------------------------------------------- 1 | import css from './TopAppBar.module.scss' 2 | import useScrolled from '../hooks/useScrolled.js'; 3 | import classNames from 'classnames'; 4 | import { forwardRef, useEffect, useLayoutEffect, useRef } from 'react'; 5 | import getCloestScrollableParent from '../utils/getCloestScrollableParent.js'; 6 | import getInViewPosition from '../utils/getInViewPosition.js'; 7 | 8 | const TopAppBar = forwardRef(function({ 9 | leftButtons = null, 10 | rightButtons = null, 11 | type = 'small', 12 | scrolled = null, 13 | children 14 | }, ref) { 15 | if (!ref) { 16 | ref = useRef(null); 17 | } 18 | 19 | const topTitleRef = useRef(null); 20 | const bigTitleRef = useRef(null); 21 | const animationRef = useRef(null); 22 | 23 | const autoScrolled = useScrolled(); 24 | const currentScrolled = scrolled != null ? scrolled : autoScrolled; 25 | 26 | 27 | useLayoutEffect(() => { 28 | if (type !== 'medium' && type !== 'large') { 29 | if (animationRef.current) { 30 | animationRef.current.currentTime = 1000; 31 | animationRef.current.pause(); 32 | animationRef.current = null; 33 | } 34 | return; 35 | } 36 | const topTitle = topTitleRef.current; 37 | animationRef.current = topTitle.animate([ 38 | { 39 | opacity: 0, 40 | }, 41 | { 42 | opacity: 1, 43 | } 44 | ], { 45 | duration: 1000, 46 | fill: 'forwards', 47 | }); 48 | animationRef.current.pause(); 49 | }, [type]); 50 | 51 | useLayoutEffect(() => { 52 | const parent = getCloestScrollableParent(ref.current); 53 | const bigText = bigTitleRef.current; 54 | const onScroll = () => { 55 | if (!animationRef.current) { 56 | return; 57 | } 58 | const bigTextTop = getInViewPosition(bigText, parent).top - 64; 59 | const bigTextHeight = bigText.offsetHeight; 60 | const percentInView = Math.min(Math.max(0, (bigTextHeight + bigTextTop) / bigTextHeight), 1); 61 | animationRef.current.currentTime = 1000 * (1 - percentInView); 62 | }; 63 | parent.addEventListener('scroll', onScroll); 64 | window.addEventListener('scroll', onScroll); 65 | window.addEventListener('resize', onScroll); 66 | onScroll(); 67 | return () => { 68 | parent.removeEventListener('scroll', onScroll); 69 | window.removeEventListener('scroll', onScroll); 70 | window.removeEventListener('resize', onScroll); 71 | }; 72 | }, [type]); 73 | 74 | return ( 75 | <> 76 |
89 | { 90 | leftButtons && 91 |
92 | { 93 | leftButtons 94 | } 95 |
96 | } 97 |
98 | {children} 99 |
100 | { 101 | rightButtons && 102 |
103 | { 104 | rightButtons 105 | } 106 |
107 | } 108 |
109 | { 110 | (type === 'medium' || type === 'large') && 111 |
121 | {children} 122 |
123 | } 124 | 125 | ) 126 | }); 127 | export default TopAppBar; -------------------------------------------------------------------------------- /src/components/TopAppBar.module.scss: -------------------------------------------------------------------------------- 1 | @import './global/elevation.scss'; 2 | .topAppBar { 3 | width: 100%; 4 | height: 64px; 5 | display: flex; 6 | align-items: center; 7 | padding-left: 16px; 8 | padding-right: 16px; 9 | box-sizing: border-box; 10 | background-color: var(--md-sys-color-background); 11 | color: var(--md-sys-color-on-surface); 12 | position: absolute; 13 | position: sticky; 14 | top: 0; 15 | z-index: 2; 16 | transition: background-color 0.2s ease, box-shadow 0.2s ease; 17 | &.scrolled { 18 | background-color: var(--md-sys-color-surface-container); 19 | @include elevation(2); 20 | } 21 | 22 | .leftButtons { 23 | margin-left: -8px; 24 | margin-right: 16px; 25 | :global { 26 | .iconButton { 27 | color: var(--md-sys-color-on-surface); 28 | } 29 | } 30 | } 31 | .title { 32 | font-size: 22px; 33 | flex: 1; 34 | white-space: nowrap; 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | .rightButtons { 39 | margin-left: 16px; 40 | margin-right: -8px; 41 | :global { 42 | .iconButton { 43 | color: var(--md-sys-color-on-surface-variant); 44 | } 45 | } 46 | } 47 | 48 | .leftButtons, .rightButtons { 49 | display: flex; 50 | flex-wrap: nowrap; 51 | flex-direction: row; 52 | gap: 8px; 53 | flex-shrink: 0; 54 | } 55 | 56 | &.center { 57 | .title { 58 | text-align: center; 59 | } 60 | } 61 | } 62 | 63 | .topAppBarHeadline { 64 | color: var(--md-sys-color-on-surface); 65 | padding-left: 16px; 66 | padding-right: 16px; 67 | white-space: nowrap; 68 | overflow: hidden; 69 | text-overflow: ellipsis; 70 | width: 100%; 71 | display: flex; 72 | align-items: flex-end; 73 | box-sizing: border-box; 74 | > span { 75 | overflow: hidden; 76 | text-overflow: ellipsis; 77 | width: 100%; 78 | } 79 | 80 | &.topAppBarHeadlineMedium { 81 | height: 48px; 82 | padding-bottom: 16px; 83 | font-size: 24px; 84 | line-height: 32px; 85 | } 86 | &.topAppBarHeadlineLarge { 87 | height: 88px; 88 | padding-bottom: 28px; 89 | font-size: 28px; 90 | line-height: 36px; 91 | } 92 | } -------------------------------------------------------------------------------- /src/components/WaveProgressBar.module.scss: -------------------------------------------------------------------------------- 1 | .waveProgressBar { 2 | height: 40px; 3 | position: relative; 4 | user-select: none; 5 | .track { 6 | $track-height: 4px; 7 | position: absolute; 8 | left: 20px; 9 | right: 20px; 10 | height: $track-height; 11 | top: calc(50% - #{$track-height * 0.5}); 12 | .played { 13 | position: absolute; 14 | left: 0; 15 | right: calc(100% - var(--progress)); 16 | top: -3px; 17 | bottom: -3px; 18 | overflow: hidden; 19 | display: flex; 20 | } 21 | .unplayed { 22 | position: absolute; 23 | left: var(--progress); 24 | height: 100%; 25 | right: 0; 26 | border-radius: 0 100px 100px 0; 27 | background-color: var(--md-sys-color-surface-container-highest); 28 | } 29 | .control { 30 | position: absolute; 31 | inset: 0; 32 | top: -2px; 33 | bottom: -2px; 34 | cursor: pointer; 35 | z-index: 1; 36 | } 37 | } 38 | .handleContainer { 39 | position: absolute; 40 | left: 20px; 41 | right: 20px; 42 | top: 0; 43 | bottom: 0; 44 | transform: translateX(calc(var(--progress) - 20px)); 45 | pointer-events: none; 46 | z-index: 2; 47 | .handle { 48 | width: 40px; 49 | height: 40px; 50 | position: absolute; 51 | border-radius: 40px; 52 | left: 0; 53 | cursor: pointer; 54 | pointer-events: all; 55 | &::before { 56 | content: ''; 57 | display: block; 58 | position: absolute; 59 | top: 10px; 60 | left: 10px; 61 | width: 20px; 62 | height: 20px; 63 | background-color: var(--md-sys-color-primary); 64 | border-radius: 20px; 65 | z-index: 2; 66 | } 67 | &::after { 68 | content: ''; 69 | display: block; 70 | position: absolute; 71 | top: 0px; 72 | left: 0px; 73 | width: 40px; 74 | height: 40px; 75 | background-color: var(--md-sys-color-primary); 76 | border-radius: 20px; 77 | opacity: 0; 78 | transform: scale(0.6); 79 | transition: opacity 0.2s ease, transform 0.2s ease; 80 | } 81 | &:hover::after { 82 | opacity: 0.08; 83 | transform: scale(0.9); 84 | } 85 | &:active::after { 86 | opacity: 0.12; 87 | transform: scale(1); 88 | } 89 | } 90 | } 91 | } 92 | 93 | .sinWave { 94 | position: absolute; 95 | width: 100vw; 96 | height: 100%; 97 | animation: sin 3s linear infinite; 98 | } 99 | @keyframes sin { 100 | 0% { 101 | transform: translateX(0px); 102 | } 103 | 100% { 104 | transform: translateX(-32px); 105 | } 106 | } 107 | .paused .sinWave { 108 | //animation-play-state: paused; 109 | } 110 | .waveProgressBar.disabled { 111 | pointer-events: none !important; 112 | * { 113 | pointer-events: none !important; 114 | } 115 | .track { 116 | .played { 117 | opacity: 0.38; 118 | } 119 | .unplayed { 120 | background-color: var(--md-sys-color-on-surface); 121 | } 122 | } 123 | .handleContainer { 124 | .handle { 125 | &::before { 126 | background-color: var(--md-sys-color-on-surface); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/components/global/elevation.scss: -------------------------------------------------------------------------------- 1 | @mixin elevation($z) { 2 | @if $z ==0 { 3 | box-shadow: none; 4 | } 5 | @else if $z ==1 { 6 | box-shadow: 0px 1px 2px rgba(var(--md-sys-color-shadow-rgb), 0.3), 0px 1px 3px rgba(var(--md-sys-color-shadow-rgb), 0.15); 7 | } 8 | @else if $z ==2 { 9 | box-shadow: 0px 1px 2px rgba(var(--md-sys-color-shadow-rgb), 0.3), 0px 2px 6px 2px rgba(var(--md-sys-color-shadow-rgb), 0.15); 10 | } 11 | @else if $z ==3 { 12 | box-shadow: 0px 1px 3px rgba(var(--md-sys-color-shadow-rgb), 0.3), 0px 4px 8px 3px rgba(var(--md-sys-color-shadow-rgb), 0.15) 13 | } 14 | @else if $z ==4 { 15 | box-shadow: 0px 2px 3px rgba(var(--md-sys-color-shadow-rgb), 0.3), 0px 6px 10px 4px rgba(var(--md-sys-color-shadow-rgb), 0.15); 16 | } 17 | @else if $z ==5 { 18 | box-shadow: 0px 4px 4px rgba(var(--md-sys-color-shadow-rgb), 0.3), 0px 8px 12px 6px rgba(var(--md-sys-color-shadow-rgb), 0.15); 19 | } 20 | @else { 21 | @warn "The maximum elevation is 5"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/contexts/QueueContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const QueueContext = createContext(null); -------------------------------------------------------------------------------- /src/contexts/RadioGroupContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const RadioGroupContext = createContext(null); 4 | 5 | export default RadioGroupContext; -------------------------------------------------------------------------------- /src/contexts/ThemeColorSetContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const ThemeColorSetContext = createContext(null); -------------------------------------------------------------------------------- /src/data/covers/CIEL - 空中散歩.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/CIEL - 空中散歩.jpg -------------------------------------------------------------------------------- /src/data/covers/ESHIKARA,MIMI,可不 - ふわり (feat. MIMI,可不,初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/ESHIKARA,MIMI,可不 - ふわり (feat. MIMI,可不,初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/I Need You - Miuna Usako.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/I Need You - Miuna Usako.jpg -------------------------------------------------------------------------------- /src/data/covers/LSCM,MIMI - そして夜と灯る (feat. MIMI).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/LSCM,MIMI - そして夜と灯る (feat. MIMI).jpg -------------------------------------------------------------------------------- /src/data/covers/LSCM,佳館杏ノ助,MIMI - てんぐ夜かぐら (feat. 佳館杏ノ助 & MIMI).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/LSCM,佳館杏ノ助,MIMI - てんぐ夜かぐら (feat. 佳館杏ノ助 & MIMI).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - Cold Waltz - in G Minor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - Cold Waltz - in G Minor.png -------------------------------------------------------------------------------- /src/data/covers/MIMI - Lilly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - Lilly.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - LyriC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - LyriC.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - NOREEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - NOREEN.png -------------------------------------------------------------------------------- /src/data/covers/MIMI - Pale.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - Pale.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - SorrowChat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - SorrowChat.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - What Call This Day ? (feat. にんじん).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - What Call This Day ? (feat. にんじん).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - parabola.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - parabola.png -------------------------------------------------------------------------------- /src/data/covers/MIMI - いいじゃない.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - いいじゃない.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - ぎゅって.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - ぎゅって.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - もーいいかい.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - もーいいかい.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - ゆめまぼろし.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - ゆめまぼろし.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - よるつむぎ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - よるつむぎ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - れじぇろ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - れじぇろ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - アンダー.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - アンダー.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - カラバコにアイ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - カラバコにアイ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - コウフク貯金 (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - コウフク貯金 (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - フィオーレ (feat. 初音ミク&可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - フィオーレ (feat. 初音ミク&可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - フローレミ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - フローレミ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - マシュマリー.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - マシュマリー.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - ミライリフレクト.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - ミライリフレクト.png -------------------------------------------------------------------------------- /src/data/covers/MIMI - モーメント.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - モーメント.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - ラピスラズリ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - ラピスラズリ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - ルルージュ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - ルルージュ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 何もない様な.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 何もない様な.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 哀の隙間.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 哀の隙間.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 夜明け前に飛び乗って.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 夜明け前に飛び乗って.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 妄想哀歌 (feat. 初音ミク&可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 妄想哀歌 (feat. 初音ミク&可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 心を刺す言葉だけ feat. 初音ミク & 可不.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 心を刺す言葉だけ feat. 初音ミク & 可不.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 愛し愛 (feat. 初音ミク & 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 愛し愛 (feat. 初音ミク & 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 星涙哀歌.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 星涙哀歌.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 水流音楽.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 水流音楽.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 水音とカーテン.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 水音とカーテン.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 淡さと微睡む.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 淡さと微睡む.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 透明夏 (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 透明夏 (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 遠く夢の奥.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 遠く夢の奥.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI - 静寂に咲く.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI - 静寂に咲く.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI feat. 初音ミク - GLACIES.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI feat. 初音ミク - GLACIES.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI feat. 可不 - オマジナイ (long ver.).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI feat. 可不 - オマジナイ (long ver.).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI, 可不 - サヨナラは言わないでさ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI, 可不 - サヨナラは言わないでさ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI, 可不 - 愛するように (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI, 可不 - 愛するように (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,わん子 - みにまむ (feat. わん子).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,わん子 - みにまむ (feat. わん子).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,わん子 - もでらーと (feat. わん子).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,わん子 - もでらーと (feat. わん子).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,アカラカイ - Without Knowing (feat. アカラカイ).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,アカラカイ - Without Knowing (feat. アカラカイ).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,ロクデナシ - 愛が灯る.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,ロクデナシ - 愛が灯る.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - GLACIES (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - GLACIES (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - コウフク貯金 (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - コウフク貯金 (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - ロココ (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - ロココ (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - 夜のあいろに (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - 夜のあいろに (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - 始点前夕暮れ (feat. 初音ミク).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - 始点前夕暮れ (feat. 初音ミク).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク - 風鈴歌.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク - 風鈴歌.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,初音ミク,可不 - はぐ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,初音ミク,可不 - はぐ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - あのね (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - あのね (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - いっせーのーで.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - いっせーのーで.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - くうになる (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - くうになる (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - それで充分だよ (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - それで充分だよ (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - だきしめるまで。.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - だきしめるまで。.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - ハナタバ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - ハナタバ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - ヒミツ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - ヒミツ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - ポシェット (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - ポシェット (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - 今はいいんだよ。 (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - 今はいいんだよ。 (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,可不 - 息をするだけ (feat. 可不).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,可不 - 息をするだけ (feat. 可不).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,星界 - えすけーぷ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,星界 - えすけーぷ.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,森先化歩,花譜,夜河世界,ヰ世界情緒 - あわく心模様.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,森先化歩,花譜,夜河世界,ヰ世界情緒 - あわく心模様.jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,羽累 - Maple (feat. 羽累).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,羽累 - Maple (feat. 羽累).jpg -------------------------------------------------------------------------------- /src/data/covers/MIMI,裏命 - ぽけっと・愛の歌.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MIMI,裏命 - ぽけっと・愛の歌.jpg -------------------------------------------------------------------------------- /src/data/covers/MORE MORE JUMP!, 鏡音レン - はぐ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/MORE MORE JUMP!, 鏡音レン - はぐ.jpg -------------------------------------------------------------------------------- /src/data/covers/May'n - 人生進行形.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/May'n - 人生進行形.jpg -------------------------------------------------------------------------------- /src/data/covers/Miuna Usako - I Need You.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/Miuna Usako - I Need You.jpg -------------------------------------------------------------------------------- /src/data/covers/NINA - VALIS - わたしマニュアル.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/NINA - VALIS - わたしマニュアル.jpg -------------------------------------------------------------------------------- /src/data/covers/Rain Drops - シャロウ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/Rain Drops - シャロウ.jpg -------------------------------------------------------------------------------- /src/data/covers/WI'P - ツキミチシルベ feat. おれんじ君, たこちゃん, MIMI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/WI'P - ツキミチシルベ feat. おれんじ君, たこちゃん, MIMI.jpg -------------------------------------------------------------------------------- /src/data/covers/konoco - ひだまりの彩度.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/konoco - ひだまりの彩度.jpg -------------------------------------------------------------------------------- /src/data/covers/konoco,こばしり。 - Bullets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/konoco,こばしり。 - Bullets.jpg -------------------------------------------------------------------------------- /src/data/covers/seiza, MIMI, 初音ミク- 流星症候群.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/seiza, MIMI, 初音ミク- 流星症候群.jpg -------------------------------------------------------------------------------- /src/data/covers/shorts/20160901-771271485478404097_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20160901-771271485478404097_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20161127-802798343961202688_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20161127-802798343961202688_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20161203-804963578797170688_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20161203-804963578797170688_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20161210-807505126428581889_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20161210-807505126428581889_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20161225-812826935415836672_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20161225-812826935415836672_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20170226-835787026842542080_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20170226-835787026842542080_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20170311-840497669558566912_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20170311-840497669558566912_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20170318-842998283240787972_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20170318-842998283240787972_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20170820-899271417874726912_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20170820-899271417874726912_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180101-947863080720928768_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180101-947863080720928768_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180122-955340979845840896_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180122-955340979845840896_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180426-989434455256256513_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180426-989434455256256513_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180520-998147054219051013_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180520-998147054219051013_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180531-1002160900265005057_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180531-1002160900265005057_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180624-1010836676455755776_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180624-1010836676455755776_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20180702-1013769075724468224_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20180702-1013769075724468224_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20200411-1248891825693184000_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20200411-1248891825693184000_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20200617-1273168511574171655_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20200617-1273168511574171655_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20200705-1279704774439403521_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20200705-1279704774439403521_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20220717-1548734689854509056_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20220717-1548734689854509056_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20221024-1584533187396784129_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20221024-1584533187396784129_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230104-1610554437269098498_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230104-1610554437269098498_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230311-1634663531298820096_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230311-1634663531298820096_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230405-1643716964257505286_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230405-1643716964257505286_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230408-1644838849959571456_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230408-1644838849959571456_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230530-1663668305754980353_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230530-1663668305754980353_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230714-1679850034739974144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230714-1679850034739974144.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230821-1693701649980850325_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230821-1693701649980850325_video.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20230831-1697179345960472839.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20230831-1697179345960472839.png -------------------------------------------------------------------------------- /src/data/covers/shorts/20231112-1723793803130392766.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/20231112-1723793803130392766.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7121730142648716545.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7121730142648716545.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7217363999069883650.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7217363999069883650.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7217728862703209730.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7217728862703209730.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7218664751784660225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7218664751784660225.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7243687154054974722.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7243687154054974722.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7243971426762411266.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7243971426762411266.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7244460440527326466.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7244460440527326466.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249008212798655745.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249008212798655745.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249388073945926913.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249388073945926913.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249388698058411265.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249388698058411265.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249487516645084418.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249487516645084418.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249488478126296322.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249488478126296322.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7249671575778151682.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7249671575778151682.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7251113652382731521.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7251113652382731521.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7251896291632893186.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7251896291632893186.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7253351896734289154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7253351896734289154.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7253366505151253761.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7253366505151253761.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7253381975820586241.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7253381975820586241.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7254148636915797249.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7254148636915797249.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7254149470517038338.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7254149470517038338.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7254150282668461314.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7254150282668461314.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7254462703803108610.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7254462703803108610.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7255219509097680130.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7255219509097680130.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7255594178279738625.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7255594178279738625.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7256023414228503810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7256023414228503810.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7256365983093280001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7256365983093280001.png -------------------------------------------------------------------------------- /src/data/covers/shorts/tiktok_7261167927523691783.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/shorts/tiktok_7261167927523691783.png -------------------------------------------------------------------------------- /src/data/covers/『 わたしマニュアル (Original Arrange Ver.) 』/ MIMI feat. 可不.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/『 わたしマニュアル (Original Arrange Ver.) 』/ MIMI feat. 可不.jpg -------------------------------------------------------------------------------- /src/data/covers/いゔどっと - 月日記.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/いゔどっと - 月日記.jpg -------------------------------------------------------------------------------- /src/data/covers/ひなの羽衣 - ルティナ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/ひなの羽衣 - ルティナ.jpg -------------------------------------------------------------------------------- /src/data/covers/むト - silence.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/むト - silence.jpg -------------------------------------------------------------------------------- /src/data/covers/わたしトラベラー - NINA - VALIS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/わたしトラベラー - NINA - VALIS.jpg -------------------------------------------------------------------------------- /src/data/covers/ロクデナシ - ただ声一つ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/ロクデナシ - ただ声一つ.jpg -------------------------------------------------------------------------------- /src/data/covers/ロクデナシ - 知らないままで.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/ロクデナシ - 知らないままで.jpg -------------------------------------------------------------------------------- /src/data/covers/原因は自分にある - ダイヤモンドリリー.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/原因は自分にある - ダイヤモンドリリー.jpg -------------------------------------------------------------------------------- /src/data/covers/夢音ちゃの×MIMI - 季節にまどろむ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/夢音ちゃの×MIMI - 季節にまどろむ.png -------------------------------------------------------------------------------- /src/data/covers/存流 - まってるよ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/存流 - まってるよ.jpg -------------------------------------------------------------------------------- /src/data/covers/涼海ネモ, MIMI - カタチのないもの.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/涼海ネモ, MIMI - カタチのないもの.jpg -------------------------------------------------------------------------------- /src/data/covers/花譜 feat. 可不(KAFU) - いっせーのーで.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/花譜 feat. 可不(KAFU) - いっせーのーで.png -------------------------------------------------------------------------------- /src/data/covers/花譜 feat. 理芽 - まほう(MIMI Remix).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solstice23/mimi-radio/0600a8533e518df4cf537f56f0885e9aaa508ad6/src/data/covers/花譜 feat. 理芽 - まほう(MIMI Remix).jpg -------------------------------------------------------------------------------- /src/data/lyrics/20220717-1548734689854509056_video.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "透明夏" 5 | }, 6 | { 7 | "time": 20000, 8 | "text": "ミライリフレクト" 9 | }, 10 | { 11 | "time": 61500, 12 | "text": "水音とカーテン" 13 | }, 14 | { 15 | "time": 106000, 16 | "text": "よるつむぎ" 17 | } 18 | ] -------------------------------------------------------------------------------- /src/data/lyrics/20230311-1634663531298820096_video.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 1300, 4 | "text": "いっせーのーで" 5 | }, 6 | { 7 | "time": 26800, 8 | "text": "マシュマリー" 9 | }, 10 | { 11 | "time": 48500, 12 | "text": "哀の隙間" 13 | }, 14 | { 15 | "time": 75200, 16 | "text": "ending" 17 | } 18 | ] -------------------------------------------------------------------------------- /src/data/lyrics/20230530-1663668305754980353_video.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 836, 4 | "text": "嗚呼 何も無い世界だな", 5 | "cn": "啊啊 真是空无一物的世界啊", 6 | "ro": "a a na ni mo na i se ka i da na" 7 | }, 8 | { 9 | "time": 5355, 10 | "text": "", 11 | "interlude": true 12 | }, 13 | { 14 | "time": 5626, 15 | "text": "ただたださようならって言わせてよ昨日の寂しさに", 16 | "cn": "仅仅 能否 让我向昨天的寂寞说再见啊", 17 | "ro": "ta da ta da sa yo u na ra tte i wa se te yo ki no u no sa bi shi sa ni" 18 | }, 19 | { 20 | "time": 11221, 21 | "text": "いつしか空っぽな心だけが夜空に咲いたんだ", 22 | "cn": "直到只有空虚的心在夜空中绽放的那天", 23 | "ro": "i tsu shi ka ka ra ppo na ko ko ro da ke ga yo zo ra ni sa i ta n da" 24 | }, 25 | { 26 | "time": 16749, 27 | "text": "ほらまたすっからかんに生きたいな何にもないからさ", 28 | "cn": "看啊 什么都没有所以仍想空无一物地活着啊", 29 | "ro": "ho ra ma ta su kka ra ka n ni i ki ta i na na n ni mo na i ka ra sa" 30 | }, 31 | { 32 | "time": 22588, 33 | "text": "このまま少しだけ君と踊る時間にハナタバを", 34 | "cn": "就这样再为与你共舞的时间献上花束吧", 35 | "ro": "ko no ma ma su ko shi da ke ki mi to o do ru ji ka n ni ha na ta ba wo" 36 | }, 37 | { 38 | "time": 28401, 39 | "text": "嗚呼", 40 | "cn": "啊啊", 41 | "ro": "a a" 42 | } 43 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI - What Call This Day ? (feat. にんじん).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 8660, 9 | "text": "ただサヨナラが言えないままに", 10 | "cn": "只是一直说不出宣告别离的话语" 11 | }, 12 | { 13 | "time": 12340, 14 | "text": "寂しいって気持ちを隠すように", 15 | "cn": "仿佛在努力隐藏叫做寂寞的心情" 16 | }, 17 | { 18 | "time": 16060, 19 | "text": "もうこれ以上 何も 要らない", 20 | "cn": "只要这样就好其他都不需要" 21 | }, 22 | { 23 | "time": 19790, 24 | "text": "って言えるまでは 僕は子供だ", 25 | "cn": "直到这样想为止我都还是个孩子" 26 | }, 27 | { 28 | "time": 23880, 29 | "text": "苦しみの数が増える度", 30 | "cn": "每当痛苦的数目增添的时候" 31 | }, 32 | { 33 | "time": 27150, 34 | "text": "空っぽな心に染みるんだ", 35 | "cn": "就会深深浸染空空如也的心" 36 | }, 37 | { 38 | "time": 30940, 39 | "text": "嗚 呼夢の奥 知らないけれど", 40 | "cn": "啊啊梦境的深处 虽然我一无所知" 41 | }, 42 | { 43 | "time": 34510, 44 | "text": "名も無い日々を何て名付けよう", 45 | "cn": "没有名字的日子 该取些什么名字" 46 | }, 47 | { 48 | "time": 38120, 49 | "text": "素直になれる まで側にいて", 50 | "cn": "直到能说出心声为止待在我身边" 51 | }, 52 | { 53 | "time": 41890, 54 | "text": "変わらない夜だから少しだけ今はさ", 55 | "cn": "正因为这夜晚一成不变现在就请稍微陪陪我吧" 56 | }, 57 | { 58 | "time": 60350, 59 | "text": "ただため息が 答えの朝は", 60 | "cn": "只以叹息作为回应的早晨" 61 | }, 62 | { 63 | "time": 64030, 64 | "text": "ひとつ ひとつ 言葉をなぞる", 65 | "cn": "一笔一划细细描摹着话语" 66 | }, 67 | { 68 | "time": 67990, 69 | "text": "嗚呼幸せを 探す 度に", 70 | "cn": "啊啊每当探寻幸福的时候" 71 | }, 72 | { 73 | "time": 71380, 74 | "text": "少し 迷子 になるばかりで", 75 | "cn": "总是会稍微变得迷失方向" 76 | }, 77 | { 78 | "time": 75560, 79 | "text": "疲れて泣いたらかくれんぼ", 80 | "cn": "一旦累得哭了起来就会藏起来" 81 | }, 82 | { 83 | "time": 79390, 84 | "text": "あなたは僕を見つけてくれる", 85 | "cn": "而你总会找到正在捉迷藏的我" 86 | }, 87 | { 88 | "time": 82970, 89 | "text": "笑えるまでもたれ掛かるの", 90 | "cn": "直到露出笑容为止都任我依靠" 91 | }, 92 | { 93 | "time": 86170, 94 | "text": "何が答えなんだっけ", 95 | "cn": "答案究竟是什么来着" 96 | }, 97 | { 98 | "time": 92200, 99 | "text": "こうやって 居られるのなら", 100 | "cn": "如果能够一直这样下去的话" 101 | }, 102 | { 103 | "time": 95430, 104 | "text": "この寂しいばかりの心臓も", 105 | "cn": "如果这颗满载寂寞的心脏也" 106 | }, 107 | { 108 | "time": 99170, 109 | "text": "いつか生きてる って笑えるなら", 110 | "cn": "有一天能为正在活着笑出来" 111 | }, 112 | { 113 | "time": 102780, 114 | "text": "このまま歌を紡げるのならば", 115 | "cn": "如果能一直这样将歌曲编织" 116 | }, 117 | { 118 | "time": 106800, 119 | "text": "嗚 呼夢の奥 知らないけれど", 120 | "cn": "啊啊梦境的深处 虽然我一无所知" 121 | }, 122 | { 123 | "time": 110210, 124 | "text": "名も無い日々を何て名付けよう", 125 | "cn": "没有名字的日子 该取些什么名字" 126 | }, 127 | { 128 | "time": 113800, 129 | "text": "素直になれる まで側にいて", 130 | "cn": "直到能说出心声为止待在我身边" 131 | }, 132 | { 133 | "time": 117600, 134 | "text": "変わらない夜だから少しだけ今だけ", 135 | "cn": "正因为这夜晚一成不变现在就请稍微陪陪我吧" 136 | }, 137 | { 138 | "time": 123150, 139 | "text": "笑えるように", 140 | "cn": "为了能露出笑容" 141 | }, 142 | { 143 | "time": 124930, 144 | "text": "切り取った今日くらいもういいじゃん", 145 | "cn": "哪怕只是一段切片的今天不也挺好" 146 | }, 147 | { 148 | "time": 127270, 149 | "text": "愛のカタチを待ってんだ", 150 | "cn": "正在等待着爱的形状" 151 | }, 152 | { 153 | "time": 129090, 154 | "text": "明日になる まで歌わせて", 155 | "cn": "让我放声歌唱直到明天到来吧" 156 | }, 157 | { 158 | "time": 132370, 159 | "text": "変わらない夜だから少しだけ今は", 160 | "cn": "正因为这夜晚一成不变现在就请稍微陪陪我" 161 | }, 162 | { 163 | "time": 136520, 164 | "text": "サヨナラが 言えないままに", 165 | "cn": "只是一直说不出宣告别离的话语" 166 | }, 167 | { 168 | "time": 139710, 169 | "text": "寂しいって気持ちを隠すように", 170 | "cn": "仿佛在努力隐藏叫做寂寞的心情" 171 | }, 172 | { 173 | "time": 143460, 174 | "text": "もうこれ以上 何も 要らない", 175 | "cn": "只要这样就好其他都不需要" 176 | }, 177 | { 178 | "time": 147130, 179 | "text": "って言えるまではあなたと微睡む", 180 | "cn": "直到这样想为止与你共入梦乡" 181 | } 182 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI - よるつむぎ.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 35250, 9 | "text": "問う様な明日を待つような", 10 | "cn": "像是发问 又像是等待着明日" 11 | }, 12 | { 13 | "time": 38640, 14 | "text": "声がまた奥で揺れて消えてゆくだけ", 15 | "cn": "声音仅仅只是在深处摇荡着逐渐消失" 16 | }, 17 | { 18 | "time": 43140, 19 | "text": "だって辛い様な何処か痛い様な", 20 | "cn": "像是有些难受 又像是哪里作痛" 21 | }, 22 | { 23 | "time": 47030, 24 | "text": "夜の数だけ覚えてる無限の今", 25 | "cn": "仅仅在无限的当下细数着度过的夜" 26 | }, 27 | { 28 | "time": 51250, 29 | "text": "嗚呼 透明くらいな感情へ", 30 | "cn": "啊 向着宛若透明的感情" 31 | }, 32 | { 33 | "time": 54590, 34 | "text": "辿った今日は空に咲く", 35 | "cn": "追寻着今日在天空中绽放" 36 | }, 37 | { 38 | "time": 59520, 39 | "text": "さらば", 40 | "cn": "再见了" 41 | }, 42 | { 43 | "time": 60440, 44 | "text": "きっと寂しさを知るけれど", 45 | "cn": "尽管一定会知晓寂寞的滋味" 46 | }, 47 | { 48 | "time": 64940, 49 | "text": "全部包み込んで居て", 50 | "cn": "也请将所有一切拥入怀中吧" 51 | }, 52 | { 53 | "time": 85500, 54 | "text": "無い様な 消えてしまう様な", 55 | "cn": "像是不存在一般 像是随时会消失一般" 56 | }, 57 | { 58 | "time": 88910, 59 | "text": "君の熱 遠く揺らめく風に乗って", 60 | "cn": "你的温度 随着向远方飘去的风一同消散" 61 | }, 62 | { 63 | "time": 93400, 64 | "text": "ずっと唄う様に 此処で触れる様に", 65 | "cn": "像是一直都在歌唱 仿佛在此处便能触及" 66 | }, 67 | { 68 | "time": 97280, 69 | "text": "満ちて征く 意味は無くても息をしてて", 70 | "cn": "逐渐填满 毫无意义也仍旧呼吸着" 71 | }, 72 | { 73 | "time": 101580, 74 | "text": "何も気づけないよそんで全部", 75 | "cn": "什么都无法注意到了啊那些所有" 76 | }, 77 | { 78 | "time": 104260, 79 | "text": "お月様みたいに変わってくの?", 80 | "cn": "都会像月亮婆婆一样改变吗?" 81 | }, 82 | { 83 | "time": 106200, 84 | "text": "傷ついた日々にばいばい", 85 | "cn": "受伤的那些日子全都byebye" 86 | }, 87 | { 88 | "time": 108570, 89 | "text": "笑えるまで", 90 | "cn": "直到能够欢笑" 91 | }, 92 | { 93 | "time": 110450, 94 | "text": "泣いたら少し暗くなって", 95 | "cn": "眼泪落下便有些昏暗" 96 | }, 97 | { 98 | "time": 112500, 99 | "text": "最終此処に居てもいいの?", 100 | "cn": "直到最后都在这里可以吗?" 101 | }, 102 | { 103 | "time": 114580, 104 | "text": "答えはいつも…", 105 | "cn": "回答一直都是..." 106 | }, 107 | { 108 | "time": 119410, 109 | "text": "Ru", 110 | "cn": "噜~" 111 | }, 112 | { 113 | "time": 136060, 114 | "text": "こうやって歩くたびに", 115 | "cn": "每当如此走着的时候" 116 | }, 117 | { 118 | "time": 139640, 119 | "text": "巡った憂いは通り道", 120 | "cn": "经过的路便是轮回的忧愁" 121 | }, 122 | { 123 | "time": 143910, 124 | "text": "独り揺れる君と笑ってたくて", 125 | "cn": "想要和独自摇曳着的你一同欢笑" 126 | }, 127 | { 128 | "time": 152350, 129 | "text": "ただ", 130 | "cn": "仅仅" 131 | }, 132 | { 133 | "time": 152770, 134 | "text": "透明くらいな感情へ", 135 | "cn": "向着宛若透明的感情" 136 | }, 137 | { 138 | "time": 156460, 139 | "text": "辿った今日は空に咲く", 140 | "cn": "追寻着今日在天空中绽放" 141 | }, 142 | { 143 | "time": 160780, 144 | "text": "さらば", 145 | "cn": "再见了" 146 | }, 147 | { 148 | "time": 161700, 149 | "text": "きっと暖かさを知るならば", 150 | "cn": "若有朝一日能够知晓何为温暖" 151 | }, 152 | { 153 | "time": 165960, 154 | "text": "全部包み込んでくれ", 155 | "cn": "那就请将一切都拥入怀中吧" 156 | } 157 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI - カラバコにアイ.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 32160, 9 | "text": "床に落とした水滴がいつか朝露になって" 10 | }, 11 | { 12 | "time": 37770, 13 | "text": "消えてゆく儚さだけポケットにしまって" 14 | }, 15 | { 16 | "time": 43160, 17 | "text": "白紙のページの隅に地球みたいな蒼さで" 18 | }, 19 | { 20 | "time": 48080, 21 | "text": "なぞる言葉の隙間を埋めるように少しだけ" 22 | }, 23 | { 24 | "time": 53720, 25 | "text": "「覚えています小さな嘘を" 26 | }, 27 | { 28 | "time": 58940, 29 | "text": "自分について歩いたことを」" 30 | }, 31 | { 32 | "time": 64110, 33 | "text": "上手く生きられない からからから" 34 | }, 35 | { 36 | "time": 66840, 37 | "text": "何かが足りない からからから" 38 | }, 39 | { 40 | "time": 69520, 41 | "text": "涙を隠してた からからから" 42 | }, 43 | { 44 | "time": 72160, 45 | "text": "昨日を反省してしまうんだ" 46 | }, 47 | { 48 | "time": 74930, 49 | "text": "世に流されて征く からからから" 50 | }, 51 | { 52 | "time": 77340, 53 | "text": "月は ねぇ 綺麗だからからから" 54 | }, 55 | { 56 | "time": 80030, 57 | "text": "心は何時だって 空 空 空" 58 | }, 59 | { 60 | "time": 82710, 61 | "text": "物足りない世界の隅で" 62 | }, 63 | { 64 | "time": 85330, 65 | "text": "日記のつかない今日" 66 | }, 67 | { 68 | "time": 87750, 69 | "text": "を閉じ込めて居たいんだ" 70 | }, 71 | { 72 | "time": 90240, 73 | "text": "気付かないフリをしてた" 74 | }, 75 | { 76 | "time": 93110, 77 | "text": "痛みも含めて" 78 | }, 79 | { 80 | "time": 95680, 81 | "text": "傷つくことは解ってるのに" 82 | }, 83 | { 84 | "time": 101120, 85 | "text": "いつまでも慣れないのはなんで" 86 | }, 87 | { 88 | "time": 106550, 89 | "text": "眠れない夜を数えてるから" 90 | }, 91 | { 92 | "time": 109060, 93 | "text": "大人になるまで笑えないから" 94 | }, 95 | { 96 | "time": 111780, 97 | "text": "その癖大人が嫌いだから" 98 | }, 99 | { 100 | "time": 114300, 101 | "text": "何にも考えさせないで!" 102 | }, 103 | { 104 | "time": 116930, 105 | "text": "やり切れない心象があるから" 106 | }, 107 | { 108 | "time": 119700, 109 | "text": "天そらは ねぇ いつも遠くあるから" 110 | }, 111 | { 112 | "time": 122310, 113 | "text": "やがて音が弾けて揺らぐから" 114 | }, 115 | { 116 | "time": 124990, 117 | "text": "形のないもの一つ持って" 118 | }, 119 | { 120 | "time": 148630, 121 | "text": "器用に喋れない からからから" 122 | }, 123 | { 124 | "time": 151350, 125 | "text": "目をわざと伏せた からからから" 126 | }, 127 | { 128 | "time": 153840, 129 | "text": "悩みながら揺れる からからから" 130 | }, 131 | { 132 | "time": 156490, 133 | "text": "覚えてるよ、その苦しみだって" 134 | }, 135 | { 136 | "time": 159260, 137 | "text": "願うほどに霞む からからから" 138 | }, 139 | { 140 | "time": 161690, 141 | "text": "月は ねぇ 綺麗だからからから" 142 | }, 143 | { 144 | "time": 164460, 145 | "text": "感情の箱は 空 空 空" 146 | }, 147 | { 148 | "time": 167140, 149 | "text": "等身大の問い方でさ" 150 | } 151 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI - モーメント.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 32240, 9 | "text": "独りを歩く何処かの街で" 10 | }, 11 | { 12 | "time": 35940, 13 | "text": "言葉を描いては飲み込んだ" 14 | }, 15 | { 16 | "time": 39750, 17 | "text": "海辺を灯る景色の奥が" 18 | }, 19 | { 20 | "time": 43620, 21 | "text": "不意に潤んで溶け出したんだ" 22 | }, 23 | { 24 | "time": 46920, 25 | "text": "音さえない夜の道を" 26 | }, 27 | { 28 | "time": 50260, 29 | "text": "寂しいねって笑わないで" 30 | }, 31 | { 32 | "time": 54080, 33 | "text": "照明を探す理由はあるの?" 34 | }, 35 | { 36 | "time": 57830, 37 | "text": "塞ぐ僕らは何処へ?" 38 | }, 39 | { 40 | "time": 61390, 41 | "text": "願う色は言葉を照らすのに" 42 | }, 43 | { 44 | "time": 65230, 45 | "text": "夜の月は道を隠す様で" 46 | }, 47 | { 48 | "time": 69070, 49 | "text": "放つ音が景色に響くから" 50 | }, 51 | { 52 | "time": 72860, 53 | "text": "過去の奥で泣くのは" 54 | }, 55 | { 56 | "time": 74810, 57 | "text": "止めにしよう" 58 | }, 59 | { 60 | "time": 93140, 61 | "text": "蒼い世界を染まる雨音" 62 | }, 63 | { 64 | "time": 96890, 65 | "text": "無くしてじゃあねって笑う日々が" 66 | }, 67 | { 68 | "time": 100760, 69 | "text": "何故か綺麗で包み込んだの" 70 | }, 71 | { 72 | "time": 104520, 73 | "text": "独り両手で抱き締めている" 74 | }, 75 | { 76 | "time": 107990, 77 | "text": "風さえない窓の外に" 78 | }, 79 | { 80 | "time": 111120, 81 | "text": "明日の痛みを隠さないで" 82 | }, 83 | { 84 | "time": 115010, 85 | "text": "覆う温度は形を成して" 86 | }, 87 | { 88 | "time": 118810, 89 | "text": "凪いだ景色を伝う" 90 | }, 91 | { 92 | "time": 122320, 93 | "text": "描く天そらが涙を映すのに" 94 | }, 95 | { 96 | "time": 126190, 97 | "text": "紡ぐ声で痛みを鳴らすのに" 98 | }, 99 | { 100 | "time": 129990, 101 | "text": "違う朝が来るなら唄わせて" 102 | }, 103 | { 104 | "time": 133830, 105 | "text": "時の中で独り息をしている" 106 | }, 107 | { 108 | "time": 154900, 109 | "text": "ふと停留所で夢を見ていた" 110 | }, 111 | { 112 | "time": 158490, 113 | "text": "また何処かで逢えたら良いなと" 114 | }, 115 | { 116 | "time": 162240, 117 | "text": "思うんだ" 118 | }, 119 | { 120 | "time": 163140, 121 | "text": "色は言葉を照らすのに" 122 | }, 123 | { 124 | "time": 166170, 125 | "text": "夜の藍は影を隠す様で" 126 | }, 127 | { 128 | "time": 169970, 129 | "text": "放つ音が景色に響くから" 130 | }, 131 | { 132 | "time": 173820, 133 | "text": "明日の中を描くと誓うから" 134 | }, 135 | { 136 | "time": 178470, 137 | "text": "想う昨日は嘘の様で" 138 | }, 139 | { 140 | "time": 181570, 141 | "text": "また夢で君に逢える" 142 | }, 143 | { 144 | "time": 184000, 145 | "text": "様な気がするんだ" 146 | }, 147 | { 148 | "time": 189340, 149 | "text": "あぁ…" 150 | } 151 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI, 可不 - 愛するように (feat. 可不).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 20425, 9 | "text": "寂しさの中僕らは生きる", 10 | "cn": "我们生于寂寞之中" 11 | }, 12 | { 13 | "time": 25178, 14 | "text": "光の先で孤独を歌う", 15 | "cn": "在光芒前歌唱孤独" 16 | }, 17 | { 18 | "time": 30432, 19 | "text": "このまま明日が来なけりゃいいの", 20 | "cn": "若明日就这样不再到来就好了" 21 | }, 22 | { 23 | "time": 35432, 24 | "text": "疲れたくらい 言わせてくれよ", 25 | "cn": "请让我倾诉至 疲惫不堪吧" 26 | }, 27 | { 28 | "time": 39682, 29 | "text": "", 30 | "interlude": true 31 | }, 32 | { 33 | "time": 41381, 34 | "text": "夢の中君の声だけが", 35 | "cn": "在我的梦中之中似乎" 36 | }, 37 | { 38 | "time": 45632, 39 | "text": "聞こえたような問いかけるような", 40 | "cn": "能听到你的声音正向我提问" 41 | }, 42 | { 43 | "time": 50387, 44 | "text": "", 45 | "interlude": true 46 | }, 47 | { 48 | "time": 50887, 49 | "text": "嗚呼 この世界で煌めくような", 50 | "cn": "啊啊 寻找着在这世界上" 51 | }, 52 | { 53 | "time": 55883, 54 | "text": "生きる意味を探しているの", 55 | "cn": "那无比耀眼的生存意义" 56 | }, 57 | { 58 | "time": 60883, 59 | "text": "泣いちゃうみたい夜の瞬き", 60 | "cn": "在那泫然欲泣的夜晚中的那一瞬" 61 | }, 62 | { 63 | "time": 66125, 64 | "text": "すらも今日は愛させてくれ", 65 | "cn": "就让我今天也去爱你" 66 | }, 67 | { 68 | "time": 71624, 69 | "text": "music..." 70 | }, 71 | { 72 | "time": 91613, 73 | "text": "きっと明日も最期もそうやって", 74 | "cn": "我想自己无论是明日还是最后" 75 | }, 76 | { 77 | "time": 94858, 78 | "text": "心のどっかに絆創膏", 79 | "cn": "心中某处的创口贴" 80 | }, 81 | { 82 | "time": 97359, 83 | "text": "貼れないまんまで生きていく", 84 | "cn": "将不再能贴完这些" 85 | }, 86 | { 87 | "time": 99854, 88 | "text": "痛いの痛いの傷口が", 89 | "cn": "隐隐作痛的伤口" 90 | }, 91 | { 92 | "time": 101104, 93 | "text": "", 94 | "interlude": true 95 | }, 96 | { 97 | "time": 102611, 98 | "text": "生まれた意味は考えない", 99 | "cn": "不去思考诞生的意义" 100 | }, 101 | { 102 | "time": 105207, 103 | "text": "考えたら辛くなるの", 104 | "cn": "思考只让我感到痛苦" 105 | }, 106 | { 107 | "time": 107207, 108 | "text": "でも君の音だけは暖かいから", 109 | "cn": "因为你的声音就已够温暖" 110 | }, 111 | { 112 | "time": 108459, 113 | "text": "", 114 | "interlude": true 115 | }, 116 | { 117 | "time": 112660, 118 | "text": "このままで居てもいいの?", 119 | "cn": "我可以这样待于此处吗" 120 | }, 121 | { 122 | "time": 117165, 123 | "text": "ありのままで居てもいいの?", 124 | "cn": "我可以表露真实的自我吗" 125 | }, 126 | { 127 | "time": 122170, 128 | "text": "1人分からないことばかり", 129 | "cn": "尽是些我独自一人无法明白的事情" 130 | }, 131 | { 132 | "time": 127411, 133 | "text": "それも全部愛していいの?", 134 | "cn": "即便如此依然能爱上一切吗" 135 | }, 136 | { 137 | "time": 132158, 138 | "text": "", 139 | "interlude": true 140 | }, 141 | { 142 | "time": 133363, 143 | "text": "この世界で煌めくような", 144 | "cn": "寻找着在这世界上" 145 | }, 146 | { 147 | "time": 137623, 148 | "text": "生きる意味を探しているの", 149 | "cn": "那无比耀眼的生存意义" 150 | }, 151 | { 152 | "time": 142617, 153 | "text": "泣いちゃうみたい夜の瞬き", 154 | "cn": "在那泫然欲泣的夜晚中的那一瞬" 155 | }, 156 | { 157 | "time": 147875, 158 | "text": "すらも今日は愛させてくれ", 159 | "cn": "就让我今天也去爱你" 160 | }, 161 | { 162 | "time": 153119, 163 | "text": "", 164 | "interlude": true 165 | } 166 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,わん子 - みにまむ (feat. わん子).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 4696, 9 | "text": "いつか日記帳やめた様に", 10 | "cn": "就像有一天我不再写日记一样" 11 | }, 12 | { 13 | "time": 7222, 14 | "text": "夢の宛先もない様に", 15 | "cn": "就像梦想没有归宿一样" 16 | }, 17 | { 18 | "time": 9702, 19 | "text": "曖昧にどうにかやってんだ", 20 | "cn": "迷迷糊糊地做事情" 21 | }, 22 | { 23 | "time": 11953, 24 | "text": "それで それで いいじゃん", 25 | "cn": "这样不就行了吗" 26 | }, 27 | { 28 | "time": 14091, 29 | "text": "ごめんなさいで生きてきたの", 30 | "cn": "我一直带着遗憾生活" 31 | }, 32 | { 33 | "time": 16771, 34 | "text": "そうやって今日も生きてきたの", 35 | "cn": "这就是我今天的生活" 36 | }, 37 | { 38 | "time": 19165, 39 | "text": "終電 お月様 眩しくって", 40 | "cn": "末班车 月亮 好耀眼" 41 | }, 42 | { 43 | "time": 21558, 44 | "text": "ふいに 伝う 感情...なんて。", 45 | "cn": "突然出现的感觉……哦,我的天" 46 | }, 47 | { 48 | "time": 33050, 49 | "text": "ただただ単調な日々に追い風を", 50 | "cn": "单调的日子里只有顺风" 51 | }, 52 | { 53 | "time": 35787, 54 | "text": "眠れない夜と踊る様に", 55 | "cn": "就像与不眠之夜共舞" 56 | }, 57 | { 58 | "time": 38170, 59 | "text": "ねぇもうちょっとだけ此処に居よう", 60 | "cn": "再在这里待一会儿吧" 61 | }, 62 | { 63 | "time": 40468, 64 | "text": "って 歌って 繋いでいて", 65 | "cn": "什么... 唱着歌 彼此连接" 66 | }, 67 | { 68 | "time": 43173, 69 | "text": "今日も繋いでいて", 70 | "cn": "今天也连接着" 71 | }, 72 | { 73 | "time": 45394, 74 | "text": "どうだいって手を振る人生に", 75 | "cn": "在挥手致意的人生中" 76 | }, 77 | { 78 | "time": 47661, 79 | "text": "お茶碗一杯分愛情を", 80 | "cn": "一茶碗分量的爱意" 81 | }, 82 | { 83 | "time": 50200, 84 | "text": "最低限度に息をして", 85 | "cn": "我尽可能减少呼吸" 86 | }, 87 | { 88 | "time": 52446, 89 | "text": "優しい夢を見るの", 90 | "cn": "做个甜蜜的梦" 91 | }, 92 | { 93 | "time": 54732, 94 | "text": "きっと向き合う後悔に", 95 | "cn": "不可避免面对的后悔" 96 | }, 97 | { 98 | "time": 57148, 99 | "text": "今日はちょっとだけ", 100 | "cn": "今天就只有一点点" 101 | }, 102 | { 103 | "time": 59023, 104 | "text": "触れる様に 混ざる様に 笑う様に", 105 | "cn": "仿佛触摸一样混合着笑意" 106 | }, 107 | { 108 | "time": 62650, 109 | "text": "そうやって 歩けるんだ", 110 | "cn": "就这样子走下去" 111 | }, 112 | { 113 | "time": 74071, 114 | "text": "どうしたい?って夢の中で", 115 | "cn": "想做什么?在梦境中" 116 | }, 117 | { 118 | "time": 76135, 119 | "text": "正解を探してる", 120 | "cn": "寻找着正确答案" 121 | }, 122 | { 123 | "time": 78800, 124 | "text": "どうしたい?って夢の中で", 125 | "cn": "明天也将会寻找吧" 126 | }, 127 | { 128 | "time": 80781, 129 | "text": "正解を探してる", 130 | "cn": "寻找着正确答案" 131 | }, 132 | { 133 | "time": 83535, 134 | "text": "明日も探すだろう", 135 | "cn": "明天也将会寻找吧" 136 | }, 137 | { 138 | "time": 85434, 139 | "text": "知らない答えに悩むように", 140 | "cn": "为了不知道的答案而烦恼着" 141 | }, 142 | { 143 | "time": 87978, 144 | "text": "知ってる答えは違うように", 145 | "cn": "知道的答案又是不同的" 146 | }, 147 | { 148 | "time": 90420, 149 | "text": "嗚呼おひとつ教えて頂戴な", 150 | "cn": "啊...请告诉我..." 151 | }, 152 | { 153 | "time": 92749, 154 | "text": "最小限で、いいよ いいよ。", 155 | "cn": "哦哦 这是最小的 好吧" 156 | }, 157 | { 158 | "time": 99313, 159 | "text": "良いんだよ", 160 | "cn": "好啊..." 161 | }, 162 | { 163 | "time": 104393, 164 | "text": "ただただ単調な日々に追い風を", 165 | "cn": "单调的日子里只有顺风" 166 | }, 167 | { 168 | "time": 107093, 169 | "text": "やんなっちゃう朝が来たる前に", 170 | "cn": "在那讨厌的早晨来临之前..." 171 | }, 172 | { 173 | "time": 109413, 174 | "text": "ねぇもうちょっとだけ此処に居よう", 175 | "cn": "再在这里待一会儿吧" 176 | }, 177 | { 178 | "time": 111659, 179 | "text": "って 歌って 繋いでいて", 180 | "cn": "什么... 唱着歌 彼此连接" 181 | }, 182 | { 183 | "time": 113933, 184 | "text": "どうだいって手を振る人生に", 185 | "cn": "在挥手致意的人生中" 186 | }, 187 | { 188 | "time": 116476, 189 | "text": "お茶碗一杯分愛情を", 190 | "cn": "一茶碗分量的爱意" 191 | }, 192 | { 193 | "time": 118943, 194 | "text": "最低限度に息をして", 195 | "cn": "我尽可能减少呼吸" 196 | }, 197 | { 198 | "time": 121318, 199 | "text": "優しい夢を見せて", 200 | "cn": "做个甜蜜的梦" 201 | }, 202 | { 203 | "time": 123640, 204 | "text": "きっと向き合う後悔に", 205 | "cn": "不可避免面对的后悔" 206 | }, 207 | { 208 | "time": 126001, 209 | "text": "今日はちょっとだけ", 210 | "cn": "今天就只有一点点" 211 | }, 212 | { 213 | "time": 128125, 214 | "text": "触れる様に 混ざる様に 笑う様に", 215 | "cn": "仿佛触摸一样混合着笑意" 216 | }, 217 | { 218 | "time": 133209, 219 | "text": "そっと", 220 | "cn": "悄悄的" 221 | }, 222 | { 223 | "time": 134030, 224 | "text": "抱きしめて 抱きしめて 抱きしめて", 225 | "cn": "紧紧拥抱着... 拥抱着... 抱着..." 226 | }, 227 | { 228 | "time": 137567, 229 | "text": "歩けるんだ", 230 | "cn": "我能走了..." 231 | } 232 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,可不 - あのね (feat. 可不).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 13958, 9 | "text": "あのね 、あのね", 10 | "cn": "听我说 听我说", 11 | "ro": "a no ne 、a no ne" 12 | }, 13 | { 14 | "time": 16866, 15 | "text": "チクチクって痛むのなんでか教えてよ", 16 | "cn": "告诉我为什么会像针刺一般疼痛吧", 17 | "ro": "chi ku chi ku tte i ta mu no na n de ka o shi e te yo" 18 | }, 19 | { 20 | "time": 20681, 21 | "text": "でもね、でもね", 22 | "cn": "但是啊 但是呢", 23 | "ro": "de mo ne、de mo ne" 24 | }, 25 | { 26 | "time": 23581, 27 | "text": "音楽であたしの心を埋めてゆく", 28 | "cn": "我会继续用音乐把我的心埋葬下去的", 29 | "ro": "o n ga ku de a ta shi no ko ko ro wo u me te yu ku" 30 | }, 31 | { 32 | "time": 27842, 33 | "text": "なにもなにも信じない", 34 | "cn": "因为我什么都不会相信", 35 | "ro": "na ni mo na ni mo shi n ji na i" 36 | }, 37 | { 38 | "time": 29699, 39 | "text": "だからだから今はさ", 40 | "cn": "所以说 所以说 现在就", 41 | "ro": "da ka ra da ka ra i ma wa sa" 42 | }, 43 | { 44 | "time": 31334, 45 | "text": "全部を忘れて唄いましょ", 46 | "cn": "忘掉一切一起歌唱吧", 47 | "ro": "ze n bu wo wa su re te u ta i ma sho" 48 | }, 49 | { 50 | "time": 34120, 51 | "text": "今日も生きてる今日も生きてる", 52 | "cn": "今天也还活着 今天也还活着", 53 | "ro": "kyo u mo i ki te ru kyo u mo i ki te ru" 54 | }, 55 | { 56 | "time": 37567, 57 | "text": "つらいの、抱えたまま笑うの", 58 | "cn": "虽然辛苦 但是可以抱在一起欢笑", 59 | "ro": "tsu ra i no、ka ka e ta ma ma wa ra u no" 60 | }, 61 | { 62 | "time": 41073, 63 | "text": "だけどいいでしょ だけどいいでしょ", 64 | "cn": "可以答应我吗 可以答应我吗", 65 | "ro": "da ke do i i de sho da ke do i i de sho" 66 | }, 67 | { 68 | "time": 44355, 69 | "text": "明日泣いちゃう夜も抱きしめて いいよ", 70 | "cn": "明天在哭泣的夜晚也相拥吧", 71 | "ro": "a shi ta na i cha u yo ru mo da ki shi me te i i yo" 72 | }, 73 | { 74 | "time": 61352, 75 | "text": "あのね、あのね", 76 | "cn": "听我说 听我说", 77 | "ro": "a no ne、a no ne" 78 | }, 79 | { 80 | "time": 64101, 81 | "text": "ザワザワって気持ちが騒ぐの教えてよ", 82 | "cn": "告诉我为什么会躁动不止吧", 83 | "ro": "za wa za wa tte ki mo chi ga sa wa gu no o shi e te yo" 84 | }, 85 | { 86 | "time": 68062, 87 | "text": "だから、だから", 88 | "cn": "因为啊 因为啊", 89 | "ro": "da ka ra、da ka ra" 90 | }, 91 | { 92 | "time": 70884, 93 | "text": "眠れない世界の真ん中息を吸う", 94 | "cn": "我们是生活在不眠的世界中", 95 | "ro": "ne mu re na i se ka i no ma n na ka i ki wo su u" 96 | }, 97 | { 98 | "time": 75245, 99 | "text": "いつもいつも泣いちゃう", 100 | "cn": "总是会哭出来", 101 | "ro": "i tsu mo i tsu mo na i cha u" 102 | }, 103 | { 104 | "time": 76983, 105 | "text": "こころこころ隠して", 106 | "cn": "把自己的心藏起来", 107 | "ro": "ko ko ro ko ko ro ka ku shi te" 108 | }, 109 | { 110 | "time": 78518, 111 | "text": "遠くで描くは流れ星", 112 | "cn": "把它变成远处的流星 捉摸不透", 113 | "ro": "to o ku de e ga ku wa na ga re bo shi" 114 | }, 115 | { 116 | "time": 81531, 117 | "text": "陽だまりの隅 陽だまりの隅", 118 | "cn": "向阳温暖的角落 向阳温暖的角落", 119 | "ro": "hi da ma ri no su mi hi da ma ri no su mi" 120 | }, 121 | { 122 | "time": 84907, 123 | "text": "揺らぐ貴方の音を聞かせてよ", 124 | "cn": "让我听听你起伏的声音吧", 125 | "ro": "yu ra gu a na ta no o to wo ki ka se te yo" 126 | }, 127 | { 128 | "time": 88348, 129 | "text": "ゆらぎのなかで ゆらぎのなかで", 130 | "cn": "在这起伏之中 在这起伏之中", 131 | "ro": "yu ra gi no na ka de yu ra gi no na ka de" 132 | }, 133 | { 134 | "time": 91717, 135 | "text": "ここで息をしていてもいいかな?", 136 | "cn": "可以接纳下我的这一份呼吸吗?", 137 | "ro": "ko ko de i ki wo shi te i te mo i i ka na?" 138 | }, 139 | { 140 | "time": 95058, 141 | "text": "今日も生きてる今日も生きてる", 142 | "cn": "今天也还活着 今天也还活着", 143 | "ro": "kyo u mo i ki te ru kyo u mo i ki te ru" 144 | }, 145 | { 146 | "time": 98475, 147 | "text": "つらいの、抱えたまま笑うの", 148 | "cn": "很痛苦的 抱在一起欢笑的今日", 149 | "ro": "tsu ra i no、ka ka e ta ma ma wa ra u no" 150 | }, 151 | { 152 | "time": 101850, 153 | "text": "だけどいいでしょ だけどいいでしょ", 154 | "cn": "不如这样吧 不如这样吧", 155 | "ro": "da ke do i i de sho da ke do i i de sho" 156 | }, 157 | { 158 | "time": 105169, 159 | "text": "明日泣いちゃう夜も抱きしめて wow", 160 | "cn": "明天就算是在夜晚哭泣也相互抱紧吧", 161 | "ro": "a shi ta na i cha u yo ru mo da ki shi me te wow" 162 | }, 163 | { 164 | "time": 109053, 165 | "text": "泣いちゃう夜も抱きしめていいよ", 166 | "cn": "就算彻夜哭泣也不分开好了", 167 | "ro": "na i cha u yo ru mo da ki shi me te i i yo" 168 | } 169 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,可不 - いっせーのーで.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 25139, 9 | "text": "ねぇいっせーの いっせーのーで", 10 | "cn": "呐一二三 数着一二三地" 11 | }, 12 | { 13 | "time": 28041, 14 | "text": "いつしか駆け出す水平線", 15 | "cn": "不知何时开始奔跑的水平线" 16 | }, 17 | { 18 | "time": 30619, 19 | "text": "ただ夢の中 淡く流れる", 20 | "cn": "只是在梦中 淡淡流淌" 21 | }, 22 | { 23 | "time": 33136, 24 | "text": "此処に居る理由はなんだっけ", 25 | "cn": "存在于此处的理由已然忘却" 26 | }, 27 | { 28 | "time": 35949, 29 | "text": "ねぇいっせーの いっせーのーで", 30 | "cn": "呐一二三 数着一二三地" 31 | }, 32 | { 33 | "time": 38997, 34 | "text": "あの日の孤独は丁重に", 35 | "cn": "那一日的孤独如此深刻" 36 | }, 37 | { 38 | "time": 41324, 39 | "text": "言葉の青さ ひとつ摘まんで", 40 | "cn": "将辞藻中的青涩轻轻摘下" 41 | }, 42 | { 43 | "time": 43991, 44 | "text": "問いかけるの返事を期待して", 45 | "cn": "期待你的回答" 46 | }, 47 | { 48 | "time": 46898, 49 | "text": "", 50 | "interlude": true 51 | }, 52 | { 53 | "time": 46898, 54 | "text": "つらいほどに向き合う", 55 | "cn": "明明面对是那样痛苦" 56 | }, 57 | { 58 | "time": 49580, 59 | "text": "体温心の隙間とか", 60 | "cn": "体温与心脏的间隙" 61 | }, 62 | { 63 | "time": 52255, 64 | "text": "どうかな今では思い出せるかな", 65 | "cn": "事到如今还能回想起来吗" 66 | }, 67 | { 68 | "time": 57731, 69 | "text": "遠くで揺らいだ透明音", 70 | "cn": "远方摇曳的透明音" 71 | }, 72 | { 73 | "time": 60195, 74 | "text": "ただ透明音 と漂うだけ", 75 | "cn": "只是透明音 漂浮着" 76 | }, 77 | { 78 | "time": 63168, 79 | "text": "眠れぬ夜明けの恒星天井", 80 | "cn": "失眠黎明的恒星天窗" 81 | }, 82 | { 83 | "time": 65944, 84 | "text": "いっせーのーで飛び込んだ", 85 | "cn": "数着一二三地一跃而起" 86 | }, 87 | { 88 | "time": 68706, 89 | "text": "抱きしめた過去の感情論", 90 | "cn": "拥抱过去的感情论" 91 | }, 92 | { 93 | "time": 71045, 94 | "text": "ただ感情論 でもほんとだよ。", 95 | "cn": "只是感情论 但也是真实的哦" 96 | }, 97 | { 98 | "time": 73989, 99 | "text": "寂しさ1個ギュッて持って", 100 | "cn": "紧紧拥抱寂寞的自己" 101 | }, 102 | { 103 | "time": 76281, 104 | "text": "まだ歌うんだ いつか笑えるまで", 105 | "cn": "依然纵情歌唱 直到某日绽放笑容" 106 | }, 107 | { 108 | "time": 89888, 109 | "text": "", 110 | "interlude": true 111 | }, 112 | { 113 | "time": 89889, 114 | "text": "ねぇいっせーのー いっせーのーで", 115 | "cn": "呐一二三 数着一二三地" 116 | }, 117 | { 118 | "time": 92762, 119 | "text": "分からず選んだABC", 120 | "cn": "随随便便选的ABC" 121 | }, 122 | { 123 | "time": 95189, 124 | "text": "何かが弾ける 秘密の欠片", 125 | "cn": "会弹出什么 秘密的碎片" 126 | }, 127 | { 128 | "time": 97919, 129 | "text": "そして知ってゆく懐かしさ", 130 | "cn": "然后才明白怀念的感觉" 131 | }, 132 | { 133 | "time": 122530, 134 | "text": "", 135 | "interlude": true 136 | }, 137 | { 138 | "time": 122531, 139 | "text": "遠くで揺らいだ透明音", 140 | "cn": "远方摇曳的透明音" 141 | }, 142 | { 143 | "time": 124879, 144 | "text": "ただ透明音 と漂うだけ", 145 | "cn": "只是透明音 漂浮着" 146 | }, 147 | { 148 | "time": 127978, 149 | "text": "眠れぬ夜明けの恒星天井", 150 | "cn": "失眠黎明的恒星天窗" 151 | }, 152 | { 153 | "time": 130557, 154 | "text": "いっせーのーで飛び込んだ", 155 | "cn": "数着一二三一跃而入" 156 | }, 157 | { 158 | "time": 133370, 159 | "text": "抱きしめた過去の感情論", 160 | "cn": "拥抱过去的感情论" 161 | }, 162 | { 163 | "time": 135683, 164 | "text": "ただ感情論 でもほんとだよ。", 165 | "cn": "只是感情论 但也是真实的哦" 166 | }, 167 | { 168 | "time": 138734, 169 | "text": "寂しさ1個ぎゅって持って", 170 | "cn": "紧紧拥抱寂寞的自己" 171 | }, 172 | { 173 | "time": 141092, 174 | "text": "また生きるんだ いつか笑えるまで", 175 | "cn": "一定要活下去 直到某日绽放笑容" 176 | } 177 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,可不 - だきしめるまで。.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 22018, 9 | "text": "ちょっぴりちょっとの寂しさが", 10 | "cn": "一点点 一点点的寂寞" 11 | }, 12 | { 13 | "time": 27035, 14 | "text": "いつしか涙に変わる様に", 15 | "cn": "不知不觉间变成了眼泪" 16 | }, 17 | { 18 | "time": 32052, 19 | "text": "ちょっぴりちょっとの疲れたを", 20 | "cn": "一点点 一点点的疲惫" 21 | }, 22 | { 23 | "time": 37074, 24 | "text": "見せない様にこらえるきみ", 25 | "cn": "看不见但是不断承受的你" 26 | }, 27 | { 28 | "time": 42351, 29 | "text": "嗚呼でもね分かってるよ", 30 | "cn": "啊啊,但是我是知道的哦" 31 | }, 32 | { 33 | "time": 46872, 34 | "text": "ホントはねって、無理してたんだ", 35 | "cn": "其实啊,只是在勉强罢了" 36 | }, 37 | { 38 | "time": 51892, 39 | "text": "きみを出さないように", 40 | "cn": "为了不失去你" 41 | }, 42 | { 43 | "time": 56479, 44 | "text": "ねぇねぇ聞いてくれ", 45 | "cn": "呐呐快听" 46 | }, 47 | { 48 | "time": 59996, 49 | "text": "独り言だ", 50 | "cn": "是我的自言自语" 51 | }, 52 | { 53 | "time": 62509, 54 | "text": "でも確かにさ", 55 | "cn": "但是不变的是" 56 | }, 57 | { 58 | "time": 65263, 59 | "text": "きみは優しい", 60 | "cn": "你的温柔" 61 | }, 62 | { 63 | "time": 67526, 64 | "text": "寂しいことも辛いことも", 65 | "cn": "寂寞也好 辛劳也好" 66 | }, 67 | { 68 | "time": 72543, 69 | "text": "きっときっとだきしめるから", 70 | "cn": "一定会拥抱着我" 71 | }, 72 | { 73 | "time": 97398, 74 | "text": "何も分かんなくて", 75 | "cn": "虽然什么都不知道" 76 | }, 77 | { 78 | "time": 100913, 79 | "text": "見えなくても", 80 | "cn": "虽然什么都看不到" 81 | }, 82 | { 83 | "time": 103177, 84 | "text": "でも知ってるよ", 85 | "cn": "但是我知道的哦" 86 | }, 87 | { 88 | "time": 105935, 89 | "text": "きみは優しい", 90 | "cn": "你很温柔" 91 | }, 92 | { 93 | "time": 108195, 94 | "text": "寂しいことも辛いことも", 95 | "cn": "寂寞也好 辛劳也好" 96 | }, 97 | { 98 | "time": 113212, 99 | "text": "きっときっとだきしめるまで", 100 | "cn": "总是以拥抱结尾" 101 | }, 102 | { 103 | "time": 119239, 104 | "text": "正直に生きれなくても", 105 | "cn": "即使没有办法真诚地活下去" 106 | }, 107 | { 108 | "time": 123755, 109 | "text": "上手に今日を笑えなくても", 110 | "cn": "即使不能笑着面对每日" 111 | }, 112 | { 113 | "time": 128777, 114 | "text": "傷ついた日々 過去の夜を", 115 | "cn": "受过伤的时光 和已然过去的深夜" 116 | }, 117 | { 118 | "time": 133800, 119 | "text": "きっときっとだきしめるから", 120 | "cn": "一直一直相互拥抱" 121 | }, 122 | { 123 | "time": 139077, 124 | "text": "きっときっと愛せるように", 125 | "cn": "一定会产生爱意" 126 | } 127 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,可不 - ヒミツ.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 6510, 9 | "text": "ひとつも知らないよ", 10 | "cn": "我对一切都一无所知" 11 | }, 12 | { 13 | "time": 8680, 14 | "text": "どうして寂しい理由だけ", 15 | "cn": "为何只剩下寂寞的理由" 16 | }, 17 | { 18 | "time": 11540, 19 | "text": "それなら最後は気づかないでいいの?", 20 | "cn": "若是如此放任其不管就好了吗?" 21 | }, 22 | { 23 | "time": 14800, 24 | "text": "そしたら楽になれるの?", 25 | "cn": "这样就会让我好受一点了吗?" 26 | }, 27 | { 28 | "time": 17040, 29 | "text": "", 30 | "interlude": true 31 | }, 32 | { 33 | "time": 17630, 34 | "text": "ねぇ なんにもないや 最終便が今日だって", 35 | "cn": "呐 无所谓了 末班车今日就将驶去" 36 | }, 37 | { 38 | "time": 20480, 39 | "text": "独り舞うように世界を通過する", 40 | "cn": "与我自己共舞 走过了这个世界" 41 | }, 42 | { 43 | "time": 23350, 44 | "text": "ただ 信じてたいの いつかはそうやって", 45 | "cn": "我只是想相信着 总会有那样的一天" 46 | }, 47 | { 48 | "time": 26240, 49 | "text": "君と笑えること", 50 | "cn": "我能与你一同欢笑" 51 | }, 52 | { 53 | "time": 30050, 54 | "text": "", 55 | "interlude": true 56 | }, 57 | { 58 | "time": 31600, 59 | "text": "夜に咲く言葉今日になる", 60 | "cn": "夜晚绽放的话语化作了今日" 61 | }, 62 | { 63 | "time": 34750, 64 | "text": "ただ夢の向こうで微睡む様に", 65 | "cn": "在梦的另一个尽头微微沉睡" 66 | }, 67 | { 68 | "time": 37980, 69 | "text": "グッバイそうしてぎゅって抱きしめているの", 70 | "cn": "道出再见 随后又将你紧紧拥入怀中" 71 | }, 72 | { 73 | "time": 41740, 74 | "text": "歌う渚のメロディー", 75 | "cn": "歌唱着潮水般的旋律" 76 | }, 77 | { 78 | "time": 43680, 79 | "text": "藍に浮かぶ 声響いた空", 80 | "cn": "飘浮在青蓝之上 天空回响着我们的歌声" 81 | }, 82 | { 83 | "time": 46230, 84 | "text": "あたしら息をするのなんでかな", 85 | "cn": "思考着我们为何仍在呼吸" 86 | }, 87 | { 88 | "time": 49420, 89 | "text": "だーれも知らない秘密を教えてみせて", 90 | "cn": "告诉你一个无人知晓的秘密" 91 | }, 92 | { 93 | "time": 53160, 94 | "text": "空っぽの此処埋めて", 95 | "cn": "来填补此处的空白" 96 | }, 97 | { 98 | "time": 55160, 99 | "text": "", 100 | "interlude": true 101 | }, 102 | { 103 | "time": 66550, 104 | "text": "ひとつも知らないよ", 105 | "cn": "我什么也不明白" 106 | }, 107 | { 108 | "time": 68700, 109 | "text": "何にもないのに涙だけ", 110 | "cn": "徒留泪水不断滴落" 111 | }, 112 | { 113 | "time": 71530, 114 | "text": "それなら今日に手を振るの駆け出して", 115 | "cn": "既然如此那就挥手奔跑吧" 116 | }, 117 | { 118 | "time": 74770, 119 | "text": "覚めないよう逃避行", 120 | "cn": "这样我就不用再醒过来" 121 | }, 122 | { 123 | "time": 77060, 124 | "text": "", 125 | "interlude": true 126 | }, 127 | { 128 | "time": 78010, 129 | "text": "ちっぽけなんだあたしは何だって", 130 | "cn": "尘粒般渺小的我究竟是什么" 131 | }, 132 | { 133 | "time": 80520, 134 | "text": "上手く飛べないチョウチョの羽ばたきで", 135 | "cn": "如同不会飞翔的蝴蝶一般拍打着翅膀" 136 | }, 137 | { 138 | "time": 83380, 139 | "text": "でも信じてたいのいつかはそうやって", 140 | "cn": "但我愿怀揣着希望 有朝一日" 141 | }, 142 | { 143 | "time": 86260, 144 | "text": "君と笑えること", 145 | "cn": "我能与你一同欢笑" 146 | }, 147 | { 148 | "time": 89900, 149 | "text": "", 150 | "interlude": true 151 | }, 152 | { 153 | "time": 106000, 154 | "text": "夜に咲く言葉今日になる", 155 | "cn": "夜晚绽放的话语化作了今日" 156 | }, 157 | { 158 | "time": 109080, 159 | "text": "ただ夢の向こうで微睡む様に", 160 | "cn": "在梦的另一个尽头微微沉睡" 161 | }, 162 | { 163 | "time": 112290, 164 | "text": "グッバイそうしてぎゅって抱きしめているの", 165 | "cn": "道出再见 随后又将你紧紧拥入怀中" 166 | }, 167 | { 168 | "time": 115980, 169 | "text": "歌う渚のメロディー", 170 | "cn": "歌唱着潮水般的旋律" 171 | }, 172 | { 173 | "time": 118030, 174 | "text": "藍に浮かぶ 声響いた空", 175 | "cn": "飘浮在青蓝之上 天空回响着我们的歌声" 176 | }, 177 | { 178 | "time": 120510, 179 | "text": "あたしら息をするのなんでかな", 180 | "cn": "思考着我们为何仍在呼吸" 181 | }, 182 | { 183 | "time": 123740, 184 | "text": "だーれも知らない秘密を教えてみせて", 185 | "cn": "告诉你一个无人知晓的秘密" 186 | }, 187 | { 188 | "time": 127470, 189 | "text": "空っぽの此処埋めて", 190 | "cn": "来填补此处的空白" 191 | }, 192 | { 193 | "time": 131410, 194 | "text": "", 195 | "interlude": true 196 | }, 197 | { 198 | "time": 134730, 199 | "text": "生きてく ただ生きてゆく", 200 | "cn": "活下去吧 活下去就够了" 201 | }, 202 | { 203 | "time": 137480, 204 | "text": "誰も知らない秘密を教えてみせて", 205 | "cn": "让他们看看只属于你的秘密吧" 206 | }, 207 | { 208 | "time": 141580, 209 | "text": "", 210 | "interlude": true 211 | } 212 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,可不 - 息をするだけ (feat. 可不).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 8190, 9 | "text": "今僕達は息をするだけ", 10 | "cn": "现在 我们只是 保持着呼吸而已", 11 | "ro": "i ma bo ku ta chi wa i ki wo su ru da ke" 12 | }, 13 | { 14 | "time": 12760, 15 | "text": "かざした言葉夜の泡となる", 16 | "cn": "曾经宣扬的话语 化作夜里的泡沫", 17 | "ro": "ka za shi ta ko to ba ya no a wa to na ru" 18 | }, 19 | { 20 | "time": 17160, 21 | "text": "ただそうやって息をするだけ", 22 | "cn": "只是就这样 保持着呼吸而已", 23 | "ro": "ta da so u ya tte i ki wo su ru da ke" 24 | }, 25 | { 26 | "time": 21630, 27 | "text": "微かに灯る温もりの欠片", 28 | "cn": "发出淡淡的光芒 是那温暖的碎片", 29 | "ro": "ka su ka ni to mo ru nu ku mo ri no ka ke ra" 30 | }, 31 | { 32 | "time": 26070, 33 | "text": "", 34 | "interlude": true 35 | }, 36 | { 37 | "time": 34880, 38 | "text": "いつか歩き疲れたら此処で", 39 | "cn": "有朝一日 走累了的话就在这里", 40 | "ro": "i tsu ka a ru ki tsu ka re ta ra ko ko de" 41 | }, 42 | { 43 | "time": 39470, 44 | "text": "優しい場所で一休みをしよう", 45 | "cn": "在这温柔的地方 稍微休息一下吧", 46 | "ro": "ya sa shi i ba sho de hi to ya su mi wo shi yo u" 47 | }, 48 | { 49 | "time": 43900, 50 | "text": "嗚呼涙が出るのはなんでかな", 51 | "cn": "啊啊 为什么泪水悄然滑落", 52 | "ro": "a a na mi da ga de ru no wa na n de ka na" 53 | }, 54 | { 55 | "time": 48300, 56 | "text": "感情のポケットに今日を隠す", 57 | "cn": "把今天藏在感情的口袋里", 58 | "ro": "ka n jo u no po ke tto ni kyo u wo ka ku su" 59 | }, 60 | { 61 | "time": 52750, 62 | "text": "", 63 | "interlude": true 64 | }, 65 | { 66 | "time": 53280, 67 | "text": "そしたらさ眠れるのかな", 68 | "cn": "这样做的话是否就能睡着呢", 69 | "ro": "so shi ta ra sa ne mu re ru no ka na" 70 | }, 71 | { 72 | "time": 57160, 73 | "text": "", 74 | "interlude": true 75 | }, 76 | { 77 | "time": 57200, 78 | "text": "今僕達は息をするだけ", 79 | "cn": "现在 我们只是 保持着呼吸而已", 80 | "ro": "i ma bo ku ta chi wa i ki wo su ru da ke" 81 | }, 82 | { 83 | "time": 61680, 84 | "text": "かざした言葉夜の泡となる", 85 | "cn": "曾经宣扬的话语 化作夜里的泡沫", 86 | "ro": "ka za shi ta ko to ba ya no a wa to na ru" 87 | }, 88 | { 89 | "time": 66090, 90 | "text": "でも泣いてても明日は来るの?", 91 | "cn": "可是一直哭泣 明天就会到来吗?", 92 | "ro": "de mo na i te te mo a shi ta wa ku ru no?" 93 | }, 94 | { 95 | "time": 70540, 96 | "text": "優しい場所を見つけられるまでまたね", 97 | "cn": "直到能找到温柔的地方为止 “再见吧”", 98 | "ro": "ya sa shi i ba sho wo mi tsu ke ra re ru ma de ma ta ne" 99 | }, 100 | { 101 | "time": 75920, 102 | "text": "", 103 | "interlude": true 104 | }, 105 | { 106 | "time": 92640, 107 | "text": "嗚呼どうやって歩いて征ける?", 108 | "cn": "啊啊该怎么做 才能继续前进?", 109 | "ro": "a a do u ya tte a ru i te yu ke ru?" 110 | }, 111 | { 112 | "time": 97170, 113 | "text": "分からないことだけ降りかかる", 114 | "cn": "尽是不懂的事情如雪层层堆积", 115 | "ro": "wa ka ra na i ko to da ke fu ri ka ka ru" 116 | }, 117 | { 118 | "time": 101610, 119 | "text": "でも叶うなら笑えるのなら", 120 | "cn": "可若愿望能实现 能露出笑容的话", 121 | "ro": "de mo ka na u na ra wa ra e ru no na ra" 122 | }, 123 | { 124 | "time": 106000, 125 | "text": "密かにそれを見つめて", 126 | "cn": "就偷偷注视着那些东西吧", 127 | "ro": "hi so ka ni so re wo mi tsu me te" 128 | }, 129 | { 130 | "time": 109790, 131 | "text": "", 132 | "interlude": true 133 | }, 134 | { 135 | "time": 110430, 136 | "text": "今僕達は息をするだけ", 137 | "cn": "现在 我们只是 保持着呼吸而已", 138 | "ro": "i ma bo ku ta chi wa i ki wo su ru da ke" 139 | }, 140 | { 141 | "time": 114940, 142 | "text": "かざした言葉夜の泡となる", 143 | "cn": "曾经宣扬的话语 化作夜里的泡沫", 144 | "ro": "ka za shi ta ko to ba ya no a wa to na ru" 145 | }, 146 | { 147 | "time": 119380, 148 | "text": "ただそうやって息をするだけ", 149 | "cn": "只是就这样 保持着呼吸而已", 150 | "ro": "ta da so u ya tte i ki wo su ru da ke" 151 | }, 152 | { 153 | "time": 123840, 154 | "text": "微かに灯る温もりの欠片", 155 | "cn": "发出淡淡的光芒 是那温暖的碎片", 156 | "ro": "ka su ka ni to mo ru nu ku mo ri no ka ke ra" 157 | }, 158 | { 159 | "time": 130730, 160 | "text": "", 161 | "interlude": true 162 | } 163 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,羽累 - Maple (feat. 羽累).json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 16910, 9 | "text": "もういいか 嗚呼もういいか", 10 | "cn": "算了吧 啊啊算了吧" 11 | }, 12 | { 13 | "time": 18598, 14 | "text": "優しい人が傷ついちゃうの", 15 | "cn": "总是温柔的人受伤" 16 | }, 17 | { 18 | "time": 20699, 19 | "text": "マイノリティ 私マイノリティ", 20 | "cn": "少数派 我是少数派" 21 | }, 22 | { 23 | "time": 22549, 24 | "text": "ココロお砂糖かけても苦いまま", 25 | "cn": "就算往心上涂抹砂糖 也依旧一样苦涩" 26 | }, 27 | { 28 | "time": 24577, 29 | "text": "", 30 | "interlude": true 31 | }, 32 | { 33 | "time": 24766, 34 | "text": "摩訶不思議 嗚呼摩訶不思議", 35 | "cn": "匪夷所思 啊啊实在匪夷所思" 36 | }, 37 | { 38 | "time": 26475, 39 | "text": "そう平々凡々憧れて", 40 | "cn": "别无奢求 只是那样平平淡淡地憧憬着" 41 | }, 42 | { 43 | "time": 28761, 44 | "text": "居場所がない あー居場所がない", 45 | "cn": "却没有容身之所 没有容身之所" 46 | }, 47 | { 48 | "time": 30620, 49 | "text": "この痛み溶かしてよ!", 50 | "cn": "请把这份痛楚溶解吧!" 51 | }, 52 | { 53 | "time": 32788, 54 | "text": "", 55 | "interlude": true 56 | }, 57 | { 58 | "time": 32909, 59 | "text": "逢うメロディー半透明だ", 60 | "cn": "遇见的旋律是半透明的颜色" 61 | }, 62 | { 63 | "time": 34840, 64 | "text": "くたくたな人生 なんだって", 65 | "cn": "筋疲力竭的人生 不管怎么做都依旧是" 66 | }, 67 | { 68 | "time": 36773, 69 | "text": "大の字で寝そべったまま", 70 | "cn": "四肢伸展躺平在地的模样" 71 | }, 72 | { 73 | "time": 38443, 74 | "text": "ねぇ このまま最終 Happy END?", 75 | "cn": "我说啊 这样就是最终的 Happy End 吗?" 76 | }, 77 | { 78 | "time": 40685, 79 | "text": "メープルの甘い香り", 80 | "cn": "枫糖的甜蜜香气" 81 | }, 82 | { 83 | "time": 42665, 84 | "text": "本当の気持ちを溶かしてさ", 85 | "cn": "溶解了真实的心情" 86 | }, 87 | { 88 | "time": 44769, 89 | "text": "数秒前から数秒後", 90 | "cn": "在几秒钟以后" 91 | }, 92 | { 93 | "time": 46770, 94 | "text": "形のない夜に包まれた", 95 | "cn": "就被无形的夜晚包裹其中" 96 | }, 97 | { 98 | "time": 49277, 99 | "text": "", 100 | "interlude": true 101 | }, 102 | { 103 | "time": 64972, 104 | "text": "なんだか 違うようだね", 105 | "cn": "总感觉 并不是这样的吧" 106 | }, 107 | { 108 | "time": 66629, 109 | "text": "だってさこの世界が", 110 | "cn": "因为这个世界" 111 | }, 112 | { 113 | "time": 68635, 114 | "text": "どこか綺麗だから", 115 | "cn": "似乎也有着美丽之处" 116 | }, 117 | { 118 | "time": 70296, 119 | "text": "嗚呼 摩訶不思議", 120 | "cn": "啊啊 真是不可思议" 121 | }, 122 | { 123 | "time": 72034, 124 | "text": "", 125 | "interlude": true 126 | }, 127 | { 128 | "time": 72078, 129 | "text": "だから", 130 | "cn": "所以" 131 | }, 132 | { 133 | "time": 72837, 134 | "text": "", 135 | "interlude": true 136 | }, 137 | { 138 | "time": 72899, 139 | "text": "逢うメロディー半透明だ", 140 | "cn": "遇见的旋律是半透明的颜色" 141 | }, 142 | { 143 | "time": 74623, 144 | "text": "くたくたな人生 なんだって", 145 | "cn": "筋疲力竭的人生 不管怎么做都依旧是" 146 | }, 147 | { 148 | "time": 76809, 149 | "text": "大の字で寝そべったまま", 150 | "cn": "四肢伸展躺平在地的模样" 151 | }, 152 | { 153 | "time": 78510, 154 | "text": "ねぇこのまま最終 Happy END?", 155 | "cn": "我说啊 这样就是最终的 Happy End 吗?" 156 | }, 157 | { 158 | "time": 80845, 159 | "text": "メープルの甘い香り", 160 | "cn": "枫糖的甜蜜香气" 161 | }, 162 | { 163 | "time": 82686, 164 | "text": "本当の気持ちを溶かしてさ", 165 | "cn": "溶解了真实的心情" 166 | }, 167 | { 168 | "time": 84846, 169 | "text": "数秒前から数秒後", 170 | "cn": "在几秒钟以后" 171 | }, 172 | { 173 | "time": 86637, 174 | "text": "形のない夜に包まれた", 175 | "cn": "就被无形的夜晚包裹其中" 176 | }, 177 | { 178 | "time": 88654, 179 | "text": "", 180 | "interlude": true 181 | }, 182 | { 183 | "time": 88716, 184 | "text": "Don't know", 185 | "cn": "Don't know" 186 | }, 187 | { 188 | "time": 89838, 189 | "text": "無い物ねだりのウソなら", 190 | "cn": "为强求不存在之物而撒下的谎言" 191 | }, 192 | { 193 | "time": 92682, 194 | "text": "Cry lie ココロに重すぎるからさ", 195 | "cn": "Cry lie 对心来说太过沉重" 196 | }, 197 | { 198 | "time": 95804, 199 | "text": "置いてって", 200 | "cn": "所以就把它放下吧" 201 | }, 202 | { 203 | "time": 96589, 204 | "text": "", 205 | "interlude": true 206 | }, 207 | { 208 | "time": 96899, 209 | "text": "メープルの甘い香り", 210 | "cn": "枫糖的甜蜜香气" 211 | }, 212 | { 213 | "time": 98648, 214 | "text": "本当の気持ちを溶かしてさ", 215 | "cn": "溶解了真实的心情" 216 | }, 217 | { 218 | "time": 100809, 219 | "text": "数秒前から数秒後", 220 | "cn": "在几秒钟以后" 221 | }, 222 | { 223 | "time": 102674, 224 | "text": "形のない夜に包まれた", 225 | "cn": "就被无形的夜晚包裹其中" 226 | } 227 | ] -------------------------------------------------------------------------------- /src/data/lyrics/MIMI,裏命 - ぽけっと・愛の歌.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 6540, 9 | "text": "浮かんだ言葉が 弾けて元に戻る", 10 | "ro": "u ka n da ko to ba ga ha ji ke te mo to ni mo do ru" 11 | }, 12 | { 13 | "time": 13900, 14 | "text": "消えたい僕らは 心に傷を作る", 15 | "ro": "ki e ta i bo ku ra wa ko ko ro ni ki zu wo tsu ku ru" 16 | }, 17 | { 18 | "time": 20500, 19 | "text": "どうしてつらいの 理由だけわからないの", 20 | "ro": "do u shi te tsu ra i no ri yu u da ke wa ka ra na i no" 21 | }, 22 | { 23 | "time": 27800, 24 | "text": "ごめんねおやすみ 隠れて布団の中", 25 | "ro": "go me n ne o ya su mi ka ku re te fu to n no na ka" 26 | }, 27 | { 28 | "time": 2840000, 29 | "text": "だってねだっ てねなんだって", 30 | "ro": "da tte ne da tsu te ne na n da tte" 31 | }, 32 | { 33 | "time": 38800, 34 | "text": "迷惑はかけた くないからさ", 35 | "ro": "me i wa ku wa ka ke ta ku na i ka ra sa" 36 | }, 37 | { 38 | "time": 42000, 39 | "text": "いつしか選んだ現実が", 40 | "ro": "i tsu shi ka e ra n da ge n ji tsu ga" 41 | }, 42 | { 43 | "time": 45200, 44 | "text": "どうにも僕には重すぎて", 45 | "ro": "do u ni mo bo ku ni wa o mo su gi te" 46 | }, 47 | { 48 | "time": 48600, 49 | "text": "でも今日も世界は綺麗だから", 50 | "ro": "de mo kyo u mo se ka i wa ki re i da ka ra" 51 | }, 52 | { 53 | "time": 52300, 54 | "text": "歌わせてよ ぽけっと・愛のうた", 55 | "ro": "u ta wa se te yo po ke tto . a i no u ta" 56 | }, 57 | { 58 | "time": 71500, 59 | "text": "形もなくして漂った", 60 | "ro": "ka ta chi mo na ku shi te ta da yo tta" 61 | }, 62 | { 63 | "time": 73700, 64 | "text": "こころこころ海を見つめて", 65 | "ro": "ko ko ro ko ko ro ka i wo mi tsu me te" 66 | }, 67 | { 68 | "time": 75500, 69 | "text": "手探り求めた今日の意味", 70 | "ro": "te sa gu ri mo to me ta kyo u no i mi" 71 | }, 72 | { 73 | "time": 80500, 74 | "text": "いつかいつか愛せるのかな", 75 | "ro": "i tsu ka i tsu ka a i se ru no ka na" 76 | }, 77 | { 78 | "time": 83500, 79 | "text": "過去から感じた寂しさも", 80 | "ro": "ka ko ka ra ka n ji ta sa bi shi sa mo" 81 | }, 82 | { 83 | "time": 87800, 84 | "text": "夜に煌めいたの三光年", 85 | "ro": "yo ru ni ki ra me i ta no sa n ko u ne n" 86 | }, 87 | { 88 | "time": 5460000, 89 | "text": "嗚呼今日も世界は綺麗だから", 90 | "ro": "a a kyo u mo se ka i wa ki re i da ka ra" 91 | }, 92 | { 93 | "time": 94200, 94 | "text": "歌わせてよ ぽけっと・愛のうた", 95 | "ro": "u ta wa se te yo po ke tto . a i no u ta" 96 | }, 97 | { 98 | "time": 97800, 99 | "text": "歌わせてよ ぽけっと・愛のうた", 100 | "ro": "u ta wa se te yo po ke tto . a i no u ta" 101 | } 102 | ] -------------------------------------------------------------------------------- /src/data/lyrics/tiktok_7243687154054974722.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 694, 4 | "text": "劣等生の脳内は", 5 | "cn": "我这坏学生的脑袋" 6 | }, 7 | { 8 | "time": 3149, 9 | "text": "少しの寂しさで埋まってんだ", 10 | "cn": "已经被星星点点的寂寞填满了啊" 11 | }, 12 | { 13 | "time": 6026, 14 | "text": "", 15 | "interlude": true 16 | }, 17 | { 18 | "time": 6164, 19 | "text": "ああ愛して今日くらい", 20 | "cn": "啊啊去爱吧至少今天" 21 | }, 22 | { 23 | "time": 8851, 24 | "text": "幸せの意味も知らないけどさ", 25 | "cn": "虽然连幸福的含义也不明白" 26 | }, 27 | { 28 | "time": 11844, 29 | "text": "妄想ばかりの現世に", 30 | "cn": "对这净是妄想的现世" 31 | }, 32 | { 33 | "time": 14437, 34 | "text": "精一杯抗っているんだ", 35 | "cn": "正在竭尽全力地反抗着啊" 36 | }, 37 | { 38 | "time": 17420, 39 | "text": "", 40 | "interlude": true 41 | }, 42 | { 43 | "time": 17525, 44 | "text": "ただああ愛して最後に", 45 | "cn": "只管啊啊去爱吧在最后" 46 | }, 47 | { 48 | "time": 20158, 49 | "text": "知る言葉は素敵なものですか", 50 | "cn": "知晓的话语会是美好的吗" 51 | }, 52 | { 53 | "time": 23177, 54 | "text": "想像してるの夜がさ", 55 | "cn": "在我的想象里夜晚啊" 56 | }, 57 | { 58 | "time": 25880, 59 | "text": "このまま僕の隣で", 60 | "cn": "就这样一直陪在我身边" 61 | }, 62 | { 63 | "time": 28374, 64 | "text": "", 65 | "interlude": true 66 | }, 67 | { 68 | "time": 28499, 69 | "text": "呜呼", 70 | "cn": "啊啊" 71 | } 72 | ] -------------------------------------------------------------------------------- /src/data/lyrics/tiktok_7243971426762411266.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 2198, 9 | "text": "劣等生の脳内は", 10 | "cn": "我这坏学生的脑袋" 11 | }, 12 | { 13 | "time": 4870, 14 | "text": "少しの寂しさで埋まってんだ", 15 | "cn": "已经被星星点点的寂寞填满了啊" 16 | }, 17 | { 18 | "time": 7872, 19 | "text": "", 20 | "interlude": true 21 | }, 22 | { 23 | "time": 8004, 24 | "text": "ああ愛して今日くらい", 25 | "cn": "啊啊去爱吧至少今天" 26 | }, 27 | { 28 | "time": 10582, 29 | "text": "幸せの意味も知らないけどさ", 30 | "cn": "虽然连幸福的含义也不明白" 31 | }, 32 | { 33 | "time": 13684, 34 | "text": "妄想ばかりの現世に", 35 | "cn": "对这净是妄想的现世" 36 | }, 37 | { 38 | "time": 16286, 39 | "text": "精一杯抗っているんだ", 40 | "cn": "正在竭尽全力地反抗着啊" 41 | }, 42 | { 43 | "time": 19116, 44 | "text": "", 45 | "interlude": true 46 | }, 47 | { 48 | "time": 19268, 49 | "text": "ただああ愛して最後に", 50 | "cn": "只管啊啊去爱吧在最后" 51 | }, 52 | { 53 | "time": 22090, 54 | "text": "知る言葉は素敵なものですか", 55 | "cn": "知晓的话语会是美好的吗" 56 | }, 57 | { 58 | "time": 25036, 59 | "text": "想像してるの夜がさ", 60 | "cn": "在我的想象里夜晚啊" 61 | }, 62 | { 63 | "time": 27737, 64 | "text": "このまま僕の隣で", 65 | "cn": "就这样一直陪在我身边" 66 | }, 67 | { 68 | "time": 30124, 69 | "text": "", 70 | "interlude": true 71 | }, 72 | { 73 | "time": 30266, 74 | "text": "呜呼", 75 | "cn": "啊啊" 76 | } 77 | ] -------------------------------------------------------------------------------- /src/data/lyrics/tiktok_7256023414228503810.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 1400, 4 | "text": "から から", 5 | "cn": "所以 所以" 6 | }, 7 | { 8 | "time": 4981, 9 | "text": "今 から から", 10 | "cn": "从现在 开始 开始" 11 | }, 12 | { 13 | "time": 8257, 14 | "text": "", 15 | "interlude": true 16 | }, 17 | { 18 | "time": 9412, 19 | "text": "遥か月を目指した", 20 | "cn": "把遥远的月亮作为目标" 21 | }, 22 | { 23 | "time": 17017, 24 | "text": "今日の空は", 25 | "cn": "而今天的天空" 26 | }, 27 | { 28 | "time": 19038, 29 | "text": "彼方", 30 | "cn": "朝着那边" 31 | }, 32 | { 33 | "time": 21525, 34 | "text": "西に流れた", 35 | "cn": "向着西面流去了" 36 | }, 37 | { 38 | "time": 26624, 39 | "text": "もう届かないや", 40 | "cn": "已经触及不到了呀" 41 | }, 42 | { 43 | "time": 32008, 44 | "text": "届かないや", 45 | "cn": "也传达不到了呀" 46 | } 47 | ] -------------------------------------------------------------------------------- /src/data/lyrics/tiktok_7256365983093280001.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "息する理由が欲しいだけ", 5 | "cn": "我只渴望一个呼吸的理由" 6 | }, 7 | { 8 | "time": 3198, 9 | "text": "今日に", 10 | "cn": "在今天" 11 | }, 12 | { 13 | "time": 3900, 14 | "text": "逃避行 泣きたい夜に", 15 | "cn": "逃避的旅程 在悲泣的夜晚" 16 | }, 17 | { 18 | "time": 6924, 19 | "text": "わたし 愛以上望まないから", 20 | "cn": "我不渴望超过爱情的何物" 21 | }, 22 | { 23 | "time": 10879, 24 | "text": "もうさ CRY しよう誰も見ちゃいない", 25 | "cn": "所以 让我们哭泣吧 没有人看见" 26 | }, 27 | { 28 | "time": 14712, 29 | "text": "それが綺麗な言葉になるよ", 30 | "cn": "这将成为美丽的话语" 31 | }, 32 | { 33 | "time": 18739, 34 | "text": "Oh Yeah", 35 | "cn": "Oh Yeah" 36 | } 37 | ] -------------------------------------------------------------------------------- /src/data/lyrics/tiktok_7261167927523691783.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 506, 4 | "text": "ただそこに居るだけで", 5 | "cn": "仅仅存在于那里" 6 | }, 7 | { 8 | "time": 5380, 9 | "text": "心の欠片柔らかくなる", 10 | "cn": "心灵的碎片变得柔软" 11 | }, 12 | { 13 | "time": 10218, 14 | "text": "微睡みの中そっと触れる", 15 | "cn": "在微睡中轻轻触摸" 16 | }, 17 | { 18 | "time": 14939, 19 | "text": "花舞い積もる景色の中で", 20 | "cn": "在花舞纷飞的景色中" 21 | }, 22 | { 23 | "time": 20261, 24 | "text": "手に取ると溶けてしまうの", 25 | "cn": "握在手中即融化消逝" 26 | }, 27 | { 28 | "time": 24647, 29 | "text": "言葉にすると消えてしまうの", 30 | "cn": "言语道出即消失无踪" 31 | }, 32 | { 33 | "time": 29948, 34 | "text": "不確かで曖味な", 35 | "cn": "不确定而含糊的" 36 | }, 37 | { 38 | "time": 34162, 39 | "text": "名も無い感情戸惑いながら", 40 | "cn": "无名且困惑的情感" 41 | }, 42 | { 43 | "time": 38988, 44 | "text": "", 45 | "interlude": true 46 | }, 47 | { 48 | "time": 41862, 49 | "text": "僕の心の隅に", 50 | "cn": "在我心灵的角落" 51 | }, 52 | { 53 | "time": 44923, 54 | "text": "ひっそり生まれて灯る気持ち", 55 | "cn": "悄然诞生的情感" 56 | }, 57 | { 58 | "time": 49578, 59 | "text": "伝えるすべもない けど", 60 | "cn": "只是我没有传达的方式" 61 | }, 62 | { 63 | "time": 55503, 64 | "text": "", 65 | "interlude": true 66 | }, 67 | { 68 | "time": 55785, 69 | "text": "ただそこに居るだけて", 70 | "cn": "仅仅存在于那里" 71 | }, 72 | { 73 | "time": 60471, 74 | "text": "心の欠片笑らかくなる", 75 | "cn": "心灵的碎片变得欢乐" 76 | }, 77 | { 78 | "time": 65178, 79 | "text": "微睡みの中そっと触れる", 80 | "cn": "在微睡中轻轻触摸" 81 | }, 82 | { 83 | "time": 69995, 84 | "text": "花舞い積もる景色の中で", 85 | "cn": "在花舞纷飞的景色中" 86 | }, 87 | { 88 | "time": 74948, 89 | "text": "", 90 | "interlude": true 91 | }, 92 | { 93 | "time": 78671, 94 | "text": "花舞い積もる景色の中で", 95 | "cn": "在花舞纷飞的景色中" 96 | } 97 | ] -------------------------------------------------------------------------------- /src/data/lyrics/むト - silence.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": 0, 4 | "text": "", 5 | "interlude": true 6 | }, 7 | { 8 | "time": 20998, 9 | "text": "何も無い僕らは歩く寂しさだけ心に秘めて", 10 | "cn": "一无所有的我们仍是步履不停 唯有将寂寞藏于心中", 11 | "ro": "na ni mo na i bo ku ra wa a ru ku sa bi shi sa da ke ko ko ro ni hi me te" 12 | }, 13 | { 14 | "time": 28707, 15 | "text": "どうにかさ構わないでよ", 16 | "cn": "别再管我了 反正车到山前必有路", 17 | "ro": "do u ni ka sa ka ma wa na i de yo" 18 | }, 19 | { 20 | "time": 32424, 21 | "text": "鳴呼 震える声でまた", 22 | "cn": "啊啊 又一次颤声着", 23 | "ro": "na ru ko fu ru e ru ko e de ma ta" 24 | }, 25 | { 26 | "time": 36090, 27 | "text": "回り出す雪の降る世界へと", 28 | "cn": "向着大雪纷飞的世界 兜兜转转 向前迈步", 29 | "ro": "ma wa ri da su yu ki no fu ru se ka i e to" 30 | }, 31 | { 32 | "time": 40619, 33 | "text": "鳴呼", 34 | "cn": "啊啊", 35 | "ro": "na ru ko" 36 | }, 37 | { 38 | "time": 41146, 39 | "text": "泣いちゃうような辛さだって", 40 | "cn": "但愿即使是令人落泪的钻心疼痛", 41 | "ro": "na i cha u yo u na tsu ra sa da tte" 42 | }, 43 | { 44 | "time": 44546, 45 | "text": "いつか夢の奥できっと笑える様に", 46 | "cn": "总有一天也定能在梦境深处绽放笑容", 47 | "ro": "i tsu ka yu me no o ku de ki tto wa ra e ru yo u ni" 48 | }, 49 | { 50 | "time": 49073, 51 | "text": "どうしたら僕は今日を許してまた季節へと", 52 | "cn": "要该怎样 我才能与当下和解", 53 | "ro": "do u shi ta ra bo ku wa kyo u wo yu ru shi te ma ta ki se tsu e to" 54 | }, 55 | { 56 | "time": 55129, 57 | "text": "溶け込めるかなって", 58 | "cn": "再次溶解进四季中去呢", 59 | "ro": "to ke ko me ru ka na tte" 60 | }, 61 | { 62 | "time": 65434, 63 | "text": "「ひとりきり。闇は過ぎ去る」", 64 | "cn": "「孤身一人。唯有黑暗从身边掠过」", 65 | "ro": "「hi to ri ki ri。ya mi wa su gi sa ru」" 66 | }, 67 | { 68 | "time": 68891, 69 | "text": "って思っても今日は辛いまんま", 70 | "cn": "就算我这样想着 今天也仍是精疲力竭", 71 | "ro": "tte o mo tte mo kyo u wa tsu ra i ma n ma" 72 | }, 73 | { 74 | "time": 73425, 75 | "text": "空回りで回る僕をどうか貴方が見つけておくれ", 76 | "cn": "只愿 你能找到 不断在原地做着无用功的我", 77 | "ro": "ka ra ma wa ri de ma wa ru bo ku wo do u ka a na ta ga mi tsu ke te o ku re" 78 | }, 79 | { 80 | "time": 80869, 81 | "text": "鳴呼", 82 | "cn": "啊啊", 83 | "ro": "na ru ko" 84 | }, 85 | { 86 | "time": 97596, 87 | "text": "「今日もまた何もできない」", 88 | "cn": "「今天也仍是一事无成」", 89 | "ro": "「kyo u mo ma ta na ni mo de ki na i」" 90 | }, 91 | { 92 | "time": 101042, 93 | "text": "って考えるの すら辞めたんだ", 94 | "cn": "就连这种想法 也已不再浮现于脑海之中", 95 | "ro": "tte ka n ga e ru no su ra ya me ta n da" 96 | }, 97 | { 98 | "time": 105287, 99 | "text": "いつか感情浮かぶように", 100 | "cn": "就好像终有一日会有感情浮现上来一般", 101 | "ro": "i tsu ka ka n jo u u ka bu yo u ni" 102 | }, 103 | { 104 | "time": 109015, 105 | "text": "ただ歌いつづけるだけ", 106 | "cn": "仅是 不断地歌唱着", 107 | "ro": "ta da u ta i tsu zu ke ru da ke" 108 | }, 109 | { 110 | "time": 113261, 111 | "text": "嗚呼", 112 | "cn": "啊啊", 113 | "ro": "a a" 114 | }, 115 | { 116 | "time": 113785, 117 | "text": "泣いちゃうような辛さだって", 118 | "cn": "但愿即使是令人落泪的钻心疼痛", 119 | "ro": "na i cha u yo u na tsu ra sa da tte" 120 | }, 121 | { 122 | "time": 117243, 123 | "text": "いつか夢の奥できっと笑えるように", 124 | "cn": "总有一天也定能在梦境深处绽放笑容", 125 | "ro": "i tsu ka yu me no o ku de ki tto wa ra e ru yo u ni" 126 | }, 127 | { 128 | "time": 121767, 129 | "text": "そうやって僕は今日を許してまた季節へと", 130 | "cn": "就这样 我与当下和解", 131 | "ro": "so u ya tte bo ku wa kyo u wo yu ru shi te ma ta ki se tsu e to" 132 | }, 133 | { 134 | "time": 127879, 135 | "text": "染まり続けるだけ", 136 | "cn": "再次遍染上四季之色", 137 | "ro": "so ma ri tsu zu ke ru da ke" 138 | } 139 | ] -------------------------------------------------------------------------------- /src/data/mirrored.json: -------------------------------------------------------------------------------- 1 | [ 2 | "SBlkzGiM5uE", 3 | "qivTJhNbqUc", 4 | "lnfYoNLrMJE", 5 | "AfteRl4ePBc", 6 | "w4fxj1toPzc", 7 | "QJaY60vjSxw", 8 | "rzamOqbbBfQ", 9 | "qtuX4cHk-vE", 10 | "fztKqreP1pk", 11 | "EGJR43xgFgQ", 12 | "Q5XzviXSHQ4", 13 | "pcRaY5kq4YY", 14 | "89p7DWIqOu8", 15 | "UGzd2dnkhME", 16 | "ZTFQs7MspEI", 17 | "Txh4DZmcbPk", 18 | "Ziz_ckzVjyA", 19 | "Ou8sl4s3NJg", 20 | "r5xaccIl1Ps", 21 | "n6GVaJL_eeY", 22 | "06YWg6Y1kxo", 23 | "tdhKalRg5OM", 24 | "P_DgrvZmXM0", 25 | "PJrjwIlWVXA", 26 | "vLigCJOcHOE", 27 | "o6vZFvmqKp8", 28 | "tI4nhL7qQdk", 29 | "jTLY-9A312c", 30 | "exo3XdtPrgs", 31 | "5GUaMOpfmr8", 32 | "0bEmLE7JrQQ", 33 | "8skrutHDrEw", 34 | "f6TytcA47rI", 35 | "yIo2ePCm4bY", 36 | "kb2ny0jSDiE", 37 | "sFoWYa6QNKk", 38 | "ZjK3la923VY", 39 | "XiKZE967BD4", 40 | "ZAJ3nfQTw4A", 41 | "1gSMjPLRJik", 42 | "vULemZ6DhM8", 43 | "QjMCTZxkiNs", 44 | "S2AhFrGXa8I", 45 | "_eBe5rD73Eg", 46 | "sVqtVjtRcN8", 47 | "HyQK7IssB9M", 48 | "7xht3kQO_TM", 49 | "WCo57WjcoJ8", 50 | "eurJC5ElRPE", 51 | "M50V-UBjqcI", 52 | "340OXvocRMM", 53 | "o2CD3DjPHmU", 54 | "E3manfqTYrw", 55 | "filTeBL7mA0", 56 | "UviUfFaQ-As", 57 | "av63OvhtmJ4", 58 | "_XeE2aEiPHk", 59 | "UZTcXWLf2Ek", 60 | "XveFDEbTo0E", 61 | "5gIf0_xpFPI", 62 | "hupkoU8e1is", 63 | "e0Py8XOcAwU", 64 | "70LogEQ3QLY", 65 | "ioW9iGDpQyw", 66 | "cGGlBYzZiJs", 67 | "SrdCXBZs4j8", 68 | "ArqagB9P1Qs", 69 | "zQqIm-EVlkQ", 70 | "YXCBQDK4DlE", 71 | "xgNeFTCzpgo", 72 | "OQd8pMuxxtM", 73 | "NihQBHOZLIw", 74 | "FXNUn2KzcE8", 75 | "6_Xd3eRSk6s", 76 | "h1QTkwJnX0E", 77 | "PAwZl3Up-hc", 78 | "wMaergT5JX4", 79 | "tW6pTE_ENeo", 80 | "Rpe5gphy3Dg", 81 | "fjnUIiHqVtM", 82 | "CHP7xCe8454", 83 | "spoQeZea7s8", 84 | "56Na2tuPOXs", 85 | "7TahNEdsKdg", 86 | "VRhZgfFOvZQ", 87 | "w3S9o1kSpqE", 88 | "5Zz_00sjwW0", 89 | "l5h5-ra3gqM" 90 | ] -------------------------------------------------------------------------------- /src/hooks/useAsyncCachedFetch.js: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useLayoutEffect } from 'react'; 2 | function useAsyncCachedFetch(url) { 3 | if (url === undefined || url === '') { 4 | url = null; 5 | } 6 | 7 | const currentUrlRef = useRef(null); 8 | 9 | if (!window.fetchCache) { 10 | window.fetchCache = { 11 | null: { 12 | status: 'fetched', 13 | content: null 14 | } 15 | }; 16 | } 17 | 18 | const [content, setContent] = useState(null); 19 | const [status, setStatus] = useState('fetched'); // 'pending' | 'fetched' | 'error' 20 | 21 | useLayoutEffect(() => { 22 | currentUrlRef.current = url; 23 | }, [url]); 24 | 25 | useLayoutEffect(() => { 26 | if (window.fetchCache[url]?.status === 'fetched') { 27 | setContent(window.fetchCache[url].content); 28 | setStatus('fetched'); 29 | return; 30 | } else if (window.fetchCache[url]?.status === 'pending') { 31 | const listener = (e) => { 32 | //console.log("received", e.detail.url, currentUrlRef.current); 33 | if (e.detail.url === currentUrlRef.current) { 34 | setContent(window.fetchCache[url].content); 35 | setStatus('fetched'); 36 | //console.log('fetchCacheReady', e.detail.url); 37 | window.removeEventListener('fetchCacheReady', listener); 38 | } 39 | } 40 | setStatus('pending'); 41 | //console.log('asdf', url); 42 | window.addEventListener('fetchCacheReady', listener); 43 | return; 44 | } 45 | async function load() { 46 | window.fetchCache[url] = { 47 | status: 'pending', 48 | content: null 49 | } 50 | const result = await fetch(url).then(res => res.json()).catch(err => { 51 | window.fetchCache[url].status = 'error'; 52 | window.fetchCache[url].content = null; 53 | window.fetchCache[url].error = err; 54 | setContent(null); 55 | setStatus('error'); 56 | return null; 57 | }); 58 | window.fetchCache[url].content = result; 59 | window.fetchCache[url].status = 'fetched'; 60 | if (currentUrlRef.current === url) { 61 | setContent(result); 62 | setStatus('fetched'); 63 | } 64 | //console.log('111111111', url); 65 | window.dispatchEvent(new CustomEvent('fetchCacheReady', { detail: { url } })); 66 | } 67 | setStatus('pending'); 68 | //console.log('qwer', url); 69 | load(); 70 | }, [url]); 71 | 72 | return [content, status]; 73 | } 74 | 75 | export default useAsyncCachedFetch; -------------------------------------------------------------------------------- /src/hooks/useRefState.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react'; 2 | const useRefState = (initialState) => { 3 | let [state, _setState] = useState(initialState); 4 | const stateRef = useRef(state); 5 | const setState = (data) => { 6 | stateRef.current = data; 7 | _setState(data); 8 | }; 9 | return [state, stateRef, setState]; 10 | } 11 | export default useRefState; -------------------------------------------------------------------------------- /src/hooks/useRefStateStorage.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react'; 2 | const useRefStateStorage = (defaultState, key) => { 3 | const [state, setState] = useState(() => { 4 | const storageState = localStorage.getItem(key); 5 | return storageState ? JSON.parse(storageState) : defaultState; 6 | }); 7 | const stateRef = useRef(state); 8 | useEffect(() => { 9 | stateRef.current = state; 10 | localStorage.setItem(key, JSON.stringify(stateRef.current)); 11 | }, [state]); 12 | return [state, stateRef, setState]; 13 | } 14 | export default useRefStateStorage; -------------------------------------------------------------------------------- /src/hooks/useScrolled.js: -------------------------------------------------------------------------------- 1 | import { useSyncExternalStore } from 'react'; 2 | 3 | function useScrolled(parent = document.documentElement) { 4 | const scrolled = useSyncExternalStore(subscribe(parent), getSnapshot(parent)); 5 | return scrolled; 6 | } 7 | 8 | function subscribe(parent) { 9 | return (callback) => { 10 | window.addEventListener('scroll', callback); 11 | parent.addEventListener('scroll', callback); 12 | window.addEventListener('resize', callback); 13 | return () => { 14 | parent.removeEventListener('scroll', callback); 15 | window.removeEventListener('scroll', callback); 16 | window.removeEventListener('resize', callback); 17 | }; 18 | } 19 | } 20 | 21 | function getSnapshot(parent) { 22 | return () => { 23 | return parent.scrollTop > 0; 24 | } 25 | } 26 | 27 | export default useScrolled; -------------------------------------------------------------------------------- /src/hooks/useShuffleDeque.js: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import { shuffle } from '../utils.js' 3 | 4 | function useShuffleDeque(initList, initDeque) { 5 | const currentIndexRef = useRef(initDeque.length ? 0 : -1); 6 | const dequeRef = useRef(initDeque); 7 | const list = useRef(initList); 8 | 9 | const setList = (newList, initDeque = []) => { 10 | list.current = newList; 11 | dequeRef.current = initDeque; 12 | if (initDeque.length) { 13 | currentIndexRef.current = 0; 14 | } else { 15 | currentIndexRef.current = -1; 16 | } 17 | } 18 | const next = () => { 19 | if (list.current.length === 0) { 20 | return null; 21 | } 22 | console.log(currentIndexRef.current, currentIndexRef.current + 1, JSON.parse(JSON.stringify(dequeRef.current))); 23 | currentIndexRef.current++; 24 | if (currentIndexRef.current >= dequeRef.current.length) { 25 | const append = shuffle(list.current); 26 | if (dequeRef.current.length && append[0] === dequeRef.current[dequeRef.current.length - 1]) { 27 | append.reverse(); 28 | } 29 | dequeRef.current = dequeRef.current.concat(append); 30 | console.log(JSON.parse(JSON.stringify(dequeRef.current)), dequeRef.current[currentIndexRef.current]); 31 | } 32 | return dequeRef.current[currentIndexRef.current]; 33 | } 34 | const prev = () => { 35 | if (list.current.length === 0) { 36 | return null; 37 | } 38 | currentIndexRef.current--; 39 | if (currentIndexRef.current < 0) { 40 | currentIndexRef.current += list.current.length; 41 | const prepend = shuffle(list.current); 42 | if (dequeRef.current.length &&prepend[prepend.length - 1] === dequeRef.current[0]) { 43 | prepend.reverse(); 44 | } 45 | dequeRef.current = prepend.concat(dequeRef.current); 46 | } 47 | return dequeRef.current[currentIndexRef.current]; 48 | } 49 | return [prev, next, setList]; 50 | } 51 | 52 | export default useShuffleDeque; -------------------------------------------------------------------------------- /src/hooks/useStateStorage.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | const useStateStorage = (defaultState, key) => { 3 | const [state, setState] = useState(() => { 4 | const storageState = localStorage.getItem(key); 5 | return storageState ? JSON.parse(storageState) : defaultState; 6 | }); 7 | useEffect(() => { 8 | localStorage.setItem(key, JSON.stringify(state)); 9 | }, [state]); 10 | return [state, setState]; 11 | } 12 | export default useStateStorage; -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | 8 | font-synthesis: none; 9 | text-rendering: optimizeLegibility; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | -webkit-text-size-adjust: 100%; 13 | padding: 0; 14 | margin: 0; 15 | overflow: hidden; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | 20 | body { 21 | color: var(--md-sys-color-on-background); 22 | background-color: var(--md-sys-color-background); 23 | padding: 0; 24 | margin: 0; 25 | width: 100%; 26 | height: 100%; 27 | } 28 | 29 | a { 30 | font-weight: 500; 31 | //color: #646cff; 32 | text-decoration: inherit; 33 | } 34 | 35 | a:hover { 36 | //color: #535bf2; 37 | } 38 | 39 | body { 40 | margin: 0; 41 | display: flex; 42 | width: 100%; 43 | height: 100%; 44 | } 45 | 46 | h1 { 47 | font-size: 3.2em; 48 | line-height: 1.1; 49 | } 50 | 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | 55 | button:focus, 56 | button:focus-visible { 57 | outline: 4px auto -webkit-focus-ring-color; 58 | } 59 | 60 | @media (prefers-color-scheme: light) { 61 | :root { 62 | color: #213547; 63 | background-color: #ffffff; 64 | } 65 | 66 | a:hover { 67 | color: #747bff; 68 | } 69 | 70 | button { 71 | background-color: #f9f9f9; 72 | } 73 | } 74 | 75 | * { 76 | box-sizing: border-box; 77 | -webkit-tap-highlight-color: transparent; 78 | } -------------------------------------------------------------------------------- /src/utils/applyThemeTokensToElement.js: -------------------------------------------------------------------------------- 1 | const applyThemeTokensToElement = (cssTokens, element = null) => { 2 | if (!element) element = document.documentElement; 3 | cssTokens.forEach(({token, rgb: [r, g, b]}) => { 4 | document.documentElement.style.setProperty(token, `rgb(${r}, ${g}, ${b})`); 5 | document.documentElement.style.setProperty(`${token}-rgb`, `${r}, ${g}, ${b}`); 6 | }); 7 | } 8 | 9 | export default applyThemeTokensToElement -------------------------------------------------------------------------------- /src/utils/calcThemeTokens.js: -------------------------------------------------------------------------------- 1 | import { Hct } from "@material/material-color-utilities"; 2 | 3 | const mdTokens = [ 4 | // ['token', 'palette', 'light_tone', 'dark_tone'], 5 | ['primary', 'primary', 40, 80], 6 | ['primary-container', 'primary', 90, 30], 7 | ['on-primary', 'primary', 100, 20], 8 | ['on-primary-container', 'primary', 10, 90], 9 | ['inverse-primary', 'primary', 80, 40], 10 | ['secondary', 'secondary', 40, 80], 11 | ['secondary-container', 'secondary', 90, 30], 12 | ['on-secondary', 'secondary', 100, 20], 13 | ['on-secondary-container', 'secondary', 10, 90], 14 | ['tertiary', 'tertiary', 40, 80], 15 | ['tertiary-container', 'tertiary', 90, 30], 16 | ['on-tertiary', 'tertiary', 100, 20], 17 | ['on-tertiary-container', 'tertiary', 10, 90], 18 | ['surface', 'neutral', 98, 6], 19 | ['surface-dim', 'neutral', 87, 6], 20 | ['surface-bright', 'neutral', 98, 24], 21 | ['surface-container-lowest', 'neutral', 100, 4], 22 | ['surface-container-low', 'neutral', 96, 10], 23 | ['surface-container', 'neutral', 94, 12], 24 | ['surface-container-high', 'neutral', 92, 17], 25 | ['surface-container-highest', 'neutral', 90, 22], 26 | ['surface-variant', 'neutralVariant', 90, 30], 27 | ['on-surface', 'neutral', 10, 90], 28 | ['on-surface-variant', 'neutralVariant', 30, 80], 29 | ['inverse-surface', 'neutral', 20, 90], 30 | ['inverse-on-surface', 'neutral', 95, 20], 31 | ['background', 'neutral', 98, 6], 32 | ['on-background', 'neutral', 10, 90], 33 | ['error', 'error', 40, 80], 34 | ['error-container', 'error', 90, 30], 35 | ['on-error', 'error', 100, 20], 36 | ['on-error-container', 'error', 10, 90], 37 | ['outline', 'neutralVariant', 50, 60], 38 | ['outline-variant', 'neutralVariant', 80, 30], 39 | ['shadow', 'neutral', 0, 0, true], 40 | ['surface-tint-color', 'primary', 40, 80], 41 | ['scrim', 'neutral', 0, 0] 42 | ] 43 | const intToRGB = (int) => [int >> 24 & 0xFF, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF]; 44 | function calcThemeTokens(theme, dark = false) { 45 | return mdTokens.map(([token, palette, lightTone, DarkTone, rgbVariable = false]) => { 46 | const {hue, chroma} = theme.palettes[palette]; 47 | const tone = dark ? DarkTone : lightTone; 48 | const int = Hct.from(hue, chroma, tone).toInt(); 49 | const [a, r, g, b] = intToRGB(int); 50 | return { 51 | token: `--md-sys-color-${token}`, 52 | rgb: [r, g, b], 53 | }; 54 | }).concat(...theme?.customColors?.map((item) => { 55 | const tmp = []; 56 | const dict = item[dark ? 'dark' : 'light']; 57 | for (const key in dict) { 58 | const [a, r, g, b] = intToRGB(dict[key]); 59 | tmp.push({ 60 | token: `--md-custom-color-${item.color.name}-${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`, 61 | rgb: [r, g, b] 62 | }); 63 | } 64 | return tmp; 65 | }) ?? []); 66 | } 67 | 68 | export default calcThemeTokens; -------------------------------------------------------------------------------- /src/utils/colorToInt.js: -------------------------------------------------------------------------------- 1 | import { argbFromHex } from '@material/material-color-utilities'; 2 | 3 | function colorToInt(color) { 4 | if (typeof color === 'string') { 5 | return argbFromHex(color); 6 | } else if (typeof color === 'number') { 7 | return color; 8 | } else { 9 | throw new Error('Invalid color type'); 10 | } 11 | } 12 | 13 | export default colorToInt; -------------------------------------------------------------------------------- /src/utils/getCloestScrollableParent.js: -------------------------------------------------------------------------------- 1 | import isScrollable from './isScrollable.js'; 2 | 3 | function getCloestScrollableParent(element) { 4 | if (!element) { 5 | return null; 6 | } 7 | const parent = element.parentElement; 8 | if (parent === document.documentElement || isScrollable(parent)) { 9 | return parent; 10 | } 11 | return getCloestScrollableParent(parent); 12 | } 13 | 14 | export default getCloestScrollableParent; -------------------------------------------------------------------------------- /src/utils/getInViewPosition.js: -------------------------------------------------------------------------------- 1 | function getInViewPosition(element, parentElement) { 2 | const parentElementRect = (parentElement === document.documentElement) ? { 3 | top: 0, 4 | left: 0 5 | } : parentElement.getBoundingClientRect(); 6 | const elementRect = element.getBoundingClientRect(); 7 | 8 | return { 9 | top: elementRect.top - parentElementRect.top, 10 | left: elementRect.left - parentElementRect.left 11 | } 12 | } 13 | 14 | export default getInViewPosition; -------------------------------------------------------------------------------- /src/utils/getRelativePosition.js: -------------------------------------------------------------------------------- 1 | function getRelativePosition(element, parentElement) { 2 | const parentElementRect = parentElement.getBoundingClientRect(); 3 | const elementRect = element.getBoundingClientRect(); 4 | 5 | return { 6 | top: elementRect.top - parentElementRect.top + parentElement.scrollTop, 7 | left: elementRect.left - parentElementRect.left + parentElement.scrollLeft 8 | } 9 | } 10 | 11 | export default getRelativePosition; -------------------------------------------------------------------------------- /src/utils/isScrollable.js: -------------------------------------------------------------------------------- 1 | function isScrollable(element) { 2 | const computedStyle = window.getComputedStyle(element); 3 | return computedStyle.overflowY === 'auto' || computedStyle.overflowY === 'scroll'; 4 | } 5 | 6 | export default isScrollable; -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { blurHash } from 'vite-plugin-blurhash-sharp-fix-fork' 2 | import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'; 3 | import { defineConfig } from 'vite' 4 | import react from '@vitejs/plugin-react-swc' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | react(), 10 | blurHash({ 11 | imageDir: '/src/data/covers', 12 | mapPath: '/blurhash-map.json' 13 | //mapPath: undefined 14 | }), 15 | ViteImageOptimizer({ 16 | test: /\.(jpe?g|png|gif|tiff|webp|avif)$/i, 17 | png: { 18 | quality: 80, 19 | }, 20 | jpg: { 21 | quality: 80, 22 | }, 23 | }), 24 | ], 25 | }) 26 | --------------------------------------------------------------------------------