Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Musify #109

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
17 changes: 15 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music Releases</title>

<!-- Emoji Favicon -->
<link
rel="icon"
type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Ctext y=%2214%22 font-size=%2214%22%3E🎵%3C/text%3E%3C/svg%3E"
/>

<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"
rel="stylesheet"
/>

<title>Musify Releases</title>
</head>
<body>
<div id="root"></div>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
6 changes: 0 additions & 6 deletions pull_request_template.md

This file was deleted.

26 changes: 26 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.album-grid {
display: grid;
gap: 20px;
padding: 20px;
}

/* 4 albums per row on desktop */
@media (min-width: 1024px) {
.album-grid {
grid-template-columns: repeat(4, 1fr);
}
}

/* 2 albums per row on tablet */
@media (min-width: 768px) and (max-width: 1023px) {
.album-grid {
grid-template-columns: repeat(2, 1fr);
}
}

/* 1 album per row on mobile */
@media (max-width: 767px) {
.album-grid {
grid-template-columns: 1fr;
}
}
20 changes: 16 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import data from "./data.json";

console.log(data);
import data from "./data.json"; // Spotify data
import { Header } from "./components/Header/Header.jsx";
import { Album } from "./components/Album/Album.jsx";
import "./App.css";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
return (
<>
<Header />

<div className="album-grid">
{/* Loop through each album in the data */}
{data.albums.items.map((album) => (
<Album key={album.id} album={album} />
))}
</div>
</>
);
};
Binary file added src/assets/icons/heroVideo.webm
Binary file not shown.
106 changes: 106 additions & 0 deletions src/components/Album/Album.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
.album {
background-color: #181818;
padding: 15px;
text-align: center;
color: #ffffff;
position: relative;
overflow: hidden;
}

.album-cover {
width: 100%;
border-radius: 4px;
transition: transform 0.3s ease, opacity 0.3s ease;
display: block;
margin-bottom: 20px;
}

.album-name {
font-size: 14px;
font-family: Helvetica, Arial, sans-serif;
}

.album:hover .album-cover {
transform: scale(1.05);
opacity: 0.7;
}

.album-cover-container {
position: relative;
width: 100%;
}

.icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 15px;
opacity: 0;
transition: opacity 0.3s ease;
align-items: center; /* Center icons vertically */
justify-content: center; /* Center icons horizontally */
}

.album:hover .icons {
opacity: 1;
}

.icon {
width: 24px;
height: 24px;
cursor: pointer;
transition: transform 0.3s ease;
filter: invert(1); /* Makes the SVG icons white */
display: flex;
align-items: center; /* Ensures icon contents are centered within */
justify-content: center;
}

.play-icon {
width: 32px;
height: 32px;
}

.icon:hover {
transform: scale(1.2);
}

.album-name {
font-size: 14px;
margin-top: 10px;
color: #ffffff;
}

.album-name a {
text-decoration: none;
color: #ffffff;
}

.album-name a:hover {
color: #ffffff;
text-decoration: underline;
}

/* Hide the checkbox */
.heart-checkbox {
display: none;
}

/* Default heart icon style */
.heart-icon {
width: 24px;
height: 24px;
background-image: url("../../assets/icons/heart.svg");
background-size: cover;
cursor: pointer;
transition: transform 0.3s ease, filter 0.3s ease;
}

/* Change heart icon color to green when checkbox is checked */
.heart-checkbox:checked + .heart-icon {
filter: invert(41%) sepia(73%) saturate(1500%) hue-rotate(90deg)
brightness(85%) contrast(95%);
transform: scale(1.1);
}
102 changes: 102 additions & 0 deletions src/components/Album/Album.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import PropTypes from "prop-types";
import { ArtistName } from "../ArtistName/ArtistName.jsx";
import playIcon from "../../assets/icons/play.svg";
import dotsIcon from "../../assets/icons/dots.svg";
import "./Album.css";

export const Album = ({ album }) => {
// Album component receives an "album" prop, which contains data (name, img and artists) for a SINGLE album.
return (
<div className="album">
<div className="album-cover-container">
{/* Container for the album cover image and icons overlay */}

{/* Album cover image */}
<img
src={album.images[0].url}
alt={album.name}
className="album-cover"
/>

{/* Icons overlay */}
<div className="icons">
{/* Hidden checkbox to toggle heart color */}
<input
type="checkbox"
id={`heart-${album.id}`}
className="heart-checkbox"
/>

{/* Label for heart icon, referencing checkbox */}
<label
htmlFor={`heart-${album.id}`}
className="icon heart-icon"
></label>

{/* Play Icon */}
<a
href={album.external_urls.spotify}
target="_blank"
rel="noopener noreferrer"
>
<img
src={playIcon}
alt="Play on Spotify"
className="icon play-icon"
/>
</a>

{/* More options icon */}
<img src={dotsIcon} alt="More options" className="icon" />
</div>
</div>

{/* Album name */}
<h3 className="album-name">
<a
href={album.external_urls.spotify}
target="_blank"
rel="noopener noreferrer"
>
{album.name}
</a>
</h3>

{/* Artist names */}
<p className="artist-names">
{/* it maps through the list of artists for the album, and for each artist, it creates an ArtistName component */}
{album.artists.map((artist, index) => (
<ArtistName
key={artist.id}
artist={artist}
showComma={index < album.artists.length - 1}
/>
))}
</p>
</div>
);
};

{
/* defines the structure of the album prop to ensure everything the Album component needs is provided. */
}
Album.propTypes = {
album: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
external_urls: PropTypes.shape({
spotify: PropTypes.string.isRequired,
}).isRequired,
images: PropTypes.arrayOf(
PropTypes.shape({
url: PropTypes.string.isRequired,
})
).isRequired,
artists: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
}).isRequired,
};
22 changes: 22 additions & 0 deletions src/components/ArtistName/ArtistName.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.artist-names a:hover {
color: #ffffff;
text-decoration: underline;
}

.artist-names {
font-size: 14px;
margin-top: 5px;
}

.artist-names a {
font-family: Helvetica, Arial, sans-serif;
color: #a0a0a0;
text-decoration: none; /* Remove underline by default */
transition: color 0.3s ease, text-decoration 0.3s ease;
}

/* Style for the & symbol to match artist names */
.artist-names .ampersand {
color: #a0a0a0; /* Same color as artist names */
font-family: Helvetica, Arial, sans-serif; /* Match font */
}
34 changes: 34 additions & 0 deletions src/components/ArtistName/ArtistName.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PropTypes } from "prop-types";
import "./ArtistName.css"; // Import the CSS file

export const ArtistName = ({ artist, showComma }) => {
return (
<span>
{/*
Display the artist’s name as a clickable link.
When clicked, it opens the artist's Spotify page in a new browser tab.
*/}
<a
href={artist.external_urls.spotify}
target="_blank"
rel="noopener noreferrer"
>
{/*
Shows the artist’s name on the screen
*/}
{artist.name}
</a>
{showComma && <span className="ampersand"> & </span>}
</span>
);
};

ArtistName.propTypes = {
artist: PropTypes.shape({
name: PropTypes.string.isRequired,
external_urls: PropTypes.shape({
spotify: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
showComma: PropTypes.bool,
};
34 changes: 34 additions & 0 deletions src/components/Header/Header.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.hero-header {
position: relative;
width: 100%;
height: 300px; /* Adjust height as needed */
overflow: hidden;
display: flex;
flex-direction: column; /* Stack items vertically */
align-items: center;
justify-content: center;
}

.hero-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1; /* Place the video behind the text */
}

/* Top-left position for the h1 title */
.header-title {
font-family: "Poppins", sans-serif;
position: absolute;
top: 20px; /* Adjust spacing from the top */
left: 20px; /* Adjust spacing from the left */
color: #ffffff;
font-size: 48px;
font-weight: bold;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.8); /* Add shadow for readability */
margin: 0;
z-index: 1;
}
Loading