Merge nucleic/warm-velvet-dingo-rmhb into main
Mirrored from corpo 969f1a3.
This commit is contained in:
+303
-49
@@ -78,16 +78,32 @@ const canonical = new URL('/', Astro.site);
|
||||
|
||||
<main>
|
||||
<section class="hero" aria-labelledby="hero-heading">
|
||||
<!-- Headline and card share one wrapper so they sit on a single flex line
|
||||
that can break. Grid alone cannot: a headline too wide for its columns
|
||||
keeps its area and simply prints over whatever occupies the next one. -->
|
||||
<!-- The copy and technology stack share a flex line that can break. Grid
|
||||
alone cannot do that safely: enlarged headline text keeps its area and
|
||||
simply prints over the technology column instead of stepping above it. -->
|
||||
<div class="hero-lead">
|
||||
<div class="hero-copy">
|
||||
<h1 id="hero-heading" class="statement" aria-label="Research that runs.">
|
||||
<span class="line line-research"><span class="word">Research</span></span>
|
||||
<span class="line line-runs"><span class="word"><span class="that">that</span> <span class="runs">runs.</span></span></span>
|
||||
</h1>
|
||||
|
||||
<a class="product-card" href="https://nucleic.blakeslee.xyz">
|
||||
<p class="mission">
|
||||
We engineer infrastructure that keeps intelligence forever online and forever
|
||||
active — immortal by design.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="technology-stack" data-technology-stack>
|
||||
<div class="technology-stage">
|
||||
<div class="technology-panel">
|
||||
<div class="technology-cards">
|
||||
<a
|
||||
id="technology-nucleic"
|
||||
class="product-card is-active"
|
||||
data-technology-card
|
||||
href="https://nucleic.blakeslee.xyz"
|
||||
>
|
||||
<span class="card-index">01 / Flagship system</span>
|
||||
<span class="product-name">Nucleic <span aria-hidden="true">↗</span></span>
|
||||
<span class="product-description">
|
||||
@@ -95,12 +111,36 @@ const canonical = new URL('/', Astro.site);
|
||||
</span>
|
||||
<span class="card-rule" aria-hidden="true"></span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
id="technology-naros-nash"
|
||||
class="product-card product-card-next"
|
||||
data-technology-card
|
||||
href="https://version.blakeslee.xyz/blakeslee-research?q=na&sort=recentupdate"
|
||||
>
|
||||
<span class="card-index">02 / Agent foundation</span>
|
||||
<span class="product-name">narOS + nash <span aria-hidden="true">↗</span></span>
|
||||
<span class="product-description">
|
||||
The agent OS and shell — ready on turn zero, watching every command.
|
||||
</span>
|
||||
<span class="card-rule" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="mission">
|
||||
We engineer infrastructure that keeps intelligence forever online and forever
|
||||
active — immortal by design.
|
||||
</p>
|
||||
<div class="technology-index" aria-label="Choose a technology">
|
||||
<button type="button" class="is-active" data-technology-jump="0" aria-label="Show Nucleic" aria-pressed="true">01</button>
|
||||
<span aria-hidden="true">/</span>
|
||||
<button type="button" data-technology-jump="1" aria-label="Show narOS and nash" aria-pressed="false">02</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="technology-steps" aria-hidden="true">
|
||||
<span data-technology-step="0"></span>
|
||||
<span data-technology-step="1"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -410,6 +450,7 @@ const canonical = new URL('/', Astro.site);
|
||||
}
|
||||
|
||||
.card-index,
|
||||
.technology-index,
|
||||
.footer {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.78rem;
|
||||
@@ -436,10 +477,7 @@ const canonical = new URL('/', Astro.site);
|
||||
--hero-span-4: calc(var(--hero-col) * 4 + 3 * var(--hero-gap));
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
column-gap: var(--hero-gap);
|
||||
row-gap: clamp(1.9rem, 4.4vh, 4rem);
|
||||
min-height: 0;
|
||||
min-height: 100%;
|
||||
align-content: center;
|
||||
padding: clamp(1.75rem, 4.5vh, 4.5rem) 0 clamp(1.5rem, 3vh, 3rem);
|
||||
}
|
||||
@@ -451,13 +489,63 @@ const canonical = new URL('/', Astro.site);
|
||||
headline's minimum width outgrows its share, the line breaks, and the card
|
||||
steps down instead of being printed over. */
|
||||
.hero-lead {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
column-gap: var(--hero-gap);
|
||||
row-gap: clamp(1.5rem, 3vh, 2.5rem);
|
||||
row-gap: clamp(2.75rem, 7vh, 6rem);
|
||||
}
|
||||
|
||||
.hero-lead.has-scroll-stack {
|
||||
position: relative;
|
||||
min-height: clamp(68rem, 185svh, 108rem);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
display: grid;
|
||||
flex: 1 1 var(--hero-span-8);
|
||||
align-self: flex-end;
|
||||
gap: clamp(1.9rem, 4.4vh, 4rem);
|
||||
}
|
||||
|
||||
.has-scroll-stack .hero-copy {
|
||||
position: sticky;
|
||||
top: 50svh;
|
||||
align-self: flex-start;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* Enlarged type and middle-width windows can make the flex line wrap before the
|
||||
phone breakpoint. The script detects that actual wrap from geometry; once it
|
||||
happens, the story becomes one column so its two sticky elements never occupy
|
||||
the same band of the viewport. */
|
||||
.hero-lead.is-stacked-stack {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.is-stacked-stack .hero-copy {
|
||||
position: relative;
|
||||
top: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.is-stacked-stack .technology-stack {
|
||||
width: min(100%, 28rem);
|
||||
min-width: 0;
|
||||
min-height: 135svh;
|
||||
margin-top: clamp(3.25rem, 9vh, 6rem);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.is-stacked-stack .technology-stage {
|
||||
top: clamp(8.5rem, 28svh, 14rem);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.is-stacked-stack .technology-cards {
|
||||
min-height: 11.9rem;
|
||||
}
|
||||
|
||||
.statement {
|
||||
@@ -470,15 +558,6 @@ const canonical = new URL('/', Astro.site);
|
||||
--optical-inset: 0.078em;
|
||||
|
||||
display: grid;
|
||||
/* Flex sizes the border box, so the basis has to give back what the negative
|
||||
margin takes or the card lands inside the eight columns. The half-pixel keeps
|
||||
sub-pixel rounding in the two calc()s from ever summing past the hero width and
|
||||
breaking the line at normal size; flex-grow hands it straight back. */
|
||||
flex: 1 1 calc(var(--hero-span-8) + var(--optical-inset) - 0.5px);
|
||||
/* Fills the row the way a stretched grid item did, so on the narrow desktop
|
||||
widths where the card is the taller of the two, the headline's own rows keep
|
||||
the extra height instead of collapsing to the card's baseline. */
|
||||
align-self: stretch;
|
||||
justify-items: start;
|
||||
margin: 0 0 0 calc(-1 * var(--optical-inset));
|
||||
font-size: clamp(3.2rem, min(9vw, 18vh), 8.25rem);
|
||||
@@ -618,8 +697,6 @@ const canonical = new URL('/', Astro.site);
|
||||
}
|
||||
|
||||
.mission {
|
||||
grid-column: 1 / span 4;
|
||||
grid-row: 2;
|
||||
max-width: 29rem;
|
||||
margin: 0;
|
||||
color: var(--secondary);
|
||||
@@ -629,19 +706,76 @@ const canonical = new URL('/', Astro.site);
|
||||
animation: fade-up 700ms 850ms ease-out forwards;
|
||||
}
|
||||
|
||||
.technology-stack {
|
||||
position: relative;
|
||||
flex: 0 1 var(--hero-span-4);
|
||||
align-self: flex-end;
|
||||
min-width: min(100%, 18rem);
|
||||
}
|
||||
|
||||
.has-scroll-stack .technology-stack {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.technology-stage {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-stage {
|
||||
position: sticky;
|
||||
top: 50svh;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.technology-panel {
|
||||
opacity: 0;
|
||||
animation: fade-up 700ms 980ms ease-out forwards;
|
||||
}
|
||||
|
||||
.technology-cards {
|
||||
display: grid;
|
||||
gap: clamp(1.5rem, 3vh, 2.25rem);
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-cards {
|
||||
min-height: 11.6rem;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
/* Never grows, so the half-pixel the headline gives up comes back to the headline
|
||||
alone and the card starts exactly on the ninth column line. */
|
||||
flex: 0 1 var(--hero-span-4);
|
||||
grid-template-columns: 1fr;
|
||||
align-content: start;
|
||||
gap: 0.55rem;
|
||||
padding: 1.15rem 0 1.05rem;
|
||||
border-top: 1px solid var(--line);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.is-scroll-stack .product-card {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
animation: fade-up 700ms 980ms ease-out forwards;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transform: translate3d(0, -1.5rem, 0) scale(0.985);
|
||||
transition:
|
||||
opacity 320ms ease,
|
||||
visibility 0s linear 320ms,
|
||||
transform 420ms cubic-bezier(0.2, 0.75, 0.2, 1);
|
||||
}
|
||||
|
||||
.is-scroll-stack .product-card-next {
|
||||
transform: translate3d(0, 1.5rem, 0) scale(0.985);
|
||||
}
|
||||
|
||||
.is-scroll-stack .product-card.is-active {
|
||||
z-index: 1;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
transition-delay: 80ms, 0s, 0s;
|
||||
}
|
||||
|
||||
.product-card::after {
|
||||
@@ -680,6 +814,54 @@ const canonical = new URL('/', Astro.site);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.technology-index {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.45rem;
|
||||
margin-top: 0.75rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-index {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.technology-index button {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
padding: 0.3rem;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
letter-spacing: inherit;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
transition: color 220ms ease;
|
||||
}
|
||||
|
||||
.technology-index button:hover,
|
||||
.technology-index button:focus-visible,
|
||||
.technology-index button.is-active {
|
||||
color: var(--pine);
|
||||
}
|
||||
|
||||
.technology-index button:focus-visible {
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.technology-steps {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: none;
|
||||
grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-steps {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--line);
|
||||
@@ -797,8 +979,6 @@ const canonical = new URL('/', Astro.site);
|
||||
}
|
||||
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
row-gap: clamp(1.75rem, 5vh, 3rem);
|
||||
padding-top: clamp(2.5rem, 8vh, 5rem);
|
||||
}
|
||||
|
||||
@@ -810,34 +990,49 @@ const canonical = new URL('/', Astro.site);
|
||||
margin-left: 1.2em;
|
||||
}
|
||||
|
||||
/* One column, so there is nothing for the headline to collide with and no need
|
||||
for the flex line. Dissolving the wrapper puts all three back on the hero grid,
|
||||
where explicit rows keep the phone order the design has always had — the DOM
|
||||
carries the card first because that is where it reads on the desktop layout. */
|
||||
.hero-lead {
|
||||
display: contents;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.statement,
|
||||
.mission,
|
||||
.product-card {
|
||||
grid-column: 1;
|
||||
.hero-lead.has-scroll-stack {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.statement {
|
||||
grid-row: 1;
|
||||
.hero-copy,
|
||||
.has-scroll-stack .hero-copy {
|
||||
position: relative;
|
||||
top: auto;
|
||||
display: grid;
|
||||
gap: clamp(1.75rem, 5vh, 3rem);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.mission {
|
||||
grid-row: 2;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
/* Stacked, the card is a separate thought from the hero copy rather than the
|
||||
column beside it, so it wants more air above than the hero's row rhythm gives. */
|
||||
.product-card {
|
||||
grid-row: 3;
|
||||
margin-top: clamp(0.75rem, 3.5vh, 2.25rem);
|
||||
.technology-stack {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-top: clamp(3.25rem, 9vh, 6rem);
|
||||
}
|
||||
|
||||
.hero-lead.is-stacked-stack .technology-stack {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.has-scroll-stack .technology-stack {
|
||||
min-height: 135svh;
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-stage {
|
||||
top: clamp(8.5rem, 28svh, 14rem);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.is-scroll-stack .technology-cards {
|
||||
min-height: 11.9rem;
|
||||
}
|
||||
|
||||
/* One line each: side by side these two need ~360px of tracking-heavy mono, which
|
||||
@@ -959,6 +1154,7 @@ const canonical = new URL('/', Astro.site);
|
||||
.masthead,
|
||||
.line,
|
||||
.mission,
|
||||
.technology-panel,
|
||||
.product-card,
|
||||
.footer {
|
||||
opacity: 1;
|
||||
@@ -966,3 +1162,61 @@ const canonical = new URL('/', Astro.site);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const technologyStack = document.querySelector<HTMLElement>('[data-technology-stack]');
|
||||
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
if (technologyStack && 'IntersectionObserver' in window && !reducedMotion.matches) {
|
||||
const heroLead = technologyStack.closest<HTMLElement>('.hero-lead');
|
||||
const heroCopy = heroLead?.querySelector<HTMLElement>('.hero-copy');
|
||||
const cards = [...technologyStack.querySelectorAll<HTMLAnchorElement>('[data-technology-card]')];
|
||||
const steps = [...technologyStack.querySelectorAll<HTMLElement>('[data-technology-step]')];
|
||||
const jumps = [...technologyStack.querySelectorAll<HTMLButtonElement>('[data-technology-jump]')];
|
||||
|
||||
heroLead?.classList.add('has-scroll-stack');
|
||||
if (heroLead && heroCopy) {
|
||||
const stackWrappedBelow = technologyStack.offsetLeft <= heroCopy.offsetLeft + 1;
|
||||
heroLead.classList.toggle('is-stacked-stack', stackWrappedBelow);
|
||||
}
|
||||
technologyStack.classList.add('is-scroll-stack');
|
||||
|
||||
const activateTechnology = (index: number) => {
|
||||
cards.forEach((card, cardIndex) => {
|
||||
const isActive = cardIndex === index;
|
||||
card.classList.toggle('is-active', isActive);
|
||||
card.toggleAttribute('inert', !isActive);
|
||||
card.setAttribute('aria-hidden', String(!isActive));
|
||||
});
|
||||
|
||||
jumps.forEach((jump, jumpIndex) => {
|
||||
const isActive = jumpIndex === index;
|
||||
jump.classList.toggle('is-active', isActive);
|
||||
jump.setAttribute('aria-pressed', String(isActive));
|
||||
});
|
||||
};
|
||||
|
||||
activateTechnology(0);
|
||||
|
||||
const stepObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return;
|
||||
const index = Number((entry.target as HTMLElement).dataset.technologyStep);
|
||||
if (Number.isInteger(index)) activateTechnology(index);
|
||||
});
|
||||
},
|
||||
{ rootMargin: '-45% 0px -45% 0px' },
|
||||
);
|
||||
|
||||
steps.forEach((step) => stepObserver.observe(step));
|
||||
jumps.forEach((jump) => {
|
||||
jump.addEventListener('click', () => {
|
||||
const index = Number(jump.dataset.technologyJump);
|
||||
if (!Number.isInteger(index) || !steps[index]) return;
|
||||
activateTechnology(index);
|
||||
steps[index].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user