diff --git a/src/js/components/Grid.js b/src/js/components/Grid.js
index 9c2d3d27..7c42e99a 100644
--- a/src/js/components/Grid.js
+++ b/src/js/components/Grid.js
@@ -6,7 +6,6 @@ const Grid = memo(({
items,
className = '',
mini,
- tile,
getLink,
sourceIcon,
}) => {
@@ -14,13 +13,12 @@ const Grid = memo(({
return (
);
diff --git a/src/js/components/GridItem.js b/src/js/components/GridItem.js
index 39dad86b..08a7e799 100755
--- a/src/js/components/GridItem.js
+++ b/src/js/components/GridItem.js
@@ -78,6 +78,7 @@ const GridItem = ({
type: item?.type?.toUpperCase() || 'UNKNOWN',
item: { item, context: item },
});
+ const tile = ['playlist_group', 'mood', 'directory', 'category'].indexOf(item?.type) > -1
const onContextMenu = (e) => {
e.preventDefault();
@@ -127,7 +128,7 @@ const GridItem = ({
return (
{
{spotify_available && (
-
+
diff --git a/src/js/locale/en.yaml b/src/js/locale/en.yaml
index fee63b96..8bf96a22 100755
--- a/src/js/locale/en.yaml
+++ b/src/js/locale/en.yaml
@@ -138,6 +138,7 @@ services:
searching: 'Searching %{provider} %{type}'
searching_providers: 'Searching %{count} Mopidy providers'
loading_albums: 'Loading %{count} local albums'
+ loading_artwork: 'Loading artwork'
spotify:
title: Spotify
pusher:
@@ -484,7 +485,7 @@ discover:
title: Discover
moods:
title: 'Genre / Mood'
- featured:
+ featured_playlists:
title: Featured playlists
new_releases:
title: New releases
diff --git a/src/js/services/core/middleware.js b/src/js/services/core/middleware.js
index 19d6740a..be1a4b6b 100755
--- a/src/js/services/core/middleware.js
+++ b/src/js/services/core/middleware.js
@@ -549,6 +549,7 @@ const CoreMiddleware = (function () {
case 'LOAD_LIBRARY':
store.dispatch(uiActions.startLoading(action.uri, action.uri));
+ console.debug(action);
const fetchLibrary = () => {
switch (uriSource(action.uri)) {
case 'spotify':
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
index da13c5e5..e13fcae0 100755
--- a/src/js/services/mopidy/actions.js
+++ b/src/js/services/mopidy/actions.js
@@ -484,6 +484,13 @@ export function getLibraryMoods(uri) {
};
}
+export function getLibraryFeaturedPlaylists(uri) {
+ return {
+ type: 'MOPIDY_GET_LIBRARY_FEATURED_PLAYLISTS',
+ uri,
+ };
+}
+
export function getLibraryPlaylists(uri) {
return {
type: 'MOPIDY_GET_LIBRARY_PLAYLISTS',
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js
index 766fe03d..98b2ca12 100755
--- a/src/js/services/mopidy/middleware.js
+++ b/src/js/services/mopidy/middleware.js
@@ -1899,7 +1899,7 @@ const MopidyMiddleware = (function () {
} = response;
// Not all endpoints give us tracks/subdirectories to library.lookup
- if (!results.length || uri.startsWith('file:')) {
+ if (!results.length || uri.startsWith('file:') || uri.startsWith('ytmusic:')) {
console.info(`No 'library.lookup' results for ${uri}, trying 'library.browse'`);
getBrowse();
} else {
@@ -1985,37 +1985,107 @@ const MopidyMiddleware = (function () {
break;
}
+ case 'MOPIDY_GET_LIBRARY_FEATURED_PLAYLISTS': {
+ store.dispatch(uiActions.startProcess(action.type, { notification: false }));
+
+ request(store, 'library.browse', { uri: action.uri })
+ .then((response) => {
+ const moods = response.map((mood) => ({
+ ...formatSimpleObject(mood),
+ type: 'playlist_group',
+ }));
+
+ store.dispatch(
+ uiActions.updateProcess(
+ action.type,
+ {
+ total: moods.length,
+ remaining: moods.length,
+ },
+ ),
+ );
+
+ store.dispatch(coreActions.libraryLoaded({
+ uri: action.uri,
+ type: 'featured_playlists',
+ items_uris: arrayOf('uri', moods),
+ }));
+ store.dispatch(coreActions.itemsLoaded(moods));
+ store.dispatch(uiActions.stopLoading(action.uri));
+ store.dispatch(uiActions.processFinished(action.type));
+ });
+ break;
+ }
+
case 'MOPIDY_GET_PLAYLIST_GROUP': {
+ const processKey = `playlist_group_${action.uri}`;
const decodedUri = action.uri ? decodeURIComponent(action.uri) : null;
const playlistGroup = formatPlaylistGroup({
uri: decodedUri,
loading: true,
});
+ store.dispatch(uiActions.startProcess(
+ processKey,
+ { content: i18n('services.mopidy.loading_artwork') },
+ ));
store.dispatch(coreActions.itemLoaded(playlistGroup));
request(store, 'library.browse', { uri: action.uri })
.then((browse) => {
- const playlists = formatPlaylists(browse);
+ const playlists = browse.map((item) => formatPlaylist({
+ ...item,
+ images: [], // Images is a playlist dependency, so this prevents triggering full load
+ }));
const playlists_uris = arrayOf('uri', playlists);
- request(store, 'library.getImages', { uris: playlists_uris })
- .then((response) => {
- const playlistsWithImages = playlists.map((playlist) => {
- let images = response[playlist.uri] || [];
- if (images) {
- images = formatImages(digestMopidyImages(store.getState().mopidy, images));
- }
- return {
- ...playlist,
- images,
- };
- });
+ const allUris = [...playlists_uris];
+ store.dispatch(coreActions.itemLoaded({
+ ...playlistGroup,
+ loading: false,
+ playlists_uris,
+ }));
+ store.dispatch(coreActions.itemsLoaded(playlists));
+ store.dispatch(
+ uiActions.updateProcess(
+ processKey,
+ {
+ total: allUris.length,
+ remaining: allUris.length,
+ },
+ ),
+ );
- store.dispatch(coreActions.itemsLoaded(playlistsWithImages));
- store.dispatch(coreActions.itemLoaded({
- ...playlistGroup,
- loading: false,
- playlists_uris,
- }));
- });
+ const run = () => {
+ if (allUris.length) {
+ const uris = allUris.splice(0, 5);
+ const processor = store.getState().ui.processes[processKey];
+ if (processor && processor.status === 'cancelling') {
+ store.dispatch(uiActions.processCancelled(processKey));
+ return;
+ }
+ store.dispatch(uiActions.updateProcess(processKey, { remaining: allUris.length }));
+
+ request(store, 'library.getImages', { uris })
+ .then(
+ (response) => {
+ const withImages = uris.map((uri) => {
+ let images = response[uri] || [];
+ if (images) {
+ images = formatImages(digestMopidyImages(store.getState().mopidy, images));
+ }
+ return {
+ uri,
+ images,
+ };
+ });
+ store.dispatch(coreActions.itemsLoaded(withImages));
+ run();
+ },
+ );
+ } else {
+ store.dispatch(uiActions.processFinished(processKey));
+ }
+ };
+
+ run();
});
break;
}
diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js
index d662fa6d..317fe346 100755
--- a/src/js/services/spotify/actions.js
+++ b/src/js/services/spotify/actions.js
@@ -362,9 +362,10 @@ export function getTrack(uri, { forceRefetch, full, lyrics }) {
};
}
-export function getFeaturedPlaylists(forceRefetch = false) {
+export function getLibraryFeaturedPlaylists(forceRefetch = false) {
return (dispatch, getState) => {
- dispatch({ type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', data: false });
+ const processKey = 'SPOTIFY_GET_LIBRARY_FEATURED_PLAYLISTS';
+ dispatch({ type: processKey, data: false });
const date = new Date();
date.setHours(date.getHours());
@@ -402,9 +403,13 @@ export function getFeaturedPlaylists(forceRefetch = false) {
);
dispatch(coreActions.itemsLoaded(playlists));
-
+ dispatch(coreActions.libraryLoaded({
+ uri: 'spotify:featured',
+ type: 'featured_playlists',
+ items_uris: arrayOf('uri', playlists),
+ }));
dispatch({
- type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED',
+ type: processKey,
data: {
message: response.message,
uris: upgradeSpotifyPlaylistUris(arrayOf('uri', playlists)),
diff --git a/src/js/util/selectors.js b/src/js/util/selectors.js
index b884a463..d4bdbf9b 100755
--- a/src/js/util/selectors.js
+++ b/src/js/util/selectors.js
@@ -205,6 +205,18 @@ const providers = {
title: i18n('services.youtube.title'),
},
],
+ featured_playlists: [
+ {
+ scheme: 'spotify:',
+ uri: 'spotify:featured',
+ title: i18n('services.spotify.title'),
+ },
+ {
+ scheme: 'ytmusic:',
+ uri: 'ytmusic:auto',
+ title: i18n('services.youtube.title'),
+ },
+ ],
};
const getProvider = (type, scheme) => providers[type]?.find((p) => p.scheme === scheme);
const getUriSchemes = (state) => state.mopidy.uri_schemes || [];
diff --git a/src/js/views/Discover/Categories.js b/src/js/views/Discover/Categories.js
deleted file mode 100755
index 9101c21b..00000000
--- a/src/js/views/Discover/Categories.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import Header from '../../components/Header';
-import Icon from '../../components/Icon';
-import Button from '../../components/Button';
-import FilterField from '../../components/Fields/FilterField';
-import { Grid } from '../../components/Grid';
-import Loader from '../../components/Loader';
-import * as uiActions from '../../services/ui/actions';
-import * as spotifyActions from '../../services/spotify/actions';
-import { encodeUri } from '../../util/format';
-import { i18n, I18n } from '../../locale';
-import { indexToArray, applyFilter } from '../../util/arrays';
-import { makeLoadingSelector } from '../../util/selectors';
-
-const Categories = ({
- loading,
- categories: categoriesProp,
- spotifyActions: {
- getCategories,
- },
-}) => {
- const {
- setWindowTitle,
- hideContextMenu,
- } = uiActions;
- const [filter, setFilter] = useState('');
- let categories = categoriesProp;
-
- useEffect(() => {
- // Check for an empty category index, or where we've only got one loaded
- // This would be the case if you've refreshed from within a category and only loaded
- // the single record.
- if (!categories || Object.keys(categories).length <= 1) {
- getCategories();
- }
- setWindowTitle(i18n('discover.categories.title'));
- }, []);
-
- const refresh = () => {
- hideContextMenu();
- getCategories();
- }
-
- if (filter && filter !== '') categories = applyFilter('name', filter, categories);
-
- const options = (
- <>
-
hideContextMenu()}
- />
-
- >
- );
-
- return (
-
-
-
- {loading ? (
-
- ) : (
- `/discover/categories/${encodeUri(item.uri)}`}
- sourceIcon={false}
- />
- )}
-
-
- );
-}
-
-const mapStateToProps = (state) => {
- const loadingSelector = makeLoadingSelector(['spotify_categories']);
-
- return {
- loading: loadingSelector(state),
- categories: indexToArray(state.spotify.categories),
- };
-};
-
-const mapDispatchToProps = (dispatch) => ({
- uiActions: bindActionCreators(uiActions, dispatch),
- spotifyActions: bindActionCreators(spotifyActions, dispatch),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Categories);
diff --git a/src/js/views/Discover/Category.js b/src/js/views/Discover/Category.js
deleted file mode 100755
index 09017738..00000000
--- a/src/js/views/Discover/Category.js
+++ /dev/null
@@ -1,173 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import Header from '../../components/Header';
-import DropdownField from '../../components/Fields/DropdownField';
-import FilterField from '../../components/Fields/FilterField';
-import Icon from '../../components/Icon';
-import { Grid } from '../../components/Grid';
-import Loader from '../../components/Loader';
-import ErrorMessage from '../../components/ErrorMessage';
-import Button from '../../components/Button';
-import * as uiActions from '../../services/ui/actions';
-import * as coreActions from '../../services/core/actions';
-import * as spotifyActions from '../../services/spotify/actions';
-import { I18n, i18n } from '../../locale';
-import {
- makeItemSelector,
- makeLoadingSelector,
- getSortSelector,
-} from '../../util/selectors';
-import { sortItems, applyFilter } from '../../util/arrays';
-import { decodeUri } from '../../util/format';
-
-const SORT_KEY = 'discover_category';
-
-const Category = ({
- uri,
- category,
- loading,
- playlists: playlistsProp,
- sortField,
- sortReverse,
- spotifyActions: {
- getCategory,
- },
- uiActions: {
- setSort,
- hideContextMenu,
- setWindowTitle,
- },
-}) => {
- const [filter, setFilter] = useState('');
- useEffect(() => {
- if (!category) getCategory(uri);
- }, [uri]);
-
- useEffect(() => {
- if (category) {
- setWindowTitle(category.name);
- } else {
- setWindowTitle(i18n('discover.category.title'));
- }
- }, [category]);
-
- const refresh = () => {
- hideContextMenu();
- getCategory(uri, { forceRefetch: true });
- }
-
- const onSortChange = (field) => {
- let reverse = false;
- if (field !== null && sortField === field) {
- reverse = !sortReverse;
- }
-
- setSort(SORT_KEY, field, reverse);
- hideContextMenu();
- }
-
- if (loading) {
- return ;
- }
-
- if (!category) {
- return (
-
-
-
-
-
- );
- }
-
- let playlists = playlistsProp;
- if (sortField) playlists = sortItems(playlists, sortField, sortReverse);
- if (filter && filter !== '') playlists = applyFilter('name', filter, playlists);
-
- const sort_options = [
- {
- value: null,
- label: i18n('fields.filters.as_loaded'),
- },
- {
- value: 'name',
- label: i18n('fields.filters.name'),
- },
- {
- value: 'tracks',
- label: i18n('fields.filters.tracks'),
- },
- ];
-
- const options = (
- <>
- uiActions.hideContextMenu()}
- />
-
-
- >
- );
-
- return (
-
- );
-}
-
-const mapStateToProps = (state, ownProps) => {
- const [sortField, sortReverse] = getSortSelector(state, SORT_KEY, null);
- const uri = decodeUri(ownProps.match.params.uri);
- const loadingSelector = makeLoadingSelector([`spotify_category_${uri}`]);
- const categorySelector = makeItemSelector(uri);
- const category = categorySelector(state);
- let playlists = null;
- if (category && category.playlists_uris) {
- const playlistsSelector = makeItemSelector(category.playlists_uris);
- playlists = playlistsSelector(state);
- }
-
- return {
- uri,
- loading: loadingSelector(state),
- playlists,
- category,
- sortField,
- sortReverse,
- };
-};
-
-const mapDispatchToProps = (dispatch) => ({
- uiActions: bindActionCreators(uiActions, dispatch),
- coreActions: bindActionCreators(coreActions, dispatch),
- spotifyActions: bindActionCreators(spotifyActions, dispatch),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Category);
diff --git a/src/js/views/Discover/Discover.js b/src/js/views/Discover/Discover.js
index 06fe47f0..6df8d7eb 100644
--- a/src/js/views/Discover/Discover.js
+++ b/src/js/views/Discover/Discover.js
@@ -1,12 +1,11 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import Recommendations from './Recommendations';
-import Featured from './Featured';
-import Categories from './Categories';
-import Category from './Category';
+import FeaturedPlaylists from './FeaturedPlaylists';
import NewReleases from './NewReleases';
import Moods from './Moods';
import PlaylistGroup from './PlaylistGroup';
+import Playlist from '../Playlist';
export default () => (
@@ -27,18 +26,18 @@ export default () => (
/>
{
- const {
- uiActions: {
- hideContextMenu,
- },
- spotifyActions: {
- getFeaturedPlaylists,
- },
- } = this.props;
- hideContextMenu();
- getFeaturedPlaylists(true);
- }
-
- render = () => {
- const {
- loading,
- uris,
- items,
- } = this.props;
-
- if (loading) {
- return (
-
-
-
-
- );
- }
-
- const playlists = indexToArray(items, uris || []);
-
- const options = (
-
- );
-
- return (
-
-
-
-
- );
- }
-}
-
-const loadingSelector = makeLoadingSelector(['(.*)featured-playlists(.*)']);
-const mapStateToProps = (state) => ({
- theme: state.ui.theme,
- loading: loadingSelector(state),
- uris: state.spotify && state.spotify.featured_playlists ? state.spotify.featured_playlists.uris : null,
- items: state.core.items,
-});
-
-const mapDispatchToProps = (dispatch) => ({
- uiActions: bindActionCreators(uiActions, dispatch),
- mopidyActions: bindActionCreators(mopidyActions, dispatch),
- spotifyActions: bindActionCreators(spotifyActions, dispatch),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Featured);
diff --git a/src/js/views/Discover/FeaturedPlaylists.js b/src/js/views/Discover/FeaturedPlaylists.js
new file mode 100644
index 00000000..6f9f15d4
--- /dev/null
+++ b/src/js/views/Discover/FeaturedPlaylists.js
@@ -0,0 +1,288 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import Header from '../../components/Header';
+import DropdownField from '../../components/Fields/DropdownField';
+import FilterField from '../../components/Fields/FilterField';
+import { Grid } from '../../components/Grid';
+import { List } from '../../components/List';
+import Icon from '../../components/Icon';
+import * as coreActions from '../../services/core/actions';
+import * as uiActions from '../../services/ui/actions';
+import * as mopidyActions from '../../services/mopidy/actions';
+import * as spotifyActions from '../../services/spotify/actions';
+import { sortItems, applyFilter } from '../../util/arrays';
+import Button from '../../components/Button';
+import { i18n, I18n } from '../../locale';
+import Loader from '../../components/Loader';
+import {
+ makeLibrarySelector,
+ makeProcessProgressSelector,
+ getLibrarySource,
+ makeProvidersSelector,
+ getSortSelector,
+} from '../../util/selectors';
+import { encodeUri } from '../../util/format';
+
+const SORT_KEY = 'library_featured_playlists';
+const processKeys = [
+ 'MOPIDY_GET_LIBRARY_FEATURED_PLAYLISTS',
+ 'SPOTIFY_GET_LIBRARY_FEATURED_PLAYLISTS',
+];
+
+class FeaturedPlaylists extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ filter: '',
+ };
+ }
+
+ componentDidMount() {
+ const {
+ uiActions: {
+ setWindowTitle,
+ },
+ } = this.props;
+
+ setWindowTitle(i18n('discover.featured_playlists.title'));
+ this.getLibraries();
+ }
+
+ componentDidUpdate = ({ source: prevSource }) => {
+ const { source } = this.props;
+
+ if (source !== prevSource) {
+ this.getLibraries();
+ }
+ }
+
+ refresh = () => {
+ const { uiActions: { hideContextMenu } } = this.props;
+
+ hideContextMenu();
+ this.getLibraries(true);
+ }
+
+ cancelRefresh = () => {
+ const { uiActions: { hideContextMenu, cancelProcess } } = this.props;
+
+ hideContextMenu();
+ cancelProcess(processKeys);
+ }
+
+ getLibraries = (forceRefetch = false) => {
+ const {
+ source,
+ providers,
+ coreActions: {
+ loadLibrary,
+ },
+ } = this.props;
+
+ let uris = [];
+ if (source === 'all') {
+ uris = providers.map((p) => p.uri);
+ } else {
+ uris.push(source);
+ }
+ uris.forEach((uri) => loadLibrary(uri, 'featuredPlaylists', { forceRefetch }));
+ };
+
+ onSortChange = (field) => {
+ const {
+ sortField,
+ sortReverse,
+ uiActions: {
+ setSort,
+ hideContextMenu,
+ },
+ } = this.props;
+
+ let reverse = false;
+ if (field !== null && sortField === field) {
+ reverse = !sortReverse;
+ }
+
+ setSort(SORT_KEY, field, reverse);
+ hideContextMenu();
+ }
+
+ renderView = () => {
+ const {
+ sortField,
+ sortReverse,
+ view,
+ loading_progress,
+ } = this.props;
+ const {
+ filter,
+ } = this.state;
+ let { featured_playlists } = this.props;
+
+ if (loading_progress) {
+ return ;
+ }
+
+ if (sortField) {
+ featured_playlists = sortItems(featured_playlists, sortField, sortReverse);
+ }
+
+ if (filter && filter !== '') {
+ featured_playlists = applyFilter('name', filter, featured_playlists);
+ }
+
+ if (view === 'list') {
+ return (
+
+ `/discover/featured-playlists/${item.type}/${encodeUri(item.uri)}/${item.name}`}
+ thumbnail
+ />
+
+ );
+ }
+ return (
+
+ `/discover/featured-playlists/${item.type}/${encodeUri(item.uri)}/${item.name}`}
+ />
+
+ );
+ }
+
+ render = () => {
+ const {
+ view,
+ source,
+ providers,
+ sortField,
+ sortReverse,
+ uiActions,
+ loading_progress,
+ } = this.props;
+ const {
+ filter,
+ per_page,
+ } = this.state;
+
+ const view_options = [
+ {
+ value: 'thumbnails',
+ label: i18n('fields.filters.thumbnails'),
+ },
+ {
+ value: 'list',
+ label: i18n('fields.filters.list'),
+ },
+ ];
+
+ const sort_options = [
+ {
+ value: null,
+ label: i18n('fields.filters.as_loaded'),
+ },
+ {
+ value: 'name',
+ label: i18n('fields.filters.name'),
+ },
+ {
+ value: 'uri',
+ label: i18n('fields.filters.source'),
+ },
+ ];
+
+ console.debug({ source, view })
+
+ const options = (
+ <>
+ this.setState({ filter: value, limit: per_page })}
+ onSubmit={() => uiActions.hideContextMenu()}
+ />
+
+ { uiActions.set({ library_featured_playlists_view: val }); uiActions.hideContextMenu(); }}
+ />
+ ({ value: p.uri, label: p.title })),
+ ]}
+ handleChange={(val) => { uiActions.set({ library_featured_playlists_source: val }); uiActions.hideContextMenu(); }}
+ />
+
+ >
+ );
+
+ return (
+
+
+ {this.renderView()}
+
+ );
+ }
+}
+
+const librarySelector = makeLibrarySelector('featured_playlists');
+const processProgressSelector = makeProcessProgressSelector(processKeys);
+const providersSelector = makeProvidersSelector('featured_playlists');
+const mapStateToProps = (state) => {
+ const [sortField, sortReverse] = getSortSelector(state, SORT_KEY, null);
+
+ return {
+ loading_progress: processProgressSelector(state),
+ uri_schemes: state.mopidy.uri_schemes,
+ featured_playlists: librarySelector(state, 'featured_playlists'),
+ providers: providersSelector(state),
+ view: state.ui.library_featured_playlists_view,
+ source: getLibrarySource(state, 'featured_playlists'),
+ sortField,
+ sortReverse,
+ };
+};
+const mapDispatchToProps = (dispatch) => ({
+ coreActions: bindActionCreators(coreActions, dispatch),
+ uiActions: bindActionCreators(uiActions, dispatch),
+ mopidyActions: bindActionCreators(mopidyActions, dispatch),
+ spotifyActions: bindActionCreators(spotifyActions, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(FeaturedPlaylists);
diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss
index 2dee6eb1..899c3092 100755
--- a/src/scss/components/_grid.scss
+++ b/src/scss/components/_grid.scss
@@ -18,7 +18,8 @@
&__thumbnail {
@include animate();
max-width: 100%;
-
+ z-index: unset;
+
&__image {
transform: scale(0.98);
}
@@ -38,38 +39,12 @@
padding-right: 6px;
}
}
+
- &:hover {
- .thumbnail {
- &__image {
- transform: scale(1.01);
-
- &--glow {
- /*@include blur(20px);*/
- transform: rotateX(6deg);
- }
- }
- }
- }
-
- &:active,
- &:focus {
- .thumbnail {
- &__image {
- @include noanimate();
- -moz-transform: scale(0.98);
- -webkit-transform: scale(0.98);
- transform: scale(0.98);
- }
- }
- }
- }
-
- &--tile {
- .grid__item {
+ &--tile {
position: relative;
-
- &__name {
+
+ .grid__item__name {
position: absolute;
bottom: 15%;
left: 0;
@@ -95,9 +70,32 @@
}
@include responsive(null, $bp_wide) {
- .grid__item {
- &__name {
- font-size: 16px;
+ .grid__item__name {
+ font-size: 16px;
+ }
+ }
+
+ &:hover {
+ .thumbnail {
+ &__image {
+ transform: scale(1.01);
+
+ &--glow {
+ /*@include blur(20px);*/
+ transform: rotateX(6deg);
+ }
+ }
+ }
+ }
+
+ &:active,
+ &:focus {
+ .thumbnail {
+ &__image {
+ @include noanimate();
+ -moz-transform: scale(0.98);
+ -webkit-transform: scale(0.98);
+ transform: scale(0.98);
}
}
}