wow
This commit is contained in:
parent
794dce869c
commit
87faa363b9
5 changed files with 434 additions and 246 deletions
|
|
@ -32,3 +32,37 @@ canvas {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flip-control {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 5;
|
||||||
|
right: 50%;
|
||||||
|
bottom: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 11px 16px;
|
||||||
|
border: 1px solid #ffffff2b;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #d7d7d7;
|
||||||
|
background: #0b0b0be6;
|
||||||
|
box-shadow: 0 10px 30px #0008;
|
||||||
|
font: 500 11px monospace;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: translateX(50%);
|
||||||
|
transition: color 160ms ease, border-color 160ms ease, background 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-control:hover,
|
||||||
|
.flip-control:focus-visible {
|
||||||
|
border-color: #ffffff70;
|
||||||
|
color: #fff;
|
||||||
|
background: #151515f2;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-control span {
|
||||||
|
font: 17px Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,31 +45,24 @@ function createBadgeTexture(pass) {
|
||||||
ctx.fillStyle = "#050505";
|
ctx.fillStyle = "#050505";
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
const sheen = ctx.createLinearGradient(0, 0, 912, 970);
|
const sheen = ctx.createLinearGradient(0, 0, 912, 1288);
|
||||||
sheen.addColorStop(0, "#ffffff09");
|
sheen.addColorStop(0, "#ffffff09");
|
||||||
sheen.addColorStop(0.45, "transparent");
|
sheen.addColorStop(0.45, "transparent");
|
||||||
sheen.addColorStop(1, "#ffffff05");
|
sheen.addColorStop(1, "#ffffff05");
|
||||||
ctx.fillStyle = sheen;
|
ctx.fillStyle = sheen;
|
||||||
ctx.fillRect(0, 0, 912, 970);
|
ctx.fillRect(0, 0, 912, 1288);
|
||||||
|
|
||||||
ctx.strokeStyle = "#ffffff24";
|
ctx.strokeStyle = "#ffffff24";
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.strokeRect(30, 30, 852, 925);
|
ctx.strokeRect(30, 30, 852, 1228);
|
||||||
|
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(58, 88);
|
|
||||||
ctx.lineTo(79, 52);
|
|
||||||
ctx.lineTo(100, 88);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.font = "700 29px Arial, sans-serif";
|
ctx.font = "700 29px Arial, sans-serif";
|
||||||
ctx.fillText("PASS", 122, 79);
|
ctx.fillText("PASS", 58, 79);
|
||||||
ctx.fillStyle = "#8f8f8f";
|
ctx.fillStyle = "#8f8f8f";
|
||||||
ctx.fillText("PORT", 204, 79);
|
ctx.fillText("PORT", 140, 79);
|
||||||
ctx.font = "500 13px monospace";
|
ctx.font = "500 13px monospace";
|
||||||
ctx.fillText("DIGITAL ACCESS SYSTEM", 122, 105);
|
ctx.fillText("DIGITAL ACCESS SYSTEM", 58, 105);
|
||||||
|
|
||||||
ctx.textAlign = "right";
|
ctx.textAlign = "right";
|
||||||
ctx.fillStyle = accent;
|
ctx.fillStyle = accent;
|
||||||
|
|
@ -91,12 +84,12 @@ function createBadgeTexture(pass) {
|
||||||
ctx.strokeStyle = "#ffffff";
|
ctx.strokeStyle = "#ffffff";
|
||||||
ctx.lineWidth = 8;
|
ctx.lineWidth = 8;
|
||||||
ctx.font = "800 350px Arial, sans-serif";
|
ctx.font = "800 350px Arial, sans-serif";
|
||||||
ctx.strokeText("P", 590, 465);
|
ctx.strokeText("P", 590, 520);
|
||||||
for (let row = 0; row < 9; row += 1) {
|
for (let row = 0; row < 9; row += 1) {
|
||||||
for (let column = 0; column < 9; column += 1) {
|
for (let column = 0; column < 9; column += 1) {
|
||||||
const radius = Math.max(1.5, 5 - (row + column) * 0.18);
|
const radius = Math.max(1.5, 5 - (row + column) * 0.18);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(620 + column * 25, 220 + row * 25, radius, 0, Math.PI * 2);
|
ctx.arc(620 + column * 25, 250 + row * 25, radius, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
@ -105,91 +98,205 @@ function createBadgeTexture(pass) {
|
||||||
|
|
||||||
ctx.fillStyle = "#929292";
|
ctx.fillStyle = "#929292";
|
||||||
ctx.font = "500 14px monospace";
|
ctx.font = "500 14px monospace";
|
||||||
ctx.fillText("TEMPORARY / GUEST", 58, 208);
|
ctx.fillText("TEMPORARY / GUEST", 58, 220);
|
||||||
ctx.textAlign = "right";
|
ctx.textAlign = "right";
|
||||||
ctx.fillText(`NO. ${pass.credentialId}`, 854, 208);
|
ctx.fillText(`NO. ${pass.credentialId}`, 854, 220);
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
|
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "700 86px Arial, sans-serif";
|
ctx.font = "700 86px Arial, sans-serif";
|
||||||
ctx.fillText("ACCESS", 52, 322);
|
ctx.fillText("ACCESS", 52, 350);
|
||||||
ctx.font = "300 86px Arial, sans-serif";
|
ctx.font = "300 86px Arial, sans-serif";
|
||||||
ctx.fillText("PASS", 52, 402);
|
ctx.fillText("PASS", 52, 430);
|
||||||
|
|
||||||
ctx.fillStyle = "#858585";
|
ctx.fillStyle = "#858585";
|
||||||
ctx.font = "500 14px monospace";
|
ctx.font = "500 14px monospace";
|
||||||
ctx.fillText("PASS HOLDER", 58, 493);
|
ctx.fillText("PASS HOLDER", 58, 545);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
const holderSize = fitText(ctx, pass.holder, 796, 66);
|
const holderSize = fitText(ctx, pass.holder, 796, 66);
|
||||||
ctx.font = `600 ${holderSize}px Arial, sans-serif`;
|
ctx.font = `600 ${holderSize}px Arial, sans-serif`;
|
||||||
ctx.fillText(pass.holder, 55, 560);
|
ctx.fillText(pass.holder, 55, 625);
|
||||||
|
|
||||||
ctx.strokeStyle = "#ffffff2d";
|
ctx.strokeStyle = "#ffffff2d";
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(58, 610);
|
ctx.moveTo(58, 685);
|
||||||
ctx.lineTo(854, 610);
|
ctx.lineTo(854, 685);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = "#858585";
|
ctx.fillStyle = "#858585";
|
||||||
ctx.font = "500 14px monospace";
|
ctx.font = "500 14px monospace";
|
||||||
ctx.fillText("AUTHORIZED LOCATION", 58, 657);
|
ctx.fillText("AUTHORIZED LOCATION", 58, 738);
|
||||||
ctx.fillText("VALID FROM", 520, 657);
|
ctx.fillText("VALID FROM", 520, 738);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
const locationSize = fitText(ctx, pass.location, 400, 32);
|
const locationSize = fitText(ctx, pass.location, 400, 32);
|
||||||
ctx.font = `500 ${locationSize}px Arial, sans-serif`;
|
ctx.font = `500 ${locationSize}px Arial, sans-serif`;
|
||||||
ctx.fillText(pass.location, 58, 699);
|
ctx.fillText(pass.location, 58, 782);
|
||||||
ctx.font = "500 20px monospace";
|
ctx.font = "500 20px monospace";
|
||||||
ctx.fillText(pass.validFrom, 520, 697);
|
ctx.fillText(pass.validFrom, 520, 780);
|
||||||
|
|
||||||
ctx.fillStyle = "#858585";
|
ctx.fillStyle = "#858585";
|
||||||
ctx.font = "500 14px monospace";
|
ctx.font = "500 14px monospace";
|
||||||
ctx.fillText("VALID UNTIL", 520, 757);
|
ctx.fillText("VALID UNTIL", 520, 842);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "500 20px monospace";
|
ctx.font = "500 20px monospace";
|
||||||
ctx.fillText(pass.validUntil, 520, 797);
|
ctx.fillText(pass.validUntil, 520, 884);
|
||||||
|
|
||||||
ctx.strokeStyle = "#ffffff2d";
|
ctx.strokeStyle = "#ffffff2d";
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(58, 840);
|
ctx.moveTo(58, 960);
|
||||||
ctx.lineTo(854, 840);
|
ctx.lineTo(854, 960);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = accent;
|
ctx.fillStyle = accent;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(70, 893, 13, 0, Math.PI * 2);
|
ctx.arc(70, 1024, 13, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.fillStyle = "#050505";
|
ctx.fillStyle = "#050505";
|
||||||
ctx.font = "700 14px Arial, sans-serif";
|
ctx.font = "700 14px Arial, sans-serif";
|
||||||
ctx.fillText("✓", 65, 898);
|
ctx.fillText("✓", 65, 1029);
|
||||||
ctx.fillStyle = "#9a9a9a";
|
ctx.fillStyle = "#9a9a9a";
|
||||||
ctx.font = "500 13px monospace";
|
ctx.font = "500 13px monospace";
|
||||||
ctx.fillText("DIGITALLY SIGNED ACCESS CREDENTIAL", 100, 890);
|
ctx.fillText("DIGITALLY SIGNED ACCESS CREDENTIAL", 100, 1020);
|
||||||
ctx.fillText("ISSUER / PASSPORT", 100, 915);
|
ctx.fillText("ISSUER / PASSPORT", 100, 1045);
|
||||||
ctx.textAlign = "right";
|
ctx.textAlign = "right";
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.fillRect(803, 876, 51, 51);
|
ctx.fillRect(803, 1003, 51, 51);
|
||||||
ctx.fillStyle = "#050505";
|
ctx.fillStyle = "#050505";
|
||||||
ctx.font = "700 26px Arial, sans-serif";
|
ctx.font = "700 26px Arial, sans-serif";
|
||||||
ctx.fillText("P", 838, 910);
|
ctx.fillText("P", 838, 1037);
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
|
|
||||||
|
ctx.fillStyle = "#616161";
|
||||||
|
ctx.font = "500 13px monospace";
|
||||||
|
ctx.fillText("TAP CARD TO VIEW VERIFICATION DETAILS", 58, 1205);
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
texture.colorSpace = THREE.SRGBColorSpace;
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
texture.anisotropy = 16;
|
texture.anisotropy = 16;
|
||||||
texture.flipY = false;
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
function createBackTexture(pass) {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 912;
|
||||||
|
canvas.height = 1288;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const accent = pass.status === "VALID" ? "#b9f8d3" : pass.status === "EXPIRED" ? "#df9298" : "#e8c986";
|
||||||
|
|
||||||
|
ctx.fillStyle = "#050505";
|
||||||
|
ctx.fillRect(0, 0, 912, 1288);
|
||||||
|
ctx.strokeStyle = "#ffffff24";
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.strokeRect(30, 30, 852, 1228);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.font = "700 27px Arial, sans-serif";
|
||||||
|
ctx.fillText("PASS PORT", 58, 82);
|
||||||
|
ctx.textAlign = "right";
|
||||||
|
ctx.fillStyle = "#777";
|
||||||
|
ctx.font = "500 14px monospace";
|
||||||
|
ctx.fillText("CREDENTIAL / REVERSE", 854, 82);
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = 0.09;
|
||||||
|
ctx.strokeStyle = "#fff";
|
||||||
|
ctx.lineWidth = 8;
|
||||||
|
ctx.font = "800 190px Arial, sans-serif";
|
||||||
|
ctx.strokeText("SIGNED", 42, 315);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
ctx.fillStyle = "#8c8c8c";
|
||||||
|
ctx.font = "500 14px monospace";
|
||||||
|
ctx.fillText("VERIFICATION STATUS", 58, 385);
|
||||||
|
ctx.fillStyle = accent;
|
||||||
|
ctx.font = "600 54px Arial, sans-serif";
|
||||||
|
ctx.fillText(pass.status, 55, 453);
|
||||||
|
|
||||||
|
ctx.strokeStyle = "#ffffff2d";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(58, 505);
|
||||||
|
ctx.lineTo(854, 505);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
["ISSUER", "PASSPORT"],
|
||||||
|
["SIGNATURE", "HS256"],
|
||||||
|
["CREDENTIAL NO.", pass.credentialId],
|
||||||
|
["PASS HOLDER", pass.holder],
|
||||||
|
["ACCESS POINT", pass.location],
|
||||||
|
];
|
||||||
|
rows.forEach(([label, value], index) => {
|
||||||
|
const y = 570 + index * 92;
|
||||||
|
ctx.fillStyle = "#777";
|
||||||
|
ctx.font = "500 14px monospace";
|
||||||
|
ctx.fillText(label, 58, y);
|
||||||
|
ctx.textAlign = "right";
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.font = "500 20px Arial, sans-serif";
|
||||||
|
ctx.fillText(value, 854, y);
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
ctx.strokeStyle = "#ffffff17";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(58, y + 34);
|
||||||
|
ctx.lineTo(854, y + 34);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.fillStyle = "#777";
|
||||||
|
ctx.font = "500 14px monospace";
|
||||||
|
ctx.fillText("USAGE", 58, 1060);
|
||||||
|
ctx.fillStyle = "#d5d5d5";
|
||||||
|
ctx.font = "500 19px Arial, sans-serif";
|
||||||
|
ctx.fillText("Present this credential at the authorized access point.", 58, 1100);
|
||||||
|
ctx.fillText("Do not share the credential URL with another person.", 58, 1135);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#666";
|
||||||
|
ctx.font = "500 13px monospace";
|
||||||
|
ctx.fillText("TAP CARD TO RETURN TO FRONT", 58, 1210);
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
texture.anisotropy = 16;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBandTexture() {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 1024;
|
||||||
|
canvas.height = 160;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.fillStyle = "#090909";
|
||||||
|
ctx.fillRect(0, 0, 1024, 160);
|
||||||
|
ctx.strokeStyle = "#333";
|
||||||
|
ctx.strokeRect(0, 4, 1024, 152);
|
||||||
|
ctx.fillStyle = "#eee";
|
||||||
|
ctx.font = "600 35px Arial, sans-serif";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
for (let x = 30; x < 1024; x += 330) {
|
||||||
|
ctx.fillText("PASS PORT", x, 80);
|
||||||
|
ctx.fillStyle = "#555";
|
||||||
|
ctx.fillText(" / ACCESS", x + 190, 80);
|
||||||
|
ctx.fillStyle = "#eee";
|
||||||
|
}
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Band({ pass, flipped, onFlip, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
const band = useRef();
|
const band = useRef();
|
||||||
const fixed = useRef();
|
const fixed = useRef();
|
||||||
const joint1 = useRef();
|
const joint1 = useRef();
|
||||||
const joint2 = useRef();
|
const joint2 = useRef();
|
||||||
const joint3 = useRef();
|
const joint3 = useRef();
|
||||||
const card = useRef();
|
const card = useRef();
|
||||||
|
const cardVisual = useRef();
|
||||||
const { nodes, materials } = useGLTF(MODEL_URL);
|
const { nodes, materials } = useGLTF(MODEL_URL);
|
||||||
const bandTexture = useTexture(BAND_URL);
|
const bandTexture = useMemo(() => createBandTexture(), []);
|
||||||
const badgeTexture = useMemo(() => createBadgeTexture(pass), [pass]);
|
const badgeTexture = useMemo(() => createBadgeTexture(pass), [pass]);
|
||||||
|
const backTexture = useMemo(() => createBackTexture(pass), [pass]);
|
||||||
const { width, height } = useThree((state) => state.size);
|
const { width, height } = useThree((state) => state.size);
|
||||||
const curve = useMemo(
|
const curve = useMemo(
|
||||||
() => new THREE.CatmullRomCurve3([
|
() => new THREE.CatmullRomCurve3([
|
||||||
|
|
@ -224,9 +331,22 @@ function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
return () => { document.body.style.cursor = "auto"; };
|
return () => { document.body.style.cursor = "auto"; };
|
||||||
}, [hovered, dragged]);
|
}, [hovered, dragged]);
|
||||||
|
|
||||||
useEffect(() => () => badgeTexture.dispose(), [badgeTexture]);
|
useEffect(() => () => {
|
||||||
|
badgeTexture.dispose();
|
||||||
|
backTexture.dispose();
|
||||||
|
bandTexture.dispose();
|
||||||
|
}, [badgeTexture, backTexture, bandTexture]);
|
||||||
|
|
||||||
useFrame((state, delta) => {
|
useFrame((state, delta) => {
|
||||||
|
if (cardVisual.current) {
|
||||||
|
cardVisual.current.rotation.y = THREE.MathUtils.damp(
|
||||||
|
cardVisual.current.rotation.y,
|
||||||
|
flipped ? Math.PI : 0,
|
||||||
|
7,
|
||||||
|
delta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (dragged) {
|
if (dragged) {
|
||||||
vec.set(state.pointer.x, state.pointer.y, 0.5).unproject(state.camera);
|
vec.set(state.pointer.x, state.pointer.y, 0.5).unproject(state.camera);
|
||||||
direction.copy(vec).sub(state.camera.position).normalize();
|
direction.copy(vec).sub(state.camera.position).normalize();
|
||||||
|
|
@ -292,6 +412,7 @@ function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
>
|
>
|
||||||
<CuboidCollider args={[0.8, 1.125, 0.01]} />
|
<CuboidCollider args={[0.8, 1.125, 0.01]} />
|
||||||
<group
|
<group
|
||||||
|
ref={cardVisual}
|
||||||
scale={2.25}
|
scale={2.25}
|
||||||
position={[0, -1.2, -0.05]}
|
position={[0, -1.2, -0.05]}
|
||||||
onPointerOver={() => setHovered(true)}
|
onPointerOver={() => setHovered(true)}
|
||||||
|
|
@ -304,11 +425,14 @@ function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
event.target.setPointerCapture(event.pointerId);
|
event.target.setPointerCapture(event.pointerId);
|
||||||
setDragged(event.point.clone().sub(vec.copy(card.current.translation())));
|
setDragged(event.point.clone().sub(vec.copy(card.current.translation())));
|
||||||
}}
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.delta < 5) onFlip();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<mesh geometry={nodes.card.geometry}>
|
<mesh geometry={nodes.card.geometry}>
|
||||||
<meshPhysicalMaterial
|
<meshPhysicalMaterial
|
||||||
map={badgeTexture}
|
color="#080808"
|
||||||
map-anisotropy={16}
|
|
||||||
clearcoat={1}
|
clearcoat={1}
|
||||||
clearcoatRoughness={0.15}
|
clearcoatRoughness={0.15}
|
||||||
iridescence={1}
|
iridescence={1}
|
||||||
|
|
@ -318,6 +442,29 @@ function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
roughness={0.3}
|
roughness={0.3}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
<mesh position={[0, 0.523, 0.0062]}>
|
||||||
|
<planeGeometry args={[0.696, 0.972]} />
|
||||||
|
<meshPhysicalMaterial
|
||||||
|
map={badgeTexture}
|
||||||
|
clearcoat={1}
|
||||||
|
clearcoatRoughness={0.12}
|
||||||
|
iridescence={0.55}
|
||||||
|
iridescenceIOR={1.2}
|
||||||
|
iridescenceThicknessRange={[0, 900]}
|
||||||
|
metalness={0.2}
|
||||||
|
roughness={0.28}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0, 0.523, 0.0004]} rotation={[0, Math.PI, 0]}>
|
||||||
|
<planeGeometry args={[0.696, 0.972]} />
|
||||||
|
<meshPhysicalMaterial
|
||||||
|
map={backTexture}
|
||||||
|
clearcoat={1}
|
||||||
|
clearcoatRoughness={0.12}
|
||||||
|
metalness={0.2}
|
||||||
|
roughness={0.28}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
<mesh geometry={nodes.clip.geometry} material={materials.metal} material-roughness={0.3} />
|
<mesh geometry={nodes.clip.geometry} material={materials.metal} material-roughness={0.3} />
|
||||||
<mesh geometry={nodes.clamp.geometry} material={materials.metal} />
|
<mesh geometry={nodes.clamp.geometry} material={materials.metal} />
|
||||||
</group>
|
</group>
|
||||||
|
|
@ -339,12 +486,12 @@ function Band({ pass, maxSpeed = 50, minSpeed = 10 }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PassScene({ pass }) {
|
function PassScene({ pass, flipped, onFlip }) {
|
||||||
return (
|
return (
|
||||||
<Canvas camera={{ position: [0, 0, 13], fov: 25 }} dpr={[1, 2]}>
|
<Canvas camera={{ position: [0, 0, 13], fov: 25 }} dpr={[1, 2]}>
|
||||||
<ambientLight intensity={Math.PI} />
|
<ambientLight intensity={Math.PI} />
|
||||||
<Physics interpolate gravity={[0, -40, 0]} timeStep={1 / 60}>
|
<Physics interpolate gravity={[0, -40, 0]} timeStep={1 / 60}>
|
||||||
<Band pass={pass} />
|
<Band pass={pass} flipped={flipped} onFlip={onFlip} />
|
||||||
</Physics>
|
</Physics>
|
||||||
<Environment background blur={0.75}>
|
<Environment background blur={0.75}>
|
||||||
<color attach="background" args={["black"]} />
|
<color attach="background" args={["black"]} />
|
||||||
|
|
@ -359,6 +506,7 @@ function PassScene({ pass }) {
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const element = document.getElementById("pass-root");
|
const element = document.getElementById("pass-root");
|
||||||
|
const [flipped, setFlipped] = useState(false);
|
||||||
const pass = useMemo(() => ({
|
const pass = useMemo(() => ({
|
||||||
holder: element.dataset.holder,
|
holder: element.dataset.holder,
|
||||||
location: element.dataset.location,
|
location: element.dataset.location,
|
||||||
|
|
@ -369,9 +517,15 @@ function App() {
|
||||||
}), [element]);
|
}), [element]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Suspense fallback={<div className="loading">Loading credential</div>}>
|
<Suspense fallback={<div className="loading">Loading credential</div>}>
|
||||||
<PassScene pass={pass} />
|
<PassScene pass={pass} flipped={flipped} onFlip={() => setFlipped((value) => !value)} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
<button className="flip-control" type="button" onClick={() => setFlipped((value) => !value)}>
|
||||||
|
<span aria-hidden="true">↻</span>
|
||||||
|
{flipped ? "View front" : "View verification"}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
2
static/dist/pass-app.css
vendored
2
static/dist/pass-app.css
vendored
|
|
@ -1 +1 @@
|
||||||
*{box-sizing:border-box}html,body,#pass-root{width:100%;height:100%;margin:0;overflow:hidden;background:#000}body{min-width:320px}canvas{display:block;touch-action:none}.loading{display:grid;width:100%;height:100%;color:#fff;background:#000;font:12px monospace;letter-spacing:.12em;text-transform:uppercase;place-items:center}
|
*{box-sizing:border-box}html,body,#pass-root{width:100%;height:100%;margin:0;overflow:hidden;background:#000}body{min-width:320px}canvas{display:block;touch-action:none}.loading{display:grid;width:100%;height:100%;color:#fff;background:#000;font:12px monospace;letter-spacing:.12em;text-transform:uppercase;place-items:center}.flip-control{position:fixed;z-index:5;right:50%;bottom:28px;display:flex;align-items:center;gap:10px;padding:11px 16px;border:1px solid #ffffff2b;border-radius:999px;color:#d7d7d7;background:#0b0b0be6;box-shadow:0 10px 30px #0008;font:500 11px monospace;letter-spacing:.08em;text-transform:uppercase;cursor:pointer;transform:translate(50%);transition:color .16s ease,border-color .16s ease,background .16s ease}.flip-control:hover,.flip-control:focus-visible{border-color:#ffffff70;color:#fff;background:#151515f2;outline:none}.flip-control span{font:17px Arial,sans-serif}
|
||||||
|
|
|
||||||
394
static/dist/pass-app.js
vendored
394
static/dist/pass-app.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue