.visually-hidden { position: absolute !important; inline-size: 1px !important; block-size: 1px !important; margin: -1px !important; overflow: hidden !important; clip-path: inset(50%) !important; white-space: nowrap !important; } .journey-world { position: fixed; z-index: 0; inset: 0; overflow: hidden; pointer-events: none; background: #040713; } .journey-poster, .journey-canvas { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; } /* A still destination grade when motion is disabled; no animation or WebGL. */ .journey-world[data-static-fallback='true'][data-arrived='true'] .journey-poster { filter: sepia(0.35) saturate(0.85); } .journey-poster { object-fit: cover; background: radial-gradient(ellipse at 50% 47%, rgb(242 31 139 / 34%) 0 7%, rgb(110 21 113 / 25%) 20%, transparent 48%), linear-gradient(180deg, #01030a 0 25%, #070817 43%, #1b0b27 59%, #02060b 100%); background-size: cover; } .journey-poster-composition, .journey-poster-composition > span { position: absolute; } .journey-poster-composition { inset: 0; overflow: hidden; } .journey-poster__stars { inset: 0; opacity: 0.72; background-image: radial-gradient(circle, rgb(225 252 255 / 90%) 0 1px, transparent 1.3px), radial-gradient(circle, rgb(120 216 229 / 72%) 0 0.8px, transparent 1.1px); background-position: 17px 29px, 83px 97px; background-size: 173px 149px, 227px 181px; mask-image: linear-gradient(180deg, #000 0 54%, transparent 72%); } .journey-poster__sun { inset: 34% auto auto 50%; inline-size: clamp(8rem, 13.5vw, 14.5rem); aspect-ratio: 1; translate: -50% 0; border-radius: 50%; background: repeating-linear-gradient( 180deg, transparent 0 8%, rgb(51 5 31 / 72%) 8.5% 11%, transparent 11.5% 15% ), linear-gradient(180deg, #ff4d9b, #d52378 58%, #8e155c); box-shadow: 0 0 2.4rem rgb(255 44 151 / 42%), 0 1.2rem 8rem 2.4rem rgb(126 29 143 / 28%); } .journey-poster__terrain { inset: 45% -12% -8%; background: repeating-linear-gradient(0deg, transparent 0 7.6%, rgb(84 221 233 / 19%) 7.8% 8.15%, transparent 8.35% 12%), repeating-linear-gradient(76deg, transparent 0 6.8%, rgb(220 50 168 / 17%) 7% 7.25%, transparent 7.5% 11%), repeating-linear-gradient(104deg, transparent 0 8.4%, rgb(73 207 228 / 14%) 8.6% 8.85%, transparent 9.1% 13%), linear-gradient(180deg, rgb(104 21 101 / 34%), rgb(3 10 17 / 86%)); clip-path: polygon(0 17%, 6% 8%, 12% 20%, 18% 13%, 24% 25%, 30% 17%, 36% 29%, 42% 23%, 47% 34%, 50% 41%, 53% 34%, 58% 23%, 64% 29%, 70% 17%, 76% 25%, 82% 13%, 88% 20%, 94% 8%, 100% 17%, 100% 100%, 0 100%); filter: drop-shadow(0 -0.35rem 1.5rem rgb(223 40 169 / 22%)); } .journey-poster__road { inset: 46% -8% -10%; background: repeating-linear-gradient(180deg, transparent 0 7%, rgb(244 177 80 / 84%) 7.35% 8.35%, transparent 8.7% 15%) 50% 0 / clamp(0.15rem, 0.32vw, 0.34rem) 100% no-repeat, linear-gradient(90deg, rgb(46 188 212 / 15%), transparent 5% 95%, rgb(46 188 212 / 15%)), linear-gradient(180deg, #050813, #000207); clip-path: polygon(49.55% 0, 50.45% 0, 76% 100%, 24% 100%); } .journey-poster__rail { inset: 0; background: #67d4e4; filter: drop-shadow(0 0 0.55rem rgb(76 207 225 / 72%)); } .journey-poster__rail--left { clip-path: polygon(49.38% 46%, 49.5% 46%, 24.3% 100%, 23.55% 100%); } .journey-poster__rail--right { clip-path: polygon(50.5% 46%, 50.62% 46%, 76.45% 100%, 75.7% 100%); } .journey-poster__car { inset: auto auto 1.6% 50%; inline-size: clamp(9.5rem, 20vw, 21rem); block-size: clamp(3.6rem, 7.4vw, 7.8rem); translate: -50% 0; border-radius: 40% 40% 8% 8% / 34% 34% 12% 12%; background: linear-gradient(106deg, transparent 0 24%, rgb(13 41 56 / 98%) 24.5% 42%, rgb(3 13 22 / 98%) 42.5% 57.5%, rgb(13 41 56 / 98%) 58% 75.5%, transparent 76%) 50% 6% / 78% 48% no-repeat, linear-gradient(180deg, #173444 0 26%, #0a2231 46%, #06131f 68%, #020711 100%); clip-path: polygon(29% 0, 71% 0, 82% 25%, 95% 43%, 100% 76%, 94% 100%, 6% 100%, 0 76%, 5% 43%, 18% 25%); filter: drop-shadow(0 0.8rem 1.2rem rgb(0 0 0 / 82%)) drop-shadow(0 0.2rem 1.1rem rgb(46 181 207 / 12%)); } .journey-poster__car::before { position: absolute; inset: 61% 8% auto; block-size: clamp(0.24rem, 0.38vw, 0.42rem); border-radius: 50%; background: linear-gradient(90deg, #ff244f, #ff7592 48% 52%, #ff244f); box-shadow: 0 0 1rem rgb(255 49 95 / 64%); content: ''; } .journey-poster__car::after { position: absolute; inset: 88% 20% auto; block-size: 0.16rem; border-radius: 50%; background: #67d4e4; box-shadow: 0 0 1.1rem 0.36rem rgb(61 208 229 / 48%); content: ''; } .journey-canvas { max-inline-size: none; } .portfolio-shell { position: relative; z-index: 1; } .site-header { position: relative; z-index: 2; } .portfolio-main { isolation: isolate; } .journey-hud { position: fixed; z-index: var(--z-hud); inset: auto max(0.75rem, env(safe-area-inset-right)) max(0.75rem, env(safe-area-inset-bottom)) auto; display: flex; min-block-size: 44px; align-items: center; gap: 0.72rem; padding: 0.48rem 0.72rem; border: 1px solid rgb(126 220 232 / 25%); border-radius: 2px; background: var(--surface-strong); box-shadow: 0 0.8rem 2.4rem rgb(0 5 14 / 28%); color: var(--text-muted); font: 560 0.72rem/1 var(--mono); letter-spacing: 0.08em; pointer-events: none; } .journey-hud__route { display: inline-flex; align-items: baseline; gap: 0.4rem; } .journey-hud__route span { color: rgb(170 185 194 / 70%); font-size: 0.61rem; } .journey-hud__route strong { color: var(--text); font-size: 0.76rem; } .journey-hud__section, .journey-hud__arrival { color: var(--cyan); } .journey-hud__section::before { margin-inline-end: 0.72rem; color: rgb(126 220 232 / 35%); content: '/'; } .journey-hud__arrival { padding-inline-start: 0.12rem; color: var(--amber); } /* END OF SHIFT: the destination eyebrow sits on the lightened sky, so it darkens with the dawn instead of keeping the chapter accent. */ #contact .section-eyebrow { transition: color 1.2s ease; } html:is([data-dawn='sunrise'], [data-dawn='day']) #contact .section-eyebrow { color: #3b1a33; text-shadow: 0 1px 12px rgb(255 226 196 / 45%); } /* Without the scene the eyebrow sits on the CSS daybreak's blue zenith. */ html:is([data-dawn='sunrise'], [data-dawn='day']):has(.journey-world[data-static-fallback='true']) #contact .section-eyebrow { color: #ffe3c4; text-shadow: 0 1px 8px rgb(20 16 48 / 60%); } /* END OF SHIFT: at full daylight the label reads SHIFT OVER (a string swap). */ html[data-dawn='day'] .journey-hud__arrival { font-size: 0; } html[data-dawn='day'] .journey-hud__arrival::after { content: attr(data-day) / ''; font-size: 0.72rem; } .drive-controls { position: fixed; z-index: var(--z-popover); inset: auto auto max(0.75rem, env(safe-area-inset-bottom)) max(0.75rem, env(safe-area-inset-left)); display: flex; align-items: center; gap: 0.38rem; max-inline-size: calc(100vw - 1.5rem); font-family: var(--mono); font-size: 0.72rem; } .drive-controls__dock { display: flex; gap: 1px; padding: 3px; border: 1px solid rgb(126 220 232 / 24%); border-radius: 3px; background: var(--surface-strong); box-shadow: 0 0.8rem 2.4rem rgb(0 5 14 / 28%); } .drive-start, .drive-control-button { min-block-size: 44px; border: 1px solid transparent; border-radius: 2px; font-weight: 620; cursor: pointer; transition: opacity var(--transition), transform var(--transition); } .drive-start { display: inline-flex; min-inline-size: 12.5rem; align-items: center; justify-content: space-between; gap: 1rem; padding-inline: 1rem; border-color: rgb(120 216 229 / 72%); background: rgb(120 216 229 / 94%); box-shadow: 0 0 1.5rem rgb(77 201 220 / 17%); color: #041017; letter-spacing: 0.1em; } .drive-start:hover:not(:disabled) { transform: translateY(-2px); } .drive-start:disabled { cursor: wait; opacity: 0.64; } .drive-control-button { position: relative; display: inline-grid; inline-size: 44px; place-items: center; padding: 0; background: rgb(8 17 29 / 92%); color: var(--text-muted); } .drive-control-button:hover { color: var(--cyan); transform: translateY(-1px); } .drive-controls__settings-trigger { border-color: rgb(126 220 232 / 24%); background: var(--surface-strong); box-shadow: 0 0.8rem 2.4rem rgb(0 5 14 / 28%); } .drive-start:focus-visible, .drive-start:focus, .drive-control-button:focus-visible, .drive-control-button:focus, .drive-popover input:focus-visible, .drive-popover select:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus), 0 0 0 1px rgb(3 8 17 / 90%); } .drive-control-button::after { position: absolute; inset: auto auto calc(100% + 0.5rem) 50%; padding: 0.32rem 0.46rem; transform: translate(-50%, 4px); border: 1px solid rgb(126 220 232 / 18%); background: rgb(3 8 17 / 96%); color: var(--text); content: attr(data-tooltip); font: 520 0.65rem/1 var(--mono); letter-spacing: 0.02em; opacity: 0; pointer-events: none; transition: opacity var(--transition), transform var(--transition); white-space: nowrap; } .drive-control-button:hover::after, .drive-control-button:focus-visible::after { transform: translate(-50%, 0); opacity: 1; } .drive-popover { position: absolute; inset: auto auto calc(100% + 0.7rem) 0; display: grid; transform-origin: left bottom; border: 1px solid rgb(126 220 232 / 27%); border-radius: 3px; background: var(--surface-strong); box-shadow: 0 1.4rem 3rem rgb(0 4 12 / 42%); color: var(--text); animation: drive-popover-in var(--transition) both; } @keyframes drive-popover-in { from { transform: translateY(6px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .drive-popover--volume { grid-template-columns: auto minmax(8rem, 1fr) auto; min-inline-size: 16.5rem; align-items: center; gap: 0.7rem; padding: 0.7rem; } .drive-popover--settings { grid-template-columns: auto minmax(7rem, 1fr); min-inline-size: 15rem; gap: 0.55rem 1rem; padding: 0.85rem; } .drive-popover strong, .drive-popover label { color: var(--text-muted); font-size: 0.64rem; font-weight: 560; letter-spacing: 0.1em; } .drive-popover strong { grid-column: 1 / -1; padding-block-end: 0.45rem; border-block-end: 1px solid rgb(126 220 232 / 14%); color: var(--cyan); } .drive-popover input, .drive-popover select { min-block-size: 44px; } .drive-popover input[type="range"] { inline-size: 100%; accent-color: var(--cyan); } .drive-popover select { border: 1px solid rgb(126 220 232 / 24%); border-radius: 2px; background: #08111d; color: var(--text); padding-inline: 0.65rem; } .drive-popover__readout { color: var(--cyan); font-size: 0.68rem; } .drive-controls__audio-status { display: inline-flex; min-block-size: 44px; align-items: center; margin: 0; padding-inline: 0.85rem; border: 1px solid rgb(244 173 103 / 26%); border-radius: 2px; background: var(--surface-strong); color: var(--text-muted); } .drive-controls__status { grid-column: 1 / -1; margin: 0; padding-block-start: 0.4rem; border-block-start: 1px solid rgb(126 220 232 / 14%); color: var(--text-muted); font-size: 0.66rem; } @media (max-width: 767px) { html { scroll-padding-block-end: 7rem; } #contact { padding-block-end: 7rem; } #contact a { scroll-margin-block-end: 7rem; } .drive-controls { inset-inline-start: max(0.5rem, env(safe-area-inset-left)); inset-block-end: max(0.5rem, env(safe-area-inset-bottom)); } .journey-hud { inset-inline-end: max(0.5rem, env(safe-area-inset-right)); inset-block-end: max(0.5rem, env(safe-area-inset-bottom)); max-inline-size: 6.5rem; gap: 0.3rem; padding-inline: 0.55rem; letter-spacing: 0.04em; } .journey-hud__route span, .journey-hud__section::before { display: none; } .journey-hud__section { max-inline-size: 4.25rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .journey-hud__arrival { display: none; } .drive-start { min-inline-size: 9rem; gap: 0.6rem; padding-inline: 0.62rem; font-size: 0.6rem; } .drive-popover { position: fixed; inset: auto 0.5rem calc(max(0.5rem, env(safe-area-inset-bottom)) + 3.25rem); max-inline-size: calc(100vw - 1rem); } } @media (max-height: 420px) { .journey-hud { display: none; } .drive-controls { position: relative; inset: auto; inline-size: fit-content; max-inline-size: calc(100% - 1.5rem); margin: 0.75rem; } #contact { padding-block-end: var(--space-3); } .drive-popover { position: absolute; inset: auto auto calc(100% + 0.5rem) 0; } } /* Round 5 outro: letterbox bars close in on arrival, on every tier (they are CSS, so the static poster gets them too). They sit in the world layer, under the reading column, so they frame the sky and never cover content. The upper bar hangs below the gantry; the lower bar carries a closing credit built from real identity facts, typed in once the bars close. */ .journey-world::before, .journey-world::after { position: absolute; z-index: 1; inset-inline: 0; /* Engine off: the bars stay within 8vh of the frame each. */ block-size: min(8vh, 72px); background: #020108; content: ''; transform: scaleY(0); transition: transform 1.6s cubic-bezier(0.65, 0, 0.35, 1), background-color 2.4s ease; } /* END OF SHIFT: the bars take on the dawn, black to a warm aubergine. */ html[data-dawn='first-light'] .journey-world::before, html[data-dawn='first-light'] .journey-world::after { background-color: #0e0810; } html:is([data-dawn='sunrise'], [data-dawn='day']) .journey-world::before, html:is([data-dawn='sunrise'], [data-dawn='day']) .journey-world::after { background-color: #1a0f14; } .journey-world::before { inset-block-start: 0; display: grid; align-items: end; justify-items: center; block-size: calc(var(--gantry-h, 72px) + min(5vh, 44px)); transform-origin: top; } /* The closing quote fades in word by word once the bars have closed. */ .journey-world__credit { --closer-delay: 0.2s; position: absolute; z-index: 2; /* In the lower bar: the upper one is shared with the contact avatar. */ inset: auto 0 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.28em; block-size: min(8vh, 72px); padding-inline: 16px; color: rgb(255 226 196 / 82%); text-align: center; pointer-events: none; } .journey-world__quote { font: italic 400 clamp(0.95rem, 1.5vw, 1.2rem) / 1.25 Charter, 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', serif; letter-spacing: 0.01em; text-wrap: balance; } .journey-world__word { display: inline-block; opacity: 0; } .journey-world__by { color: rgb(255 226 196 / 68%); font: 600 0.66rem/1.2 var(--mono, ui-monospace, monospace); font-variant-caps: all-small-caps; letter-spacing: 0.22em; opacity: 0; } .journey-world__by::before { content: '\2014\00a0'; } /* END OF SHIFT: the quote arrives as the sun clears the skyline (html[data-dawn] reaches sunrise about 3.8 s after arrival). Until the scene reports a dawn (still loading), it keeps a fixed 3.4 s delay. */ @media (prefers-reduced-motion: no-preference) { .journey-world__word, .journey-world__by { transition: opacity 0.9s ease; } html:is([data-dawn='sunrise'], [data-dawn='day'], :not([data-dawn])) .journey-world[data-arrived='true'] .journey-world__word { opacity: 1; transition-delay: calc(var(--closer-delay) + var(--i, 0) * 0.14s); } html:is([data-dawn='sunrise'], [data-dawn='day'], :not([data-dawn])) .journey-world[data-arrived='true'] .journey-world__by { opacity: 1; transition-delay: calc(var(--closer-delay) + 1.9s); } html:not([data-dawn]) .journey-world[data-arrived='true'] .journey-world__credit { --closer-delay: 3.4s; } /* No scene, no dawn to wait for: the bars' own timing. */ html .journey-world[data-static-fallback='true'][data-arrived='true'] .journey-world__credit { --closer-delay: 1.9s; } } /* Reduced motion: the bars and the dimming only; the quote simply shows. */ @media (prefers-reduced-motion: reduce) { .journey-world[data-arrived='true'] .journey-world__word, .journey-world[data-arrived='true'] .journey-world__by { opacity: 1; transition: none; } } .journey-poster { transition: filter 1.6s ease, opacity 1.6s ease; } /* END OF SHIFT without the scene: the world crossfades to the daybreak sky under the dimmed night poster. The poster's sky and its magenta sunset sun are masked out (they read as dusk); the daybreak sky shows above the horizon with a low gold sun, a cool clear zenith, and the night terrain dimmed below. */ .journey-world[data-static-fallback='true'][data-arrived='true'] { background: radial-gradient(circle at 50% 44%, #fff6df 0 3.2%, rgb(255 222 160 / 75%) 6%, rgb(255 190 130 / 28%) 16%, transparent 34%), linear-gradient(180deg, #2f3a6e 0%, #565a92 26%, #a77890 38%, #f2a676 42.5%, #ffe3b8 44.5%, #2a1820 52%, #1a0f14 100%); } .journey-world[data-static-fallback='true'][data-arrived='true'] .journey-poster { opacity: 0.5; mask-image: linear-gradient(180deg, transparent 0 43%, #000 54%); } .journey-world[data-arrived='true'] .journey-poster { filter: brightness(0.9); } .journey-world::after { inset-block-end: 0; transform-origin: bottom; } .journey-world[data-arrived='true']::before, .journey-world[data-arrived='true']::after { transform: scaleY(1); transition-delay: 0.5s; } @media (max-width: 767px) { .journey-world::after { block-size: min(7vh, 56px); } /* Phones: the quote may wrap to two lines inside the thinner bar. */ .journey-world__credit { block-size: min(7vh, 56px); gap: 0.2em; padding-inline: 12px; } .journey-world__quote { font-size: 0.8rem; line-height: 1.2; } .journey-world__by { font-size: 0.56rem; letter-spacing: 0.16em; } } @media (prefers-reduced-motion: reduce) { .journey-world::before, .journey-world::after, .journey-world[data-arrived='true']::before, .journey-world[data-arrived='true']::after { transition: none; } } @media (forced-colors: active) { .journey-world::before, .journey-world::after, .journey-world__credit { display: none; } } @media (prefers-reduced-motion: reduce) { .journey-world[data-arrived='true'] .journey-poster { filter: saturate(0.86) brightness(0.83); } .journey-poster { transition: none; } } @media (prefers-reduced-motion: reduce) { .journey-poster { filter: saturate(0.86) brightness(0.92); } } @media print { .journey-world, .journey-hud, .drive-controls, #journey-scene-description { display: none !important; } } /* The outer stage retains the native document height. The viewport owns one reusable alpha mask; only its content translates when the document scrolls. */ .reading-stage[data-reading-stage='live'] > .reading-viewport { position: sticky; top: 0; /* A real scroll container (scrolled from script, never by the user), so sticky children and view timelines resolve against the reading window. */ overflow: hidden; /* Wheel and touch scrolling must chain through to the document, which drives this container: no overscroll-behavior here. */ isolation: isolate; } /* The stationary reading edge supplies the live reveal. View timelines use layout positions, which no longer follow the translated content here. */ .reading-stage[data-reading-stage='live'] :is(.portfolio-section, .project-stage__lead) > h2, .reading-stage[data-reading-stage='live'] .project-case-study__header { animation: none; } @media print { .reading-stage, .reading-viewport { height: auto !important; } .reading-stage[data-reading-stage='live'] > .reading-viewport { position: static; overflow: visible; mask-image: none !important; } .reading-stage .portfolio-main { transform: none !important; will-change: auto; } } /* UNDERPASS: under a deck the section label takes the sodium light. */ html[data-underpass='under'] .journey-hud__section { color: #ffb45c; } @media (prefers-reduced-motion: no-preference) { html .journey-hud__section { transition: color 0.3s ease; } }