Phone fixes: section label, cards that stayed put, company sites as links
Changes
@@@ -101,7 +101,10 @@ import fcAlbum from "../data/fc-album.json";.hero__veil { position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(7,9,15,.35) 0%, transparent 30%, transparent 55%, var(--bg) 100%), radial-gradient(60% 50% at 30% 45%, rgba(7,9,15,.35), transparent 70%); }:root[data-theme="light"] .hero__veil { background: linear-gradient(180deg, transparent 55%, var(--bg) 100%); }-- .hero__in { opacity: 0; animation: heroIn .9s var(--ease) forwards; animation-delay: var(--d, 0ms); }++ /* Entrances fill backwards: hidden during their delay, then the element's own (visible) style. A++ forwards fill kept every animated element on its own GPU layer for good, and phones dropped some++ of those layers' paint (blank square tiles where the first row of tags should be). */++ .hero__in { animation: heroIn .9s var(--ease) backwards; animation-delay: var(--d, 0ms); }.hero__in--scale { animation-name: heroInScale; }@keyframes heroIn { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }@keyframes heroInScale { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }@@@ -126,16 +129,14 @@ import fcAlbum from "../data/fc-album.json";}.hero__word { display: inline-flex; white-space: nowrap; }.hero__ch {-- display: inline-block; opacity: 0;-- transform: translateY(.6em) rotate(6deg) scale(.9);-- filter: blur(6px);-- animation: chIn .9s var(--ease) forwards;++ display: inline-block;++ animation: chIn .9s var(--ease) backwards;animation-delay: calc(var(--i) * 45ms + 120ms);transition: color .3s;}.hero__ch:hover { color: var(--accent); }.hero__ch--last::after { content: "."; display: inline-block; color: transparent; background: var(--grad); -webkit-background-clip: text; background-clip: text; opacity: 0; animation: dotIn .8s var(--ease) forwards 1s; }-- @keyframes chIn { to { opacity: 1; transform: none; filter: blur(0); } }++ @keyframes chIn { from { opacity: 0; transform: translateY(.6em) rotate(6deg) scale(.9); filter: blur(6px); } to { opacity: 1; transform: none; filter: blur(0); } }@keyframes dotIn { 0% { opacity: 0; transform: scale(0) translateY(.2em); } 60% { transform: scale(1.3); } 100% { opacity: 1; transform: none; } }.hero__role {@@@ -233,10 +234,11 @@ import fcAlbum from "../data/fc-album.json";display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; min-height: 44px;background: var(--surface); border: 1px solid var(--border); color: var(--fg);font: 500 .82rem/1.2 var(--font-body); letter-spacing: -.005em;-- opacity: 0; animation: tagIn .6s var(--ease) forwards; animation-delay: calc(1000ms + var(--i) * 45ms);++ animation: tagIn .6s var(--ease) backwards; animation-delay: calc(1000ms + var(--i) * 45ms);transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);}-- .hero__tag:hover { border-color: rgba(var(--accent-rgb), .5); background: var(--surface-2); transform: translateY(-2px); }++ .hero__tag:hover { border-color: rgba(var(--accent-rgb), .5); background: var(--surface-2); }++ @media (hover: hover) and (pointer: fine) { .hero__tag:hover { transform: translateY(-2px); } }@keyframes tagIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }.hero__tag-ico { flex: none; width: 28px; height: 28px; border-radius: 9px; display: inline-grid; place-items: center; font-size: .78rem; background: rgba(var(--accent-rgb), .14); color: var(--accent); }.hero__tag:nth-child(3n+2) .hero__tag-ico { background: rgba(var(--accent-2-rgb), .14); color: var(--accent-2); }@@@ -144,9 +144,14 @@ const path = Astro.url.pathname;@keyframes whereIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }@media (max-width: 920px) { .nav__where { margin-left: auto; } }/* Phones: the drawer has its own dark-mode switch, so the standalone toggle goes; the section-- chip drops its label below 520px so the PDF button, palette and burger always fit. */++ chip keeps its label (an icon alone reads as a stray button) in smaller type, and shrinks with an++ ellipsis before it can push the PDF button, palette or burger out. */@media (max-width: 560px) { .nav__inner { gap: 8px; padding: 0 8px 0 10px; } .nav__icon[data-theme-toggle] { display: none; } }-- @media (max-width: 520px) { .nav__where .nav__where-txt { display: none; } .nav__where { padding: 0 10px; } }++ @media (max-width: 520px) {++ .nav__where { flex: 0 1 auto; min-width: 0; gap: 6px; padding: 0 10px 0 9px; font-size: .64rem; letter-spacing: .05em; }++ .nav__where i { flex: none; font-size: .72rem; }++ .nav__where-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }++ }.nav__links { flex: 1; display: flex; justify-content: center; }.nav__links ul { position: relative; list-style: none; margin: 0; padding: 4px; display: flex; gap: 2px; border-radius: 999px; }@@@ -36,7 +36,9 @@ const kindIcon: Record<string, string> = {<h3 class="tl__role">{e.data.role}</h3><div class="tl__co">{e.data.link ? <a href={e.data.link} target="_blank" rel="noopener">{e.data.company}</a> : <span>{e.data.company}</span>}-- {e.data.location && <span class="tl__loc"><i class="fa-solid fa-location-dot"></i> {e.data.location}</span>}++ {e.data.link++ ? <a class="tl__loc tl__site" href={e.data.link} target="_blank" rel="noopener"><i class="fa-solid fa-globe" aria-hidden="true"></i> {e.data.location?.includes(new URL(e.data.link).hostname) ? e.data.location : new URL(e.data.link).hostname}<i class="fa-solid fa-arrow-up-right-from-square tl__site-go" aria-hidden="true"></i></a>++ : e.data.location && <span class="tl__loc"><i class="fa-solid fa-location-dot"></i> {e.data.location}</span>}</div></header>{e.data.summary && <p class="tl__sum">{e.data.summary}</p>}@@@ -75,6 +77,9 @@ const kindIcon: Record<string, string> = {.tl__co { display: flex; flex-wrap: wrap; gap: 6px 18px; color: var(--muted); font-size: .95rem; }.tl__co a { color: var(--fg); font-weight: 600; }.tl__loc { color: var(--dim); font-size: .85rem; display: inline-flex; align-items: center; gap: 6px; }++ .tl__co a.tl__site { color: var(--accent); font-weight: 500; text-decoration: none; }++ .tl__co a.tl__site:hover { text-decoration: underline; text-underline-offset: 3px; }++ .tl__site-go { font-size: .7em; opacity: .75; }.tl__sum { font-size: .95rem; color: var(--muted); font-style: italic; border-left: 2px solid rgba(var(--accent-rgb), .4); padding-left: 12px; }.tl__bullets { margin: 0; padding: 0 0 0 1.1rem; display: grid; gap: 8px; color: var(--muted); line-height: 1.65; font-size: .95rem; }.tl__bullets li::marker { color: var(--accent-2); }@@@ -254,7 +254,7 @@ const imageUrl = new URL(image, site).toString();: kind === 'right' ? { opacity: 0, transform: 'translateX(12px)' }: kind === 'scale' ? { opacity: 0, transform: 'scale(.975)' }: { opacity: 0, transform: 'translateY(8px) scale(.99)' };-- el.style.opacity = '1'; el.style.transform = 'none';++ el.classList.add('is-in');el.animate([from, { opacity: 1, transform: 'none' }], { duration: 600, delay, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });io.unobserve(el);}@@@ -14,6 +14,8 @@ html {font-family: var(--font-body);-webkit-font-smoothing: antialiased;text-rendering: optimizeLegibility;++ /* Cards and buttons give their own press feedback; the browser's square tap flash drew over rounded ones. */++ -webkit-tap-highlight-color: transparent;scroll-padding-top: calc(var(--nav-h) + 12px);}html:not(.lenis) { scroll-behavior: smooth; }@@@ -211,6 +213,9 @@ main > .section:nth-of-type(even) { background: linear-gradient(180deg, transpar[data-reveal="right"] { transform: translateX(30px); }[data-reveal="scale"] { transform: scale(.94); }.no-js [data-reveal] { opacity: 1; transform: none; }++/* Revealed: a class, not inline styles, so a card's tilt can clear its own inline transform on++ pointerleave without the pre-reveal offset above coming back (it dropped cards onto the next one). */++[data-reveal].is-in { opacity: 1; transform: none; }/* ---- Utilities ------------------------------------------------------------ */.grad-text { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
@@ -101,7 +101,10 @@ import fcAlbum from "../data/fc-album.json";
@@ -101,7 +101,10 @@ import fcAlbum from "../data/fc-album.json";
.hero__veil { position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(7,9,15,.35) 0%, transparent 30%, transparent 55%, var(--bg) 100%), radial-gradient(60% 50% at 30% 45%, rgba(7,9,15,.35), transparent 70%); }
.hero__veil { position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(7,9,15,.35) 0%, transparent 30%, transparent 55%, var(--bg) 100%), radial-gradient(60% 50% at 30% 45%, rgba(7,9,15,.35), transparent 70%); }
:root[data-theme="light"] .hero__veil { background: linear-gradient(180deg, transparent 55%, var(--bg) 100%); }
:root[data-theme="light"] .hero__veil { background: linear-gradient(180deg, transparent 55%, var(--bg) 100%); }
- .hero__in { opacity: 0; animation: heroIn .9s var(--ease) forwards; animation-delay: var(--d, 0ms); }
+ /* Entrances fill backwards: hidden during their delay, then the element's own (visible) style. A
+ forwards fill kept every animated element on its own GPU layer for good, and phones dropped some
+ of those layers' paint (blank square tiles where the first row of tags should be). */
+ .hero__in { animation: heroIn .9s var(--ease) backwards; animation-delay: var(--d, 0ms); }
.hero__in--scale { animation-name: heroInScale; }
.hero__in--scale { animation-name: heroInScale; }
@keyframes heroIn { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
@keyframes heroIn { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
@keyframes heroInScale { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
@keyframes heroInScale { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
@@ -126,16 +129,14 @@ import fcAlbum from "../data/fc-album.json";
@@ -126,16 +129,14 @@ import fcAlbum from "../data/fc-album.json";
}
}
.hero__word { display: inline-flex; white-space: nowrap; }
.hero__word { display: inline-flex; white-space: nowrap; }
.hero__ch {
.hero__ch {
- display: inline-block; opacity: 0;
- transform: translateY(.6em) rotate(6deg) scale(.9);
- filter: blur(6px);
- animation: chIn .9s var(--ease) forwards;
+ display: inline-block;
+ animation: chIn .9s var(--ease) backwards;
animation-delay: calc(var(--i) * 45ms + 120ms);
animation-delay: calc(var(--i) * 45ms + 120ms);
transition: color .3s;
transition: color .3s;
}
}
.hero__ch:hover { color: var(--accent); }
.hero__ch:hover { color: var(--accent); }
.hero__ch--last::after { content: "."; display: inline-block; color: transparent; background: var(--grad); -webkit-background-clip: text; background-clip: text; opacity: 0; animation: dotIn .8s var(--ease) forwards 1s; }
.hero__ch--last::after { content: "."; display: inline-block; color: transparent; background: var(--grad); -webkit-background-clip: text; background-clip: text; opacity: 0; animation: dotIn .8s var(--ease) forwards 1s; }
- @keyframes chIn { to { opacity: 1; transform: none; filter: blur(0); } }
+ @keyframes chIn { from { opacity: 0; transform: translateY(.6em) rotate(6deg) scale(.9); filter: blur(6px); } to { opacity: 1; transform: none; filter: blur(0); } }
@keyframes dotIn { 0% { opacity: 0; transform: scale(0) translateY(.2em); } 60% { transform: scale(1.3); } 100% { opacity: 1; transform: none; } }
@keyframes dotIn { 0% { opacity: 0; transform: scale(0) translateY(.2em); } 60% { transform: scale(1.3); } 100% { opacity: 1; transform: none; } }
.hero__role {
.hero__role {
@@ -233,10 +234,11 @@ import fcAlbum from "../data/fc-album.json";
@@ -233,10 +234,11 @@ import fcAlbum from "../data/fc-album.json";
display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; min-height: 44px;
display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; min-height: 44px;
background: var(--surface); border: 1px solid var(--border); color: var(--fg);
background: var(--surface); border: 1px solid var(--border); color: var(--fg);
font: 500 .82rem/1.2 var(--font-body); letter-spacing: -.005em;
font: 500 .82rem/1.2 var(--font-body); letter-spacing: -.005em;
- opacity: 0; animation: tagIn .6s var(--ease) forwards; animation-delay: calc(1000ms + var(--i) * 45ms);
+ animation: tagIn .6s var(--ease) backwards; animation-delay: calc(1000ms + var(--i) * 45ms);
transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);
transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);
}
}
- .hero__tag:hover { border-color: rgba(var(--accent-rgb), .5); background: var(--surface-2); transform: translateY(-2px); }
+ .hero__tag:hover { border-color: rgba(var(--accent-rgb), .5); background: var(--surface-2); }
+ @media (hover: hover) and (pointer: fine) { .hero__tag:hover { transform: translateY(-2px); } }
@keyframes tagIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes tagIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.hero__tag-ico { flex: none; width: 28px; height: 28px; border-radius: 9px; display: inline-grid; place-items: center; font-size: .78rem; background: rgba(var(--accent-rgb), .14); color: var(--accent); }
.hero__tag-ico { flex: none; width: 28px; height: 28px; border-radius: 9px; display: inline-grid; place-items: center; font-size: .78rem; background: rgba(var(--accent-rgb), .14); color: var(--accent); }
.hero__tag:nth-child(3n+2) .hero__tag-ico { background: rgba(var(--accent-2-rgb), .14); color: var(--accent-2); }
.hero__tag:nth-child(3n+2) .hero__tag-ico { background: rgba(var(--accent-2-rgb), .14); color: var(--accent-2); }
@@ -144,9 +144,14 @@ const path = Astro.url.pathname;
@@ -144,9 +144,14 @@ const path = Astro.url.pathname;
@keyframes whereIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes whereIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (max-width: 920px) { .nav__where { margin-left: auto; } }
@media (max-width: 920px) { .nav__where { margin-left: auto; } }
/* Phones: the drawer has its own dark-mode switch, so the standalone toggle goes; the section
/* Phones: the drawer has its own dark-mode switch, so the standalone toggle goes; the section
- chip drops its label below 520px so the PDF button, palette and burger always fit. */
+ chip keeps its label (an icon alone reads as a stray button) in smaller type, and shrinks with an
+ ellipsis before it can push the PDF button, palette or burger out. */
@media (max-width: 560px) { .nav__inner { gap: 8px; padding: 0 8px 0 10px; } .nav__icon[data-theme-toggle] { display: none; } }
@media (max-width: 560px) { .nav__inner { gap: 8px; padding: 0 8px 0 10px; } .nav__icon[data-theme-toggle] { display: none; } }
- @media (max-width: 520px) { .nav__where .nav__where-txt { display: none; } .nav__where { padding: 0 10px; } }
+ @media (max-width: 520px) {
+ .nav__where { flex: 0 1 auto; min-width: 0; gap: 6px; padding: 0 10px 0 9px; font-size: .64rem; letter-spacing: .05em; }
+ .nav__where i { flex: none; font-size: .72rem; }
+ .nav__where-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+ }
.nav__links { flex: 1; display: flex; justify-content: center; }
.nav__links { flex: 1; display: flex; justify-content: center; }
.nav__links ul { position: relative; list-style: none; margin: 0; padding: 4px; display: flex; gap: 2px; border-radius: 999px; }
.nav__links ul { position: relative; list-style: none; margin: 0; padding: 4px; display: flex; gap: 2px; border-radius: 999px; }
@@ -36,7 +36,9 @@ const kindIcon: Record<string, string> = {
@@ -36,7 +36,9 @@ const kindIcon: Record<string, string> = {
<h3 class="tl__role">{e.data.role}</h3>
<h3 class="tl__role">{e.data.role}</h3>
<div class="tl__co">
<div class="tl__co">
{e.data.link ? <a href={e.data.link} target="_blank" rel="noopener">{e.data.company}</a> : <span>{e.data.company}</span>}
{e.data.link ? <a href={e.data.link} target="_blank" rel="noopener">{e.data.company}</a> : <span>{e.data.company}</span>}
- {e.data.location && <span class="tl__loc"><i class="fa-solid fa-location-dot"></i> {e.data.location}</span>}
+ {e.data.link
+ ? <a class="tl__loc tl__site" href={e.data.link} target="_blank" rel="noopener"><i class="fa-solid fa-globe" aria-hidden="true"></i> {e.data.location?.includes(new URL(e.data.link).hostname) ? e.data.location : new URL(e.data.link).hostname}<i class="fa-solid fa-arrow-up-right-from-square tl__site-go" aria-hidden="true"></i></a>
+ : e.data.location && <span class="tl__loc"><i class="fa-solid fa-location-dot"></i> {e.data.location}</span>}
</div>
</div>
</header>
</header>
{e.data.summary && <p class="tl__sum">{e.data.summary}</p>}
{e.data.summary && <p class="tl__sum">{e.data.summary}</p>}
@@ -75,6 +77,9 @@ const kindIcon: Record<string, string> = {
@@ -75,6 +77,9 @@ const kindIcon: Record<string, string> = {
.tl__co { display: flex; flex-wrap: wrap; gap: 6px 18px; color: var(--muted); font-size: .95rem; }
.tl__co { display: flex; flex-wrap: wrap; gap: 6px 18px; color: var(--muted); font-size: .95rem; }
.tl__co a { color: var(--fg); font-weight: 600; }
.tl__co a { color: var(--fg); font-weight: 600; }
.tl__loc { color: var(--dim); font-size: .85rem; display: inline-flex; align-items: center; gap: 6px; }
.tl__loc { color: var(--dim); font-size: .85rem; display: inline-flex; align-items: center; gap: 6px; }
+ .tl__co a.tl__site { color: var(--accent); font-weight: 500; text-decoration: none; }
+ .tl__co a.tl__site:hover { text-decoration: underline; text-underline-offset: 3px; }
+ .tl__site-go { font-size: .7em; opacity: .75; }
.tl__sum { font-size: .95rem; color: var(--muted); font-style: italic; border-left: 2px solid rgba(var(--accent-rgb), .4); padding-left: 12px; }
.tl__sum { font-size: .95rem; color: var(--muted); font-style: italic; border-left: 2px solid rgba(var(--accent-rgb), .4); padding-left: 12px; }
.tl__bullets { margin: 0; padding: 0 0 0 1.1rem; display: grid; gap: 8px; color: var(--muted); line-height: 1.65; font-size: .95rem; }
.tl__bullets { margin: 0; padding: 0 0 0 1.1rem; display: grid; gap: 8px; color: var(--muted); line-height: 1.65; font-size: .95rem; }
.tl__bullets li::marker { color: var(--accent-2); }
.tl__bullets li::marker { color: var(--accent-2); }
@@ -254,7 +254,7 @@ const imageUrl = new URL(image, site).toString();
@@ -254,7 +254,7 @@ const imageUrl = new URL(image, site).toString();
: kind === 'right' ? { opacity: 0, transform: 'translateX(12px)' }
: kind === 'right' ? { opacity: 0, transform: 'translateX(12px)' }
: kind === 'scale' ? { opacity: 0, transform: 'scale(.975)' }
: kind === 'scale' ? { opacity: 0, transform: 'scale(.975)' }
: { opacity: 0, transform: 'translateY(8px) scale(.99)' };
: { opacity: 0, transform: 'translateY(8px) scale(.99)' };
- el.style.opacity = '1'; el.style.transform = 'none';
+ el.classList.add('is-in');
el.animate([from, { opacity: 1, transform: 'none' }], { duration: 600, delay, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
el.animate([from, { opacity: 1, transform: 'none' }], { duration: 600, delay, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
io.unobserve(el);
io.unobserve(el);
}
}
@@ -14,6 +14,8 @@ html {
@@ -14,6 +14,8 @@ html {
font-family: var(--font-body);
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
+ /* Cards and buttons give their own press feedback; the browser's square tap flash drew over rounded ones. */
+ -webkit-tap-highlight-color: transparent;
scroll-padding-top: calc(var(--nav-h) + 12px);
scroll-padding-top: calc(var(--nav-h) + 12px);
}
}
html:not(.lenis) { scroll-behavior: smooth; }
html:not(.lenis) { scroll-behavior: smooth; }
@@ -211,6 +213,9 @@ main > .section:nth-of-type(even) { background: linear-gradient(180deg, transpar
@@ -211,6 +213,9 @@ main > .section:nth-of-type(even) { background: linear-gradient(180deg, transpar
[data-reveal="right"] { transform: translateX(30px); }
[data-reveal="right"] { transform: translateX(30px); }
[data-reveal="scale"] { transform: scale(.94); }
[data-reveal="scale"] { transform: scale(.94); }
.no-js [data-reveal] { opacity: 1; transform: none; }
.no-js [data-reveal] { opacity: 1; transform: none; }
+/* Revealed: a class, not inline styles, so a card's tilt can clear its own inline transform on
+ pointerleave without the pre-reveal offset above coming back (it dropped cards onto the next one). */
+[data-reveal].is-in { opacity: 1; transform: none; }
/* ---- Utilities ------------------------------------------------------------ */
/* ---- Utilities ------------------------------------------------------------ */
.grad-text { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.grad-text { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }