diff --git a/api/funkwhale_api/common/tasks.py b/api/funkwhale_api/common/tasks.py
index c7deee7f5e8d527fb274ac1cbd69fdba969d4290..74ce3b0e198fca2b11d9d874fc27bc209a9d907c 100644
--- a/api/funkwhale_api/common/tasks.py
+++ b/api/funkwhale_api/common/tasks.py
@@ -80,9 +80,10 @@ def fetch_remote_attachment(attachment, filename=None, save=True):
             for chunk in r.iter_content():
                 tf.write(chunk)
             tf.seek(0)
-            attachment.file.save(
-                filename or attachment.url.split("/")[-1], File(tf), save=save
-            )
+            if not filename:
+                filename = attachment.url.split("/")[-1]
+                filename = filename[-50:]
+            attachment.file.save(filename, File(tf), save=save)
 
 
 @celery.app.task(name="common.prune_unattached_attachments")
diff --git a/api/tests/common/test_views.py b/api/tests/common/test_views.py
index 0ca7cbfd9346399fc1e98ec65baa41a5863ddc1f..358d85736bdcb37251093acb6e1d33dcd1603da1 100644
--- a/api/tests/common/test_views.py
+++ b/api/tests/common/test_views.py
@@ -216,6 +216,27 @@ def test_attachment_proxy_redirects_original(
     assert response["Location"] == urls[expected]
 
 
+def test_attachment_proxy_dont_crash_on_long_filename(
+    factories, logged_in_api_client, avatar, r_mock, now
+):
+    long_filename = "a" * 400
+    attachment = factories["common.Attachment"](
+        file=None, url="https://domain/{}.jpg".format(long_filename)
+    )
+
+    avatar_content = avatar.read()
+    r_mock.get(attachment.url, body=io.BytesIO(avatar_content))
+    proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
+
+    response = logged_in_api_client.get(proxy_url, {"next": next})
+    attachment.refresh_from_db()
+
+    assert response.status_code == 302
+    assert attachment.file.read() == avatar_content
+    assert attachment.file.name.endswith("/{}.jpg".format("a" * 46))
+    assert attachment.last_fetch_date == now
+
+
 def test_attachment_create(logged_in_api_client, avatar):
     actor = logged_in_api_client.user.create_actor()
     url = reverse("api:v1:attachments-list")
diff --git a/front/src/components/audio/album/Card.vue b/front/src/components/audio/album/Card.vue
index 67d1eb706cc68e3145583c26f464f0017bd3a1ec..6be2dc37ce05c385070307a540981159f9f826f2 100644
--- a/front/src/components/audio/album/Card.vue
+++ b/front/src/components/audio/album/Card.vue
@@ -1,109 +1,51 @@
 <template>
-    <div :class="['ui', 'card', mode]">
-      <div class="content">
-        <div class="right floated tiny ui image">
-          <img v-if="album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](album.cover.square_crop)">
-          <img v-else src="../../../assets/audio/default-cover.png">
-        </div>
-        <div class="header">
-          <router-link class="discrete link" :to="{name: 'library.albums.detail', params: {id: album.id }}">{{ album.title }} </router-link>
-        </div>
-        <div class="meta">
-          <span>
-            <router-link :title="album.artist.name" tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}">
-              <span v-translate="{artist: album.artist.name}" translate-context="Content/Album/Card" :translate-params="{artist: album.artist.name}">By %{ artist }</span>
-            </router-link>
-          </span><span class="time" v-if="album.release_date">– {{ album.release_date | year }}</span>
-        </div>
-        <div class="description" v-if="mode === 'rich'">
-          <table class="ui very basic fixed single line compact unstackable table">
-            <tbody>
-              <tr v-for="track in tracks">
-                <td class="play-cell">
-                  <play-button :class="['basic', {orange: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :track="track"></play-button>
-                </td>
-                <td class="content-cell" colspan="5">
-                  <track-favorite-icon :track="track"></track-favorite-icon>
-                  <router-link :title="track.title" class="track discrete link" :to="{name: 'library.tracks.detail', params: {id: track.id }}">
-                    <template v-if="track.position">
-                      {{ track.position }}.
-                    </template>
-                    {{ track.title }}
-                  </router-link>
-                </td>
-              </tr>
-            </tbody>
-          </table>
-          <div class="center aligned segment" v-if="album.tracks.length > initialTracks">
-            <em v-if="!showAllTracks" @click="showAllTracks = true" class="expand">
-              <translate translate-context="Content/Album/Card.Link/Verb" :translate-params="{count: album.tracks.length - initialTracks}" :translate-n="album.tracks.length - initialTracks" translate-plural="Show %{ count } more tracks">Show %{ count } more track</translate>
-            </em>
-            <em v-else @click="showAllTracks = false" class="expand">
-              <translate translate-context="*/*/Button,Label">Collapse</translate>
-            </em>
-          </div>
-        </div>
-      </div>
-      <div class="extra content">
-        <play-button class="mini basic orange right floated" :tracks="tracksWithAlbum" :album="album">
-          <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate>
-        </play-button>
+  <div class="card app-card">
+    <div
+      @click="$router.push({name: 'library.albums.detail', params: {id: album.id}})"
+      :class="['ui', 'head-image', 'image', {'default-cover': !album.cover.original}]" v-lazy:background-image="imageUrl">
+      <play-button :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :album="album"></play-button>
+    </div>
+    <div class="content">
+      <strong>
+        <router-link class="discrete link" :title="album.title" :to="{name: 'library.albums.detail', params: {id: album.id}}">
+          {{ album.title }}
+        </router-link>
+      </strong>
+      <div class="description">
         <span>
-          <i class="music icon"></i>
-          <translate translate-context="*/*/*" :translate-params="{count: album.tracks.length}" :translate-n="album.tracks.length" translate-plural="%{ count } tracks">%{ count } track</translate>
+          <router-link :title="album.artist.name" class="discrete link" :to="{name: 'library.artists.detail', params: {id: album.artist.id}}">
+            {{ album.artist.name }}
+          </router-link>
         </span>
       </div>
     </div>
+    <div class="extra content">
+      <translate translate-context="*/*/*" :translate-params="{count: album.tracks.length}" :translate-n="album.tracks.length" translate-plural="%{ count } tracks">%{ count } track</translate>
+      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="album.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :album="album"></play-button>
+    </div>
+  </div>
 </template>
 
 <script>
-import { mapGetters } from "vuex"
-import backend from '@/audio/backend'
-import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
 import PlayButton from '@/components/audio/PlayButton'
 
 export default {
   props: {
     album: {type: Object},
-    mode: {type: String, default: 'rich'},
   },
   components: {
-    TrackFavoriteIcon,
     PlayButton
   },
-  data () {
-    return {
-      backend: backend,
-      initialTracks: 5,
-      showAllTracks: false
-    }
-  },
   computed: {
-    tracks () {
-      if (this.showAllTracks) {
-        return this.album.tracks
+    imageUrl () {
+      let url = '../../../assets/audio/default-cover.png'
+
+      if (this.album.cover.original) {
+        url = this.$store.getters['instance/absoluteUrl'](this.album.cover.medium_square_crop)
+      } else {
+        return null
       }
-      return this.album.tracks.slice(0, this.initialTracks)
-    },
-    ...mapGetters({
-      currentTrack: "queue/currentTrack",
-    }),
-    isPlaying () {
-      return this.$store.state.player.playing
-    },
-    tracksWithAlbum () {
-      // needed to include album data (especially cover)
-      // with tracks appended in queue (#795)
-      let self = this
-      return this.album.tracks.map(t => {
-        return  {
-          ...t,
-          album: {
-            ...self.album,
-            tracks: []
-          }
-        }
-      })
+      return url
     }
   }
 }
@@ -111,35 +53,13 @@ export default {
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style scoped lang="scss">
-.content-cell {
-  .link,
-  .button {
-    padding: 0.5em 0;
-  }
-  .link {
-    margin-left: 0.5em;
-    display: block;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-}
-tr {
-  .favorite-icon:not(.favorited) {
-    visibility: hidden;
-  }
-  &:hover .favorite-icon {
-    visibility: visible;
-  }
-  .favorite-icon {
-    float: right;
-  }
-}
-.expand {
-  cursor: pointer;
+
+.default-cover {
+  background-image: url("../../../assets/audio/default-cover.png") !important;
 }
 
-.ui .card.rich {
-  align-self: flex-start;
+.card.app-card > .head-image > .icon {
+  margin: 0.5em;
+
 }
 </style>
diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue
index e5ee7f74204479c41f4f32e2a048a3c2b234d85a..b0d381a68ec907c1e7f97272db622e202abf2766 100644
--- a/front/src/components/audio/album/Widget.vue
+++ b/front/src/components/audio/album/Widget.vue
@@ -9,31 +9,11 @@
     <button v-if="controls" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
     <button v-if="controls" @click="fetchData('albums/')" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button>
     <div class="ui hidden divider"></div>
-    <div class="ui five cards">
+    <div class="ui app-cards cards">
       <div v-if="isLoading" class="ui inverted active dimmer">
         <div class="ui loader"></div>
       </div>
-      <div class="card" v-for="album in albums" :key="album.id">
-        <div :class="['ui', 'image', 'with-overlay', {'default-cover': !album.cover.original}]" v-lazy:background-image="getImageUrl(album)">
-          <play-button class="play-overlay" :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :album="album"></play-button>
-        </div>
-        <div class="content">
-          <router-link :title="album.title" :to="{name: 'library.albums.detail', params: {id: album.id}}">
-            {{ album.title|truncate(25) }}
-          </router-link>
-          <div class="description">
-            <span>
-              <router-link :title="album.artist.name" class="discrete link" :to="{name: 'library.artists.detail', params: {id: album.artist.id}}">
-                {{ album.artist.name|truncate(23) }}
-              </router-link>
-            </span>
-          </div>
-        </div>
-        <div class="extra content">
-          <human-date class="left floated" :date="album.creation_date"></human-date>
-          <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="album.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :album="album"></play-button>
-        </div>
-      </div>
+      <album-card v-for="album in albums" :album="album" :key="album.id" />
     </div>
     <template v-if="!isLoading && albums.length === 0">
       <div class="ui placeholder segment">
@@ -49,7 +29,7 @@
 <script>
 import _ from '@/lodash'
 import axios from 'axios'
-import PlayButton from '@/components/audio/PlayButton'
+import AlbumCard from '@/components/audio/album/Card'
 
 export default {
   props: {
@@ -59,7 +39,7 @@ export default {
     limit: {type: Number, default: 12},
   },
   components: {
-    PlayButton
+    AlbumCard
   },
   data () {
     return {
@@ -102,16 +82,6 @@ export default {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
     },
-    getImageUrl (album) {
-      let url = '../../../assets/audio/default-cover.png'
-
-      if (album.cover.original) {
-        url = this.$store.getters['instance/absoluteUrl'](album.cover.medium_square_crop)
-      } else {
-        return null
-      }
-      return url
-    }
   },
   watch: {
     offset () {
@@ -124,11 +94,7 @@ export default {
 }
 </script>
 <style scoped lang="scss">
-@import "../../../style/vendor/media";
 
-.default-cover {
-  background-image: url("../../../assets/audio/default-cover.png") !important;
-}
 
 .wrapper {
   width: 100%;
@@ -136,18 +102,6 @@ export default {
 .ui.cards {
   justify-content: flex-start;
 }
-.ui.five.cards > .card {
-  width: 15em;
-}
-.with-overlay {
-  background-size: cover !important;
-  background-position: center !important;
-  height: 15em;
-  width: 15em;
-  display: flex !important;
-  justify-content: center !important;
-  align-items: center !important;
-}
 </style>
 <style>
 .ui.cards .ui.button {
diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue
index 6fe70e266717c7e4ef8e1bec82591d27bac2b1f8..71192914e1ba8c430f5ef0de3da4ca4398eaa14e 100644
--- a/front/src/components/audio/artist/Card.vue
+++ b/front/src/components/audio/artist/Card.vue
@@ -1,28 +1,22 @@
 <template>
-  <div class="flat inline card">
-    <div :class="['ui', 'image', 'with-overlay', {'default-cover': !cover.original}]" v-lazy:background-image="imageUrl">
-      <play-button class="play-overlay" :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :artist="artist"></play-button>
+  <div class="app-card card">
+    <div
+      @click="$router.push({name: 'library.artists.detail', params: {id: artist.id}})"
+      :class="['ui', 'head-image', 'circular', 'image', {'default-cover': !cover.original}]" v-lazy:background-image="imageUrl">
+      <play-button :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :artist="artist"></play-button>
     </div>
     <div class="content">
-      <router-link :title="artist.name" :to="{name: 'library.artists.detail', params: {id: artist.id}}">
-        {{ artist.name|truncate(30) }}
-      </router-link>
-      <div v-if="artist.albums.length > 0">
-        <i class="small sound icon"></i>
-        <translate translate-context="Content/Artist/Card" :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate>
-      </div>
-      <div v-else-if="artist.tracks_count">
-        <i class="small sound icon"></i>
-        <translate translate-context="Content/Artist/Card" :translate-params="{count: artist.tracks_count}" :translate-n="artist.tracks_count" translate-plural="%{ count } tracks">1 track</translate>
-      </div>
-      <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="artist.tags"></tags-list>
+      <strong>
+        <router-link class="discrete link" :title="artist.name" :to="{name: 'library.artists.detail', params: {id: artist.id}}">
+          {{ artist.name|truncate(30) }}
+        </router-link>
+      </strong>
 
-      <play-button
-        class="play-button basic icon"
-        :dropdown-only="true"
-        :is-playable="artist.is_playable"
-        :dropdown-icon-classes="['ellipsis', 'vertical', 'large', 'grey']"
-        :artist="artist"></play-button>
+      <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="artist.tags"></tags-list>
+    </div>
+    <div class="extra content">
+      <translate translate-context="*/*/*" :translate-params="{count: artist.tracks_count}" :translate-n="artist.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
+      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="artist.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :artist="artist"></play-button>
     </div>
   </div>
 </template>
@@ -72,24 +66,4 @@ export default {
 .default-cover {
   background-image: url("../../../assets/audio/default-cover.png") !important;
 }
-
-.play-button {
-  position: absolute;
-  right: 0;
-  bottom: 40%;
-}
-
-.with-overlay {
-  background-size: cover !important;
-  background-position: center !important;
-  height: 8em;
-  width: 8em;
-  display: flex !important;
-  justify-content: center !important;
-  align-items: center !important;
-}
-.flat.card .with-overlay.image {
-  border-radius: 50% !important;
-  margin: 0 auto;
-}
 </style>
diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue
index 2fd3ec783e95cb5aab2e0a828263bf2500fb3a8f..8508762cc32aeab64ebeb1f0f2b6886f04763314 100644
--- a/front/src/components/library/Albums.vue
+++ b/front/src/components/library/Albums.vue
@@ -51,10 +51,8 @@
         class="ui stackable three column doubling grid">
         <div
           v-if="result.results.length > 0"
-          class="ui cards">
+          class="ui app-cards cards">
           <album-card
-            :mode="'simple'"
-            v-masonry-tile
             v-for="album in result.results"
             :key="album.id"
             :album="album"></album-card>
diff --git a/front/src/components/library/ArtistDetail.vue b/front/src/components/library/ArtistDetail.vue
index 1dfbdd0d244720ec21fe731ae01e9629c30f6ffb..725bedac0e7c67d6b83a32a3314de41892867cc7 100644
--- a/front/src/components/library/ArtistDetail.vue
+++ b/front/src/components/library/ArtistDetail.vue
@@ -21,8 +21,8 @@
       <h2>
         <translate translate-context="Content/Artist/Title">Albums by this artist</translate>
       </h2>
-      <div class="ui cards">
-        <album-card :mode="'rich'" :album="album" :key="album.id" v-for="album in allAlbums"></album-card>
+      <div class="ui cards app-cards">
+        <album-card :album="album" :key="album.id" v-for="album in allAlbums"></album-card>
       </div>
       <div class="ui hidden divider"></div>
       <button :class="['ui', {loading: isLoadingMoreAlbums}, 'button']" v-if="nextAlbumsUrl && loadMoreAlbumsUrl" @click="loadMoreAlbums(loadMoreAlbumsUrl)">
diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue
index 7f83fb0a0d362d294f6f58d249d2e14ab1210469..10ef24f3b1c844b756c4197d5d34f44c37b2b790 100644
--- a/front/src/components/library/Artists.vue
+++ b/front/src/components/library/Artists.vue
@@ -42,7 +42,7 @@
         </div>
       </div>
       <div class="ui hidden divider"></div>
-      <div v-if="result && result.results.length > 0" class="ui three cards">
+      <div v-if="result && result.results.length > 0" class="ui five app-cards cards">
         <div v-if="isLoading" class="ui inverted active dimmer">
           <div class="ui loader"></div>
         </div>
diff --git a/front/src/components/playlists/Card.vue b/front/src/components/playlists/Card.vue
index 39b8a583fc6b7e7c2cc478f21315ec3eab795092..081624c25cdc138955172bd51723478c7d2a734b 100644
--- a/front/src/components/playlists/Card.vue
+++ b/front/src/components/playlists/Card.vue
@@ -1,45 +1,24 @@
 <template>
-  <div class="ui playlist card">
-    <div class="ui top attached icon button" :style="coversStyle">
+  <div class="ui app-card card">
+    <div
+      @click="$router.push({name: 'library.playlists.detail', params: {id: playlist.id }})"
+      :class="['ui', 'head-image', 'squares']">
+      <img v-lazy="url" v-for="(url, idx) in images" :key="idx" />
+      <play-button :icon-only="true" :is-playable="playlist.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :playlist="playlist"></play-button>
     </div>
     <div class="content">
-      <div class="header">
-        <div class="right floated">
-          <play-button
-            :is-playable="playlist.is_playable"
-            :icon-only="true" class="ui inline"
-            :button-classes="['ui', 'circular', 'large', {orange: playlist.tracks_count > 0}, 'icon', 'button', {disabled: playlist.tracks_count === 0}]"
-            :playlist="playlist"></play-button>
-          <play-button
-            :is-playable="playlist.is_playable"
-            class="basic inline icon"
-            :dropdown-only="true"
-            :dropdown-icon-classes="['ellipsis', 'vertical', 'large', {disabled: playlist.tracks_count === 0}, 'grey']"
-            :account="playlist.actor"
-            :playlist="playlist"></play-button>
-        </div>
-        <router-link :title="playlist.name" class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
-          {{ playlist.name | truncate(30) }}
+      <strong>
+        <router-link class="discrete link" :title="playlist.name" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
+          {{ playlist.name }}
         </router-link>
-      </div>
-      <div class="meta">
-        <duration :seconds="playlist.duration" />
-         |
-        <i class="sound icon"></i>
-        <translate translate-context="Content/*/Card/List item"
-          translate-plural="%{ count } tracks"
-          :translate-n="playlist.tracks_count"
-          :translate-params="{count: playlist.tracks_count}">
-          %{ count} track
-        </translate>&nbsp;
+      </strong>
+      <div class="description">
+        <user-link :user="playlist.user" class="left floated" />
       </div>
     </div>
     <div class="extra content">
-      <user-link :user="playlist.user" class="left floated" />
-      <span class="right floated">
-        <i class="clock outline icon" />
-        <human-date :date="playlist.creation_date" />
-      </span>
+      <translate translate-context="*/*/*" :translate-params="{count: playlist.tracks_count}" :translate-n="playlist.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
+      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="playlist.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :playlist="playlist"></play-button>
     </div>
   </div>
 </template>
@@ -53,41 +32,18 @@ export default {
     PlayButton
   },
   computed: {
-    coversStyle () {
+    images () {
       let self = this
       let urls = this.playlist.album_covers.map((url) => {
-        url = self.$store.getters['instance/absoluteUrl'](url)
-        return `url("${url}")`
+        return self.$store.getters['instance/absoluteUrl'](url)
       }).slice(0, 4)
-      return {
-        'background-image': urls.join(', ')
+      while (urls.length < 4) {
+        urls.push(
+          '../../../assets/audio/default-cover.png'
+        )
       }
+      return urls
     }
   }
 }
 </script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style>
-.playlist.card .header .ellipsis.vertical.large.grey {
-  font-size: 1.2em;
-  margin-right: 0;
-}
-</style>
-<style scoped>
-.card .header {
-  margin-bottom: 0.25em;
-}
-
-.attached.button {
-  background-size: 25%;
-  background-repeat: no-repeat;
-  background-origin: border-box;
-  background-position: 0 0, 33.33% 0, 66.67% 0, 100% 0;
-  /* background-position: 0 0, 50% 0, 100% 0; */
-  /* background-position: 0 0, 25% 0, 50% 0, 75% 0, 100% 0; */
-  font-size: 4em;
-  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset !important;
-  padding: unset;
-}
-</style>
diff --git a/front/src/components/playlists/CardList.vue b/front/src/components/playlists/CardList.vue
index 44504a5738ad1d83ded1884a9ed454495f0bc52d..8ed7405680a6b086fc647874ecd662a0835ed8fd 100644
--- a/front/src/components/playlists/CardList.vue
+++ b/front/src/components/playlists/CardList.vue
@@ -1,15 +1,8 @@
 <template>
-  <div
-    v-if="playlists.length > 0"
-    v-masonry
-    transition-duration="0"
-    item-selector=".card"
-    percent-position="true"
-    stagger="0">
-    <div class="ui cards">
+  <div v-if="playlists.length > 0">
+    <div class="ui app-cards cards">
       <playlist-card
         :playlist="playlist"
-        v-masonry-tile
         v-for="playlist in playlists"
         :key="playlist.id"
       ></playlist-card>
diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss
index 06124c8f4a679b12b8306db38e36be7058f1d7a2..9af436acc12ad854b8e8dd333d32005d7545a860 100644
--- a/front/src/style/_main.scss
+++ b/front/src/style/_main.scss
@@ -354,20 +354,6 @@ td.align.right {
   word-wrap: break-word;
 }
 
-.ui.cards > .flat.card, .flat.card {
-  box-shadow: none;
-  .content {
-    border: none;
-  }
-}
-
-.ui.cards > .inline.card {
-  flex-direction: row;
-  .content {
-    padding: 0.5em 0.75em;
-  }
-}
-
 .ui.checkbox label {
   cursor: pointer;
 }
@@ -438,5 +424,61 @@ input + .help {
     }
   }
 }
+.ui.cards.app-cards {
+  $card-width: 14em;
+  $card-hight: 22em;
+  .app-card {
+    display: flex;
+    width: $card-width;
+    height: $card-hight;
+    .head-image {
+      height: $card-width;
+      background-size: cover !important;
+      background-position: center !important;
+      display: flex !important;
+      justify-content: flex-end !important;
+      align-items: flex-end !important;
+      .button {
+        margin: 0;
+      }
+      &.circular {
+        overflow: visible;
+        border-radius: 50% !important;
+        height: $card-width - 1em;
+        width: $card-width - 1em;
+        margin: 0.5em;
+
+      }
+      &.squares {
+        display: block !important;
+        position: relative;
+        .button {
+          position: absolute;
+          bottom: 0.5em;
+          right: 0.5em;
+        }
+        img {
+          display: inline-block;
+          width: 50%;
+          height: 50%;
+          margin: 0;
+          border-radius: 0;
+        }
+      }
+    }
+    .extra {
+      border-top: 0 !important;
+    }
+    .content:not(.extra) {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      padding-bottom: 0;
+    }
+    .floating.dropdown > .icon {
+      margin-right: 0;
+    }
+  }
+}
 @import "./themes/_light.scss";
 @import "./themes/_dark.scss";