feat(web): movie detail page at /movies/{id}
This commit is contained in:
@@ -1284,6 +1284,274 @@ body {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* ---- movie page (§9.6, issue #149) ------------------------------------- */
|
||||
|
||||
/* The hero is one machined module carrying what the film IS. The backdrop
|
||||
hotlinks from image.tmdb.org (§9.6) and sinks into the console charcoal
|
||||
under a scrim, so the readouts keep their contrast — a photo never sits
|
||||
behind text bare. */
|
||||
|
||||
.movie-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 0 0 var(--space-8);
|
||||
}
|
||||
|
||||
/* the backdrop layer: painted only when detail supplied one */
|
||||
.movie-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: var(--backdrop);
|
||||
background-size: cover;
|
||||
background-position: center 20%;
|
||||
opacity: 0;
|
||||
transition: opacity 400ms var(--ease-out);
|
||||
}
|
||||
|
||||
.movie-hero.has-backdrop::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* the scrim: ground at the bottom, heavy everywhere the text sits */
|
||||
.movie-hero::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(
|
||||
to top,
|
||||
oklch(from var(--ground) l c h / 92%) 0%,
|
||||
oklch(from var(--ground) l c h / 72%) 45%,
|
||||
oklch(from var(--ground) l c h / 40%) 100%
|
||||
),
|
||||
oklch(from var(--ground) l c h / 35%);
|
||||
}
|
||||
|
||||
.movie-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
gap: var(--space-6);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* the poster is framed evidence, like every other image on a panel */
|
||||
.movie-poster {
|
||||
flex: none;
|
||||
width: 10.5rem;
|
||||
aspect-ratio: 2 / 3;
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 2px 12px oklch(from var(--lowlight) l c h / 75%);
|
||||
}
|
||||
|
||||
.movie-info {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-6) 0;
|
||||
}
|
||||
|
||||
.movie-idline {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
/* the page's voice: bigger than a deck title, same display face */
|
||||
.movie-title {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.movie-year {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.movie-chips {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* rating and meta are readouts on one line each; the star is drawn */
|
||||
.movie-rating {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.star {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
fill: var(--ink-muted);
|
||||
}
|
||||
|
||||
.movie-meta {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.movie-tagline {
|
||||
margin: var(--space-1) 0 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.movie-overview {
|
||||
max-width: 65ch;
|
||||
margin: var(--space-1) 0 0;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.movie-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.movie-actions .add-note[data-tone="fault"] {
|
||||
margin: 0;
|
||||
color: var(--signal-fault);
|
||||
}
|
||||
|
||||
/* external links wear the control skin; anchors must drop the underline */
|
||||
.movie-actions a.control {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* library controls follow the hero on their own row: state left, errands right */
|
||||
.movie-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2) var(--space-3);
|
||||
padding: var(--space-2) var(--space-1);
|
||||
}
|
||||
|
||||
.movie-controls .control {
|
||||
min-height: 2.25rem;
|
||||
padding: 0 var(--space-3);
|
||||
}
|
||||
|
||||
/* wanted and blocked read as rules, pressed = on — the tracked-toggle idiom */
|
||||
.movie-controls .control[aria-pressed="true"] {
|
||||
color: var(--accent-bright);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.movie-root-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.movie-root {
|
||||
width: auto;
|
||||
min-width: 11rem;
|
||||
}
|
||||
|
||||
.movie-controls-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* cast: top billed, each linking out to TMDB (§9.6) */
|
||||
.cast-grid {
|
||||
margin: 0;
|
||||
padding: var(--space-3) var(--space-1) 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
|
||||
gap: var(--space-4) var(--space-2);
|
||||
}
|
||||
|
||||
.cast-link {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
justify-items: start;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cast-link:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.cast-photo {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel-raised);
|
||||
transition: border-color 150ms var(--ease-out);
|
||||
}
|
||||
|
||||
.cast-photo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* no profile photo: dead air, not a broken frame */
|
||||
.cast-photo-none::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 40%;
|
||||
aspect-ratio: 1;
|
||||
margin: 30% auto 0;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.cast-link:hover .cast-photo,
|
||||
.cast-link:focus-visible .cast-photo {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.cast-name {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.cast-character {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* on-disk rows share the deck's rel grammar: name leads, chips trail */
|
||||
.disk-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.rel[data-tone="fault"] {
|
||||
color: var(--signal-fault);
|
||||
}
|
||||
|
||||
/* ---- small screens --------------------------------------------------- */
|
||||
|
||||
@media (max-width: 46rem) {
|
||||
@@ -1357,6 +1625,35 @@ body {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* the hero stacks: poster above the info block, both full width */
|
||||
.movie-body {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.movie-poster {
|
||||
width: 8.5rem;
|
||||
}
|
||||
|
||||
.movie-info {
|
||||
padding-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* state controls wrap to their own line, errands stay right-aligned */
|
||||
.movie-controls-space {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.movie-root {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cast-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
|
||||
}
|
||||
|
||||
.board {
|
||||
padding: var(--space-6) var(--space-4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user