Home.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257
  1. <template>
  2. <div>
  3. <page-metadata title="Home" />
  4. <div class="app">
  5. <main-header
  6. :hide-logo="true"
  7. :transparent="true"
  8. :hide-logged-out="true"
  9. />
  10. <div class="header" :class="{ loggedIn }">
  11. <img class="background" src="/assets/homebg.jpeg" />
  12. <div class="overlay"></div>
  13. <div class="content-container">
  14. <div class="content">
  15. <img
  16. v-if="siteSettings.sitename === 'Musare'"
  17. :src="siteSettings.logo_white"
  18. :alt="siteSettings.sitename || `Musare`"
  19. class="logo"
  20. />
  21. <span v-else class="logo">{{
  22. siteSettings.sitename
  23. }}</span>
  24. <div v-if="!loggedIn" class="buttons">
  25. <button
  26. class="button login"
  27. @click="openModal('login')"
  28. >
  29. Login
  30. </button>
  31. <button
  32. class="button register"
  33. @click="openModal('register')"
  34. >
  35. Register
  36. </button>
  37. </div>
  38. </div>
  39. </div>
  40. </div>
  41. <div class="group" v-show="favoriteStations.length > 0">
  42. <div class="group-title">
  43. <div>
  44. <h2>My Favorites</h2>
  45. </div>
  46. </div>
  47. <draggable
  48. item-key="_id"
  49. v-model="favoriteStations"
  50. v-bind="dragOptions"
  51. @change="changeFavoriteOrder"
  52. >
  53. <template #item="{ element }">
  54. <router-link
  55. :to="{
  56. name: 'station',
  57. params: { id: element.name }
  58. }"
  59. :class="{
  60. card: true,
  61. 'station-card': true,
  62. 'item-draggable': true,
  63. isPrivate: element.privacy === 'private',
  64. isMine: isOwner(element)
  65. }"
  66. :style="
  67. '--primary-color: var(--' + element.theme + ')'
  68. "
  69. >
  70. <song-thumbnail
  71. class="card-image"
  72. :song="element.currentSong"
  73. />
  74. <div class="card-content">
  75. <div class="media">
  76. <div class="media-left displayName">
  77. <i
  78. v-if="
  79. loggedIn && !element.isFavorited
  80. "
  81. @click.prevent="
  82. favoriteStation(element._id)
  83. "
  84. class="favorite material-icons"
  85. content="Favorite Station"
  86. v-tippy
  87. >star_border</i
  88. >
  89. <i
  90. v-if="
  91. loggedIn && element.isFavorited
  92. "
  93. @click.prevent="
  94. unfavoriteStation(element._id)
  95. "
  96. class="favorite material-icons"
  97. content="Unfavorite Station"
  98. v-tippy
  99. >star</i
  100. >
  101. <h5>{{ element.displayName }}</h5>
  102. <i
  103. v-if="element.type === 'official'"
  104. class="
  105. material-icons
  106. verified-station
  107. "
  108. content="Verified Station"
  109. v-tippy="{
  110. theme: 'info'
  111. }"
  112. >
  113. check_circle
  114. </i>
  115. </div>
  116. </div>
  117. <div class="content">
  118. {{ element.description }}
  119. </div>
  120. <div class="under-content">
  121. <p class="hostedBy">
  122. Hosted by
  123. <span class="host">
  124. <span
  125. v-if="
  126. element.type === 'official'
  127. "
  128. title="Musare"
  129. >Musare</span
  130. >
  131. <user-id-to-username
  132. v-else
  133. :user-id="element.owner"
  134. :link="true"
  135. />
  136. </span>
  137. </p>
  138. <div class="icons">
  139. <i
  140. v-if="
  141. element.type === 'community' &&
  142. isOwner(element)
  143. "
  144. class="homeIcon material-icons"
  145. content="This is your station."
  146. v-tippy="{ theme: 'info' }"
  147. >home</i
  148. >
  149. <i
  150. v-if="element.privacy === 'private'"
  151. class="privateIcon material-icons"
  152. content="This station is not visible to other users."
  153. v-tippy="{ theme: 'info' }"
  154. >lock</i
  155. >
  156. <i
  157. v-if="
  158. element.privacy === 'unlisted'
  159. "
  160. class="unlistedIcon material-icons"
  161. content="Unlisted Station"
  162. v-tippy="{ theme: 'info' }"
  163. >link</i
  164. >
  165. </div>
  166. </div>
  167. </div>
  168. <div class="bottomBar">
  169. <i
  170. v-if="
  171. element.paused &&
  172. element.currentSong.title
  173. "
  174. class="material-icons"
  175. content="Station Paused"
  176. v-tippy="{ theme: 'info' }"
  177. >pause</i
  178. >
  179. <i
  180. v-else-if="element.currentSong.title"
  181. class="material-icons"
  182. >music_note</i
  183. >
  184. <i v-else class="material-icons">music_off</i>
  185. <span
  186. v-if="element.currentSong.title"
  187. class="songTitle"
  188. :title="
  189. element.currentSong.artists.length > 0
  190. ? 'Now Playing: ' +
  191. element.currentSong.title +
  192. ' by ' +
  193. element.currentSong.artists.join(
  194. ', '
  195. )
  196. : 'Now Playing: ' +
  197. element.currentSong.title
  198. "
  199. >{{ element.currentSong.title }}
  200. {{
  201. element.currentSong.artists.length > 0
  202. ? " by " +
  203. element.currentSong.artists.join(
  204. ", "
  205. )
  206. : ""
  207. }}</span
  208. >
  209. <span v-else class="songTitle"
  210. >No Songs Playing</span
  211. >
  212. <i
  213. class="material-icons stationMode"
  214. :content="
  215. element.partyMode
  216. ? 'Station in Party mode'
  217. : 'Station in Playlist mode'
  218. "
  219. v-tippy="{ theme: 'info' }"
  220. >{{
  221. element.partyMode
  222. ? "emoji_people"
  223. : "playlist_play"
  224. }}</i
  225. >
  226. </div>
  227. </router-link>
  228. </template>
  229. </draggable>
  230. </div>
  231. <div class="group bottom">
  232. <div class="group-title">
  233. <div>
  234. <h1>Stations</h1>
  235. </div>
  236. </div>
  237. <a
  238. v-if="loggedIn"
  239. @click="openModal('createCommunityStation')"
  240. class="card station-card createStation"
  241. >
  242. <div class="card-image">
  243. <figure class="image is-square">
  244. <i class="material-icons">radio</i>
  245. </figure>
  246. </div>
  247. <div class="card-content">
  248. <div class="media">
  249. <div class="media-left displayName">
  250. <h5>Create Station</h5>
  251. </div>
  252. </div>
  253. <div class="content">
  254. Click here to create your own station!
  255. </div>
  256. </div>
  257. <div class="bottomBar"></div>
  258. </a>
  259. <a
  260. v-else
  261. @click="openModal('login')"
  262. class="card station-card createStation"
  263. >
  264. <div class="card-image">
  265. <figure class="image is-square">
  266. <i class="material-icons">radio</i>
  267. </figure>
  268. </div>
  269. <div class="card-content">
  270. <div class="media">
  271. <div class="media-left displayName">
  272. <h5>Create Station</h5>
  273. </div>
  274. </div>
  275. <div class="content">Login to create a station!</div>
  276. </div>
  277. <div class="bottomBar"></div>
  278. </a>
  279. <router-link
  280. v-for="station in filteredStations"
  281. :key="station._id"
  282. :to="{
  283. name: 'station',
  284. params: { id: station.name }
  285. }"
  286. class="card station-card"
  287. :class="{
  288. isPrivate: station.privacy === 'private',
  289. isMine: isOwner(station)
  290. }"
  291. :style="'--primary-color: var(--' + station.theme + ')'"
  292. >
  293. <song-thumbnail
  294. class="card-image"
  295. :song="station.currentSong"
  296. />
  297. <div class="card-content">
  298. <div class="media">
  299. <div class="media-left displayName">
  300. <i
  301. v-if="loggedIn && !station.isFavorited"
  302. @click.prevent="
  303. favoriteStation(station._id)
  304. "
  305. class="favorite material-icons"
  306. content="Favorite Station"
  307. v-tippy
  308. >star_border</i
  309. >
  310. <i
  311. v-if="loggedIn && station.isFavorited"
  312. @click.prevent="
  313. unfavoriteStation(station._id)
  314. "
  315. class="favorite material-icons"
  316. content="Unfavorite Station"
  317. v-tippy
  318. >star</i
  319. >
  320. <h5>{{ station.displayName }}</h5>
  321. <i
  322. v-if="station.type === 'official'"
  323. class="material-icons verified-station"
  324. content="Verified Station"
  325. v-tippy="{ theme: 'info' }"
  326. >
  327. check_circle
  328. </i>
  329. </div>
  330. </div>
  331. <div class="content">
  332. {{ station.description }}
  333. </div>
  334. <div class="under-content">
  335. <p class="hostedBy">
  336. Hosted by
  337. <span class="host">
  338. <span
  339. v-if="station.type === 'official'"
  340. title="Musare"
  341. >Musare</span
  342. >
  343. <user-id-to-username
  344. v-else
  345. :user-id="station.owner"
  346. :link="true"
  347. />
  348. </span>
  349. </p>
  350. <div class="icons">
  351. <i
  352. v-if="
  353. station.type === 'community' &&
  354. isOwner(station)
  355. "
  356. class="homeIcon material-icons"
  357. content="This is your station."
  358. v-tippy="{ theme: 'info' }"
  359. >home</i
  360. >
  361. <i
  362. v-if="station.privacy === 'private'"
  363. class="privateIcon material-icons"
  364. content="This station is not visible to other users."
  365. v-tippy="{ theme: 'info' }"
  366. >lock</i
  367. >
  368. <i
  369. v-if="station.privacy === 'unlisted'"
  370. class="unlistedIcon material-icons"
  371. content="Unlisted Station"
  372. v-tippy="{ theme: 'info' }"
  373. >link</i
  374. >
  375. </div>
  376. </div>
  377. </div>
  378. <div class="bottomBar">
  379. <i
  380. v-if="station.paused && station.currentSong.title"
  381. class="material-icons"
  382. content="Station Paused"
  383. v-tippy="{ theme: 'info' }"
  384. >pause</i
  385. >
  386. <i
  387. v-else-if="station.currentSong.title"
  388. class="material-icons"
  389. >music_note</i
  390. >
  391. <i v-else class="material-icons">music_off</i>
  392. <span
  393. v-if="station.currentSong.title"
  394. class="songTitle"
  395. :title="
  396. station.currentSong.artists.length > 0
  397. ? 'Now Playing: ' +
  398. station.currentSong.title +
  399. ' by ' +
  400. station.currentSong.artists.join(', ')
  401. : 'Now Playing: ' +
  402. station.currentSong.title
  403. "
  404. >{{ station.currentSong.title }}
  405. {{
  406. station.currentSong.artists.length > 0
  407. ? " by " +
  408. station.currentSong.artists.join(", ")
  409. : ""
  410. }}</span
  411. >
  412. <span v-else class="songTitle">No Songs Playing</span>
  413. <i
  414. class="material-icons stationMode"
  415. :content="
  416. station.partyMode
  417. ? 'Station in Party mode'
  418. : 'Station in Playlist mode'
  419. "
  420. v-tippy="{ theme: 'info' }"
  421. >{{
  422. station.partyMode
  423. ? "emoji_people"
  424. : "playlist_play"
  425. }}</i
  426. >
  427. </div>
  428. </router-link>
  429. <h4 v-if="stations.length === 0">
  430. There are no stations to display
  431. </h4>
  432. </div>
  433. <main-footer />
  434. </div>
  435. <create-community-station v-if="modals.createCommunityStation" />
  436. </div>
  437. </template>
  438. <script>
  439. import { mapState, mapGetters, mapActions } from "vuex";
  440. import { defineAsyncComponent } from "vue";
  441. import draggable from "vuedraggable";
  442. import Toast from "toasters";
  443. import MainHeader from "@/components/layout/MainHeader.vue";
  444. import MainFooter from "@/components/layout/MainFooter.vue";
  445. import SongThumbnail from "@/components/SongThumbnail.vue";
  446. import UserIdToUsername from "@/components/UserIdToUsername.vue";
  447. import ws from "@/ws";
  448. export default {
  449. components: {
  450. MainHeader,
  451. MainFooter,
  452. SongThumbnail,
  453. CreateCommunityStation: defineAsyncComponent(() =>
  454. import("@/components/modals/CreateCommunityStation.vue")
  455. ),
  456. UserIdToUsername,
  457. draggable
  458. },
  459. data() {
  460. return {
  461. recaptcha: { key: "" },
  462. stations: [],
  463. favoriteStations: [],
  464. searchQuery: "",
  465. siteSettings: {
  466. logo_white: "",
  467. sitename: ""
  468. },
  469. orderOfFavoriteStations: []
  470. };
  471. },
  472. computed: {
  473. ...mapState({
  474. loggedIn: state => state.user.auth.loggedIn,
  475. userId: state => state.user.auth.userId,
  476. modals: state => state.modalVisibility.modals
  477. }),
  478. ...mapGetters({
  479. socket: "websockets/getSocket"
  480. }),
  481. filteredStations() {
  482. const privacyOrder = ["public", "unlisted", "private"];
  483. return this.stations
  484. .filter(
  485. station =>
  486. JSON.stringify(Object.values(station)).indexOf(
  487. this.searchQuery
  488. ) !== -1
  489. )
  490. .sort(
  491. (a, b) =>
  492. this.isOwner(b) - this.isOwner(a) ||
  493. this.isPlaying(b) - this.isPlaying(a) ||
  494. a.paused - b.paused ||
  495. privacyOrder.indexOf(a.privacy) -
  496. privacyOrder.indexOf(b.privacy) ||
  497. b.userCount - a.userCount
  498. );
  499. },
  500. dragOptions() {
  501. return {
  502. animation: 200,
  503. group: "favoriteStations",
  504. disabled: false,
  505. ghostClass: "draggable-list-ghost",
  506. filter: ".ignore-elements",
  507. fallbackTolerance: 50
  508. };
  509. }
  510. },
  511. watch: {
  512. orderOfFavoriteStations: {
  513. deep: true,
  514. handler() {
  515. this.calculateFavoriteStations();
  516. }
  517. }
  518. },
  519. async mounted() {
  520. this.siteSettings = await lofig.get("siteSettings");
  521. if (this.socket.readyState === 1) this.init();
  522. ws.onConnect(() => this.init());
  523. this.socket.on("event:station.created", res => {
  524. const { station } = res.data;
  525. if (this.stations.find(_station => _station._id === station._id)) {
  526. this.stations.forEach(s => {
  527. const _station = s;
  528. if (_station._id === station._id) {
  529. _station.privacy = station.privacy;
  530. }
  531. });
  532. } else {
  533. if (!station.currentSong)
  534. station.currentSong = {
  535. thumbnail: "/assets/notes-transparent.png"
  536. };
  537. if (station.currentSong && !station.currentSong.thumbnail)
  538. station.currentSong.ytThumbnail = `https://img.youtube.com/vi/${station.currentSong.youtubeId}/mqdefault.jpg`;
  539. this.stations.push(station);
  540. }
  541. });
  542. this.socket.on("event:station.deleted", res => {
  543. const { stationId } = res.data;
  544. const station = this.stations.find(
  545. station => station._id === stationId
  546. );
  547. if (station) {
  548. const stationIndex = this.stations.indexOf(station);
  549. this.stations.splice(stationIndex, 1);
  550. if (station.isFavorited)
  551. this.orderOfFavoriteStations.filter(
  552. favoritedId => favoritedId !== stationId
  553. );
  554. }
  555. });
  556. this.socket.on("event:station.userCount.updated", res => {
  557. const station = this.stations.find(
  558. station => station._id === res.data.stationId
  559. );
  560. if (station) station.userCount = res.data.userCount;
  561. });
  562. this.socket.on("event:station.privacy.updated", res => {
  563. const station = this.stations.find(
  564. station => station._id === res.data.stationId
  565. );
  566. if (station) station.privacy = res.data.privacy;
  567. });
  568. this.socket.on("event:station.name.updated", res => {
  569. const station = this.stations.find(
  570. station => station._id === res.data.stationId
  571. );
  572. if (station) station.name = res.data.name;
  573. });
  574. this.socket.on("event:station.displayName.updated", res => {
  575. const station = this.stations.find(
  576. station => station._id === res.data.stationId
  577. );
  578. if (station) station.displayName = res.data.displayName;
  579. });
  580. this.socket.on("event:station.description.updated", res => {
  581. const station = this.stations.find(
  582. station => station._id === res.data.stationId
  583. );
  584. if (station) station.description = res.data.description;
  585. });
  586. this.socket.on("event:station.theme.updated", res => {
  587. const { stationId, theme } = res.data;
  588. const station = this.stations.find(
  589. station => station._id === stationId
  590. );
  591. if (station) station.theme = theme;
  592. });
  593. this.socket.on("event:station.partyMode.updated", res => {
  594. const { stationId, partyMode } = res.data;
  595. const station = this.stations.find(
  596. station => station._id === stationId
  597. );
  598. if (station) station.partyMode = partyMode;
  599. });
  600. this.socket.on("event:station.nextSong", res => {
  601. const station = this.stations.find(
  602. station => station._id === res.data.stationId
  603. );
  604. if (station) {
  605. let newSong = res.data.currentSong;
  606. if (!newSong)
  607. newSong = {
  608. thumbnail: "/assets/notes-transparent.png"
  609. };
  610. station.currentSong = newSong;
  611. }
  612. });
  613. this.socket.on("event:station.pause", res => {
  614. const station = this.stations.find(
  615. station => station._id === res.data.stationId
  616. );
  617. if (station) station.paused = true;
  618. });
  619. this.socket.on("event:station.resume", res => {
  620. const station = this.stations.find(
  621. station => station._id === res.data.stationId
  622. );
  623. if (station) station.paused = false;
  624. });
  625. this.socket.on("event:user.station.favorited", res => {
  626. const { stationId } = res.data;
  627. const station = this.stations.find(
  628. station => station._id === stationId
  629. );
  630. if (station) {
  631. station.isFavorited = true;
  632. this.orderOfFavoriteStations.push(stationId);
  633. }
  634. });
  635. this.socket.on("event:user.station.unfavorited", res => {
  636. const { stationId } = res.data;
  637. const station = this.stations.find(
  638. station => station._id === stationId
  639. );
  640. if (station) {
  641. station.isFavorited = false;
  642. this.orderOfFavoriteStations =
  643. this.orderOfFavoriteStations.filter(
  644. favoritedId => favoritedId !== stationId
  645. );
  646. }
  647. });
  648. this.socket.on("event:user.orderOfFavoriteStations.updated", res => {
  649. this.orderOfFavoriteStations = res.data.order;
  650. });
  651. },
  652. beforeUnmount() {
  653. this.socket.dispatch("apis.leaveRoom", "home", () => {});
  654. },
  655. methods: {
  656. init() {
  657. this.socket.dispatch("stations.index", res => {
  658. this.stations = [];
  659. if (res.status === "success") {
  660. res.data.stations.forEach(station => {
  661. const modifiableStation = station;
  662. if (!modifiableStation.currentSong)
  663. modifiableStation.currentSong = {
  664. thumbnail: "/assets/notes-transparent.png"
  665. };
  666. if (
  667. modifiableStation.currentSong &&
  668. !modifiableStation.currentSong.thumbnail
  669. )
  670. modifiableStation.currentSong.ytThumbnail = `https://img.youtube.com/vi/${station.currentSong.youtubeId}/mqdefault.jpg`;
  671. this.stations.push(modifiableStation);
  672. });
  673. this.orderOfFavoriteStations = res.data.favorited;
  674. }
  675. });
  676. this.socket.dispatch("apis.joinRoom", "home");
  677. },
  678. isOwner(station) {
  679. return station.owner === this.userId;
  680. },
  681. isPlaying(station) {
  682. return typeof station.currentSong.title !== "undefined";
  683. },
  684. favoriteStation(stationId) {
  685. this.socket.dispatch("stations.favoriteStation", stationId, res => {
  686. if (res.status === "success") {
  687. new Toast("Successfully favorited station.");
  688. } else new Toast(res.message);
  689. });
  690. },
  691. unfavoriteStation(stationId) {
  692. this.socket.dispatch(
  693. "stations.unfavoriteStation",
  694. stationId,
  695. res => {
  696. if (res.status === "success") {
  697. new Toast("Successfully unfavorited station.");
  698. } else new Toast(res.message);
  699. }
  700. );
  701. },
  702. calculateFavoriteStations() {
  703. this.favoriteStations = this.filteredStations
  704. .filter(station => station.isFavorited === true)
  705. .sort(
  706. (a, b) =>
  707. this.orderOfFavoriteStations.indexOf(a._id) -
  708. this.orderOfFavoriteStations.indexOf(b._id)
  709. );
  710. },
  711. changeFavoriteOrder() {
  712. const recalculatedOrder = [];
  713. this.favoriteStations.forEach(station =>
  714. recalculatedOrder.push(station._id)
  715. );
  716. this.socket.dispatch(
  717. "users.updateOrderOfFavoriteStations",
  718. recalculatedOrder,
  719. res => new Toast(res.message)
  720. );
  721. },
  722. ...mapActions("modalVisibility", ["openModal"]),
  723. ...mapActions("station", ["updateIfStationIsFavorited"])
  724. }
  725. };
  726. </script>
  727. <style lang="scss">
  728. * {
  729. box-sizing: border-box;
  730. }
  731. html {
  732. width: 100%;
  733. height: 100%;
  734. color: rgba(0, 0, 0, 0.87);
  735. body {
  736. width: 100%;
  737. height: 100%;
  738. margin: 0;
  739. padding: 0;
  740. }
  741. }
  742. .night-mode {
  743. .header .overlay {
  744. background: linear-gradient(
  745. 180deg,
  746. rgba(34, 34, 34, 0.8) 0%,
  747. rgba(34, 34, 34, 0.95) 31.25%,
  748. rgba(34, 34, 34, 0.9) 54.17%,
  749. rgba(34, 34, 34, 0.8) 100%
  750. );
  751. }
  752. .card,
  753. .card-content,
  754. .card-content div {
  755. background-color: var(--dark-grey-3);
  756. }
  757. .card-content .icons i,
  758. .group-title i {
  759. color: var(--light-grey-2);
  760. }
  761. .card-image i {
  762. user-select: none;
  763. -webkit-user-select: none;
  764. }
  765. .card-image.thumbnail {
  766. background-color: var(--dark-grey-2);
  767. }
  768. .card-content .under-content .hostedBy {
  769. color: var(--light-grey-2);
  770. }
  771. }
  772. @media only screen and (min-width: 1200px) {
  773. html {
  774. font-size: 15px;
  775. }
  776. }
  777. @media only screen and (min-width: 992px) {
  778. html {
  779. font-size: 14.5px;
  780. }
  781. }
  782. @media only screen and (min-width: 0) {
  783. html {
  784. font-size: 14px;
  785. }
  786. }
  787. .header {
  788. display: flex;
  789. height: 35vh;
  790. margin-top: -64px;
  791. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  792. img.background {
  793. height: 35vh;
  794. width: 100%;
  795. object-fit: cover;
  796. object-position: center;
  797. filter: blur(1px);
  798. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  799. overflow: hidden;
  800. user-select: none;
  801. }
  802. .overlay {
  803. background: linear-gradient(
  804. 180deg,
  805. rgba(3, 169, 244, 0.8) 0%,
  806. rgba(3, 169, 244, 0.95) 31.25%,
  807. rgba(3, 169, 244, 0.9) 54.17%,
  808. rgba(3, 169, 244, 0.8) 100%
  809. );
  810. position: absolute;
  811. height: 35vh;
  812. width: 100%;
  813. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  814. overflow: hidden;
  815. }
  816. .content-container {
  817. position: absolute;
  818. left: 0;
  819. right: 0;
  820. margin-left: auto;
  821. margin-right: auto;
  822. text-align: center;
  823. height: 100%;
  824. height: 35vh;
  825. .content {
  826. position: absolute;
  827. top: 50%;
  828. left: 0;
  829. right: 0;
  830. transform: translateY(-50%);
  831. background-color: transparent !important;
  832. .logo {
  833. max-height: 90px;
  834. font-size: 50px;
  835. color: var(--white);
  836. font-family: Pacifico, cursive;
  837. user-select: none;
  838. white-space: nowrap;
  839. }
  840. .buttons {
  841. display: flex;
  842. justify-content: center;
  843. margin-top: 20px;
  844. flex-wrap: wrap;
  845. .login,
  846. .register {
  847. margin: 5px 10px;
  848. padding: 10px 15px;
  849. border-radius: 5px;
  850. font-size: 18px;
  851. width: 100%;
  852. max-width: 250px;
  853. font-weight: 600;
  854. border: 0;
  855. height: inherit;
  856. }
  857. .login {
  858. background: var(--white);
  859. color: var(--primary-color);
  860. }
  861. .register {
  862. background: var(--purple);
  863. color: var(--white);
  864. }
  865. }
  866. }
  867. }
  868. &.loggedIn {
  869. height: 20vh;
  870. .overlay,
  871. .content-container,
  872. img.background {
  873. height: 20vh;
  874. }
  875. }
  876. }
  877. @media only screen and (max-width: 550px) {
  878. .header {
  879. height: 45vh;
  880. .overlay,
  881. .content-container,
  882. img.background {
  883. height: 45vh;
  884. }
  885. }
  886. }
  887. .under-content {
  888. height: 20px;
  889. position: relative;
  890. line-height: 1;
  891. font-size: 24px;
  892. display: flex;
  893. align-items: center;
  894. text-align: left;
  895. margin-top: 10px;
  896. p {
  897. font-size: 15px;
  898. line-height: 15px;
  899. display: inline;
  900. }
  901. i {
  902. font-size: 20px;
  903. }
  904. * {
  905. z-index: 10;
  906. position: relative;
  907. }
  908. .icons {
  909. position: absolute;
  910. right: 0;
  911. .material-icons {
  912. font-size: 22px;
  913. }
  914. .material-icons:first-child {
  915. margin-left: 5px;
  916. }
  917. .unlistedIcon {
  918. color: var(--orange);
  919. }
  920. .privateIcon {
  921. color: var(--dark-pink);
  922. }
  923. .homeIcon {
  924. color: var(--light-purple);
  925. }
  926. }
  927. .hostedBy {
  928. font-weight: 400;
  929. font-size: 12px;
  930. color: var(--black);
  931. .host,
  932. .host a {
  933. font-weight: 400;
  934. color: var(--primary-color);
  935. &:hover,
  936. &:focus {
  937. filter: brightness(90%);
  938. }
  939. }
  940. }
  941. }
  942. .app {
  943. display: flex;
  944. flex-direction: column;
  945. }
  946. .users-count {
  947. font-size: 20px;
  948. position: relative;
  949. top: -4px;
  950. }
  951. .group {
  952. min-height: 64px;
  953. flex: 1 0 auto;
  954. }
  955. .station-card {
  956. display: inline-flex;
  957. flex-direction: row;
  958. overflow: hidden;
  959. margin: 10px;
  960. cursor: pointer;
  961. height: 150px;
  962. width: calc(100% - 30px);
  963. max-width: 400px;
  964. flex-wrap: wrap;
  965. border-radius: 5px;
  966. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
  967. transition: all ease-in-out 0.2s;
  968. .card-content {
  969. padding: 10px 10px 10px 15px;
  970. display: flex;
  971. flex-direction: column;
  972. flex-grow: 1;
  973. -webkit-line-clamp: 2;
  974. .media {
  975. display: flex;
  976. align-items: center;
  977. margin-bottom: 0;
  978. .displayName {
  979. display: flex;
  980. align-items: center;
  981. width: 100%;
  982. overflow: hidden;
  983. text-overflow: ellipsis;
  984. display: flex;
  985. line-height: 30px;
  986. max-height: 30px;
  987. .favorite {
  988. position: absolute;
  989. color: var(--yellow);
  990. right: 10px;
  991. top: 10px;
  992. font-size: 28px;
  993. }
  994. h5 {
  995. font-size: 20px;
  996. font-weight: 400;
  997. margin: 0;
  998. display: inline;
  999. margin-right: 6px;
  1000. line-height: 30px;
  1001. text-overflow: ellipsis;
  1002. overflow: hidden;
  1003. white-space: nowrap;
  1004. max-width: 200px;
  1005. }
  1006. i {
  1007. font-size: 22px;
  1008. }
  1009. .verified-station {
  1010. color: var(--primary-color);
  1011. }
  1012. }
  1013. }
  1014. .content {
  1015. word-wrap: break-word;
  1016. overflow: hidden;
  1017. text-overflow: ellipsis;
  1018. display: -webkit-box;
  1019. -webkit-box-orient: vertical;
  1020. -webkit-line-clamp: 3;
  1021. line-height: 20px;
  1022. flex-grow: 1;
  1023. text-align: left;
  1024. word-wrap: break-word;
  1025. margin-bottom: 0;
  1026. }
  1027. }
  1028. .card-image.thumbnail {
  1029. min-width: 120px;
  1030. width: 120px;
  1031. height: 120px;
  1032. margin: 0;
  1033. }
  1034. .bottomBar {
  1035. position: relative;
  1036. display: flex;
  1037. align-items: center;
  1038. background: var(--primary-color);
  1039. // box-shadow: inset 0px 2px 4px rgba(100, 100, 100, 0.3);
  1040. width: 100%;
  1041. height: 30px;
  1042. line-height: 30px;
  1043. color: var(--white);
  1044. font-weight: 400;
  1045. font-size: 12px;
  1046. padding: 0 5px;
  1047. flex-basis: 100%;
  1048. i.material-icons {
  1049. vertical-align: middle;
  1050. margin-left: 5px;
  1051. font-size: 22px;
  1052. }
  1053. .songTitle {
  1054. text-align: left;
  1055. vertical-align: middle;
  1056. margin-left: 5px;
  1057. line-height: 30px;
  1058. flex: 2 1 0;
  1059. overflow: hidden;
  1060. text-overflow: ellipsis;
  1061. white-space: nowrap;
  1062. }
  1063. }
  1064. &.createStation {
  1065. height: auto;
  1066. .card-image {
  1067. .image.is-square {
  1068. width: 120px;
  1069. @media screen and (max-width: 330px) {
  1070. width: 50px;
  1071. .material-icons {
  1072. font-size: 35px !important;
  1073. }
  1074. }
  1075. .material-icons {
  1076. position: absolute;
  1077. top: 25px;
  1078. bottom: 25px;
  1079. left: 0;
  1080. right: 0;
  1081. text-align: center;
  1082. font-size: 70px;
  1083. color: var(--primary-color);
  1084. }
  1085. }
  1086. }
  1087. .card-content {
  1088. width: min-content;
  1089. .media {
  1090. margin-top: auto;
  1091. .displayName h5 {
  1092. font-weight: 600;
  1093. }
  1094. }
  1095. .content {
  1096. flex-grow: unset;
  1097. margin-bottom: auto;
  1098. }
  1099. }
  1100. }
  1101. }
  1102. .station-card:hover {
  1103. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.3), 0 0 10px rgba(10, 10, 10, 0.3);
  1104. transition: all ease-in-out 0.2s;
  1105. }
  1106. .community-button {
  1107. cursor: pointer;
  1108. transition: 0.25s ease color;
  1109. font-size: 30px;
  1110. color: var(--dark-grey);
  1111. }
  1112. .community-button:hover {
  1113. color: var(--primary-color);
  1114. }
  1115. .station-privacy {
  1116. text-transform: capitalize;
  1117. }
  1118. .label {
  1119. display: flex;
  1120. }
  1121. .g-recaptcha {
  1122. display: flex;
  1123. justify-content: center;
  1124. margin-top: 20px;
  1125. }
  1126. .group {
  1127. text-align: center;
  1128. width: 100%;
  1129. margin: 10px 0;
  1130. .group-title {
  1131. display: flex;
  1132. align-items: center;
  1133. justify-content: center;
  1134. margin: 25px 0;
  1135. h1 {
  1136. display: inline-block;
  1137. font-size: 45px;
  1138. margin: 0;
  1139. }
  1140. h2 {
  1141. font-size: 35px;
  1142. margin: 0;
  1143. }
  1144. a {
  1145. display: flex;
  1146. margin-left: 8px;
  1147. }
  1148. }
  1149. &.bottom {
  1150. margin-bottom: 40px;
  1151. }
  1152. }
  1153. </style>