feat: add Night Shift layer (boot intro, ops strip, headlight, chapter numerals, film grain)

This commit is contained in:
2026-10-10 14:13:26 +03:00
parent 475e494675
commit d16ce88639
8 changed files with 389 additions and 0 deletions

185
src/styles/nightshift.css Normal file
View File

@@ -0,0 +1,185 @@
/* Night Shift: cinematic layer on top of the night-drive world. Imported last. */
:root {
--ns-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='.55'/></svg>");
}
/* Film stock: grain + vignette above the world, below the UI. */
body::after {
content: '';
position: fixed;
inset: 0;
z-index: var(--z-atmosphere);
pointer-events: none;
background:
radial-gradient(120% 90% at 50% 45%, transparent 55%, rgb(2 4 12 / 55%) 100%),
var(--ns-grain);
background-size: auto, 160px 160px;
mix-blend-mode: soft-light;
opacity: 0.5;
}
/* Operations strip */
.ops-strip {
position: fixed;
inset-block-end: 22px;
inset-inline: 0;
margin-inline: auto;
width: max-content;
max-width: calc(100vw - 32px);
z-index: var(--z-hud);
display: none;
align-items: center;
gap: 0.7rem;
padding: 0.5rem 1rem;
border: var(--panel-border);
border-radius: 999px;
background: var(--surface);
color: var(--text-muted);
font: 500 0.72rem/1 var(--mono);
letter-spacing: 0.08em;
text-transform: uppercase;
pointer-events: none;
}
.ops-strip strong { color: var(--text); font-weight: 600; }
.ops-strip__sep { opacity: 0.4; }
.ops-strip__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #6ee7a8;
box-shadow: 0 0 10px #6ee7a8;
}
@media (min-width: 1100px) {
.ops-strip { display: inline-flex; }
}
@media (prefers-reduced-motion: no-preference) {
.ops-strip__dot { animation: ns-pulse 2.4s ease-in-out infinite; }
}
@keyframes ns-pulse { 50% { opacity: 0.35; } }
/* Boot sequence */
.boot-sequence {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
align-content: center;
justify-items: start;
gap: 2rem;
padding: clamp(1.5rem, 8vw, 8rem);
background: #02040c;
color: var(--accent);
font: 500 clamp(0.8rem, 0.7rem + 0.5vw, 1.05rem)/1.8 var(--mono);
cursor: pointer;
transition: opacity 600ms ease, filter 600ms ease;
}
.boot-sequence[data-leaving] { opacity: 0; filter: blur(6px); pointer-events: none; }
.boot-sequence__log { list-style: none; margin: 0; padding: 0; }
.boot-sequence__log li::before { content: '> '; color: var(--world-magenta); }
.boot-sequence__log li:last-child::after {
content: '_';
margin-inline-start: 0.2em;
animation: ns-blink 700ms steps(1) infinite;
}
.boot-sequence__skip { color: var(--text-muted); font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; }
@keyframes ns-blink { 50% { opacity: 0; } }
/* Headlight: soft beam that follows a fine pointer. */
html[data-headlight='on'] .reading-viewport::before {
content: '';
position: fixed;
inset: 0;
z-index: var(--z-atmosphere);
pointer-events: none;
background: radial-gradient(420px circle at var(--hx, 50%) var(--hy, 50%), rgb(255 214 147 / 10%), transparent 70%);
mix-blend-mode: screen;
}
/* Spotlight borders on interactive surfaces. */
.project-case-study,
.project-card,
.contact-destination,
.infrastructure-flow-figure {
position: relative;
}
html[data-headlight='on'] :is(.project-case-study, .project-card, .contact-destination, .infrastructure-flow-figure)::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background: radial-gradient(320px circle at var(--mx, 50%) var(--my, 50%), rgb(120 216 229 / 14%), transparent 65%);
opacity: 0;
transition: opacity 240ms ease;
}
html[data-headlight='on'] :is(.project-case-study, .project-card, .contact-destination, .infrastructure-flow-figure):hover::after {
opacity: 1;
}
/* Chapter numerals: outlined, drifting section numbers. */
.reading-stage { counter-reset: chapter; }
.portfolio-section > h2,
.project-stage__lead > h2 { position: relative; }
.portfolio-section { counter-increment: chapter; }
.portfolio-section > h2::before,
.project-stage__lead > h2::before {
content: '0' counter(chapter);
position: absolute;
inset-block-end: 100%;
inset-inline-start: 0;
margin-block-end: 0.15em;
font: 700 var(--step-4)/0.85 var(--mono);
letter-spacing: -0.04em;
color: transparent;
-webkit-text-stroke: 1px rgb(120 216 229 / 38%);
pointer-events: none;
user-select: none;
}
@media (max-width: 720px) {
.portfolio-section > h2::before,
.project-stage__lead > h2::before { font-size: var(--step-3); }
}
@media (forced-colors: active) {
.portfolio-section > h2::before,
.project-stage__lead > h2::before { display: none; }
}
/* Kinetic type + scroll-linked reveals (progressive enhancement). */
@media (prefers-reduced-motion: no-preference) {
.hero-name__text {
font-variation-settings: 'wght' 800;
transition: font-variation-settings 400ms ease, letter-spacing 400ms ease;
}
.hero-name:hover .hero-name__text {
font-variation-settings: 'wght' 520;
letter-spacing: 0.02em;
}
@supports (animation-timeline: view()) {
.portfolio-section > h2::before,
.project-stage__lead > h2::before {
animation: ns-drift linear both;
animation-timeline: view();
animation-range: entry 0% cover 60%;
}
.project-card,
.project-case-study {
animation: ns-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
}
}
@keyframes ns-drift {
from { transform: translateX(-12%); opacity: 0; }
to { transform: none; opacity: 1; }
}
@keyframes ns-rise {
from { transform: translateY(28px); opacity: 0.2; }
to { transform: none; opacity: 1; }
}
@media print {
body::after, .ops-strip, .boot-sequence { display: none !important; }
.portfolio-section > h2::before { display: none; }
}