Skip to content

Commit d43b260

Browse files
committed
finalize page
1 parent ae1dc0a commit d43b260

10 files changed

Lines changed: 193 additions & 90 deletions

apps/docs/app/components/LandingComponentShowcase.vue

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ const futureFlags = {
2727
// reserves space and the section doesn't shift when the cards hydrate. Each
2828
// inner array matches one column's cards below, in order.
2929
const skeletonColumns = [
30-
[340, 470, 340, 60],
31-
[260, 280, 340, 60],
32-
[400, 240, 60],
33-
[360, 320, 200, 60],
30+
[317, 398, 164, 90],
31+
[283, 373, 202, 90],
32+
[516, 432, 90],
33+
[462, 303, 180, 90],
3434
];
3535
</script>
3636

@@ -110,37 +110,53 @@ const skeletonColumns = [
110110
inset-inline: 0;
111111
bottom: 0;
112112
height: 640px;
113-
background: linear-gradient(to bottom, transparent, var(--ui-bg) 90%);
113+
background: linear-gradient(to bottom, transparent, var(--ui-bg) 75%);
114114
pointer-events: none;
115115
}
116116
.dark .showcase-fade {
117117
background-image: linear-gradient(
118118
to bottom,
119119
transparent,
120-
var(--ui-bg-muted) 90%
120+
var(--ui-bg-muted) 75%
121121
);
122122
}
123123
124124
/* Explicit 4-column grid: card placement per column is decided in the template
125-
* (each .showcase-col stacks its cards). Collapses to 2 then 1 column on
126-
* narrower viewports. --showcase-gutter is both the column gap and the vertical
127-
* gap between stacked cards. */
125+
* (each .showcase-col stacks its cards). --showcase-gutter is both the column
126+
* gap and the vertical gap between stacked cards. */
128127
.showcase-grid {
129128
--showcase-gutter: var(--scale-size-20);
130129
display: grid;
131130
grid-template-columns: repeat(4, minmax(0, 1fr));
132131
gap: var(--showcase-gutter);
133132
}
134133
135-
@media (max-width: 1024px) {
134+
/* Each column is a curated stack, so narrower viewports drop whole columns
135+
* (4 -> 3 -> 2) instead of reflowing everything into one endless stack. The
136+
* skeleton fallback uses the same classes, so it collapses in step. */
137+
@media (max-width: 1439.98px) {
136138
.showcase-grid {
137-
grid-template-columns: repeat(2, minmax(0, 1fr));
139+
grid-template-columns: repeat(3, minmax(0, 1fr));
140+
}
141+
.showcase-col:nth-child(4) {
142+
display: none;
138143
}
139144
}
140145
141-
@media (max-width: 640px) {
146+
@media (max-width: 1023.98px) {
142147
.showcase-grid {
143-
grid-template-columns: minmax(0, 1fr);
148+
grid-template-columns: repeat(2, minmax(0, 1fr));
149+
}
150+
.showcase-col:nth-child(3) {
151+
display: none;
152+
}
153+
}
154+
155+
/* Smartphones skip the showcase entirely; the hero hands over to the next
156+
* section directly (the hero fade shortens to match, see index.vue). */
157+
@media (max-width: 639.98px) {
158+
.showcase-section {
159+
display: none;
144160
}
145161
}
146162

apps/docs/app/components/LandingDocsCarousel.vue

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -246,65 +246,58 @@ function onKeydown(event: KeyboardEvent) {
246246
}
247247
248248
.docs-carousel__viewport {
249-
cursor: grab;
250249
touch-action: pan-y;
251250
outline: none;
252251
user-select: none;
253252
-webkit-user-select: none;
253+
/* Contain the arc horizontally (so the off-screen cards never create a page
254+
scrollbar). The clip box is grown with padding, then handed back to the
255+
layout via matching negative margins, because WebKit clips BOTH axes when
256+
either one is clipped: without the headroom, the side cards curving down
257+
the arc (and the hovered card's top) get cut at the box edge. */
258+
overflow-x: clip;
259+
padding-top: 32px;
260+
margin-top: -32px;
261+
padding-bottom: 240px;
262+
margin-bottom: -240px;
254263
}
255264
.doc-slide {
256265
-webkit-user-drag: none;
257266
}
258267
259-
/* The arc spans the full viewport width. */
268+
/* The arc spans the full viewport width. The height leaves room below the
269+
centered card for its hover expansion ("Read more" growing the card
270+
downward); the side cards' arc drop spills into the viewport's padding
271+
headroom above. No edge-fade mask: the per-card opacity falloff already
272+
fades the arc out toward the sides, and a mask would put a visible cut
273+
through the slides where they curve toward the edges. */
274+
/* The grab cursor lives on the wheel (not the viewport) so the viewport's
275+
clip-headroom padding below the arc doesn't advertise dragging. */
260276
.docs-carousel__wheel {
261277
position: relative;
262-
height: 430px;
263-
/* Clip horizontally (no page scroll) but let cards spill vertically so the
264-
lower cards in the arc aren't cut off. */
265-
overflow-x: clip;
266-
overflow-y: visible;
267-
/* Fade the arc out toward the viewport edges, past the content width. The
268-
gradient is horizontal; the mask is deliberately far taller than the wheel
269-
(and centered) so the cards spilling above/below aren't clipped by it. */
270-
--edge-fade: max(64px, calc((100% - 1620px) / 2));
271-
-webkit-mask-image: linear-gradient(
272-
to right,
273-
transparent,
274-
#000 var(--edge-fade),
275-
#000 calc(100% - var(--edge-fade)),
276-
transparent
277-
);
278-
mask-image: linear-gradient(
279-
to right,
280-
transparent,
281-
#000 var(--edge-fade),
282-
#000 calc(100% - var(--edge-fade)),
283-
transparent
284-
);
285-
-webkit-mask-size: 100% 3000px;
286-
mask-size: 100% 3000px;
287-
-webkit-mask-position: center;
288-
mask-position: center;
289-
-webkit-mask-repeat: no-repeat;
290-
mask-repeat: no-repeat;
291-
}
292-
.docs-carousel__wheel.is-dragging {
293-
cursor: grabbing;
278+
height: 460px;
279+
cursor: grab;
294280
}
295-
/* Only one card shows on small screens, so the edge fade would just clip its
296-
sides — drop it there. */
281+
/* One card at a time on phones (and its "Read more" is always expanded there),
282+
so the wheel doesn't need the desktop headroom; tighten the gap down to the
283+
controls. */
297284
@media (max-width: 639.98px) {
298285
.docs-carousel__wheel {
299-
-webkit-mask-image: none;
300-
mask-image: none;
286+
height: 420px;
301287
}
302288
}
289+
.docs-carousel__wheel.is-dragging {
290+
cursor: grabbing;
291+
}
303292
.docs-carousel__card {
304293
position: absolute;
305294
top: 45%;
306295
left: 50%;
307-
width: clamp(300px, 25vw, 390px);
296+
/* Desktop width everywhere; on small screens the card just caps at 90% of
297+
the viewport instead of shrinking early. */
298+
width: 390px;
299+
max-width: 90vw;
300+
max-width: 90dvw;
308301
transition:
309302
transform 0.55s var(--ease-out),
310303
opacity 0.55s var(--ease-out);
@@ -325,7 +318,9 @@ function onKeydown(event: KeyboardEvent) {
325318
flex-direction: column;
326319
overflow: hidden;
327320
border: 1px solid var(--ui-border);
328-
border-radius: 1.25rem;
321+
/* 1rem matches the landing page's other card surfaces (accordion items and
322+
the copy-command line). */
323+
border-radius: 1rem;
329324
background: var(--ui-bg);
330325
/* Shared landing elevation (defined on .landing) so cards match the accordion
331326
* and copy line in both themes. */

apps/docs/app/components/LandingUfo.vue

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ let raf = 0;
1515
let running = false;
1616
let reduced = false;
1717
let hovering = false;
18+
let heroVisible = false;
19+
let heroObserver: IntersectionObserver | null = null;
1820
1921
// Coordinates are relative to the hero's top-left.
2022
const target = { x: 0, y: 0 }; // cursor position, offset to sit above the cursor
@@ -24,7 +26,7 @@ const dest = { x: 0, y: 0 }; // where the current creep ends
2426
2527
const OFFSET_X = -4; // sit a tiny bit to the left of the cursor
2628
const OFFSET_Y = -26; // hover a bit above the cursor
27-
const IDLE_DELAY = 1600; // cursor must be still this long before it dares appear
29+
const IDLE_DELAY = 2600; // cursor must be still this long before it dares appear
2830
const MOVE_EPS = 5; // movement under this counts as "still"
2931
const SWAY_AMP = 2; // tiny left-right sway while scanning
3032
@@ -136,7 +138,11 @@ function frame(now: number) {
136138
137139
if (phase === "hidden") {
138140
scaleVal = 0;
139-
if (hovering && now - lastMoveTime > IDLE_DELAY) startAppear(now);
141+
// Re-checked at the moment of appearing (not just at pointerenter): the
142+
// hero must still be on screen and the page still in dark mode, so a
143+
// scroll-away or a theme switch mid-run can't summon an invisible UFO.
144+
if (hovering && heroVisible && isDark() && now - lastMoveTime > IDLE_DELAY)
145+
startAppear(now);
140146
} else if (phase === "appear") {
141147
const t = clamp01((now - tStart) / tDur);
142148
scaleVal = easeOut(t);
@@ -171,11 +177,20 @@ function frame(now: number) {
171177
pos.y = from.y + (dest.y - from.y) * e;
172178
if (t >= 1) {
173179
gliding = false;
174-
phase = "scan";
175-
scanning.value = true;
176-
scanRestX = pos.x;
177-
scanRestY = pos.y;
178-
scanStart = now;
180+
// Only start scanning (and the cursor-cycle gag) when the cursor is
181+
// actually still being watched: pointer inside the hero, hero on
182+
// screen, dark mode. If the pointer left mid-approach (the 3s linger
183+
// keeps the machine running), flee instead of scanning a cursor that
184+
// is somewhere else on the page.
185+
if (!hovering || !heroVisible || !isDark()) {
186+
startFlee(now);
187+
} else {
188+
phase = "scan";
189+
scanning.value = true;
190+
scanRestX = pos.x;
191+
scanRestY = pos.y;
192+
scanStart = now;
193+
}
179194
}
180195
} else if (phase === "scan") {
181196
// tiny left-right sway while it scans
@@ -259,13 +274,35 @@ function onLeave() {
259274
}, 3000);
260275
}
261276
277+
// Scrolling moves the hero under a stationary cursor without firing any
278+
// pointer events, which would otherwise leave `hovering` and the idle timer
279+
// stale (the UFO then "randomly" appears while the user reads another
280+
// section). Treat scrolling as cursor activity and spook the UFO like any
281+
// other movement.
282+
function onScroll() {
283+
lastMoveTime = performance.now();
284+
if (phase !== "hidden" && phase !== "flee") startFlee(performance.now());
285+
}
286+
262287
onMounted(() => {
263288
reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
264289
hero = root.value?.parentElement ?? null;
265290
if (!hero) return;
266291
hero.addEventListener("pointermove", onMove);
267292
hero.addEventListener("pointerenter", onEnter);
268293
hero.addEventListener("pointerleave", onLeave);
294+
window.addEventListener("scroll", onScroll, { passive: true });
295+
296+
// Hard stop (which also tears down the cursor cycle) whenever the hero
297+
// scrolls out of view: the UFO must never act while it can't be seen.
298+
heroObserver = new IntersectionObserver(
299+
([entry]) => {
300+
heroVisible = entry?.isIntersecting ?? false;
301+
if (!heroVisible && running) stop();
302+
},
303+
{ threshold: 0.2 },
304+
);
305+
heroObserver.observe(hero);
269306
});
270307
271308
onBeforeUnmount(() => {
@@ -274,6 +311,8 @@ onBeforeUnmount(() => {
274311
hero.removeEventListener("pointerenter", onEnter);
275312
hero.removeEventListener("pointerleave", onLeave);
276313
}
314+
window.removeEventListener("scroll", onScroll);
315+
heroObserver?.disconnect();
277316
if (leaveTimer) clearTimeout(leaveTimer);
278317
stopCursorCycle();
279318
cancelAnimationFrame(raf);

apps/docs/app/components/showcase/ConnectNexusCard.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import MtPasswordField from "@shopware-ag/meteor-component-library/MtPasswordFie
77
import MtButton from "@shopware-ag/meteor-component-library/MtButton";
88
import MtLink from "@shopware-ag/meteor-component-library/MtLink";
99
10-
const email = ref("mia.chen@company.io");
10+
const email = ref("mia.chen@example.com");
1111
const password = ref("peekaboo");
1212
const muted = "color-text-secondary-default";
1313
@@ -92,7 +92,8 @@ onMounted(() => {
9292
<mt-text size="xs" :color="muted">
9393
Don't have an account?
9494
</mt-text>
95-
<mt-link as="a" href="#">Register now</mt-link>
95+
<!-- Decorative demo link: prevent the href="#" scroll-to-top jump. -->
96+
<mt-link as="a" href="#" @click.prevent>Register now</mt-link>
9697
</div>
9798
</div>
9899
</div>
@@ -149,6 +150,7 @@ onMounted(() => {
149150
}
150151
.connect__footer {
151152
display: flex;
153+
flex-wrap: wrap;
152154
align-items: center;
153155
gap: var(--scale-size-4);
154156
font-size: var(--font-size-xs);

apps/docs/app/components/showcase/CustomDomainCard.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ const statusColor = computed(() =>
3838
<mt-text size="xs">SSL certificate valid</mt-text>
3939
</div>
4040
</div>
41-
<mt-link to="#" type="external">Learn about custom domains</mt-link>
41+
<!-- Decorative demo link: a plain anchor (not router-link, whose own
42+
click handler could navigate before a prevent) with the jump to
43+
"#" suppressed. -->
44+
<mt-link as="a" href="#" type="external" @click.prevent>
45+
Learn about custom domains
46+
</mt-link>
4247
</div>
4348
</mt-card>
4449
</div>

apps/docs/app/components/showcase/ProductSpecificationCard.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const productImage = "/showcase-product.jpg";
5858
label="Price"
5959
size="small"
6060
:min="0"
61+
:step="0.25"
6162
:digits="2"
6263
:show-controls="false"
6364
>

apps/docs/app/components/showcase/ShareInviteCard.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const { addSnackbar } = useSnackbar();
1919
2020
const inviteEmail = ref("");
2121
const shareUsers = [
22-
{ firstName: "Mia", lastName: "Chen", email: "mia.chen@company.io" },
23-
{ firstName: "Jonas", lastName: "Weber", email: "jonas.weber@company.io" },
24-
{ firstName: "Adrian", lastName: "Silva", email: "adrian.silva@company.io" },
22+
{ firstName: "Mia", lastName: "Chen", email: "mia.chen@example.com" },
23+
{ firstName: "Jonas", lastName: "Weber", email: "jonas.weber@example.com" },
24+
{ firstName: "Adrian", lastName: "Silva", email: "adrian.silva@example.com" },
2525
];
2626
const muted = "color-text-secondary-default";
2727
</script>
@@ -61,10 +61,12 @@ const muted = "color-text-secondary-default";
6161
variant="circle"
6262
/>
6363
<div class="min-w-0 flex-1">
64-
<mt-text size="xs" weight="medium"
64+
<mt-text size="xs" weight="medium" class="w-full truncate"
6565
>{{ u.firstName }} {{ u.lastName }}</mt-text
6666
>
67-
<mt-text size="xs" :color="muted">{{ u.email }}</mt-text>
67+
<mt-text size="xs" :color="muted" class="w-full truncate">{{
68+
u.email
69+
}}</mt-text>
6870
</div>
6971
<mt-dropdown-menu-root>
7072
<mt-dropdown-menu-trigger as-child>

0 commit comments

Comments
 (0)