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:

  1. ✅ Diseño profesional con gradientes y efectos modernos

  2. ✅ Animaciones suaves en hover y transiciones

  3. ✅ Responsive completo que funciona en todos los dispositivos

  4. ✅ Estilos modularizados por componente

  5. ✅ Efectos visuales atractivos como shine animation

  6. ✅ Scrollbar personalizada para mejor experiencia

  7. ✅ Header sticky con gradiente y blur effect

Tips adicionales:

  1. Optimización: Las imágenes tienen lazy loading automático

  2. Accesibilidad: Textos alternativos para imágenes

  3. Performance: Transiciones CSS optimizadas

  4. Mantenibilidad: Estilos separados por componentes

¡Tu aplicación ahora tiene un aspecto profesional y moderno!


Comentarios

Entradas más populares de este blog

11. Creando un proyecto react con create-react-app

29. useEffect

38. 4 Routers