Repository navigation
Expand file tree
/
Copy pathglobals.css
More file actions
127 lines (112 loc) · 4.19 KB
/
Copy pathglobals.css
File metadata and controls
127 lines (112 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@import "tailwindcss";
/* ---------------------------------------------------------------------------
Palette lifted from the invitation: blush border, cream card, soft charcoal
type. The stage behind the photos is a warm near-black so colour photos pop
without the screen glowing at people for three hours.
--------------------------------------------------------------------------- */
:root {
--stage: #14100f;
--cream: #fbf7f5;
--blush: #e8b4b0;
--blush-deep: #c98d89;
--ink: #2f2b2a;
/* Row loop durations. Deliberately non-harmonic so the three rows never
fall back into the same arrangement — see PhotoWall. */
--row-1: 430s;
--row-2: 497s;
--row-3: 571s;
/* Gap between photos, and the height of one row. The wall is three rows
inside a 100dvh grid with 0.5vh of padding top and bottom and 0.5vh
between rows, so each row loses 2vh in total. Frame widths are derived
from this rather than from a percentage height, which is what keeps the
track's max-content width honest — see .frame. */
--gap: 0.5vh;
--row-h: calc((100dvh - 2vh) / 3);
}
@theme inline {
--color-stage: var(--stage);
--color-cream: var(--cream);
--color-blush: var(--blush);
--color-blush-deep: var(--blush-deep);
--color-ink: var(--ink);
--font-display: var(--font-display);
--font-detail: var(--font-detail);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden; /* a TV never scrolls */
background: var(--stage);
overscroll-behavior: none;
}
body {
color: var(--cream);
font-family: var(--font-detail), ui-sans-serif, system-ui, sans-serif;
cursor: none; /* no mouse pointer parked on the screen */
}
/* --- the marquee ---------------------------------------------------------
Each track holds its photos twice. Sliding by exactly -50% lands copy two
precisely where copy one started, so the wrap is invisible and the animation
restarts from a clean transform every cycle rather than accumulating drift
over a three-hour run.
------------------------------------------------------------------------- */
@keyframes drift-left {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(-50%, 0, 0); }
}
@keyframes drift-right {
from { transform: translate3d(-50%, 0, 0); }
to { transform: translate3d(0, 0, 0); }
}
.track {
display: flex;
width: max-content;
height: 100%;
will-change: transform;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.track-left { animation-name: drift-left; }
.track-right { animation-name: drift-right; }
/* Every frame carries a definite width computed from the row height and its
own aspect ratio, and spaces itself with margin rather than the track using
`gap`. Both details matter:
- With only `height: 100%` and `aspect-ratio`, the percentage height is
indefinite while the browser measures `width: max-content`, so it sizes
the track off each <img>'s natural width instead of its rendered width.
The track then comes out far wider than its photos and the tail of every
row is empty — a long stretch of background with nothing in it.
- `gap` applies between items but not after the last one, so half a gap goes
missing and -50% of the track no longer equals one copy's pitch. A margin
on every frame keeps the pitch uniform across the wrap. */
.frame {
position: relative;
height: 100%;
width: calc(var(--row-h) * var(--ar));
margin-right: var(--gap);
flex: none;
overflow: hidden;
border-radius: 2px;
background: #241d1c;
box-shadow: 0 0 0 1px rgb(255 255 255 / 0.05);
}
.frame img {
display: block;
height: 100%;
width: 100%;
object-fit: cover;
}
/* Title card. A plain translucent fill rather than backdrop-filter: blurring a
region that has moving photos under it means re-compositing every frame, and
this has to survive hours on whatever hardware the TV has. The vignette
underneath does the legibility work instead. */
.card {
background: color-mix(in srgb, var(--cream) 96%, transparent);
box-shadow:
0 2px 60px rgb(0 0 0 / 0.55),
0 0 0 1px rgb(255 255 255 / 0.35);
}
@media (prefers-reduced-motion: reduce) {
.track { animation-duration: calc(var(--dur) * 3); }
}