41. 7 Dando estilos
Tutorial 41.7: Dando estilos a los componentes de películas
Configurando los estilos para nuestra aplicación de películas
Ahora que tenemos nuestros componentes funcionando, es hora de darles estilos profesionales para que nuestra aplicación sea visualmente atractiva y responsiva.
Paso 1: Estructura final de ContextMovieCard
ContextMovieCard.jsx actualizado
jsx
import { useEffect, useState } from "react";
import { get } from "../data/httpClient";
import { MovieCard } from "../components/MovieCard";
import "./ContextMovieCard.css";
export function ContextMovieCard() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
get("/discover/movie").then((data) => {
setMovies(data.results);
console.log("Películas obtenidas:", data);
setLoading(false);
});
}, []);
if (loading) {
return (
<div className="loading">
<p>Cargando películas...</p>
</div>
);
}
return (
<div className="container">
<ul className="movies-grid">
{movies.map((movie) => (
<MovieCard
key={movie.id}
movie={movie}
/>
))}
</ul>
</div>
);
}
Paso 2: MovieCard.jsx con estilos integrados
jsx
import "./MovieCard.css";
export function MovieCard({ movie }) {
// Construir URL de la imagen
const imageUrl = movie.poster_path
? `https://image.tmdb.org/t/p/w300${movie.poster_path}`
: "";
return (
<li className="movie-card">
<img
src={imageUrl}
alt={movie.title}
className="movie-image"
width="230"
height="345"
/>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
</div>
</li>
);
}
Paso 3: Estilos para ContextMovieCard
ContextMovieCard.css
css
/* Contenedor principal */
.container {
padding: 40px;
max-width: 1400px;
margin: 0 auto;
}
/* Grid de películas */
.movies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 30px;
justify-content: center;
padding: 0;
margin: 0;
list-style: none;
}
/* Estado de carga */
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
font-size: 1.2rem;
color: #ffd700;
background: rgba(106, 13, 173, 0.1);
border-radius: 10px;
margin: 40px;
}
.loading::before {
content: "";
width: 40px;
height: 40px;
border: 4px solid rgba(255, 215, 0, 0.3);
border-top: 4px solid #ffd700;
border-radius: 50%;
margin-right: 15px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 20px;
}
.movies-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px;
}
}
@media (max-width: 560px) {
.movies-grid {
grid-template-columns: 1fr;
max-width: 100%;
}
.container {
padding: 15px;
}
}
Paso 4: Estilos completos para MovieCard
MovieCard.css
css
/* Reset de estilos para lista */
.movie-card {
list-style: none;
margin: 0;
padding: 0;
font-size: 1.5rem; /* 16px equivalent */
text-align: center;
transition: all 0.5s ease;
border-radius: 15px;
overflow: hidden;
background: linear-gradient(135deg, #16213e 0%, #0f3460 100%);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
position: relative;
cursor: pointer;
}
/* Efecto hover */
.movie-card:hover {
transform: translateY(-30px);
opacity: 0.95;
box-shadow: 0 20px 40px rgba(255, 215, 0, 0.2);
z-index: 10;
}
.movie-card:hover .movie-image {
transform: scale(1.05);
}
/* Contenedor de la imagen */
.movie-image {
width: 100%;
height: 345px;
object-fit: cover;
display: block;
transition: transform 0.5s ease;
background: linear-gradient(45deg, #1a1a2e, #16213e);
}
/* Placeholder para imágenes faltantes */
.movie-image:not([src]),
.movie-image[src=""] {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(45deg, #1a1a2e, #16213e);
color: rgba(255, 255, 255, 0.3);
font-size: 4rem;
}
.movie-image:not([src])::after,
.movie-image[src=""]::after {
content: "🎬";
}
/* Información de la película */
.movie-info {
padding: 20px 15px;
background: rgba(22, 33, 62, 0.95);
backdrop-filter: blur(10px);
border-top: 2px solid rgba(255, 215, 0, 0.3);
}
.movie-title {
margin: 0;
color: #ffffff;
font-size: 1.1rem;
font-weight: 600;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
/* Badge de calificación (opcional) */
.rating-badge {
position: absolute;
top: 15px;
right: 15px;
background: rgba(255, 215, 0, 0.9);
color: #000;
padding: 6px 12px;
border-radius: 20px;
font-weight: bold;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
z-index: 5;
}
/* Efecto de brillo en hover */
.movie-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 215, 0, 0.1) 50%,
transparent 70%
);
z-index: 1;
opacity: 0;
transition: opacity 0.5s ease;
}
.movie-card:hover::before {
opacity: 1;
animation: shine 1.5s ease;
}
@keyframes shine {
0% {
background-position: -200% center;
}
100% {
background-position: 200% center;
}
}
/* Responsive */
@media (max-width: 768px) {
.movie-card {
font-size: 1.3rem;
}
.movie-image {
height: 300px;
}
.movie-info {
padding: 15px 10px;
}
.movie-title {
font-size: 1rem;
}
}
@media (max-width: 560px) {
.movie-card {
max-width: 90%;
margin: 0 auto;
}
.movie-image {
width: 90%;
height: auto;
max-height: 400px;
margin: 0 auto;
}
.movie-card:hover {
transform: translateY(-15px);
}
}
Paso 5: Estilos globales actualizados
App.css
css
/* Importar fuente */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap');
/* Reset global */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
color: #ffffff;
min-height: 100vh;
line-height: 1.6;
}
/* Contenedor principal de la app */
.App {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
header {
padding: 2rem 1rem;
text-align: center;
background: linear-gradient(135deg,
rgba(22, 33, 62, 0.95) 0%,
rgba(15, 52, 96, 0.95) 100%);
backdrop-filter: blur(10px);
border-bottom: 3px solid #ffd700;
position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.title {
font-size: 3rem;
background: linear-gradient(90deg, #ffd700, #ffed4e, #ffd700);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 2px 10px rgba(255, 215, 0, 0.3);
animation: shine 3s linear infinite;
margin-bottom: 0.5rem;
font-weight: 800;
letter-spacing: 1px;
}
@keyframes shine {
0% {
background-position: 0% center;
}
100% {
background-position: 200% center;
}
}
/* Main content */
main {
flex-grow: 1;
padding: 2rem 0;
}
/* Scrollbar personalizada */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: rgba(22, 33, 62, 0.3);
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #ffd700, #ffed4e);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #ffed4e, #ffd700);
}
/* Responsive */
@media (max-width: 1024px) {
.title {
font-size: 2.5rem;
}
}
@media (max-width: 768px) {
.title {
font-size: 2rem;
}
header {
padding: 1.5rem 1rem;
}
main {
padding: 1rem 0;
}
}
@media (max-width: 480px) {
.title {
font-size: 1.8rem;
}
header {
padding: 1rem;
}
}
Paso 6: Estructura final del proyecto
text
src/
├── components/
│ ├── ContextMovieCard.jsx
│ ├── ContextMovieCard.css
│ ├── MovieCard.jsx
│ └── MovieCard.css
├── pages/
│ └── LandingPage.jsx
├── routers/
│ └── routes.jsx
├── App.jsx
└── App.css
Paso 7: LandingPage.jsx actualizado
jsx
import { ContextMovieCard } from "../components/ContextMovieCard";
import "./LandingPage.css";
export function LandingPage() {
return (
<div className="landing-page">
<div className="hero-section">
<h2>Descubre las mejores películas</h2>
<p>Explora nuestro catálogo y encuentra tu próxima película favorita</p>
</div>
<section className="movies-section">
<ContextMovieCard />
</section>
<footer className="app-footer">
<p>© 2024 Películas App - Desarrollado con React y TMDb API</p>
</footer>
</div>
);
}
Paso 8: LandingPage.css (opcional)
css
.landing-page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.hero-section {
text-align: center;
padding: 3rem 1rem;
background: linear-gradient(135deg,
rgba(255, 215, 0, 0.1) 0%,
rgba(255, 237, 78, 0.05) 100%);
border-bottom: 1px solid rgba(255, 215, 0, 0.2);
margin-bottom: 2rem;
}
.hero-section h2 {
font-size: 2.2rem;
color: #ffd700;
margin-bottom: 1rem;
font-weight: 700;
}
.hero-section p {
font-size: 1.1rem;
color: #b0b0b0;
max-width: 600px;
margin: 0 auto;
}
.movies-section {
flex-grow: 1;
}
.app-footer {
text-align: center;
padding: 1.5rem;
background: rgba(22, 33, 62, 0.8);
border-top: 1px solid rgba(255, 215, 0, 0.2);
margin-top: 3rem;
font-size: 0.9rem;
color: #a0a0a0;
}
@media (max-width: 768px) {
.hero-section {
padding: 2rem 1rem;
}
.hero-section h2 {
font-size: 1.8rem;
}
.hero-section p {
font-size: 1rem;
}
}
Resultado final
¡Excelente! Ahora tienes una aplicación de películas con:
✅ Diseño profesional con gradientes y efectos modernos
✅ Animaciones suaves en hover y transiciones
✅ Responsive completo que funciona en todos los dispositivos
✅ Estilos modularizados por componente
✅ Efectos visuales atractivos como shine animation
✅ Scrollbar personalizada para mejor experiencia
✅ Header sticky con gradiente y blur effect
Tips adicionales:
Optimización: Las imágenes tienen lazy loading automático
Accesibilidad: Textos alternativos para imágenes
Performance: Transiciones CSS optimizadas
Mantenibilidad: Estilos separados por componentes
¡Tu aplicación ahora tiene un aspecto profesional y moderno!
Comentarios
Publicar un comentario