feat: sign interlude, window plate and a two-button control bay
The sign closes in over the road on a gantry truss, arcs into a wider window and hands off to a flat 2D plate that mirrors its face; content steps aside for the interlude and rises after. The bay keeps sound and settings; volume and Pause motion move into the settings popover.
This commit is contained in:
@@ -17,12 +17,22 @@
|
||||
/* mask-position percentages resolve against (box - layer), so the cut-out
|
||||
needs its own x: 50% centres it, 100% - n puts its right edge n in. */
|
||||
--window-mask-x: 50%;
|
||||
--window-y: calc((var(--gantry-h) - var(--window-h)) / 2);
|
||||
/* The beam's centre bulges below its lower edge around the window. */
|
||||
--bulge: 0px;
|
||||
--window-y: calc((var(--gantry-h) + var(--bulge) - var(--window-h)) / 2);
|
||||
--gantry-steel-1: #1d1230;
|
||||
--gantry-steel-2: #120c22;
|
||||
--gantry-rail: rgb(214 140 222 / 30%);
|
||||
--gantry-rail: rgb(214 140 222 / 40%);
|
||||
--site-navigation-block-size: var(--gantry-h);
|
||||
}
|
||||
/* Wide: a 320 x 72 window in an 8 px bulge at the centre of the beam. */
|
||||
@media (min-width: 1280px) {
|
||||
:root {
|
||||
--window-w: 320px;
|
||||
--window-h: 72px;
|
||||
--bulge: 8px;
|
||||
}
|
||||
}
|
||||
/* Mid widths: the window sits beside the controls bay. */
|
||||
@media (min-width: 768px) and (max-width: 1279px) {
|
||||
:root {
|
||||
@@ -61,27 +71,31 @@ html .site-navigation[data-enhanced='true'][data-docked='true'] {
|
||||
html .site-navigation::before,
|
||||
html .site-navigation[data-enhanced='true']::before,
|
||||
html .site-navigation[data-enhanced='true'][data-docked='true']::before {
|
||||
inset: 0;
|
||||
inset: 0 0 auto;
|
||||
block-size: calc(var(--gantry-h) + var(--bulge));
|
||||
transform: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
/* Rivet rails top and bottom, brushed steel between, a cut-out window. */
|
||||
background:
|
||||
repeating-linear-gradient(90deg, rgb(255 190 240 / 26%) 0 2px, transparent 2px 26px) 0 5px / 100% 2px no-repeat,
|
||||
repeating-linear-gradient(90deg, rgb(255 190 240 / 18%) 0 2px, transparent 2px 26px) 13px calc(100% - 6px) / 100% 2px no-repeat,
|
||||
linear-gradient(180deg, rgb(255 255 255 / 5%), transparent 30% 70%, rgb(0 0 0 / 30%)),
|
||||
repeating-linear-gradient(90deg, rgb(255 190 240 / 40%) 0 2px, transparent 2px 26px) 0 5px / 100% 2px no-repeat,
|
||||
repeating-linear-gradient(90deg, rgb(255 190 240 / 32%) 0 2px, transparent 2px 26px) 13px calc(var(--gantry-h) - 6px) / 100% 2px no-repeat,
|
||||
linear-gradient(180deg, rgb(255 255 255 / 5%), transparent 30% 70%, rgb(0 0 0 / 30%)) 0 0 / 100% var(--gantry-h) no-repeat,
|
||||
linear-gradient(100deg, var(--gantry-steel-1), var(--gantry-steel-2) 55%, #170f2a);
|
||||
box-shadow:
|
||||
0 1px 0 var(--gantry-rail),
|
||||
0 14px 34px -8px rgb(6 1 16 / 70%);
|
||||
/* Window hole XOR (bulge + beam). The bulge is 0 px tall below 1280. */
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0),
|
||||
linear-gradient(#000 0 0) var(--window-mask-x) var(--window-y) / var(--window-w) var(--window-h) no-repeat;
|
||||
-webkit-mask-composite: xor;
|
||||
linear-gradient(#000 0 0) var(--window-mask-x) var(--window-y) / var(--window-w) var(--window-h) no-repeat,
|
||||
linear-gradient(#000 0 0) 50% 0 / calc(var(--window-w) + 56px) 100% no-repeat,
|
||||
linear-gradient(#000 0 0) 0 0 / 100% var(--gantry-h) no-repeat;
|
||||
-webkit-mask-composite: xor, source-over;
|
||||
mask:
|
||||
linear-gradient(#000 0 0),
|
||||
linear-gradient(#000 0 0) var(--window-mask-x) var(--window-y) / var(--window-w) var(--window-h) no-repeat;
|
||||
mask-composite: exclude;
|
||||
linear-gradient(#000 0 0) var(--window-mask-x) var(--window-y) / var(--window-w) var(--window-h) no-repeat,
|
||||
linear-gradient(#000 0 0) 50% 0 / calc(var(--window-w) + 56px) calc(var(--gantry-h) + var(--bulge)) no-repeat,
|
||||
linear-gradient(#000 0 0) 0 0 / 100% var(--gantry-h) no-repeat;
|
||||
mask-composite: exclude, add;
|
||||
}
|
||||
|
||||
/* Brand plate in the left bay (decorative; the h1 carries the name). */
|
||||
@@ -258,8 +272,16 @@ html .site-navigation::after {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: 6px;
|
||||
}
|
||||
html[data-gantry='docked'] .gantry-window,
|
||||
/* Incoming: the lip takes the arriving chapter's sign colour. Docked: amber. */
|
||||
html[data-gantry='incoming'] .gantry-window {
|
||||
box-shadow:
|
||||
0 0 0 1px var(--sign-accent, #ff83c2),
|
||||
0 0 0 4px rgb(10 5 20 / 92%),
|
||||
0 0 0 5px color-mix(in srgb, var(--sign-accent, #ff83c2) 45%, transparent),
|
||||
0 0 26px color-mix(in srgb, var(--sign-accent, #ff83c2) 30%, transparent),
|
||||
inset 0 0 18px rgb(6 2 16 / 40%);
|
||||
}
|
||||
html[data-gantry='docked'] .gantry-window {
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(255 214 147 / 70%),
|
||||
0 0 0 4px rgb(10 5 20 / 92%),
|
||||
@@ -267,12 +289,49 @@ html[data-gantry='incoming'] .gantry-window {
|
||||
0 0 26px rgb(255 160 220 / 22%),
|
||||
inset 0 0 18px rgb(6 2 16 / 40%);
|
||||
}
|
||||
/* A faint amber cone of light falls from the window onto the road. */
|
||||
.gantry-window__cone {
|
||||
position: absolute;
|
||||
inset: 100% -8% auto;
|
||||
block-size: clamp(90px, 14vh, 120px);
|
||||
background: linear-gradient(180deg, rgb(255 214 147 / 6%), rgb(255 214 147 / 2.5%) 55%, transparent);
|
||||
clip-path: polygon(6% 0, 94% 0, 100% 100%, 0 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Hover or focus names where the window drives to. */
|
||||
.gantry-window__hint {
|
||||
position: absolute;
|
||||
inset: calc(100% + 12px) 50% auto auto;
|
||||
translate: 50% -4px;
|
||||
padding: 0.38rem 0.6rem;
|
||||
border: 1px solid rgb(255 214 147 / 45%);
|
||||
border-radius: 3px;
|
||||
background: #170e29;
|
||||
color: #ffe3b8;
|
||||
font: 650 0.64rem/1 var(--mono);
|
||||
letter-spacing: 0.14em;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 160ms ease, translate 320ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.gantry-window__hint::before { content: attr(data-hint) / ''; }
|
||||
.gantry-window:is(:hover, :focus-visible) .gantry-window__hint {
|
||||
opacity: 1;
|
||||
translate: 50% 0;
|
||||
}
|
||||
@media (max-width: 359px) {
|
||||
.gantry-window { display: none; }
|
||||
}
|
||||
|
||||
/* ---------- The dash read-out, shown through the window ---------- */
|
||||
/* ---------- The flat exit plate, shown through the window ---------- */
|
||||
/* Between signs, and once a sign has docked, the window shows a flat 2D
|
||||
twin of the sign: the exit tab in the sign colour, the chapter, a
|
||||
next-exit chevron breathing at 0.5 Hz and a route rule along its lower
|
||||
edge. It steps aside only while a 3D sign is arriving, or while the
|
||||
pointer calls up the 3D twin. */
|
||||
html .journey-hud {
|
||||
--sign-accent: #ff83c2;
|
||||
position: fixed;
|
||||
z-index: var(--z-hud);
|
||||
inset: var(--window-y) auto auto 0;
|
||||
@@ -282,25 +341,44 @@ html .journey-hud {
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-rows: 1fr auto;
|
||||
align-items: center;
|
||||
column-gap: 9px;
|
||||
column-gap: 10px;
|
||||
row-gap: 3px;
|
||||
inline-size: var(--window-w);
|
||||
max-inline-size: none;
|
||||
block-size: var(--window-h);
|
||||
min-block-size: 0;
|
||||
padding: 7px 11px 6px;
|
||||
padding: 8px 12px 9px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: linear-gradient(180deg, rgb(20 10 34 / 94%), rgb(12 7 24 / 94%));
|
||||
background:
|
||||
linear-gradient(var(--sign-accent), var(--sign-accent)) 0 100% / calc(var(--route, 0) * 100%) 2px no-repeat,
|
||||
linear-gradient(rgb(217 200 244 / 14%), rgb(217 200 244 / 14%)) 0 100% / 100% 2px no-repeat,
|
||||
linear-gradient(180deg, #170b28, #0e0820);
|
||||
background-color: #120a22;
|
||||
box-shadow: none;
|
||||
color: #cbb9de;
|
||||
letter-spacing: 0.06em;
|
||||
pointer-events: none;
|
||||
transition: opacity 260ms ease, transform 360ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
transition:
|
||||
opacity 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
background-size 720ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
/* The window's left edge, without reading the nav's own custom properties. */
|
||||
@media (min-width: 1280px) {
|
||||
html .journey-hud { inset-inline-start: calc(50% - var(--window-w) / 2); }
|
||||
/* Wide: the plate mirrors the docked 3D face (a 4:1 face, 288 x 72 in the
|
||||
window): a 42 x 52 tab, the label from 77 px, the lane rule under it,
|
||||
so the 3D face hands off to it in place. */
|
||||
html .journey-hud {
|
||||
grid-template-rows: 1fr;
|
||||
column-gap: 9px;
|
||||
padding: 10px 30px 10px 26px;
|
||||
background:
|
||||
linear-gradient(var(--sign-accent), var(--sign-accent)) 77px calc(100% - 13px) / calc(var(--route, 0) * (100% - 107px)) 2px no-repeat,
|
||||
linear-gradient(rgb(217 200 244 / 16%), rgb(217 200 244 / 16%)) 77px calc(100% - 13px) / calc(100% - 107px) 2px no-repeat,
|
||||
linear-gradient(180deg, #170b28, #0e0820);
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 1279px) {
|
||||
html .journey-hud { inset-inline-start: calc(100% - var(--bay-r) - var(--window-w) - 6px); }
|
||||
@@ -309,124 +387,198 @@ html .journey-hud {
|
||||
html .journey-hud { inset-inline-start: var(--bay-l); }
|
||||
}
|
||||
html .journey-hud[data-exit]::before {
|
||||
content: 'EXIT\A' attr(data-exit) / '';
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
align-self: center;
|
||||
font-size: 0.6rem;
|
||||
grid-row: 1 / -1;
|
||||
align-self: stretch;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 0 0.5rem;
|
||||
clip-path: var(--exit-chamfer);
|
||||
background: var(--sign-accent);
|
||||
color: #160b22;
|
||||
font: 760 0.56rem/1.15 var(--mono);
|
||||
letter-spacing: 0.14em;
|
||||
text-align: center;
|
||||
white-space: pre;
|
||||
}
|
||||
html .journey-hud__section {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
overflow: hidden;
|
||||
max-inline-size: none;
|
||||
color: #fff4fb;
|
||||
font: 620 0.98rem/1 var(--sans);
|
||||
color: #fff2df;
|
||||
font: 650 1.02rem/1 var(--sans);
|
||||
letter-spacing: -0.01em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
html .journey-hud__section::before { display: none; }
|
||||
html .journey-hud[data-clock]::after {
|
||||
/* Next-exit chevron; at the destination it offers the drive again. */
|
||||
html .journey-hud::after {
|
||||
content: '\203A' / '';
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
padding-inline-start: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: #a998bd;
|
||||
font: 560 0.58rem/1 var(--mono);
|
||||
color: var(--sign-accent);
|
||||
font: 500 1.5rem/1 var(--sans);
|
||||
}
|
||||
html .journey-hud__route {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
gap: 0.35rem;
|
||||
html .journey-hud[data-arrived='true']::after {
|
||||
content: 'AGAIN \21BA' / '';
|
||||
color: var(--focus);
|
||||
font: 700 0.56rem/1 var(--mono);
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
html .journey-hud:not([data-arrived='true'])::after {
|
||||
animation: hud-chevron 2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
@keyframes hud-chevron {
|
||||
0%, 100% { opacity: 0.45; transform: translateX(0); }
|
||||
50% { opacity: 1; transform: translateX(3px); }
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
html .journey-hud[data-exit]::before { inline-size: 42px; padding: 0; font-size: 0.6rem; }
|
||||
/* The face fits its label to 1250 of 2048 px, from 236 down to 124 px; in
|
||||
the window that is about 314 px / characters, clamped to 17-33 px. */
|
||||
html .journey-hud__section {
|
||||
font-size: clamp(17px, calc(314px / var(--label-chars, 10)), 33px);
|
||||
padding-block-end: 6px;
|
||||
}
|
||||
}
|
||||
/* The route figures stay in the text (tests and the SSR read them) but the
|
||||
plate shows progress as the rule along its lower edge, like the sign. */
|
||||
html .journey-hud__route,
|
||||
html .journey-hud__arrival {
|
||||
position: absolute;
|
||||
inline-size: 1px;
|
||||
block-size: 1px;
|
||||
overflow: hidden;
|
||||
color: #a998bd;
|
||||
font: 560 0.56rem/1 var(--mono);
|
||||
letter-spacing: 0.12em;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
html .journey-hud__route span { font-size: inherit; color: inherit; display: inline; }
|
||||
html .journey-hud__route strong { color: var(--focus); font-size: inherit; }
|
||||
html .journey-hud__route[data-next]::after {
|
||||
content: '\00b7 ' attr(data-next) / '';
|
||||
overflow: hidden;
|
||||
color: #d6c3ea;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
}
|
||||
html .journey-hud__arrival {
|
||||
grid-column: 3;
|
||||
grid-row: 2;
|
||||
display: block;
|
||||
color: var(--focus);
|
||||
font: 720 0.56rem/1 var(--mono);
|
||||
letter-spacing: 0.16em;
|
||||
}
|
||||
/* The read-out yields to the docked sign and returns once it has passed. */
|
||||
html[data-gantry='incoming'] .journey-hud,
|
||||
html[data-gantry='docked'] .journey-hud,
|
||||
html[data-gantry='passing'] .journey-hud {
|
||||
/* The plate yields while a 3D sign arrives, and to the pointer's 3D twin. */
|
||||
html[data-gantry='incoming'] .journey-hud {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
html[data-gantry]:has(.gantry-window:hover) .journey-hud { opacity: 0; }
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
html .journey-hud {
|
||||
grid-template-rows: 1fr;
|
||||
padding: 0 8px;
|
||||
padding: 4px 8px 5px 4px;
|
||||
column-gap: 7px;
|
||||
}
|
||||
html .journey-hud__route,
|
||||
html .journey-hud__arrival,
|
||||
html .journey-hud[data-clock]::after { display: none; }
|
||||
html .journey-hud[data-exit]::before { display: block; font-size: 0.56rem; }
|
||||
html .journey-hud[data-exit]::before { content: attr(data-exit) / ''; display: grid; font-size: 0.6rem; padding: 0 0.4rem; }
|
||||
html .journey-hud__section { font-size: 0.82rem; }
|
||||
html .journey-hud::after { font-size: 1.15rem; }
|
||||
html .journey-hud[data-arrived='true']::after { content: '\21BA' / ''; font-size: 0.9rem; }
|
||||
}
|
||||
@media (max-width: 359px) {
|
||||
html .journey-hud { display: none; }
|
||||
}
|
||||
|
||||
/* ---------- Drive controls in the right bay ---------- */
|
||||
/* Two monochrome lilac tiles: the soundtrack (a three-bar equaliser that
|
||||
moves, at most 1 Hz, while it plays) and the settings. Each button stays
|
||||
a 44 px target; the tile inside it is drawn 36 px. Active is amber. */
|
||||
html .drive-controls {
|
||||
--tile-ink: #d9c8f4;
|
||||
--tile-face: #1b1130;
|
||||
--tile-edge: rgb(217 200 244 / 30%);
|
||||
position: fixed;
|
||||
z-index: var(--z-popover);
|
||||
inset: calc((var(--gantry-h) - 44px) / 2) max(12px, env(safe-area-inset-right)) auto auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
inline-size: auto;
|
||||
max-inline-size: none;
|
||||
gap: 4px;
|
||||
gap: 0;
|
||||
}
|
||||
html .drive-controls__dock {
|
||||
padding: 0;
|
||||
gap: 2px;
|
||||
gap: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
background-color: var(--gantry-steel-2);
|
||||
box-shadow: none;
|
||||
}
|
||||
html .drive-start,
|
||||
html .drive-control-button,
|
||||
html .drive-controls__settings-trigger {
|
||||
border: 1px solid rgb(214 140 222 / 26%);
|
||||
border-radius: 4px;
|
||||
background: rgb(24 14 40 / 92%);
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
inline-size: 44px;
|
||||
min-inline-size: 44px;
|
||||
block-size: 44px;
|
||||
min-block-size: 44px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background:
|
||||
linear-gradient(var(--tile-face), var(--tile-face)) center / 34px 34px no-repeat,
|
||||
linear-gradient(var(--tile-edge), var(--tile-edge)) center / 36px 36px no-repeat;
|
||||
box-shadow: none;
|
||||
color: #d9c9e6;
|
||||
color: var(--tile-ink);
|
||||
letter-spacing: 0;
|
||||
transition: color 160ms ease, background-size 160ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
html .drive-control-button:hover {
|
||||
border-color: rgb(255 160 220 / 55%);
|
||||
background: #2f1c45;
|
||||
color: #ffd1ec;
|
||||
html .drive-start:hover:not(:disabled),
|
||||
html .drive-control-button:hover,
|
||||
html .drive-controls__settings-trigger:hover {
|
||||
--tile-edge: rgb(217 200 244 / 62%);
|
||||
--tile-face: #261843;
|
||||
transform: none;
|
||||
color: #f1e8ff;
|
||||
}
|
||||
html .drive-start {
|
||||
min-inline-size: 0;
|
||||
gap: 10px;
|
||||
padding-inline: 12px 14px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.12em;
|
||||
html .drive-control-button:focus-visible,
|
||||
html .drive-start:focus-visible {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
@media (max-width: 1279px) {
|
||||
html .drive-start { inline-size: 44px; padding: 0; justify-content: center; }
|
||||
html .drive-start > span { display: none; }
|
||||
html .drive-controls__settings-trigger[aria-expanded='true'],
|
||||
html .drive-controls[data-drive-state='running'][data-muted='false'] .drive-sound {
|
||||
--tile-edge: rgb(255 214 147 / 70%);
|
||||
color: var(--focus);
|
||||
}
|
||||
/* Tooltips and popovers open downward from the beam. */
|
||||
html .drive-control-button svg { inline-size: 18px; block-size: 18px; }
|
||||
|
||||
/* The equaliser glyph. */
|
||||
.drive-eq {
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
block-size: 14px;
|
||||
}
|
||||
.drive-eq i {
|
||||
inline-size: 3px;
|
||||
block-size: 100%;
|
||||
border-radius: 1px;
|
||||
background: currentColor;
|
||||
transform-origin: bottom;
|
||||
}
|
||||
.drive-eq i:nth-child(1) { transform: scaleY(0.55); }
|
||||
.drive-eq i:nth-child(3) { transform: scaleY(0.75); }
|
||||
.drive-controls[data-muted='true'] .drive-eq i { transform: scaleY(0.22); }
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.drive-controls[data-drive-state='running'][data-muted='false'] .drive-eq i {
|
||||
animation: drive-eq 1s ease-in-out infinite alternate;
|
||||
}
|
||||
.drive-controls[data-drive-state='running'][data-muted='false'] .drive-eq i:nth-child(2) { animation-delay: -0.35s; }
|
||||
.drive-controls[data-drive-state='running'][data-muted='false'] .drive-eq i:nth-child(3) { animation-delay: -0.7s; }
|
||||
}
|
||||
@keyframes drive-eq {
|
||||
from { transform: scaleY(0.3); }
|
||||
to { transform: scaleY(1); }
|
||||
}
|
||||
|
||||
/* Tooltips and the settings popover open downward from the beam. */
|
||||
html .drive-control-button::after {
|
||||
inset: calc(100% + 0.5rem) auto auto 50%;
|
||||
transform: translate(-50%, -4px);
|
||||
@@ -437,9 +589,49 @@ html .drive-control-button:focus-visible::after {
|
||||
}
|
||||
html .drive-popover {
|
||||
position: absolute;
|
||||
inset: calc(100% + 0.7rem) 0 auto auto;
|
||||
inset: calc(100% + 10px) 6px auto auto;
|
||||
min-inline-size: 16rem;
|
||||
max-inline-size: calc(100vw - 1rem);
|
||||
border: 1px solid rgb(217 200 244 / 26%);
|
||||
border-radius: 4px;
|
||||
background: #170e29;
|
||||
box-shadow: 0 22px 48px -18px rgb(4 1 12 / 85%);
|
||||
transform-origin: right top;
|
||||
animation: drive-popover-drop 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
html .drive-popover strong { color: var(--focus); border-color: rgb(217 200 244 / 16%); }
|
||||
html .drive-popover input[type='range'] { accent-color: var(--focus); }
|
||||
html .drive-popover output { color: var(--focus); }
|
||||
html .drive-popover select { border-color: rgb(217 200 244 / 26%); background: #10091e; }
|
||||
.drive-popover__volume {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(7rem, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.drive-popover__action {
|
||||
grid-column: 1 / -1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
min-block-size: 44px;
|
||||
padding-inline: 0.8rem;
|
||||
border: 1px solid rgb(217 200 244 / 26%);
|
||||
border-radius: 3px;
|
||||
background: #1f1436;
|
||||
color: #e6d8fa;
|
||||
font: 620 0.64rem/1 var(--mono);
|
||||
letter-spacing: 0.12em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.drive-popover__action:hover { border-color: rgb(217 200 244 / 60%); }
|
||||
.drive-popover__action:focus-visible,
|
||||
html .drive-popover select:focus-visible,
|
||||
html .drive-popover input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
||||
.drive-popover__action svg { inline-size: 16px; block-size: 16px; }
|
||||
@keyframes drive-popover-drop {
|
||||
from { opacity: 0; transform: translateY(-8px) scale(0.98); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
html .drive-controls__audio-status {
|
||||
position: absolute;
|
||||
@@ -451,10 +643,30 @@ html .drive-controls__audio-status {
|
||||
border: 1px solid rgb(214 140 222 / 32%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Phones: the settings rise as a bottom sheet. */
|
||||
@media (max-width: 767px) {
|
||||
html .drive-popover {
|
||||
position: fixed;
|
||||
inset: auto 0 0;
|
||||
min-inline-size: 0;
|
||||
max-inline-size: none;
|
||||
padding: 1rem max(1rem, env(safe-area-inset-right)) max(1.2rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
|
||||
border-width: 1px 0 0;
|
||||
border-radius: 12px 12px 0 0;
|
||||
transform-origin: bottom;
|
||||
animation-name: drive-sheet-rise;
|
||||
}
|
||||
}
|
||||
@keyframes drive-sheet-rise {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: none; }
|
||||
}
|
||||
@media (max-height: 420px) {
|
||||
html .drive-controls { position: fixed; inset: calc((var(--gantry-h) - 44px) / 2) 12px auto auto; margin: 0; }
|
||||
html .journey-hud { display: grid; }
|
||||
html .drive-popover { inset: calc(100% + 0.5rem) 0 auto auto; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html .drive-popover { animation: none; }
|
||||
}
|
||||
|
||||
/* ---------- Compact screens ---------- */
|
||||
@@ -486,6 +698,82 @@ html .drive-controls__audio-status {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- The interlude under the gantry ---------- */
|
||||
/* While a sign approaches (environment.ts sets data-interlude='on'), the
|
||||
reading plates step up and out in 260 ms, so the road and the sign have
|
||||
the frame to themselves; then ('rise') they come back up from below in
|
||||
sequence. Headings only fade: journey and audio measure them. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
html[data-interlude='on'] .reading-stage .portfolio-main > * > * {
|
||||
opacity: 0;
|
||||
transition: opacity 260ms cubic-bezier(0.5, 0, 0.75, 0);
|
||||
}
|
||||
html[data-interlude='on'] .reading-stage .portfolio-main > * > :not(h1, h2, :has(h1, h2)) {
|
||||
transform: translateY(-24px);
|
||||
transition:
|
||||
opacity 260ms cubic-bezier(0.5, 0, 0.75, 0),
|
||||
transform 260ms cubic-bezier(0.5, 0, 0.75, 0);
|
||||
}
|
||||
/* The exit tag's strike-on animation restarts as the plates return. */
|
||||
html[data-interlude='on'] .reading-stage .portfolio-section::before,
|
||||
html[data-interlude='on'] .reading-stage .portfolio-section[data-exit='08']::after {
|
||||
opacity: 0;
|
||||
animation: none;
|
||||
transition: opacity 260ms cubic-bezier(0.5, 0, 0.75, 0);
|
||||
}
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-section[data-exit='08']::after,
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section > *,
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > .hero-journey > * {
|
||||
animation: plate-fade 720ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section > :not(h1, h2, :has(h1, h2)),
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > .hero-journey > :not(h1, h2, :has(h1, h2)) {
|
||||
animation: plate-rise 720ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
/* The chapter body keeps its light-edge sweep alongside the rise. */
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > .portfolio-section[data-current] > .chapter-body {
|
||||
animation:
|
||||
plate-rise 720ms cubic-bezier(0.22, 1, 0.36, 1) both,
|
||||
exit-panel-light 1.6s cubic-bezier(0.2, 0.75, 0.2, 1) both;
|
||||
}
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(2) { animation-delay: 90ms; }
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(3) { animation-delay: 180ms; }
|
||||
html[data-interlude='rise'] .reading-stage .portfolio-main > * > :nth-child(n + 4) { animation-delay: 270ms; }
|
||||
}
|
||||
@keyframes plate-rise {
|
||||
from { opacity: 0; transform: translateY(28px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
@keyframes plate-fade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ---------- The current exit carries a ghost of its sign colour ---------- */
|
||||
@media (min-width: 768px) {
|
||||
html .site-navigation li[data-route='here'] a::after {
|
||||
content: '' / '';
|
||||
position: absolute;
|
||||
inset: auto calc(10% - 22px) 9px auto;
|
||||
inline-size: 18px;
|
||||
block-size: 3px;
|
||||
border-radius: 2px;
|
||||
background: color-mix(in srgb, var(--sign-accent, #ff83c2) 55%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
html .site-navigation li[data-route='here'] a::after { display: none; }
|
||||
}
|
||||
|
||||
/* ---------- Retired: the end-of-chapter plaque and the resume chip ---------- */
|
||||
/* The window names the next exit and offers the resume, so the advance
|
||||
plaque under each chapter goes; the destination keeps END OF ROUTE. */
|
||||
html .reading-stage .portfolio-section[data-exit]:not([data-exit='08'])::after,
|
||||
html .portfolio-section[data-exit]:not([data-exit='08'])::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* ---------- Content lands right under the beam ---------- */
|
||||
html[data-expressway='live'] .portfolio-section,
|
||||
html[data-expressway='live'] #contact,
|
||||
|
||||
Reference in New Issue
Block a user