)}
{text && text}
diff --git a/src/library/VRMExporter.js b/src/library/VRMExporter.js
index 2b61cb7f..f152a027 100644
--- a/src/library/VRMExporter.js
+++ b/src/library/VRMExporter.js
@@ -62,7 +62,7 @@ const SPRINGBONE_COLLIDER_NAME = "vrmColliderSphere";
// const GLTF_VERSION = 2;
// const HEADER_SIZE = 12;
export default class VRMExporter {
- parse(vrm, avatar, onDone) {
+ parse(vrm, avatar, screenshot, onDone) {
const humanoid = vrm.humanoid;
const vrmMeta = vrm.meta;
const materials = vrm.materials;
diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js
index fec08cac..3c96f27c 100644
--- a/src/library/VRMExporterv0.js
+++ b/src/library/VRMExporterv0.js
@@ -97,7 +97,7 @@ function getVRM0BoneName(name){
return name;
}
export default class VRMExporterv0 {
- parse(vrm, avatar, onDone) {
+ parse(vrm, avatar, screenshot, onDone) {
const vrmMeta = convertMetaToVRM0(vrm.meta);
const humanoid = convertHumanoidToVRM0(vrm.humanoid);
@@ -137,9 +137,11 @@ export default class VRMExporterv0 {
.map((material) => material);
const uniqueMaterialNames = uniqueMaterials.map((material) => material.name);
- const icon = vrmMeta.texture
- ? { name: "icon", imageBitmap: vrmMeta.texture.image }
+
+ const icon = screenshot
+ ? { name: "icon", imageBitmap: screenshot.image }
: null; // TODO: ない場合もある
+
const mainImages = uniqueMaterials
.filter((material) => material.map)
.map((material) => {
@@ -154,10 +156,7 @@ export default class VRMExporterv0 {
throw new Error(material.userData.shadeTexture + " map is null");
return { name: material.name + "_shade", imageBitmap: material.userData.shadeTexture.image };
}); // TODO: 画像がないMaterialもある\
-
-
-
const images = mainImages.concat(shadeImages);
const outputImages = toOutputImages(images, icon);
@@ -190,6 +189,7 @@ export default class VRMExporterv0 {
const meshes = avatar.children.filter((child) => child.type === VRMObjectType.Group ||
child.type === VRMObjectType.SkinnedMesh);
const meshDatas = [];
+
meshes.forEach((object) => {
const mesh = (object.type === VRMObjectType.Group
? object.children[0]
@@ -417,6 +417,8 @@ export default class VRMExporterv0 {
//const outputVrmMeta = ToOutputVRMMeta(vrmMeta, icon, outputImages);
const outputVrmMeta = vrmMeta;
+ outputVrmMeta.texture = icon ? outputImages.length - 1 : undefined;
+
//const outputSecondaryAnimation = toOutputSecondaryAnimation(springBone, nodeNames);
const bufferViews = [];
bufferViews.push(...images.map((image) => ({
diff --git a/src/library/animationManager.js b/src/library/animationManager.js
index 31201aaf..f563f277 100644
--- a/src/library/animationManager.js
+++ b/src/library/animationManager.js
@@ -48,6 +48,15 @@ class AnimationControl {
this.actions[curIdx].play();
}
+ reset() {
+ this.mixer.setTime(0);
+ this.to.paused = true;
+ }
+
+ resume() {
+ this.to.paused = false;
+ }
+
dispose(){
this.animationManager.disposeAnimation(this);
//console.log("todo dispose animation control")
@@ -93,6 +102,18 @@ export class AnimationManager{
}
+ enableScreenshot() {
+ this.animationControls.forEach(control => {
+ control.reset()
+ });
+ }
+
+ disableScreenshot() {
+ this.animationControls.forEach(control => {
+ control.resume()
+ });
+ }
+
offsetHips(){
this.animations.forEach(anim => {
for (let i =0; i < anim.tracks.length; i++){
@@ -201,4 +222,4 @@ export class AnimationManager{
else this.weightOut = 0;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/library/blinkManager.js b/src/library/blinkManager.js
index 1650e8ae..ae744e6d 100644
--- a/src/library/blinkManager.js
+++ b/src/library/blinkManager.js
@@ -1,6 +1,7 @@
import { VRMExpressionPresetName } from "@pixiv/three-vrm";
import { Clock } from "three";
+const SCREENSHOT_EYES_OPEN_THRESHOLD = 2;
export class BlinkManager {
constructor(closeTime = 0.5, openTime = 0.5, continuity = 1, randomness = 5) {
@@ -17,6 +18,8 @@ export class BlinkManager {
this._eyeOpen = 1
this._blinkCounter = 0;
+ this.isTakingScreenShot = false;
+
this.update()
}
@@ -24,8 +27,21 @@ export class BlinkManager {
this.vrmBlinkers.push(vrm)
}
+ enableScreenshot() {
+ this.isTakingScreenShot = true;
+ this._eyeOpen = SCREENSHOT_EYES_OPEN_THRESHOLD;
+ this._updateBlinkers();
+ }
+
+ disableScreenshot() {
+ this.isTakingScreenShot = false;
+ }
+
update(){
setInterval(() => {
+ if (this.isTakingScreenShot) {
+ return;
+ }
const deltaTime = this.clock.getDelta()
switch (this.mode){
@@ -33,7 +49,7 @@ export class BlinkManager {
if ( this._eyeOpen > 0)
this._eyeOpen -= deltaTime / this.closeTime;
else{
- this._eyeOpen =0
+ this._eyeOpen = 0
this.mode = 'open'
}
this._updateBlinkers();
@@ -42,7 +58,7 @@ export class BlinkManager {
if ( this._eyeOpen < 1)
this._eyeOpen += deltaTime / this.openTime;
else{
- this._eyeOpen =1
+ this._eyeOpen = 1
this.mode = 'ready'
}
this._updateBlinkers();
diff --git a/src/library/download-utils.js b/src/library/download-utils.js
index 07e35401..f23f0284 100644
--- a/src/library/download-utils.js
+++ b/src/library/download-utils.js
@@ -69,16 +69,16 @@ function getOptimizedGLB(avatarToDownload, atlasSize, isVrm0 = false){
}
export async function getGLBBlobData(avatarToDownload, atlasSize = 4096, optimized = true){
- const model = await optimized ?
- getOptimizedGLB(avatarToDownload, atlasSize) :
- getUnopotimizedGLB(avatarToDownload)
+ const model = await (optimized ?
+ getOptimizedGLB(avatarToDownload, atlasSize) :
+ getUnopotimizedGLB(avatarToDownload))
const glb = await parseGLB(model);
return new Blob([glb], { type: 'model/gltf-binary' });
}
-export async function getVRMBlobData(avatarToDownload, avatar, atlasSize = 4096, isVrm0 = false){
+export async function getVRMBlobData(avatarToDownload, avatar, screenshot = null, atlasSize = 4096, isVrm0 = false){
const model = await getOptimizedGLB(avatarToDownload, atlasSize, isVrm0)
- const vrm = await parseVRM(model, avatar, isVrm0);
+ const vrm = await parseVRM(model, avatar, screenshot, isVrm0);
// save it as glb now
return new Blob([vrm], { type: 'model/gltf-binary' });
}
@@ -94,17 +94,17 @@ async function getGLBData(avatarToDownload, atlasSize = 4096, optimized = true)
return parseGLB(model);
}
}
-async function getVRMData(avatarToDownload, avatar, atlasSize = 4096, isVrm0 = false){
+async function getVRMData(avatarToDownload, avatar, screenshot = null, atlasSize = 4096, isVrm0 = false){
const vrmModel = await getOptimizedGLB(avatarToDownload, atlasSize, isVrm0);
- return parseVRM(vrmModel,avatar, isVrm0)
+ return parseVRM(vrmModel,avatar,screenshot, isVrm0)
}
-export async function downloadVRM(avatarToDownload, avatar, fileName = "", atlasSize = 4096, isVrm0 = false){
+export async function downloadVRM(avatarToDownload, avatar, fileName = "", screenshot = null, atlasSize = 4096, isVrm0 = false){
const downloadFileName = `${
fileName && fileName !== "" ? fileName : "AvatarCreatorModel"
}`
- getVRMData(avatarToDownload, avatar, atlasSize, isVrm0).then((vrm)=>{
+ getVRMData(avatarToDownload, avatar, screenshot, atlasSize, isVrm0).then((vrm)=>{
saveArrayBuffer(vrm, `${downloadFileName}.vrm`)
})
}
@@ -150,7 +150,7 @@ function parseGLB (glbModel){
})
}
-function parseVRM (glbModel, avatar, isVrm0 = false){
+function parseVRM (glbModel, avatar, screenshot = null, isVrm0 = false){
return new Promise((resolve) => {
const exporter = isVrm0 ? new VRMExporterv0() : new VRMExporter()
const vrmData = {
@@ -174,7 +174,7 @@ function parseVRM (glbModel, avatar, isVrm0 = false){
skinnedMesh.skeleton.calculateInverses();
skinnedMesh.skeleton.computeBoneTexture();
skinnedMesh.skeleton.update();
- exporter.parse(vrmData, glbModel, (vrm) => {
+ exporter.parse(vrmData, glbModel,screenshot, (vrm) => {
resolve(vrm)
})
})
diff --git a/src/library/mint-utils.js b/src/library/mint-utils.js
new file mode 100644
index 00000000..cc70884d
--- /dev/null
+++ b/src/library/mint-utils.js
@@ -0,0 +1,242 @@
+import { BigNumber, ethers } from "ethers"
+import { getVRMBlobData } from "./download-utils"
+import { CharacterContract, EternalProxyContract, webaverseGenesisAddress } from "../components/Contract"
+import axios from "axios"
+
+const pinataApiKey = import.meta.env.VITE_PINATA_API_KEY
+const pinataSecretApiKey = import.meta.env.VITE_PINATA_API_SECRET
+
+//const mintCost = 0.01
+const chainId = "0x89";
+let tokenPrice;
+
+
+async function getTokenPrice(){
+ if (tokenPrice != null)
+ return tokenPrice
+ const defaultProvider = new ethers.providers.StaticJsonRpcProvider('https://polygon-rpc.com/')
+ const contract = new ethers.Contract(CharacterContract.address, CharacterContract.abi, defaultProvider)
+ const tp = await contract.tokenPrice()
+ tokenPrice = BigNumber.from(tp).mul(1);
+ return tokenPrice
+}
+
+// ready to test
+async function connectWallet(){
+ if (window.ethereum) {
+ try {
+ const chain = await window.ethereum.request({ method: 'eth_chainId' })
+ if (parseInt(chain, 16) == parseInt(chainId, 16)) {
+ const addressArray = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ })
+ return addressArray.length > 0 ? addressArray[0] : ""
+ } else {
+ try {
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainId }],
+ })
+ const addressArray = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ })
+ return addressArray.length > 0 ? addressArray[0] : ""
+ } catch (err) {
+ console.log("polygon not find:", err)
+ // Add Polygon chain to the metamask.
+ try {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [
+ {
+ chainId: '0x89',
+ chainName: 'Polygon Mainnet',
+ rpcUrls: ['https://polygon-rpc.com'],
+ nativeCurrency: {
+ name: "Matic",
+ symbol: "MATIC",
+ decimals: 18
+ },
+ blockExplorerUrls: ['https://polygonscan.com/'] },
+ ]
+ });
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainId }],
+ })
+ const addressArray = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ })
+ return addressArray.length > 0 ? addressArray[0] : ""
+ } catch (error) {
+ console.log("Adding polygon chain failed", error);
+ }
+ }
+ }
+ } catch (err) {
+ return "";
+ }
+ } else {
+ return "";
+ }
+}
+
+// ready to test
+async function saveFileToPinata(fileData, fileName) {
+ if (!fileData) return console.warn("Error saving to pinata: No file data")
+ const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`
+ let data = new FormData()
+
+ data.append("file", fileData, fileName)
+ let resultOfUpload = await axios.post(url, data, {
+ maxContentLength: "Infinity", //this is needed to prevent axios from erroring out with large files
+ maxBodyLength: "Infinity", //this is needed to prevent axios from erroring out with large files
+ headers: {
+ "Content-Type": `multipart/form-data; boundary=${data._boundary}`,
+ pinata_api_key: pinataApiKey,
+ pinata_secret_api_key: pinataSecretApiKey,
+ },
+ })
+ return resultOfUpload.data
+}
+
+const getAvatarTraits = (avatar) => {
+ let metadataTraits = []
+ Object.keys(avatar).map((trait) => {
+ if (Object.keys(avatar[trait]).length !== 0) {
+ metadataTraits.push({
+ trait_type: trait,
+ value: avatar[trait].name,
+ })
+ }
+ })
+ return metadataTraits
+}
+
+export async function mintAsset(avatar, screenshot, model, name, needCheckOT){
+ if (!avatar)
+ throw new Error("No avatar was provided")
+ if (!screenshot)
+ throw new Error("No screenshot was provided")
+ if (!model)
+ throw new Error("No model was provided")
+
+ const walletAddress = await connectWallet();
+ if (walletAddress == "")
+ return ("Please Connect Wallet")
+
+ const pass = !needCheckOT || await checkOT(walletAddress);
+ if (pass){
+ console.log("minting")
+ // set image
+ let imageName = "AvatarImage_" + Date.now() + ".png";
+ let imageHash = await (async() => {
+ for (let i = 0; i < 10; i++) { // hack: give it a few tries, sometimes uploading to pinata fail for some reason
+ try {
+ const img_hash = await saveFileToPinata(
+ screenshot,
+ imageName
+ ).catch((reason) => {
+ console.error(i, "---", reason)
+ })
+ return img_hash
+ } catch(err) {
+ console.warn(err);
+ return err;
+ }
+ }
+ return 'Failed to upload screenshot';
+ //throw new Error('failed to upload screenshot');
+ })()
+ const glb = await getVRMBlobData(model,avatar,4096,true)
+ let glbHash;
+ if (glb) {
+ let glbName = "AvatarGlb_" + Date.now() + ".glb";
+ glbHash = await (async() => {
+ for (let i = 0; i < 10; i++) { // hack: give it a few tries, sometimes uploading to pinata fail for some reason
+ try {
+ const glb_hash = await saveFileToPinata(
+ glb,
+ glbName
+ ).catch((reason) => {
+ console.error(i, "---", reason)
+ return "Couldn't save glb to pinata"
+ //setMintStatus("Couldn't save glb to pinata")
+ })
+ return glb_hash
+ } catch(err) {
+ console.warn(err);
+ return "Couldn't save glb to pinata"
+ }
+ }
+ return 'Failed to upload glb'
+ //throw new Error('failed to upload glb');
+ })();
+ } else {
+ return 'Unable to get glb'
+ }
+ const metadata = {
+ name: name || "Avatars",
+ description: "Character Studio Avatars.",
+ image: `ipfs://${imageHash.IpfsHash}`,
+ animation_url: `ipfs://${glbHash.IpfsHash}`,
+ attributes: getAvatarTraits(avatar)
+ }
+ const str = JSON.stringify(metadata)
+ const metaDataHash = await saveFileToPinata(
+ new Blob([str]),
+ "AvatarMetadata_" + Date.now() + ".json",
+ )
+ const metadataIpfs = `ipfs://${metaDataHash.IpfsHash}`
+
+ let price = await getTokenPrice()
+
+ const signer = new ethers.providers.Web3Provider(
+ window.ethereum,
+ ).getSigner()
+ const contract = new ethers.Contract(CharacterContract.address, CharacterContract.abi, signer)
+ try {
+ const options = {
+ value: price,
+ from: walletAddress
+ }
+ const tx = await contract.mintToken(1, metadataIpfs, options)
+ let res = await tx.wait()
+ if (res.transactionHash) {
+ console.log("Mint success!")
+ return "Mint success!";
+ }
+ } catch (err) {
+ //console.log("Public Mint failed! Please check your wallet.")
+ return "Public Mint failed."
+ }
+
+ }
+}
+
+const checkOT = async (address) => {
+ if(address) {
+ const address = '0x6e58309CD851A5B124E3A56768a42d12f3B6D104'
+ const ethersigner = ethers.getDefaultProvider("mainnet", {
+ alchemy: import.meta.env.VITE_ALCHEMY_API_KEY,
+ })
+ const contract = new ethers.Contract(EternalProxyContract.address, EternalProxyContract.abi, ethersigner);
+ const webaBalance = await contract.beneficiaryBalanceOf(address, webaverseGenesisAddress, 1);
+ if(parseInt(webaBalance) > 0) return true;
+ else {
+ console.log("Currently in alpha. You need a genesis pass to mint. \n Will be public soon!")
+ return false;
+ }
+ } else {
+ console.log("Please connect your wallet")
+ return false;
+ }
+}
+
+// const showTrait = (trait) => {
+// if (trait.name in avatar) {
+// if ("traitInfo" in avatar[trait.name]) {
+// return avatar[trait.name].name
+// } else return "Default " + trait.name
+// } else return "No set"
+// }
\ No newline at end of file
diff --git a/src/library/screenshotManager.js b/src/library/screenshotManager.js
new file mode 100644
index 00000000..7989c309
--- /dev/null
+++ b/src/library/screenshotManager.js
@@ -0,0 +1,116 @@
+import * as THREE from "three"
+import { Buffer } from "buffer";
+
+const screenshotSize = 4096;
+
+const localVector = new THREE.Vector3();
+
+const textureLoader = new THREE.TextureLoader();
+const backgroundTexture = textureLoader.load(`/assets/backgrounds/main-background2.jpg`);
+backgroundTexture.wrapS = backgroundTexture.wrapT = THREE.RepeatWrapping;
+
+export class ScreenshotManager {
+ constructor() {
+ this.renderer = new THREE.WebGLRenderer({
+ preserveDrawingBuffer: true,
+ antialias: true
+ });
+ this.renderer.outputEncoding = THREE.sRGBEncoding;
+ this.renderer.setSize(screenshotSize, screenshotSize);
+
+ this.camera = new THREE.PerspectiveCamera( 30, 1, 0.1, 1000 );
+ }
+
+ setCamera(headPosition, playerCameraDistance) {
+ this.camera.position.copy(headPosition);
+
+ localVector.set(0, 0, -1);
+ this.cameraDir = localVector.applyQuaternion(this.camera.quaternion);
+ this.cameraDir.normalize();
+ this.camera.position.x -= this.cameraDir.x * playerCameraDistance;
+ this.camera.position.z -= this.cameraDir.z * playerCameraDistance;
+
+ }
+
+ saveAsImage(imageName) {
+ let imgData;
+ try {
+ this.scene.background = backgroundTexture;
+ this.renderer.render(this.scene, this.camera);
+ const strDownloadMime = "image/octet-stream";
+ const strMime = "image/png";
+ imgData = this.renderer.domElement.toDataURL(strMime);
+
+ const base64Data = Buffer.from(
+ imgData.replace(/^data:image\/\w+;base64,/, ""),
+ "base64"
+ );
+ const blob = new Blob([base64Data], { type: "image/jpeg" });
+
+ this.saveFile(imgData.replace(strMime, strDownloadMime), imageName);
+ this.scene.background = null;
+ return blob;
+ } catch (e) {
+ console.log(e);
+ return false;
+ }
+
+ }
+
+ _createImage(width, height){
+ this.renderer.setSize(width, height);
+ try {
+ this.scene.background = backgroundTexture;
+ this.renderer.render(this.scene, this.camera);
+ const strMime = "image/png";
+ let imgData = this.renderer.domElement.toDataURL(strMime);
+ this.scene.background = null;
+ return imgData
+ } catch (e) {
+ console.log(e);
+ return null;
+ }
+ }
+ saveScreenshot(imageName,width, height){
+ const imgData = this._createImage(width, height)
+ const strDownloadMime = "image/octet-stream";
+ const strMime = "image/png";
+ this.saveFile(imgData.replace(strMime, strDownloadMime), imageName);
+ }
+
+ getScreenshotImage(width, height){
+ const imgData = this._createImage(width, height);
+ const img = new Image();
+ img.src = imgData;
+ return img;
+ }
+ getScreenshotTexture(width, height){
+ const img = this.getScreenshotImage(width,height)
+ const texture = new THREE.Texture(img);
+ texture.needsUpdate = true;
+ return texture;
+ }
+ getScreenshotBlob(width, height){
+ const imgData = this._createImage(width, height)
+ const base64Data = Buffer.from(
+ imgData.replace(/^data:image\/\w+;base64,/, ""),
+ "base64"
+ );
+ const blob = new Blob([base64Data], { type: "image/jpeg" });
+ return blob;
+ }
+ saveFile (strData, filename) {
+ const link = document.createElement('a');
+ if (typeof link.download === 'string') {
+ document.body.appendChild(link); //Firefox requires the link to be in the body
+ link.download = filename;
+ link.href = strData;
+ link.click();
+ document.body.removeChild(link); //remove the link when done
+ } else {
+ const win = window.open(strData, "_blank");
+ win.document.write("
" + filename + "
");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/pages/Mint.jsx b/src/pages/Mint.jsx
index e8abe2fb..63814b0d 100644
--- a/src/pages/Mint.jsx
+++ b/src/pages/Mint.jsx
@@ -1,20 +1,21 @@
import React from "react"
-import styles from "./Mint.module.css"
+import styles from "./Mint.module.scss"
import { ViewMode, ViewContext } from "../context/ViewContext"
-
-import Mint from "../components/Mint"
-import ResizableDiv from "../components/Resizable"
+import { SceneContext } from "../context/SceneContext"
import CustomButton from "../components/custom-button"
-
import { SoundContext } from "../context/SoundContext"
import { AudioContext } from "../context/AudioContext"
+import { mintAsset } from "../library/mint-utils"
-function MintComponent() {
+function MintComponent({getFaceScreenshot}) {
+ const { templateInfo, model, avatar } = React.useContext(SceneContext)
const { setViewMode } = React.useContext(ViewContext)
- const [screenshotPosition, setScreenshotPosition] = React.useState({x:250,y:25,width:256,height:256});
const { playSound } = React.useContext(SoundContext)
const { isMute } = React.useContext(AudioContext)
+ const [status, setStatus] = React.useState("")
+ const [minting, setMinting]= React.useState(false)
+
const back = () => {
setViewMode(ViewMode.SAVE)
!isMute && playSound('backNextButton');
@@ -25,19 +26,70 @@ function MintComponent() {
!isMute && playSound('backNextButton');
}
+ function MenuTitle() {
+ return (
+
+ )
+ }
+ async function Mint(){
+ !isMute && playSound('backNextButton');
+ setMinting(true)
+ setStatus("Please check your wallet")
+ const fullBioStr = localStorage.getItem(`${templateInfo.id}_fulBio`)
+ const fullBio = JSON.parse(fullBioStr)
+ const screenshot = getFaceScreenshot(256,256,true);
+ const result = await mintAsset(avatar,screenshot,model, fullBio.name)
+ setStatus(result)
+ setMinting(false)
+ console.log(result);
+ }
+
return (
Mint Your Character
-
+
+ {/* */}
+
+ {/* */}
+
-
-
-
+
+
+
+
+
+
+
+ {/* Genesis pass holders only */}
+
(Coming Soon!)
-
+
{status}
+
-
+
+
div {
+ padding: 16px !important;
+ }
+
+ .genesisText {
+ opacity: 0.4;
+ margin-top: 5px;
+ .required:after {
+ content: "*";
+ color: red;
+ }
+ }
+
+ .divider {
+ width: 80%;
+ height: 1px;
+ margin: 8px 0;
+ opacity: 0.2;
+ background: #e0e6e5;
+ }
+ }
+ }
+
+ .bottomContainer {
+ z-index: 0;
+ display: flex;
+ padding: 20px 32px;
+ justify-content: space-between;
+
+ button {
+ min-width: 120px;
+ }
+ }
+}
+.mintInfo {
+ height: 15px;
+ padding-bottom: 25px;
+ opacity: 0.5;
+}
+
+.topLine {
+ background: rgb(0, 149, 100);
+ background: -moz-linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+
+ background: -webkit-linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+
+ background: linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ position: absolute;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#009564",endColorstr="#009564",GradientType=1);
+}
+
+.bottomLine {
+ background: rgb(0, 149, 100);
+ background: -moz-linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+ background: -webkit-linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+ background: linear-gradient(
+ 90deg,
+ rgba(0, 149, 100, 0) 0%,
+ rgba(8, 234, 160, 1) 50%,
+ rgba(0, 149, 100, 0) 100%
+ );
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#009564",endColorstr="#009564",GradientType=1);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 1px;
+}
diff --git a/src/pages/Save.jsx b/src/pages/Save.jsx
index dbd826a5..c3c9bc35 100644
--- a/src/pages/Save.jsx
+++ b/src/pages/Save.jsx
@@ -1,4 +1,4 @@
-import React, { useContext } from "react"
+ import React, { useContext } from "react"
import styles from "./Save.module.css"
import { ExportMenu } from "../components/ExportMenu"
@@ -8,26 +8,29 @@ import { LanguageContext } from "../context/LanguageContext"
import { SoundContext } from "../context/SoundContext"
import { AudioContext } from "../context/AudioContext"
-function Save() {
- const { setViewMode } = React.useContext(ViewContext);
+
+function Save({getFaceScreenshot}) {
+
+ // Translate hook
+ const { t } = useContext(LanguageContext);
const { playSound } = React.useContext(SoundContext)
const { isMute } = React.useContext(AudioContext)
+ const { setViewMode } = React.useContext(ViewContext);
+
const back = () => {
setViewMode(ViewMode.BIO)
!isMute && playSound('backNextButton');
}
const mint = () => {
- setViewMode(ViewMode.CHAT)
+ setViewMode(ViewMode.MINT)
+ !isMute && playSound('backNextButton');
}
const next = () => {
setViewMode(ViewMode.CHAT)
!isMute && playSound('backNextButton');
}
- // Translate hook
- const { t } = useContext(LanguageContext);
-
return (
{t("pageTitles.saveCharacter")}
@@ -39,16 +42,17 @@ function Save() {
className={styles.buttonLeft}
onClick={back}
/>
-
- {/*
-
- */}
+
+
+
{
+ // setViewMode(ViewMode.MINT)
+ // }
// Translate hook
const { t } = useContext(LanguageContext);