feat: finish portfolio title, navigation, and destination effects
Add the approved name animation, rounded docking navigation, localized keyboard focus cue, unified builds card, and destination copy hierarchy.
Introduce lightweight one-shot meteors and a sun ring that repeats every three seconds at Contact. Open the email action in a separate browsing context. Preserve reduced-motion behavior and keep private handoff and visual-check artifacts out of version control.
Validation: 649 tests passed; all 20 failures in world-models and SceneCanvas reproduce on baseline 103427f. Typecheck, artifact verification, and transfer budgets passed. Client/SSR preview build passed before this commit. Existing baseline test failures remain unchanged.
This commit is contained in:
@@ -52,7 +52,10 @@ summary:focus-visible,
|
||||
}
|
||||
|
||||
.portfolio-main:focus-visible {
|
||||
box-shadow: inset 0 0 0 3px var(--focus);
|
||||
/* The localized cue lives outside the reading mask. Keep the main landmark
|
||||
focusable for native skip links without outlining the entire document. */
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
var(--panel-shadow),
|
||||
inset 0 1px color-mix(in srgb, var(--chapter-accent) 38%, transparent);
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
color: color-mix(in srgb, var(--chapter-accent) 30%, var(--text));
|
||||
}
|
||||
.portfolio-section .section-eyebrow,
|
||||
@@ -241,18 +241,20 @@
|
||||
color: #f6c7dc;
|
||||
}
|
||||
|
||||
.project-case-study:nth-child(3n + 1) {
|
||||
.project-stage__lead,
|
||||
.project-stage__projects > .project-case-study:nth-child(3n) {
|
||||
--chapter-accent: var(--coral);
|
||||
--chapter-glow: 196 76 125;
|
||||
}
|
||||
.project-case-study:nth-child(3n + 2) {
|
||||
.project-stage__projects > .project-case-study:nth-child(3n + 1) {
|
||||
--chapter-accent: var(--electric);
|
||||
--chapter-glow: 64 77 188;
|
||||
}
|
||||
.project-case-study:nth-child(3n) {
|
||||
.project-stage__projects > .project-case-study:nth-child(3n + 2) {
|
||||
--chapter-accent: var(--violet);
|
||||
--chapter-glow: 121 59 190;
|
||||
}
|
||||
.project-stage__lead,
|
||||
.project-case-study,
|
||||
.project-case-study[data-project-priority="supporting"],
|
||||
.project-case-study[data-project-id][data-media-layout] {
|
||||
|
||||
@@ -119,7 +119,7 @@ body {
|
||||
margin-block: clamp(140px, 23vh, 250px);
|
||||
border-color: var(--line);
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
font-size: clamp(42px, 4.7vw, 72px);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.055em;
|
||||
@@ -498,7 +498,7 @@ body {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
@supports (animation-timeline: view()) {
|
||||
.portfolio-section > h2,
|
||||
:is(.portfolio-section, .project-stage__lead) > h2,
|
||||
.project-case-study__header {
|
||||
animation: grand-tour-enter linear both;
|
||||
animation-timeline: view();
|
||||
@@ -627,7 +627,7 @@ body {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
font-size: 44px;
|
||||
margin-block-end: 26px;
|
||||
}
|
||||
@@ -764,7 +764,7 @@ body {
|
||||
}
|
||||
@media print {
|
||||
.hero-journey__content > *,
|
||||
.portfolio-section > h2,
|
||||
:is(.portfolio-section, .project-stage__lead) > h2,
|
||||
.project-case-study__header {
|
||||
animation: none !important;
|
||||
}
|
||||
@@ -922,7 +922,7 @@ html[data-expressway='live'] .portfolio-section[data-layout='project-stage'] {
|
||||
.site-navigation a:hover {
|
||||
background: rgb(158 220 226 / 8%);
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
.project-case-study {
|
||||
@@ -970,7 +970,7 @@ html[data-expressway='live'] .portfolio-section[data-layout='project-stage'] {
|
||||
measure these headings. Reveal opacity alone, leaving their geometry stable. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@supports (animation-timeline: view()) {
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
animation-name: expressway-heading;
|
||||
}
|
||||
}
|
||||
@@ -1002,7 +1002,7 @@ html[data-expressway='live'] .portfolio-section[data-layout='project-stage'] {
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
@@ -1024,7 +1024,7 @@ html[data-expressway='live'] .portfolio-section[data-layout='project-stage'] {
|
||||
html[data-expressway='live'] .portfolio-section[data-layout='project-stage'] {
|
||||
padding: 0;
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
:is(.portfolio-section, .project-stage__lead) > h2 {
|
||||
font-size: 38px;
|
||||
}
|
||||
.experience-record {
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/* The section introduction belongs to the first build's reading surface. */
|
||||
.project-stage__lead {
|
||||
padding: clamp(28px, 4vw, 64px);
|
||||
margin-block-end: clamp(90px, 14vh, 160px);
|
||||
border: 1px solid color-mix(in srgb, var(--chapter-accent) 30%, transparent);
|
||||
border-radius: 16px;
|
||||
}
|
||||
.project-stage__lead > .project-case-study[data-project-id][data-media-layout] {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Contact follows the same compact reading card as the rest of the journey. */
|
||||
#contact {
|
||||
position: relative;
|
||||
@@ -20,6 +35,13 @@
|
||||
max-inline-size: 34ch;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.contact-destination__availability {
|
||||
margin: 0 0 8px;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.contact-email {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -173,6 +195,10 @@
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.project-stage__lead {
|
||||
padding: 28px 22px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
#contact > h2 {
|
||||
padding-inline-end: 76px;
|
||||
}
|
||||
@@ -205,6 +231,13 @@
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
.project-stage__lead {
|
||||
padding: 0;
|
||||
margin-block-end: 1.25rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.contact-avatar {
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: 0;
|
||||
|
||||
145
src/styles/title-navigation.css
Normal file
145
src/styles/title-navigation.css
Normal file
@@ -0,0 +1,145 @@
|
||||
/* The name remains ordinary, crawlable text; only this small decoration moves. */
|
||||
.hero-name {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
inline-size: min-content;
|
||||
overflow-wrap: normal;
|
||||
/* Reserve the waves' full travel plus breathing room before the role. This
|
||||
stays in place when motion pauses, so the content does not jump. */
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
.hero-name__text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.hero-name-particles {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.hero-name-particles[data-active='true'] {
|
||||
opacity: 1;
|
||||
}
|
||||
.hero-name-particles canvas {
|
||||
position: absolute;
|
||||
inset: -8px auto auto -12px;
|
||||
inline-size: calc(100% + 24px);
|
||||
block-size: calc(100% + 32px);
|
||||
max-inline-size: none;
|
||||
}
|
||||
.hero-name-particles::after {
|
||||
content: attr(data-name);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(110deg, transparent 30%, #fff8e8 46%, white 49%, transparent 62%);
|
||||
background-size: 260% 100%;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: name-sheen 3.2s ease-in-out infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.hero-name-particles[data-active='true']::after {
|
||||
animation-play-state: running;
|
||||
}
|
||||
@keyframes name-sheen {
|
||||
0%, 12% { background-position: 150% 0; opacity: 0; }
|
||||
25%, 75% { opacity: 0.95; }
|
||||
88%, 100% { background-position: -70% 0; opacity: 0; }
|
||||
}
|
||||
|
||||
/* A scaled background expands independently of the links, keeping glyphs sharp
|
||||
and avoiding per-frame document layout during the docking transition. */
|
||||
.site-navigation {
|
||||
border: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
isolation: isolate;
|
||||
}
|
||||
.site-navigation::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
border: 1px solid rgb(198 122 209 / 34%);
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(110deg, rgb(39 19 52 / 97%), rgb(22 22 48 / 97%));
|
||||
box-shadow: 0 8px 32px rgb(13 2 28 / 24%), inset 0 1px rgb(255 159 216 / 7%);
|
||||
}
|
||||
.site-navigation a {
|
||||
border-block-end-color: transparent;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.site-navigation a[aria-current='page'],
|
||||
.site-navigation a:hover {
|
||||
border-block-end-color: transparent;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.site-navigation ul {
|
||||
grid-auto-rows: 44px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
.site-navigation a {
|
||||
block-size: 44px;
|
||||
}
|
||||
.site-navigation[data-enhanced='true'] {
|
||||
inset: 0 0 auto;
|
||||
transform: translateY(14px);
|
||||
transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.site-navigation[data-enhanced='true']::before {
|
||||
transform: scaleX(var(--nav-rest-scale));
|
||||
transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.site-navigation[data-enhanced='true'] .site-navigation__disclosure {
|
||||
inline-size: min(1400px, calc(100% - 48px));
|
||||
max-inline-size: none;
|
||||
}
|
||||
.site-navigation[data-enhanced='true'][data-docked='true'] {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.site-navigation[data-enhanced='true'][data-docked='true']::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
.site-navigation[data-motion='false'],
|
||||
.site-navigation[data-motion='false']::before {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* This cue is a sibling of the masked viewport, not a pseudo-element inside it.
|
||||
Native main focus and skip-link behavior also work without JavaScript. */
|
||||
.content-focus-cue {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: var(--z-hud);
|
||||
inset-block-start: calc(var(--site-navigation-block-size) + 4px);
|
||||
inset-inline-start: max(24px, calc((100vw - 1400px) / 2));
|
||||
max-inline-size: calc(100vw - 48px);
|
||||
padding: 6px 12px;
|
||||
border: 2px solid var(--focus);
|
||||
border-radius: 6px;
|
||||
background: var(--surface-strong);
|
||||
color: var(--focus);
|
||||
font: 560 0.8rem/1.5 var(--sans);
|
||||
pointer-events: none;
|
||||
}
|
||||
.reading-stage:has(.portfolio-main:focus-visible) + .content-focus-cue {
|
||||
display: block;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-name-particles { display: none; }
|
||||
.site-navigation, .site-navigation::before { transition: none; }
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
.hero-name-particles { display: none; }
|
||||
.site-navigation::before { background: Canvas; border-color: ButtonText; }
|
||||
.site-navigation a[aria-current='page'] { outline: 1px solid Highlight; outline-offset: -4px; }
|
||||
.content-focus-cue { background: Canvas; border-color: Highlight; color: CanvasText; }
|
||||
}
|
||||
@media print {
|
||||
.hero-name-particles, .content-focus-cue { display: none !important; }
|
||||
}
|
||||
@@ -25,6 +25,11 @@
|
||||
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:
|
||||
@@ -528,7 +533,7 @@
|
||||
|
||||
/* 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'] .portfolio-section > h2,
|
||||
.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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user