save
Deploy Static Site / deploy (push) Successful in 10m1s

This commit is contained in:
Lars Nolden
2026-08-14 17:23:27 +02:00
parent 44fe30a781
commit 467dae14a2
2 changed files with 29 additions and 6 deletions
@@ -31,6 +31,15 @@ type ExpandingCard = {
/** which side of the container the open card sits on the rail takes the other */ /** which side of the container the open card sits on the rail takes the other */
type Side = "left" | "right" type Side = "left" | "right"
/**
* Where the minimised cards are allowed to gather. `auto` mirrors the pointer
* the picked card keeps its half, so the rail changes sides as you browse.
* Pinning it to one side trades that spatial cue for calm: picking out of the
* rail then shifts the column by a single slot instead of sending every card
* across the container.
*/
type RailSide = Side | "auto"
/** the face a card currently shows */ /** the face a card currently shows */
type Face = "brief" | "rail" | "open" type Face = "brief" | "rail" | "open"
@@ -567,9 +576,10 @@ function GridCard({
/** /**
* Card grid that unpacks in place. Clicking a card grows it to the full height * Card grid that unpacks in place. Clicking a card grows it to the full height
* of the grid it was part of, while the remaining cards gather into a column of * of the grid it was part of, while the remaining cards gather into a column of
* a third of the width beside it on the side the open card came *from*, so * a third of the width beside it by default on the side the open card came
* the card you picked stays where you clicked it and everything else moves out * *from*, so the card you picked stays where you clicked it and everything else
* of its way. Opening a card out of the rail therefore flips the composition. * moves out of its way. Opening a card out of the rail therefore flips the
* composition; pass `railSide` to pin the column and keep the travel short.
* *
* Every card is absolutely positioned from measured geometry, which keeps a * Every card is absolutely positioned from measured geometry, which keeps a
* state change to a transform plus a size: the faces are pre-laid out at the * state change to a transform plus a size: the faces are pre-laid out at the
@@ -578,9 +588,12 @@ function GridCard({
*/ */
function ExpandingCardGrid({ function ExpandingCardGrid({
items, items,
railSide = "auto",
className, className,
}: { }: {
items: ExpandingCard[] items: ExpandingCard[]
/** side the minimised cards stack on; `auto` follows the card you clicked */
railSide?: RailSide
className?: string className?: string
}) { }) {
const [open, setOpen] = React.useState<{ id: string; side: Side } | null>( const [open, setOpen] = React.useState<{ id: string; side: Side } | null>(
@@ -592,13 +605,18 @@ function ExpandingCardGrid({
const uid = React.useId() const uid = React.useId()
const active = open ? items.findIndex((item) => item.id === open.id) : -1 const active = open ? items.findIndex((item) => item.id === open.id) : -1
/* a pinned rail decides the composition outright; the pointer-driven side
stays in state either way, so switching back to `auto` resumes from the
half the reader last picked instead of jumping */
const pinned =
railSide === "auto" ? null : railSide === "left" ? "right" : "left"
const plan = planLayout({ const plan = planLayout({
count: items.length, count: items.length,
width: metrics.width, width: metrics.width,
teaserHeights: metrics.teaser, teaserHeights: metrics.teaser,
copyHeights: metrics.copy, copyHeights: metrics.copy,
active, active,
side: open?.side ?? "left", side: pinned ?? open?.side ?? "left",
}) })
/* transitions only after the measured geometry has been painted once, and /* transitions only after the measured geometry has been painted once, and
@@ -622,7 +640,8 @@ function ExpandingCardGrid({
} }
const frame = plan.frames[index] const frame = plan.frames[index]
/* the card keeps its side: whichever half it is sitting in when clicked is /* the card keeps its side: whichever half it is sitting in when clicked is
the half it opens into, so the rail always forms on the other one */ the half it opens into, so the rail forms on the other one unless a
pinned `railSide` overrules it above */
const onRight = frame const onRight = frame
? frame.x + frame.w / 2 > metrics.width / 2 + 1 ? frame.x + frame.w / 2 > metrics.width / 2 + 1
: false : false
+5 -1
View File
@@ -531,7 +531,11 @@ export default function HomePage() {
title="Alles, was für den laufenden Messbetrieb benötigt wird" title="Alles, was für den laufenden Messbetrieb benötigt wird"
lead="SolenOS verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System. Statt einzelne Komponenten selbst zusammenzustellen und miteinander zu verbinden, erhalten Sie eine Lösung, bei der Bestellung, Hardware, Konfiguration und laufender Betrieb zusammenspielen." lead="SolenOS verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System. Statt einzelne Komponenten selbst zusammenzustellen und miteinander zu verbinden, erhalten Sie eine Lösung, bei der Bestellung, Hardware, Konfiguration und laufender Betrieb zusammenspielen."
/> />
<ExpandingCardGrid items={operationsScope} className="mt-12" /> <ExpandingCardGrid
items={operationsScope}
railSide="right"
className="mt-12"
/>
</Section> </Section>
<Section> <Section>