mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
ricochet toy
This commit is contained in:
1 parent
f935adb70e
commit
de80519f06
7 files changed
+271
-166
No files matched your search
@@ -28,6 +28,16 @@
|
|||||||
"all": "All tanks"
|
"all": "All tanks"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"toys": {
|
||||||
|
"ricochet": {
|
||||||
|
"angle": "Angle",
|
||||||
|
"normalization": "Normalization",
|
||||||
|
"effective": "Effective",
|
||||||
|
"armor": "Armor",
|
||||||
|
"no_ricochet": "Explosive shells don't ricochet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"titles": {
|
"titles": {
|
||||||
"gun": "Gun",
|
"gun": "Gun",
|
||||||
"clip": "Clip",
|
"clip": "Clip",
|
||||||
|
|||||||
File renamed without changes.
+1
-1
@@ -296,7 +296,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
height="100%"
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
opacity: 2 ** -3,
|
opacity: 2 ** -3,
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
backgroundImage: `url(/media/random/armor-hash.png)`,
|
||||||
boxShadow: "inset 0 0 4rem 1rem black",
|
boxShadow: "inset 0 0 4rem 1rem black",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
height="100%"
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
opacity: 2 ** -3,
|
opacity: 2 ** -3,
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
backgroundImage: `url(/media/random/armor-hash.png)`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
.visualizer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
touch-action: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
background-color: var(--gray-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.armor {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 33%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, transparent, var(--gray-a4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.armor-texture {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0.125;
|
||||||
|
background-image: url("/media/random/armor-hash.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.zones {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 33%;
|
||||||
|
width: 67%;
|
||||||
|
height: 100%;
|
||||||
|
background: conic-gradient(
|
||||||
|
at 0 50%,
|
||||||
|
var(--ruby-a2),
|
||||||
|
var(--ruby-a1) calc(90deg - var(--ricochet-angle)),
|
||||||
|
var(--jade-a2) calc(90deg - var(--ricochet-angle)),
|
||||||
|
var(--jade-a4) calc(90deg - var(--normalization-angle)),
|
||||||
|
var(--cyan-a5) calc(90deg - var(--normalization-angle)),
|
||||||
|
var(--cyan-a3) calc(90deg + var(--normalization-angle)),
|
||||||
|
var(--jade-a2) calc(90deg + var(--normalization-angle)),
|
||||||
|
var(--jade-a4) calc(90deg + var(--ricochet-angle)),
|
||||||
|
var(--ruby-a1) calc(90deg + var(--ricochet-angle)),
|
||||||
|
var(--ruby-a2) 180deg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-path,
|
||||||
|
.effective-path {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 33%;
|
||||||
|
transform: translateX(-50%) rotate(var(--rotation-angle));
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-path {
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-path-extension {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 100%;
|
||||||
|
width: var(--space-0);
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: var(--gray-a8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-path-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: var(--space-2);
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(var(--gray-a11), var(--gray-a4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
position: absolute;
|
||||||
|
top: 6rem;
|
||||||
|
left: 0;
|
||||||
|
width: var(--space-5);
|
||||||
|
height: var(--space-5);
|
||||||
|
object-fit: contain;
|
||||||
|
transform: translate(-50%, -50%) rotate(-45deg);
|
||||||
|
filter: drop-shadow(0 0 var(--space-4) black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.effective-path {
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effective-path-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: -100%;
|
||||||
|
width: var(--space-2);
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(var(--gray-a4), var(--gray-a11));
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
color: var(--gray-11);
|
||||||
|
font-size: var(--font-size-minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat-value {
|
||||||
|
color: var(--gray-12);
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat[data-position="top-left"] {
|
||||||
|
top: var(--space-3);
|
||||||
|
left: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat[data-position="top-right"] {
|
||||||
|
top: var(--space-3);
|
||||||
|
right: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat[data-position="bottom-left"] {
|
||||||
|
bottom: var(--space-3);
|
||||||
|
left: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-stat[data-position="bottom-right"] {
|
||||||
|
right: var(--space-3);
|
||||||
|
bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.explosive-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-3);
|
||||||
|
color: var(--gray-11);
|
||||||
|
text-align: center;
|
||||||
|
background: radial-gradient(var(--black-a7), var(--black-a11));
|
||||||
|
}
|
||||||
@@ -1,174 +1,108 @@
|
|||||||
import { isExplosive } from "@blitzkit/core";
|
import { alias, isExplosive } from "@blitzkit/core";
|
||||||
import { useRef, useState } from "react";
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { degToRad } from "three/src/math/MathUtils.js";
|
import {
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type CSSProperties,
|
||||||
|
type PointerEvent,
|
||||||
|
} from "react";
|
||||||
|
import { clamp, degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||||
import type { ToyProps } from "../../config/toys";
|
import type { ToyProps } from "../../config/toys";
|
||||||
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||||
import { useStrings } from "../../hooks/useStrings";
|
import { useStrings } from "../../hooks/useStrings";
|
||||||
|
import { Code } from "../Code";
|
||||||
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
export function RicochetToy({ characteristics }: ToyProps) {
|
export function RicochetToy({ characteristics }: ToyProps) {
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
|
|
||||||
const shell = useDuel("protagonist", DuelModule.Shell);
|
const shell = useDuel("protagonist", DuelModule.Shell);
|
||||||
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
const normalizationValue = characteristics.get("normalization");
|
||||||
|
const ricochetValue = characteristics.get("ricochet");
|
||||||
const normalization = degToRad(
|
const normalization = degToRad(
|
||||||
(characteristics.get("normalization") as number) ?? 0,
|
typeof normalizationValue === "number" ? normalizationValue : 0,
|
||||||
|
);
|
||||||
|
const ricochet = degToRad(
|
||||||
|
typeof ricochetValue === "number" ? ricochetValue : 90,
|
||||||
);
|
);
|
||||||
const ricochet = degToRad((characteristics.get("ricochet") as number) ?? 90);
|
|
||||||
const explodes = isExplosive(shell.type);
|
const explodes = isExplosive(shell.type);
|
||||||
|
|
||||||
const [rawAngle, setAngle] = useState(degToRad(-25));
|
const [rawAngle, setAngle] = useState(degToRad(-25));
|
||||||
|
|
||||||
const angle = explodes ? degToRad(-70) : rawAngle;
|
const angle = explodes ? degToRad(-70) : rawAngle;
|
||||||
const doesRicochet = Math.abs(angle) >= ricochet;
|
const doesRicochet = Math.abs(angle) >= ricochet;
|
||||||
const effectiveAngle = doesRicochet
|
const effectiveAngle = doesRicochet
|
||||||
? -angle + Math.PI
|
? -angle + Math.PI
|
||||||
: Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
: Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
||||||
|
|
||||||
const armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle));
|
const armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle));
|
||||||
|
|
||||||
|
function updateAngle(event: PointerEvent<HTMLDivElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!container.current || explodes) return;
|
||||||
|
|
||||||
|
const rect = container.current.getBoundingClientRect();
|
||||||
|
const x = event.clientX - rect.left - rect.width / 3;
|
||||||
|
const y = event.clientY - rect.top - rect.height / 2;
|
||||||
|
|
||||||
|
setAngle(clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<div
|
||||||
onPointerMove={(event) => {
|
ref={container}
|
||||||
event.preventDefault();
|
className={styles.visualizer}
|
||||||
|
onPointerMove={updateAngle}
|
||||||
if (!container.current || explodes) return;
|
|
||||||
|
|
||||||
const rect = container.current.getBoundingClientRect();
|
|
||||||
const x = event.clientX - rect.left - rect.width / 3;
|
|
||||||
const y = event.clientY - rect.top - rect.height / 2;
|
|
||||||
const angle = clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2);
|
|
||||||
|
|
||||||
setAngle(angle);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box
|
<div className={styles.armor}>
|
||||||
width="33%"
|
<div className={styles["armor-texture"]} />
|
||||||
height="100%"
|
</div>
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
left="0"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(to right, transparent, ${Var("gray-a4")})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
opacity: 2 ** -3,
|
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
<div
|
||||||
left="33%"
|
className={styles.zones}
|
||||||
top="0"
|
style={
|
||||||
width="67%"
|
{
|
||||||
height="100%"
|
"--normalization-angle": `${normalization}rad`,
|
||||||
position="absolute"
|
"--ricochet-angle": `${ricochet}rad`,
|
||||||
style={{
|
} as CSSProperties
|
||||||
background: `conic-gradient(
|
}
|
||||||
at 0 50%,
|
|
||||||
|
|
||||||
${Var("ruby-a2")},
|
|
||||||
${Var("ruby-a1")} ${Math.PI / 2 - ricochet}rad,
|
|
||||||
|
|
||||||
${Var("jade-a2")} ${Math.PI / 2 - ricochet}rad,
|
|
||||||
${Var("jade-a4")} ${Math.PI / 2 - normalization}rad,
|
|
||||||
|
|
||||||
${Var("cyan-a5")} ${Math.PI / 2 - normalization}rad,
|
|
||||||
${Var("cyan-a3")} ${Math.PI / 2 + normalization}rad,
|
|
||||||
|
|
||||||
${Var("jade-a2")} ${Math.PI / 2 + normalization}rad,
|
|
||||||
${Var("jade-a4")} ${Math.PI / 2 + ricochet}rad,
|
|
||||||
|
|
||||||
${Var("ruby-a1")} ${Math.PI / 2 + ricochet}rad,
|
|
||||||
${Var("ruby-a2")} 180deg
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box
|
<div
|
||||||
position="absolute"
|
className={styles["shell-path"]}
|
||||||
left="33%"
|
style={
|
||||||
top="50%"
|
{
|
||||||
height="18rem"
|
"--rotation-angle": `${-Math.PI / 2 - angle}rad`,
|
||||||
style={{
|
} as CSSProperties
|
||||||
transformOrigin: "top center",
|
}
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box
|
<div className={styles["shell-path-extension"]} />
|
||||||
position="absolute"
|
<div className={styles["shell-path-line"]} />
|
||||||
width="1px"
|
|
||||||
height="100%"
|
|
||||||
bottom="100%"
|
|
||||||
style={{
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
backgroundColor: Var("gray-a8"),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="3px"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a4")})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
|
className={styles.shell}
|
||||||
|
alt=""
|
||||||
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
|
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
|
||||||
style={{
|
|
||||||
objectFit: "contain",
|
|
||||||
transform: "translate(-50%, -50%) rotate(-45deg)",
|
|
||||||
position: "absolute",
|
|
||||||
width: "2rem",
|
|
||||||
height: "2rem",
|
|
||||||
top: "6rem",
|
|
||||||
left: 0,
|
|
||||||
filter: "drop-shadow(0px 0px 0.25rem black)",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
|
|
||||||
<Box
|
<div
|
||||||
position="absolute"
|
className={styles["effective-path"]}
|
||||||
left="33%"
|
style={
|
||||||
top="50%"
|
{
|
||||||
height="16rem"
|
"--rotation-angle": `${-Math.PI / 2 - effectiveAngle}rad`,
|
||||||
style={{
|
} as CSSProperties
|
||||||
transformOrigin: "top center",
|
}
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box
|
<div className={styles["effective-path-line"]} />
|
||||||
position="absolute"
|
</div>
|
||||||
width="3px"
|
|
||||||
height="100%"
|
|
||||||
top="-100%"
|
|
||||||
style={{
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
background: `linear-gradient(${Var("gray-a4")}, ${Var("gray-a11")})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<VisualizerCornerStat
|
<CornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
label={strings.tanks.characteristics.toys.ricochet.angle}
|
||||||
value={literals(strings.common.units.deg, {
|
value={literals(strings.common.units.deg, {
|
||||||
value: radToDeg(Math.abs(angle)).toFixed(0),
|
value: radToDeg(Math.abs(angle)).toFixed(0),
|
||||||
})}
|
})}
|
||||||
side="top-left"
|
position={CornerStatPosition.TopLeft}
|
||||||
/>
|
/>
|
||||||
<VisualizerCornerStat
|
<CornerStat
|
||||||
label={
|
label={strings.tanks.characteristics.toys.ricochet.normalization}
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet.normalization
|
|
||||||
}
|
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? "-"
|
? "-"
|
||||||
@@ -178,12 +112,10 @@ export function RicochetToy({ characteristics }: ToyProps) {
|
|||||||
).toFixed(0),
|
).toFixed(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
side="top-right"
|
position={CornerStatPosition.TopRight}
|
||||||
/>
|
/>
|
||||||
<VisualizerCornerStat
|
<CornerStat
|
||||||
label={
|
label={strings.tanks.characteristics.toys.ricochet.effective}
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet.effective_angle
|
|
||||||
}
|
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? "-"
|
? "-"
|
||||||
@@ -191,10 +123,10 @@ export function RicochetToy({ characteristics }: ToyProps) {
|
|||||||
value: radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
value: radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
side="bottom-left"
|
position={CornerStatPosition.BottomLeft}
|
||||||
/>
|
/>
|
||||||
<VisualizerCornerStat
|
<CornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
label={strings.tanks.characteristics.toys.ricochet.armor}
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? "-"
|
? "-"
|
||||||
@@ -202,28 +134,38 @@ export function RicochetToy({ characteristics }: ToyProps) {
|
|||||||
value: armorPercentage.toFixed(0),
|
value: armorPercentage.toFixed(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
side="bottom-right"
|
position={CornerStatPosition.BottomRight}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{explodes && (
|
{explodes && (
|
||||||
<Flex
|
<div className={styles["explosive-overlay"]}>
|
||||||
p="3"
|
{strings.tanks.characteristics.toys.ricochet.no_ricochet}
|
||||||
justify="center"
|
</div>
|
||||||
align="center"
|
|
||||||
height="100%"
|
|
||||||
width="100%"
|
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
left="0"
|
|
||||||
style={{
|
|
||||||
background: `radial-gradient(${Var("black-a7")}, ${Var("black-a11")})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text color="gray" align="center">
|
|
||||||
{strings.website.tools.tankopedia.visualizers.ricochet.no_ricochet}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CornerStatPosition {
|
||||||
|
TopLeft = "top-left",
|
||||||
|
TopRight = "top-right",
|
||||||
|
BottomLeft = "bottom-left",
|
||||||
|
BottomRight = "bottom-right",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CornerStatProps {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
position: CornerStatPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CornerStat({ label, value, position }: CornerStatProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles["corner-stat"]} data-position={position}>
|
||||||
|
<span>{label}</span>
|
||||||
|
<Code className={styles["corner-stat-value"]} size="minor">
|
||||||
|
{value}
|
||||||
|
</Code>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -21,11 +21,13 @@ const { id, locale } = Astro.props;
|
|||||||
|
|
||||||
<BaseLayout {locale} accent="violet">
|
<BaseLayout {locale} accent="violet">
|
||||||
<ColorScale color="amber" alpha />
|
<ColorScale color="amber" alpha />
|
||||||
|
|
||||||
<ColorScale color="red" />
|
<ColorScale color="red" />
|
||||||
|
<ColorScale color="ruby" alpha />
|
||||||
<ColorScale color="orange" />
|
<ColorScale color="orange" />
|
||||||
<ColorScale color="amber" />
|
<ColorScale color="amber" />
|
||||||
<ColorScale color="jade" />
|
<ColorScale color="jade" />
|
||||||
|
<ColorScale color="jade" alpha />
|
||||||
|
<ColorScale color="cyan" alpha />
|
||||||
|
|
||||||
<Page {id} {locale} client:only="react">
|
<Page {id} {locale} client:only="react">
|
||||||
<Page slot="fallback" {id} {locale} skeleton />
|
<Page slot="fallback" {id} {locale} skeleton />
|
||||||
|
|||||||
Reference in new issue
Block a user