@lynx-example/design-guide 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/color_wheels.lynx.bundle +0 -0
- package/dist/color_wheels.web.bundle +1 -1
- package/dist/force_field.lynx.bundle +0 -0
- package/dist/force_field.web.bundle +1 -0
- package/dist/gooey_effect.lynx.bundle +0 -0
- package/dist/gooey_effect.web.bundle +1 -1
- package/dist/soft_glow.lynx.bundle +0 -0
- package/dist/soft_glow.web.bundle +1 -0
- package/lynx.config.mjs +6 -0
- package/package.json +2 -1
- package/src/color_wheels/index.css +0 -26
- package/src/color_wheels/index.tsx +6 -5
- package/src/force_field/cells.ts +20 -0
- package/src/force_field/color.ts +58 -0
- package/src/force_field/field-force.ts +97 -0
- package/src/force_field/field.css +23 -0
- package/src/force_field/field.tsx +55 -0
- package/src/force_field/field.types.ts +50 -0
- package/src/force_field/index.css +17 -0
- package/src/force_field/index.tsx +100 -0
- package/src/force_field/math.ts +8 -0
- package/src/gooey_effect/index.css +0 -26
- package/src/gooey_effect/index.tsx +6 -5
- package/src/shared/components/caption/index.css +27 -0
- package/src/shared/components/caption/index.tsx +17 -0
- package/src/shared/hooks/use-pointer-field-point/index.ts +85 -0
- package/src/shared/hooks/use-pointer-interaction/index.ts +100 -0
- package/src/shared/hooks/use-pointer-interaction/types.ts +30 -0
- package/src/soft_glow/index.css +87 -0
- package/src/soft_glow/index.tsx +31 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { root } from "@lynx-js/react";
|
|
2
|
+
import { useMemo } from "@lynx-js/react";
|
|
3
|
+
|
|
4
|
+
import { Caption } from "../shared/components/caption/index.jsx";
|
|
5
|
+
import { usePointerFieldPoint } from "../shared/hooks/use-pointer-field-point/index.js";
|
|
6
|
+
import { CELLS } from "./cells.js";
|
|
7
|
+
import { lerpColor } from "./color.js";
|
|
8
|
+
import { makeForceField } from "./field-force.js";
|
|
9
|
+
import { Dot, DotField } from "./field.jsx";
|
|
10
|
+
import { clamp01 } from "./math.js";
|
|
11
|
+
import "./index.css";
|
|
12
|
+
|
|
13
|
+
const RADIUS = 0.60;
|
|
14
|
+
|
|
15
|
+
const forceAt = makeForceField({
|
|
16
|
+
radius: RADIUS,
|
|
17
|
+
strength: 0.15,
|
|
18
|
+
falloff: 1.25,
|
|
19
|
+
mode: "repel",
|
|
20
|
+
swirl: 0.6,
|
|
21
|
+
clampMax: 0.14,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
const {
|
|
26
|
+
p, // Force point in normalized space
|
|
27
|
+
handlePointerDown,
|
|
28
|
+
handlePointerMove,
|
|
29
|
+
handlePointerUp,
|
|
30
|
+
handleElementLayoutChange,
|
|
31
|
+
} = usePointerFieldPoint({ x0: 0.6, y0: 1 });
|
|
32
|
+
|
|
33
|
+
const models = useMemo(() => {
|
|
34
|
+
let minD = Infinity;
|
|
35
|
+
let accentId: string | null = null;
|
|
36
|
+
|
|
37
|
+
// First pass: compute everything and track nearest dot
|
|
38
|
+
const tmp = CELLS.map((cell) => {
|
|
39
|
+
const f = forceAt({ x: cell.cx, y: cell.cy }, p);
|
|
40
|
+
|
|
41
|
+
if (f.d < minD) {
|
|
42
|
+
minD = f.d;
|
|
43
|
+
accentId = cell.id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const x = clamp01(cell.cx + f.dx);
|
|
47
|
+
const y = clamp01(cell.cy + f.dy);
|
|
48
|
+
|
|
49
|
+
const dist01 = clamp01(f.d / RADIUS);
|
|
50
|
+
const g = Math.pow(dist01, 1.5);
|
|
51
|
+
|
|
52
|
+
const s = 0.5 + (1 - g) * 2.5;
|
|
53
|
+
const color = lerpColor(g, "#ff7385", "#00d0f1");
|
|
54
|
+
|
|
55
|
+
return { id: cell.id, x, y, s, color };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Second pass: mark accent
|
|
59
|
+
return tmp.map((m) => ({
|
|
60
|
+
...m,
|
|
61
|
+
useAccent: m.id === accentId,
|
|
62
|
+
}));
|
|
63
|
+
}, [p.x, p.y]);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<view className="design-container">
|
|
67
|
+
<DotField
|
|
68
|
+
fieldSize={300}
|
|
69
|
+
dotSize={5}
|
|
70
|
+
dotAccentColor="#ff1a6e"
|
|
71
|
+
bindtouchstart={handlePointerDown}
|
|
72
|
+
bindtouchmove={handlePointerMove}
|
|
73
|
+
bindtouchend={handlePointerUp}
|
|
74
|
+
bindlayoutchange={handleElementLayoutChange}
|
|
75
|
+
>
|
|
76
|
+
{models.map((m) => (
|
|
77
|
+
<Dot
|
|
78
|
+
key={m.id}
|
|
79
|
+
x={m.x}
|
|
80
|
+
y={m.y}
|
|
81
|
+
s={m.s}
|
|
82
|
+
color={m.color}
|
|
83
|
+
useAccent={m.useAccent}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
</DotField>
|
|
87
|
+
<Caption
|
|
88
|
+
title="Force Field"
|
|
89
|
+
subtitle="Powered by Inline CSS Variables"
|
|
90
|
+
footnote="Requires Lynx SDK 3.6+"
|
|
91
|
+
/>
|
|
92
|
+
</view>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
root.render(<App />);
|
|
97
|
+
|
|
98
|
+
if (import.meta.webpackHot) {
|
|
99
|
+
import.meta.webpackHot.accept();
|
|
100
|
+
}
|
|
@@ -78,29 +78,3 @@
|
|
|
78
78
|
padding-left: 0;
|
|
79
79
|
padding-right: 0;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
.caption-container {
|
|
83
|
-
display: flex;
|
|
84
|
-
flex-direction: column;
|
|
85
|
-
align-items: center;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.title {
|
|
89
|
-
font-size: 16px;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
line-height: 21px;
|
|
92
|
-
color: #f8f8f8;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.subtitle {
|
|
96
|
-
line-height: 15px;
|
|
97
|
-
font-size: 12px;
|
|
98
|
-
font-weight: 400;
|
|
99
|
-
color: #b3b3b3;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.footnote {
|
|
103
|
-
line-height: 14px;
|
|
104
|
-
font-size: 11px;
|
|
105
|
-
color: #4f4f4f;
|
|
106
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { root } from "@lynx-js/react";
|
|
2
2
|
|
|
3
|
+
import { Caption } from "../shared/components/caption/index.jsx";
|
|
3
4
|
import "./index.css";
|
|
4
5
|
|
|
5
6
|
function App() {
|
|
@@ -13,11 +14,11 @@ function App() {
|
|
|
13
14
|
<view className="dot g4" />
|
|
14
15
|
</view>
|
|
15
16
|
</view>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<Caption
|
|
18
|
+
title="Gooey Effect"
|
|
19
|
+
subtitle="Powered by CSS filter: contrast + blur"
|
|
20
|
+
footnote="Requires Lynx SDK 3.6+"
|
|
21
|
+
/>
|
|
21
22
|
</view>
|
|
22
23
|
);
|
|
23
24
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.caption-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.caption-title {
|
|
8
|
+
font-size: 16px;
|
|
9
|
+
font-weight: 600;
|
|
10
|
+
line-height: 21px;
|
|
11
|
+
color: #f8f8f8;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.caption-subtitle {
|
|
15
|
+
margin-top: 2px;
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
font-weight: 400;
|
|
18
|
+
line-height: 15px;
|
|
19
|
+
color: #b3b3b3;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.caption-footnote {
|
|
23
|
+
margin-top: 4px;
|
|
24
|
+
font-size: 11px;
|
|
25
|
+
line-height: 14px;
|
|
26
|
+
color: #4f4f4f;
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./index.css";
|
|
2
|
+
|
|
3
|
+
type CaptionProps = {
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
footnote?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function Caption({ title, subtitle, footnote }: CaptionProps) {
|
|
10
|
+
return (
|
|
11
|
+
<view className="caption-container">
|
|
12
|
+
<text className="caption-title">{title}</text>
|
|
13
|
+
{subtitle && <text className="caption-subtitle">{subtitle}</text>}
|
|
14
|
+
{footnote && <text className="caption-footnote">{footnote}</text>}
|
|
15
|
+
</view>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useRef, useState } from "@lynx-js/react";
|
|
2
|
+
import type { LayoutChangeEvent, TouchEvent } from "@lynx-js/types";
|
|
3
|
+
import { usePointerInteraction } from "../use-pointer-interaction/index.js";
|
|
4
|
+
|
|
5
|
+
type FieldPoint = {
|
|
6
|
+
/** Normalized x in [0, 1] (not clamped). */
|
|
7
|
+
x: number;
|
|
8
|
+
/** Normalized y in [0, 1] (not clamped). */
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type UsePointerFieldPointProps = {
|
|
13
|
+
/** Initial normalized x, defaults to 0.5*/
|
|
14
|
+
x0?: number;
|
|
15
|
+
y0?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function usePointerFieldPoint({ x0 = 0.5, y0 = 0.5 }: UsePointerFieldPointProps = {}) {
|
|
19
|
+
const [p, setP] = useState<FieldPoint>({ x: x0, y: y0 });
|
|
20
|
+
|
|
21
|
+
const eleTopRef = useRef<number | null>(null);
|
|
22
|
+
const eleHeightRef = useRef(0);
|
|
23
|
+
|
|
24
|
+
const lastYRatioRef = useRef(y0);
|
|
25
|
+
|
|
26
|
+
const pointer = usePointerInteraction({
|
|
27
|
+
onUpdate(pos) {
|
|
28
|
+
setP({
|
|
29
|
+
x: pos.offsetRatio,
|
|
30
|
+
y: lastYRatioRef.current,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const updateYFromEvent = (e: TouchEvent) => {
|
|
36
|
+
const top = eleTopRef.current;
|
|
37
|
+
const height = eleHeightRef.current;
|
|
38
|
+
if (top != null && height > 0) {
|
|
39
|
+
lastYRatioRef.current = (e.detail.y - top) / height;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handlePointerDown2D = (e: TouchEvent) => {
|
|
44
|
+
updateYFromEvent(e);
|
|
45
|
+
pointer.handlePointerDown(e);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handlePointerMove2D = (e: TouchEvent) => {
|
|
49
|
+
updateYFromEvent(e);
|
|
50
|
+
// X handled by existing hook
|
|
51
|
+
pointer.handlePointerMove(e);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleLayoutChange2D = (e: LayoutChangeEvent) => {
|
|
55
|
+
eleHeightRef.current = e.detail.height;
|
|
56
|
+
|
|
57
|
+
const currentTarget = lynx
|
|
58
|
+
.createSelectorQuery()
|
|
59
|
+
// @ts-expect-error
|
|
60
|
+
.selectUniqueID(e.currentTarget.uid);
|
|
61
|
+
|
|
62
|
+
currentTarget
|
|
63
|
+
?.invoke({
|
|
64
|
+
method: "boundingClientRect",
|
|
65
|
+
params: { relativeTo: "screen" },
|
|
66
|
+
success: (res: { top: number }) => {
|
|
67
|
+
eleTopRef.current = res.top;
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
.exec();
|
|
71
|
+
|
|
72
|
+
pointer.handleElementLayoutChange(e);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
p,
|
|
77
|
+
handlePointerDown: handlePointerDown2D,
|
|
78
|
+
handlePointerMove: handlePointerMove2D,
|
|
79
|
+
handlePointerUp: pointer.handlePointerUp,
|
|
80
|
+
handleElementLayoutChange: handleLayoutChange2D,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { usePointerFieldPoint };
|
|
85
|
+
export type { FieldPoint };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useRef } from "@lynx-js/react";
|
|
2
|
+
import type { LayoutChangeEvent, TouchEvent } from "@lynx-js/types";
|
|
3
|
+
import type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValueBase } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pointer → element-local coordinates adapter.
|
|
7
|
+
*
|
|
8
|
+
* - The container usually hosts touch/pointer events (recommended for larger hit area).
|
|
9
|
+
* - The element provides the measurement frame (layout + bounding rect).
|
|
10
|
+
* - If you don't need a separate container, you may bind all handlers on the element
|
|
11
|
+
* itself (container === element).
|
|
12
|
+
*
|
|
13
|
+
* No clamping/step logic is applied here.
|
|
14
|
+
*/
|
|
15
|
+
function usePointerInteraction({
|
|
16
|
+
onUpdate,
|
|
17
|
+
onCommit,
|
|
18
|
+
}: UsePointerInteractionProps = {}): UsePointerInteractionReturnValue {
|
|
19
|
+
/** Element (coordinate frame) & metrics */
|
|
20
|
+
const eleLeftRef = useRef<number | null>(null);
|
|
21
|
+
const eleWidthRef = useRef(0);
|
|
22
|
+
|
|
23
|
+
/** Last computed pointer position snapshot */
|
|
24
|
+
const posRef = useRef<PointerPosition | null>(null);
|
|
25
|
+
|
|
26
|
+
const draggingRef = useRef(false);
|
|
27
|
+
|
|
28
|
+
const buildPosition = (x: number): PointerPosition | null => {
|
|
29
|
+
const width = eleWidthRef.current;
|
|
30
|
+
const left = eleLeftRef.current;
|
|
31
|
+
|
|
32
|
+
if (width > 0 && left != null) {
|
|
33
|
+
const offset = x - left;
|
|
34
|
+
const offsetRatio = offset / width;
|
|
35
|
+
const pos = { offset, offsetRatio, elementWidth: width };
|
|
36
|
+
posRef.current = pos;
|
|
37
|
+
return pos;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handlePointerDown = (e: TouchEvent) => {
|
|
43
|
+
draggingRef.current = true;
|
|
44
|
+
buildPosition(e.detail.x);
|
|
45
|
+
if (posRef.current) {
|
|
46
|
+
onUpdate?.(posRef.current);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handlePointerMove = (e: TouchEvent) => {
|
|
51
|
+
if (!draggingRef.current) return;
|
|
52
|
+
buildPosition(e.detail.x);
|
|
53
|
+
if (posRef.current) {
|
|
54
|
+
onUpdate?.(posRef.current);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handlePointerUp = (e: TouchEvent) => {
|
|
59
|
+
draggingRef.current = false;
|
|
60
|
+
buildPosition(e.detail.x);
|
|
61
|
+
if (posRef.current) {
|
|
62
|
+
onUpdate?.(posRef.current);
|
|
63
|
+
onCommit?.(posRef.current);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleElementLayoutChange = (e: LayoutChangeEvent) => {
|
|
68
|
+
eleWidthRef.current = e.detail.width;
|
|
69
|
+
|
|
70
|
+
const currentTarget = lynx
|
|
71
|
+
.createSelectorQuery()
|
|
72
|
+
// @ts-expect-error
|
|
73
|
+
.selectUniqueID(e.currentTarget.uid);
|
|
74
|
+
|
|
75
|
+
currentTarget
|
|
76
|
+
?.invoke({
|
|
77
|
+
method: "boundingClientRect",
|
|
78
|
+
params: { relativeTo: "screen" }, // screen-based so it matches e.detail.x
|
|
79
|
+
success: (res: { left: number }) => {
|
|
80
|
+
eleLeftRef.current = res.left;
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
.exec();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
handlePointerDown,
|
|
88
|
+
handlePointerMove,
|
|
89
|
+
handlePointerUp,
|
|
90
|
+
handleElementLayoutChange,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type UsePointerInteractionReturnValue = UsePointerInteractionReturnValueBase<
|
|
95
|
+
TouchEvent,
|
|
96
|
+
LayoutChangeEvent
|
|
97
|
+
>;
|
|
98
|
+
|
|
99
|
+
export { usePointerInteraction };
|
|
100
|
+
export type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValue };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Pointer position in the element’s local frame. */
|
|
2
|
+
interface PointerPosition {
|
|
3
|
+
/** Horizontal offset from element's left edge (px). Can be <0 or >width. */
|
|
4
|
+
offset: number;
|
|
5
|
+
/** offset / elementWidth. Can be <0 or >1. */
|
|
6
|
+
offsetRatio: number;
|
|
7
|
+
/** Measured width of the element (px). */
|
|
8
|
+
elementWidth: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Interaction callbacks. */
|
|
12
|
+
interface UsePointerInteractionProps {
|
|
13
|
+
/** Fires during drag/move. */
|
|
14
|
+
onUpdate?: (pos: PointerPosition) => void;
|
|
15
|
+
/** Fires on pointer up (final value). */
|
|
16
|
+
onCommit?: (pos: PointerPosition) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type UsePointerInteractionReturnValueBase<TTouch, TLayout> = {
|
|
20
|
+
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchstart={handlePointerDown} /> */
|
|
21
|
+
handlePointerDown: (e: TTouch) => void;
|
|
22
|
+
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchmove={handlePointerMove} /> */
|
|
23
|
+
handlePointerMove: (e: TTouch) => void;
|
|
24
|
+
/** Bind on CONTAINER (or ELEMENT if container===element): <view bindtouchend|bindtouchcancel={handlePointerUp} /> */
|
|
25
|
+
handlePointerUp: (e: TTouch) => void;
|
|
26
|
+
/** Bind on ELEMENT: <view bindlayoutchange={handleElementLayoutChange} /> */
|
|
27
|
+
handleElementLayoutChange: (e: TLayout) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValueBase };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* Semantic shadow tokens are progressively resolved into
|
|
2
|
+
* ambient and rim shadows through nested CSS variables,
|
|
3
|
+
* allowing visual intensity to be adjusted without changing structure.
|
|
4
|
+
*/
|
|
5
|
+
.card {
|
|
6
|
+
--primary: 254, 105, 161;
|
|
7
|
+
--secondary: 255, 115, 133;
|
|
8
|
+
--accent: 169, 152, 191;
|
|
9
|
+
|
|
10
|
+
--color: rgba(var(--secondary), 0.65);
|
|
11
|
+
--rim-color: rgba(var(--primary), 0.9);
|
|
12
|
+
|
|
13
|
+
--blur: 3px;
|
|
14
|
+
--spread: 0;
|
|
15
|
+
--rim-blur: 2px;
|
|
16
|
+
--rim-spread: -1px;
|
|
17
|
+
|
|
18
|
+
--ambient-shadow: 0 0 var(--blur) var(--spread) var(--color);
|
|
19
|
+
--rim-shadow: 0 0 var(--rim-blur) var(--rim-spread) var(--rim-color);
|
|
20
|
+
border-radius: 24px;
|
|
21
|
+
box-shadow:
|
|
22
|
+
var(--ambient-shadow),
|
|
23
|
+
var(--rim-shadow);
|
|
24
|
+
width: 84px;
|
|
25
|
+
height: 84px;
|
|
26
|
+
background: linear-gradient(
|
|
27
|
+
rgb(var(--accent)),
|
|
28
|
+
rgb(var(--primary))
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Elevation levels adjust shadow parameters
|
|
33
|
+
* while preserving the same compositional structure.
|
|
34
|
+
*/
|
|
35
|
+
.ele-1 {
|
|
36
|
+
--blur: 3px;
|
|
37
|
+
--spread: 0;
|
|
38
|
+
--rim-blur: 2px;
|
|
39
|
+
--rim-spread: -1px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ele-2 {
|
|
43
|
+
--blur: 10px;
|
|
44
|
+
--spread: -1px;
|
|
45
|
+
--rim-blur: 6px;
|
|
46
|
+
--rim-spread: -2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ele-3 {
|
|
50
|
+
--blur: 24px;
|
|
51
|
+
--spread: -3px;
|
|
52
|
+
--rim-blur: 12px;
|
|
53
|
+
--rim-spread: -4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ele-4 {
|
|
57
|
+
--blur: 44px;
|
|
58
|
+
--spread: -5px;
|
|
59
|
+
--rim-blur: 24px;
|
|
60
|
+
--rim-spread: -8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* =========================
|
|
64
|
+
* Layout
|
|
65
|
+
* ========================= */
|
|
66
|
+
|
|
67
|
+
.design-container {
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 100%;
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
align-items: center;
|
|
74
|
+
background-color: #0d0d0d;
|
|
75
|
+
gap: 36px;
|
|
76
|
+
padding: 84px;
|
|
77
|
+
padding-left: 48px;
|
|
78
|
+
padding-right: 48px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.canvas {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 24px;
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { root } from "@lynx-js/react";
|
|
2
|
+
|
|
3
|
+
import { Caption } from "../shared/components/caption/index.jsx";
|
|
4
|
+
import "./index.css";
|
|
5
|
+
|
|
6
|
+
/* Shadow intensity is derived from nested tokens,
|
|
7
|
+
* resolving into layered ambient and rim shadows.
|
|
8
|
+
*/
|
|
9
|
+
function App() {
|
|
10
|
+
return (
|
|
11
|
+
<view className="design-container">
|
|
12
|
+
<view className="canvas">
|
|
13
|
+
<view className="card ele-1" />
|
|
14
|
+
<view className="card ele-2" />
|
|
15
|
+
<view className="card ele-3" />
|
|
16
|
+
<view className="card ele-4" />
|
|
17
|
+
</view>
|
|
18
|
+
<Caption
|
|
19
|
+
title="Soft Glow"
|
|
20
|
+
subtitle="Powered by Nested CSS Variables"
|
|
21
|
+
footnote="Requires Lynx SDK 3.6+"
|
|
22
|
+
/>
|
|
23
|
+
</view>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
root.render(<App />);
|
|
28
|
+
|
|
29
|
+
if (import.meta.webpackHot) {
|
|
30
|
+
import.meta.webpackHot.accept();
|
|
31
|
+
}
|