diff --git a/CHANGELOG b/CHANGELOG index 4d5957e..96ef060 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +v0.1.6 -> 15/10/2023 -----------------------------------> TAG: v0.1.6-beta + - Changed PWA + - Added PWA install popup + - Changed playBot() function test + - Changed combos logic (Rebalanced) + - Excluded ultra thin outbox generation + - Changed Best Score label to Score + - Added Screenshot feature to share records + - Added micro zoom-out to better views + v0.1.5 -> 09/10/2023 -----------------------------------> TAG: v0.1.5-alpha - Added randomization of colors - Fixed pwa update problem diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1f71c38 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,32 @@ +Contributor Code of Conduct +=========================== + +As contributors and maintainers of this project, we pledge to respect all +people who contribute through reporting issues, posting feature requests, +updating documentation, submitting pull requests or patches, and other +activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual +language or imagery, derogatory comments or personal attacks, trolling, public +or private harassment, insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. Project maintainers who do not +follow the Code of Conduct may be removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by opening an issue or contacting one or more of the project +maintainers. + +This Code of Conduct is adapted from the [Contributor +Covenant](https://contributor-covenant.org/), version 1.1.0, available at +[https://contributor-covenant.org/version/1/1/0/](https://contributor-covenant.org/version/1/1/0/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d55d628..373a439 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,4 +4,17 @@ PULL REQUEST A COMMIT WITH TEMPLATE: EXAMPLE: - "Screen lag fixed #234" \ No newline at end of file + "Screen lag fixed #234" + + + +MAKE ALL THE PULL REQUEST INTO THE BRANCH "DEV" + +ROADMAP: + + BRANCH_NAME_1 ¯¯\ + BRANCH_NAME_2 __ DEV ---> VERSION ---------------> MASTER + BRANCH_NAME_3 __/ ^ \______ RELEASE + | \___ DEPLOY + COMMIT : CHANGELOG + MIN FILES \ No newline at end of file diff --git a/README.md b/README.md index d16dc8a..c675fc6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Live web preview [here](https://zhenglinlei.github.io/stackblock.io)

- intro pic + intro pic


@@ -26,7 +26,7 @@ Live web preview [here](https://zhenglinlei.github.io/stackblock.io) License   - Version + Version

diff --git a/css/style.css b/css/style.css index fbdd30b..f9cc196 100644 --- a/css/style.css +++ b/css/style.css @@ -1 +1 @@ -*{margin:0;padding:0;color:#fff;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Code Pro",monospace;text-align:center;cursor:pointer}body .point-result{position:fixed;top:calc(20vh + 50px);z-index:99;left:50%;transform:translateX(-50%);transition:.8s}body .point-result.active{top:40px}body .combo-extra-point{position:fixed;top:200px;left:50%;transform:translateX(-50%);transition:.3s;opacity:0}body .combo-extra-point.active{animation:extraPointFlash 1s ease-out}body .combo-strike{font-weight:800;font-size:54px;position:fixed;top:50vh;left:50vw;transform:translate(-50%, 15vh);transition:.3s;opacity:0}body .combo-strike.open{transform:translate(-50%, 10vh) scale(1.7) rotate(-10deg);opacity:1}body .result-area{transition:.4s;position:fixed;width:100vw;height:100vh;background-color:rgba(0,0,0,.8);display:flex;justify-content:center}body .result-area.disable{opacity:0}body .result-area header{margin-top:20vh;position:relative;transition:1s}body .result-area header .best{display:none}body .result-area header.new .best,body .result-area header.new .color-shape{display:block}body .result-area header.new .score{display:none}body .result-area header .color-shape{height:5px;display:none;width:100%;margin-top:5px;background:linear-gradient(90deg, #42c2f1 0%, #fbda4f 25%, #ab63df 50%, #5fc581 75%, #42c2f1 100%);background-size:200%;animation:moveGradient 3s linear infinite}@keyframes moveGradient{0%{background-position:0% 0%}100%{background-position:-200% 0%}}body .result-area footer.flash-text{position:absolute;bottom:20vh;animation:flash 1s infinite ease}body .result-area footer.flash-text .mobile-version{display:none}@media screen and (max-width: 700px){body .result-area footer.flash-text .mobile-version{display:block}body .result-area footer.flash-text .desktop-version{display:none}}body #pwa-install{position:fixed;top:30px;left:50%;transform:translateX(-50%) translateY(-100%);background-color:#fff;z-index:999;padding:10px;border-radius:5px;display:none;justify-content:space-between;grid-gap:20px;width:250px;height:60px;flex-wrap:nowrap;overflow:hidden}body #pwa-install.display{display:flex;animation:scrollDown .3s ease forwards}body #pwa-install .pwa-install-action{flex:1;justify-content:space-between;display:flex;flex-direction:column}body #pwa-install .pwa-install-text>*{font-size:12px;color:#000;text-align:start}body #pwa-install .pwa-install-text>*.pwa-install-description{font-size:10px;color:#999}body #pwa-install .pwa-install-icon>img{width:60px;border-radius:5px;height:60px}body #pwa-install .pwa-install-button{display:flex;justify-content:end}body #pwa-install .pwa-install-button button{border:none;background-color:#42c2f1;color:#fff;padding:7px 15px;border-radius:5px;cursor:pointer;font-size:10px;transition:.3s;font-weight:bold}body #pwa-install .pwa-install-button button:hover{background-color:#5fc581}@keyframes flash{50%{opacity:0}100%{opacity:1}}@keyframes extraPointFlash{60%{top:100px;opacity:1}100%{top:90px;opacity:0}}@keyframes scrollDown{100%{transform:translateX(-50%) translateY(0%)}}/*# sourceMappingURL=style.css.map */ \ No newline at end of file +*{margin:0;padding:0;color:#fff;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0)}html{min-height:calc(100% + env(safe-area-inset-top)) !important;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left) !important}body{font-family:"Source Code Pro",monospace;text-align:center;cursor:pointer}body .point-result{position:fixed;top:calc(20vh + 50px);z-index:99;left:50%;transform:translateX(-50%);transition:.8s}body .point-result.active{top:40px}body .combo-extra-point{position:fixed;top:200px;left:50%;transform:translateX(-50%);transition:.3s;opacity:0}body .combo-extra-point.active{animation:extraPointFlash 1s ease-out}body .combo-strike{font-weight:800;font-size:54px;position:fixed;top:50vh;left:50vw;transform:translate(-50%, 15vh);transition:.3s;opacity:0}body .combo-strike.open{transform:translate(-50%, 10vh) scale(1.7) rotate(-10deg);opacity:1}body .result-area{transition:.4s;position:fixed;width:100vw;height:100vh;background-color:rgba(0,0,0,.8);display:flex;justify-content:center}body .result-area.disable{opacity:0}body .result-area header{margin-top:20vh;position:relative;transition:1s}body .result-area header .best{display:none}body .result-area header.new .best,body .result-area header.new .color-shape{display:block}body .result-area header.new .score{display:none}body .result-area header .color-shape{height:5px;display:none;width:100%;margin-top:5px;background:linear-gradient(90deg, #42c2f1 0%, #fbda4f 25%, #ab63df 50%, #5fc581 75%, #42c2f1 100%);background-size:200%;animation:moveGradient 3s linear infinite}@keyframes moveGradient{0%{background-position:0% 0%}100%{background-position:-200% 0%}}body .result-area footer.flash-text{position:absolute;bottom:20vh;animation:flash 1s infinite ease}body .result-area footer.flash-text .mobile-version{display:none}@media screen and (max-width: 700px){body .result-area footer.flash-text .mobile-version{display:block}body .result-area footer.flash-text .desktop-version{display:none}}body #click-event{width:100vw;height:100vh;z-index:2;position:fixed;top:0;left:0}body #pwa-install{position:fixed;top:30px;left:50%;transform:translateX(-50%) translateY(calc(-100% - 30px));background-color:#fff;z-index:2;padding:10px;border-radius:5px;display:flex;justify-content:space-between;grid-gap:20px;width:250px;height:60px;flex-wrap:nowrap;overflow:hidden}body #pwa-install.display{animation:scrollDown .3s ease forwards}body #pwa-install .pwa-install-action{flex:1;justify-content:space-between;display:flex;flex-direction:column}body #pwa-install .pwa-install-text>*{font-size:12px;color:#000;text-align:start}body #pwa-install .pwa-install-text>*.pwa-install-description{font-size:10px;color:#999}body #pwa-install .pwa-install-icon>img{width:60px;border-radius:5px;height:60px}body #pwa-install .pwa-install-button{display:flex;justify-content:space-between;align-items:end}body #pwa-install .pwa-install-button button{border:none;background-color:#42c2f1;color:#fff;padding:7px 15px;border-radius:5px;cursor:pointer;font-size:10px;transition:.3s;font-weight:bold}body #pwa-install .pwa-install-button button:hover{background-color:#5fc581}body #pwa-install .pwa-install-button a{visibility:hidden;color:#727272;text-decoration:underline;font-size:10px;margin-bottom:5px}body #pwa-install .pwa-install-button a:hover{color:#4a4a4a}body #pwa-install .pwa-install-button a.display{visibility:visible}body #record-share{position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);z-index:99;border-radius:5px;overflow:hidden;background-color:#fff;transition:.5s;display:none}body #record-share.display{display:block;animation:jumpShaking .5s}body #record-share:hover{transform:translate(-50%, -50%) rotate(-5deg) scale(1.1)}body #record-share *{color:#000}body #record-share img{width:35vw;height:35vw;min-width:140px;min-height:140px;max-width:200px;max-height:200px;-o-object-fit:cover;object-fit:cover}@media screen and (max-height: 700px){body #record-share img{width:28vw;height:28vw;max-height:180px;min-width:180px}}@media(hover: none){body #record-share.display{animation:jumpShakingTouch .5s forwards}}body #record-share small{display:block;padding:10px;font-size:10px}@keyframes flash{50%{opacity:0}100%{opacity:1}}@keyframes extraPointFlash{60%{top:100px;opacity:1}100%{top:90px;opacity:0}}@keyframes scrollDown{100%{transform:translateX(-50%) translateY(0%)}}@keyframes jumpShaking{0%{transform:translate(-50%, -50%)}25%{transform:translate(-50%, calc(-50% - 9px))}35%{transform:translate(-50%, calc(-50% - 9px)) rotate(-5deg)}55%{transform:translate(-50%, calc(-50% - 9px)) rotate(5deg)}65%{transform:translate(-50%, calc(-50% - 9px)) rotate(-5deg)}75%{transform:translate(-50%, calc(-50% - 9px)) rotate(5deg)}100%{transform:translate(-50%, -50%)}}@keyframes jumpShakingTouch{0%{transform:translate(-50%, -50%)}25%{transform:translate(-50%, calc(-50% - 9px))}35%{transform:translate(-50%, calc(-50% - 9px)) rotate(-5deg)}55%{transform:translate(-50%, calc(-50% - 9px)) rotate(5deg)}65%{transform:translate(-50%, calc(-50% - 9px)) rotate(-5deg)}75%{transform:translate(-50%, calc(-50% - 9px)) rotate(5deg)}100%{transform:translate(-50%, -50%) rotate(-5deg) scale(1.1)}}/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/css/style.css.map b/css/style.css.map index 776119f..63497ef 100644 --- a/css/style.css.map +++ b/css/style.css.map @@ -1 +1 @@ -{"version":3,"sources":["../scss/style.scss"],"names":[],"mappings":"AAAA,EACI,QAAA,CACA,SAAA,CACA,UAAA,CACA,0BAAA,CACA,wBAAA,CAEG,qBAAA,CAEK,gBAAA,CAER,yCAAA,CACA,yCAAA,CAGJ,KACI,uCAAA,CACA,iBAAA,CACA,cAAA,CAEA,mBACI,cAAA,CACA,qBAAA,CACA,UAAA,CACA,QAAA,CACA,0BAAA,CACA,cAAA,CAEA,0BACI,QAAA,CAGR,wBACI,cAAA,CACA,SAAA,CACA,QAAA,CACA,0BAAA,CACA,cAAA,CACA,SAAA,CAGA,+BACI,qCAAA,CAGR,mBACI,eAAA,CACA,cAAA,CACA,cAAA,CACA,QAAA,CACA,SAAA,CACA,+BAAA,CACA,cAAA,CACA,SAAA,CAEA,wBACI,yDAAA,CACA,SAAA,CAGR,kBAII,cAAA,CACA,cAAA,CACA,WAAA,CACA,YAAA,CACA,+BAAA,CACA,YAAA,CACA,sBAAA,CATA,0BACI,SAAA,CAUJ,yBACI,eAAA,CACA,iBAAA,CACA,aAAA,CACA,+BACI,YAAA,CAIA,6EACI,aAAA,CAEJ,oCACI,YAAA,CAGR,sCACI,UAAA,CACA,YAAA,CACA,UAAA,CACA,cAAA,CAEA,kGAAA,CACA,oBAAA,CACA,yCAAA,CAGJ,wBACI,GACI,yBAAA,CAEJ,KACI,4BAAA,CAAA,CAIZ,oCACI,iBAAA,CACA,WAAA,CACA,gCAAA,CACA,oDACI,YAAA,CAEJ,qCACI,oDACI,aAAA,CAEJ,qDACI,YAAA,CAAA,CAMhB,kBAGI,cAAA,CACA,QAAA,CACA,QAAA,CACA,4CAAA,CACA,qBAAA,CACA,WAAA,CACA,YAAA,CACA,iBAAA,CACA,YAAA,CACA,6BAAA,CACA,aAAA,CACA,WAAA,CACA,WAda,CAeb,gBAAA,CACA,eAAA,CACA,0BACI,YAAA,CAEA,sCAAA,CAEJ,sCACI,MAAA,CACA,6BAAA,CACA,YAAA,CACA,qBAAA,CAEJ,sCACI,cAAA,CACA,UAAA,CACA,gBAAA,CAEA,8DACI,cAAA,CACA,UAAA,CAGR,wCACI,UAvCS,CAwCT,iBAAA,CACA,WAzCS,CA2Cb,sCACI,YAAA,CACA,mBAAA,CACA,6CAEI,WAAA,CACA,wBAAA,CACA,UAAA,CACA,gBAAA,CACA,iBAAA,CACA,cAAA,CACA,cAAA,CACA,cAAA,CACA,gBAAA,CAEA,mDACI,wBAAA,CAOpB,iBACI,IACI,SAAA,CAEJ,KACI,SAAA,CAAA,CAIR,2BACI,IACI,SAAA,CACA,SAAA,CAEJ,KACI,QAAA,CACA,SAAA,CAAA,CAIR,sBACI,KACI,yCAAA,CAAA","file":"style.css"} \ No newline at end of file +{"version":3,"sources":["../scss/style.scss"],"names":[],"mappings":"AAAA,EACI,QAAA,CACA,SAAA,CACA,UAAA,CACA,0BAAA,CACA,wBAAA,CAEG,qBAAA,CAEK,gBAAA,CAER,yCAAA,CACA,yCAAA,CAGJ,KAEI,2DAAA,CACA,4HAAA,CAGJ,KACI,uCAAA,CACA,iBAAA,CACA,cAAA,CAEA,mBACI,cAAA,CACA,qBAAA,CACA,UAAA,CACA,QAAA,CACA,0BAAA,CACA,cAAA,CAEA,0BACI,QAAA,CAGR,wBACI,cAAA,CACA,SAAA,CACA,QAAA,CACA,0BAAA,CACA,cAAA,CACA,SAAA,CAGA,+BACI,qCAAA,CAGR,mBACI,eAAA,CACA,cAAA,CACA,cAAA,CACA,QAAA,CACA,SAAA,CACA,+BAAA,CACA,cAAA,CACA,SAAA,CAEA,wBACI,yDAAA,CACA,SAAA,CAGR,kBAII,cAAA,CACA,cAAA,CACA,WAAA,CACA,YAAA,CACA,+BAAA,CACA,YAAA,CACA,sBAAA,CATA,0BACI,SAAA,CAUJ,yBACI,eAAA,CACA,iBAAA,CACA,aAAA,CACA,+BACI,YAAA,CAIA,6EACI,aAAA,CAEJ,oCACI,YAAA,CAGR,sCACI,UAAA,CACA,YAAA,CACA,UAAA,CACA,cAAA,CAEA,kGAAA,CACA,oBAAA,CACA,yCAAA,CAGJ,wBACI,GACI,yBAAA,CAEJ,KACI,4BAAA,CAAA,CAIZ,oCACI,iBAAA,CACA,WAAA,CACA,gCAAA,CACA,oDACI,YAAA,CAEJ,qCACI,oDACI,aAAA,CAEJ,qDACI,YAAA,CAAA,CAMhB,kBACI,WAAA,CACA,YAAA,CACA,SAAA,CACA,cAAA,CACA,KAAA,CACA,MAAA,CAEJ,kBAGI,cAAA,CACA,QAAA,CACA,QAAA,CACA,yDAAA,CACA,qBAAA,CACA,SAAA,CACA,YAAA,CACA,iBAAA,CACA,YAAA,CACA,6BAAA,CACA,aAAA,CACA,WAAA,CACA,WAda,CAeb,gBAAA,CACA,eAAA,CACA,0BAEI,sCAAA,CAEJ,sCACI,MAAA,CACA,6BAAA,CACA,YAAA,CACA,qBAAA,CAEJ,sCACI,cAAA,CACA,UAAA,CACA,gBAAA,CAEA,8DACI,cAAA,CACA,UAAA,CAGR,wCACI,UAtCS,CAuCT,iBAAA,CACA,WAxCS,CA0Cb,sCACI,YAAA,CACA,6BAAA,CACA,eAAA,CACA,6CAEI,WAAA,CACA,wBAAA,CACA,UAAA,CACA,gBAAA,CACA,iBAAA,CACA,cAAA,CACA,cAAA,CACA,cAAA,CACA,gBAAA,CAEA,mDACI,wBAAA,CAGR,wCACI,iBAAA,CACA,aAAA,CACA,yBAAA,CACA,cAAA,CACA,iBAAA,CACA,8CACI,aAAA,CAEJ,gDACI,kBAAA,CAMhB,mBAGI,cAAA,CACA,OAAA,CACA,QAAA,CACA,+BAAA,CACA,UAAA,CACA,iBAAA,CACA,eAAA,CACA,qBAAA,CACA,cAAA,CACA,YAAA,CACA,2BACI,aAAA,CACA,yBAAA,CAEJ,yBACI,wDAAA,CAEJ,qBACI,UAAA,CAGJ,uBACI,UAxBU,CAyBV,WAzBU,CA0BV,eAAA,CACA,gBAAA,CACA,eAAA,CACA,gBAAA,CACA,mBAAA,CAAA,gBAAA,CAGJ,sCACI,uBACI,UAAA,CACA,WAAA,CACA,gBAAA,CACA,eAAA,CAAA,CAIR,oBACI,2BACI,uCAAA,CAAA,CAGR,yBACI,aAAA,CACA,YAAA,CACA,cAAA,CAKZ,iBACI,IACI,SAAA,CAEJ,KACI,SAAA,CAAA,CAIR,2BACI,IACI,SAAA,CACA,SAAA,CAEJ,KACI,QAAA,CACA,SAAA,CAAA,CAIR,sBACI,KACI,yCAAA,CAAA,CAIR,uBACI,GAAA,+BAAA,CACA,IAAA,2CAAA,CACA,IAAA,yDAAA,CACA,IAAA,wDAAA,CACA,IAAA,yDAAA,CACA,IAAA,wDAAA,CACA,KAAA,+BAAA,CAAA,CAEJ,4BACI,GAAA,+BAAA,CACA,IAAA,2CAAA,CACA,IAAA,yDAAA,CACA,IAAA,wDAAA,CACA,IAAA,yDAAA,CACA,IAAA,wDAAA,CACA,KAAA,wDAAA,CAAA","file":"style.css"} \ No newline at end of file diff --git a/docs/intro.png b/doc/intro.png similarity index 100% rename from docs/intro.png rename to doc/intro.png diff --git a/index.html b/index.html index 598f192..0940311 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + @@ -24,7 +24,6 @@ StackBlock.io - Minigame -
@@ -45,6 +44,9 @@

BEST SCORE

+ +
+
@@ -56,17 +58,25 @@

BEST SCORE

Download the APP

+ Not now
+ +
+ + Tap to share picture +
+ + - - + \ No newline at end of file diff --git a/js/lib/png2share.js b/js/lib/png2share.js new file mode 100644 index 0000000..41c50a6 --- /dev/null +++ b/js/lib/png2share.js @@ -0,0 +1,118 @@ +// ================================================================================ + +// Github: https://github.com/hughsk/canvas-pixels +// +// Get Pixel amount in a 3D canvas +function getPixels3d(gl) { + var canvas = gl.canvas + var height = canvas.height + var width = canvas.width + var buffer = new Uint8Array(width * height * 4) + + gl.readPixels(0, 0 + , canvas.width + , canvas.height + , gl.RGBA + , gl.UNSIGNED_BYTE + , buffer + ); + + return buffer +} + +// Get Pixel amount in a 2D canvas +function getPixels2d(ctx) { + var canvas = ctx.canvas + var height = canvas.height + var width = canvas.width + + return ctx.getImageData(0, 0, width, height).data +} + + + +// ================================================================================ + + +// WebGL to 2D CANVAS +// Github: https://github.com/Experience-Monks/webgl-to-canvas2d +const Webgl2canvas = (webgl, canvas2D) => { + + var outCanvas = canvas2D ? canvas2D.canvas || canvas2D : document.createElement('canvas'); + var outContext = outCanvas.getContext('2d'); + var outImageData; + + webgl = (webgl instanceof WebGLRenderingContext) ? webgl : (webgl.getContext('webgl2') || webgl.getContext('webgl') || webgl.getContext('experimental-webgl')); + + outCanvas.width = webgl.canvas.width; + outCanvas.height = webgl.canvas.height; + + outImageData = outContext.getImageData(0, 0, outCanvas.width, outCanvas.height); + + outImageData.data.set(new Uint8ClampedArray(getPixels3d(webgl).buffer)); + outContext.putImageData(outImageData, 0, 0); + outContext.translate(0, outCanvas.height); + outContext.scale(1, -1); + outContext.drawImage(outCanvas, 0, 0); + outContext.setTransform(1, 0, 0, 1, 0, 0); + + return outCanvas; +}; + + +// ================================================================================ + +// Stackoverflow: https://stackoverflow.com/a/17906462 +// Stackoverflow: https://stackoverflow.com/questions/17906169/how-to-copy-from-one-canvas-to-other-canvas +// +// Convert WebGL Canvas to Image +// Canvas(WebGL) -> Canvas(2D){Do changes} -> Image(JPG, PNG) +const DrawCanvasCopy = (canvas, callback, callbackBlob) => { + let c = Webgl2canvas(canvas); + // Prepare new canvas + callback(c.getContext('2d')); + // Export + c.toBlob(callbackBlob); + // Delete + c.remove(); +} + +// ================================================================================ + +// Stackblock: https://stackoverflow.com/questions/68362603/share-image-via-social-media-from-pwa +// +// Convert blob to image share +const Blob2Share = async(blob) => { + if (!('share' in navigator) || !('canShare' in navigator)) { + return false; + } + const files = [new File([blob], 'newRecord.png', { type: blob.type })]; + const shareData = { + text: 'Play with me. In Stackblock.io', + title: 'Stackblock.io', + files, + }; + if (navigator.canShare(shareData)) { + try { + await navigator.share(shareData); + } catch (err) { + if (err.name !== 'AbortError') { + console.error(err.name, err.message); + return false; + } + } + } else { + console.warn('Sharing not supported', shareData); + return false; + } +}; + +const Blob2Download = (blob) => { + let a = document.createElement('a'); + let url = window.URL.createObjectURL(blob); + a.href = url; + a.download = "StackBlock_newRecord.png"; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); +} \ No newline at end of file diff --git a/js/lib/png2share.min.js b/js/lib/png2share.min.js new file mode 100644 index 0000000..94171a6 --- /dev/null +++ b/js/lib/png2share.min.js @@ -0,0 +1 @@ +function getPixels3d(e){var t=e.canvas,a=t.height,n=t.width,r=new Uint8Array(n*a*4);return e.readPixels(0,0,t.width,t.height,e.RGBA,e.UNSIGNED_BYTE,r),r}function getPixels2d(e){var t=e.canvas,a=t.height,n=t.width;return e.getImageData(0,0,n,a).data}const Webgl2canvas=(e,t)=>{var a,n=t?t.canvas||t:document.createElement("canvas"),r=n.getContext("2d");return e=e instanceof WebGLRenderingContext?e:e.getContext("webgl2")||e.getContext("webgl")||e.getContext("experimental-webgl"),n.width=e.canvas.width,n.height=e.canvas.height,(a=r.getImageData(0,0,n.width,n.height)).data.set(new Uint8ClampedArray(getPixels3d(e).buffer)),r.putImageData(a,0,0),r.translate(0,n.height),r.scale(1,-1),r.drawImage(n,0,0),r.setTransform(1,0,0,1,0,0),n},DrawCanvasCopy=(e,t,a)=>{let n=Webgl2canvas(e);t(n.getContext("2d")),n.toBlob(a),n.remove()},Blob2Share=async e=>{if(!("share"in navigator&&"canShare"in navigator))return!1;const t={text:"Play with me. In Stackblock.io",title:"Stackblock.io",files:[new File([e],"newRecord.png",{type:e.type})]};if(!navigator.canShare(t))return console.warn("Sharing not supported",t),!1;try{await navigator.share(t)}catch(e){if("AbortError"!==e.name)return console.error(e.name,e.message),!1}},Blob2Download=e=>{let t=document.createElement("a"),a=window.URL.createObjectURL(e);t.href=a,t.download="StackBlock_newRecord.png",t.click(),window.URL.revokeObjectURL(a),t.remove()}; \ No newline at end of file diff --git a/js/pwa.min.js b/js/pwa.min.js deleted file mode 100644 index 8776c44..0000000 --- a/js/pwa.min.js +++ /dev/null @@ -1 +0,0 @@ -let deferredPrompt,installBtn,enableDownload=!1;window.addEventListener("load",()=>{console.log("PWA ready!");let e=localStorage.getItem("PWA_installed");installBtn=document.querySelector("#pwa-install-btn"),window.addEventListener("beforeinstallprompt",l=>{l.preventDefault(),deferredPrompt=l,console.log("Prepared"),e||(window.matchMedia("(display-mode: standalone)").matches||window.matchMedia("(display-mode: fullscreen)").matches||!0===window.navigator.standalone||"true"===localStorage.getItem("PWA_installed"))&&(localStorage.setItem("PWA_installed","true"),e=localStorage.getItem("PWA_installed")),e||"true"==e?(enableDownload=!0,installBtn.addEventListener("click",()=>{console.log("Installing..."),deferredPrompt.prompt(),deferredPrompt.userChoice.then(e=>{"accepted"===e.outcome?console.log("User accepted the install prompt"):console.log("User dismissed the install prompt")}),enableDownload=!1,installBtn.classList.remove("display")})):console.log("Installed before")}),window.addEventListener("appinstalled",e=>{deferredPrompt=null,console.log("👍","appinstalled",e),localStorage.setItem("PWA_installed","true")})}); \ No newline at end of file diff --git a/js/pwa.js b/js/pwa/pwa.js similarity index 60% rename from js/pwa.js rename to js/pwa/pwa.js index 6911237..4567d2b 100644 --- a/js/pwa.js +++ b/js/pwa/pwa.js @@ -1,11 +1,11 @@ -let deferredPrompt, enableDownload = false, installBtn; +let deferredPrompt, enableDownload = false, installBtn, installRejector; window.addEventListener("load", () => { console.log("PWA ready!"); let activeDownload = localStorage.getItem("PWA_installed"); - + let installDismissed = localStorage.getItem("installDismissed"); installBtn = document.querySelector("#pwa-install-btn"); - + installRejector = document.querySelector("#pwa-dismiss-btn"); //cool variable name window.addEventListener('beforeinstallprompt', (e) => { // Prevent the mini-infobar from appearing on mobile e.preventDefault(); @@ -15,14 +15,24 @@ window.addEventListener("load", () => { // Check installation if(!activeDownload){ - if (window.matchMedia('(display-mode: standalone)').matches || window.matchMedia('(display-mode: fullscreen)').matches || window.navigator.standalone === true || localStorage.getItem('PWA_installed') === "true") { + // REMOVED: window.matchMedia('(display-mode: full-screen)').matches ----> Full-screen doesn't that user has downloaded the PWA + if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true || localStorage.getItem('PWA_installed') === "true") { localStorage.setItem("PWA_installed", 'true'); activeDownload = localStorage.getItem("PWA_installed"); + } else { + localStorage.removeItem("PWA_installed"); + document.querySelector("#pwa-install").classList.remove("display"); + activeDownload = false; } } - if(activeDownload || activeDownload == 'true') { + if((!activeDownload || activeDownload == 'false') && (!installDismissed || installDismissed == 'false')) { enableDownload = true; + + installRejector.addEventListener("click", ()=>{ + localStorage.setItem("installDismissed", 'true'); + document.querySelector("#pwa-install").classList.remove("display"); + }); installBtn.addEventListener("click", () => { console.log("Installing..."); @@ -38,10 +48,9 @@ window.addEventListener("load", () => { }); enableDownload = false; - installBtn.classList.remove("display"); }); } else { - console.log('Installed before') + console.log('Installed before or install dismissed') } }); diff --git a/js/pwa/pwa.min.js b/js/pwa/pwa.min.js new file mode 100644 index 0000000..bd841bb --- /dev/null +++ b/js/pwa/pwa.min.js @@ -0,0 +1 @@ +let deferredPrompt,installBtn,installRejector,enableDownload=!1;window.addEventListener("load",()=>{console.log("PWA ready!");let e=localStorage.getItem("PWA_installed"),l=localStorage.getItem("installDismissed");installBtn=document.querySelector("#pwa-install-btn"),installRejector=document.querySelector("#pwa-dismiss-btn"),window.addEventListener("beforeinstallprompt",t=>{t.preventDefault(),deferredPrompt=t,console.log("Prepared"),e||(window.matchMedia("(display-mode: standalone)").matches||!0===window.navigator.standalone||"true"===localStorage.getItem("PWA_installed")?(localStorage.setItem("PWA_installed","true"),e=localStorage.getItem("PWA_installed")):(localStorage.removeItem("PWA_installed"),document.querySelector("#pwa-install").classList.remove("display"),e=!1)),e&&"false"!=e||l&&"false"!=l?console.log("Installed before or install dismissed"):(enableDownload=!0,installRejector.addEventListener("click",()=>{localStorage.setItem("installDismissed","true"),document.querySelector("#pwa-install").classList.remove("display")}),installBtn.addEventListener("click",()=>{console.log("Installing..."),deferredPrompt.prompt(),deferredPrompt.userChoice.then(e=>{"accepted"===e.outcome?console.log("User accepted the install prompt"):console.log("User dismissed the install prompt")}),enableDownload=!1}))}),window.addEventListener("appinstalled",e=>{deferredPrompt=null,console.log("👍","appinstalled",e),localStorage.setItem("PWA_installed","true")})}); \ No newline at end of file diff --git a/js/script.js b/js/script.js index 179bd4b..d832e9a 100644 --- a/js/script.js +++ b/js/script.js @@ -1,4 +1,4 @@ -const log = console.log; +// const log = console.log; // GAME DETAILS let GAME_ = { status: false, @@ -6,9 +6,29 @@ let GAME_ = { active: false, // ACTIVE THE GAME IN THE FIRST TIME score: 0, combo: 0, + gamesPlayed: 0, bestResult: (window.localStorage.getItem('bestResult')) ? window.localStorage.getItem('bestResult') : 0, newRecord: false, // TO RESET SCREEN - designPalette: 0 + designPalette: 0, + // + // Stackoverflow: https://stackoverflow.com/questions/30628064/how-to-toggle-preservedrawingbuffer-in-three-js + // Stackoverflow: https://stackoverflow.com/a/30647502 + screenshot: { + service: true, // If this service mus be disable, change value to false + enable: false, + frames: 0, + // Take screenshot after x frames + callback: null, + blob: null + // Image content buffer + }, + botMode: false, + zoomOut: { + service: true, // If this service must be disable, change value to false + enable: false, + frames: 0, + finished: false + },// Zoom out the camera fter every game over } // ================================== @@ -19,7 +39,8 @@ const colorDesign = [ [224, 68, 62], [251, 50, 60], [339, 62, 48], - [231, 50, 47] + [231, 50, 47], + [165, 30, 68] ]; // DEFINE BOX SIZE @@ -52,6 +73,10 @@ window.addEventListener('load', ()=>{ let scoreTab = document.querySelector('.score-tab'); let comboStrike = document.querySelector('.combo-strike'); let comboExtraPoint = document.querySelector('.combo-extra-point'); + // EVENT LAYER DOM + let eventLayer = document.querySelector('#click-event'); + // RECORD SHARE + let recordShare = document.querySelector('#record-share'); /* ========================================== = FUNCTIONS ============================================*/ @@ -139,15 +164,29 @@ window.addEventListener('load', ()=>{ }; return `#${f(0)}${f(8)}${f(4)}`; } + + // CHANGE CAMERA DATA + function refreshCameraView() { + camera.right = cameraPos.width / cameraPos.size; + camera.left = cameraPos.width / -cameraPos.size; + camera.top = cameraPos.height / cameraPos.size; + camera.bottom = cameraPos.height / -cameraPos.size; + camera.updateProjectionMatrix(); + } - // RESET COLISION WORLD AND SCENE + // RESET COLISION WORLD AND SCENE AND OTHER STUFFS (LIKE MY MADNESS BECAUSE THERE ARE MANY BUGS) function reset(){ GAME_.designPalette = Math.floor(Math.random() * colorDesign.length); // CAMERA + cameraPos.size = (window.innerWidth > 700)? 1 : 2; + refreshCameraView(); camera.position.set(4, 4, 4); camera.lookAt(0, 0, 0); + // OPEN zoomOut + GAME_.zoomOut.finished = false; + // SCENE & WORLD stackBoxArr.forEach(i =>{ scene.remove(i.threejs); @@ -181,8 +220,14 @@ window.addEventListener('load', ()=>{ // FIRST LAYER addLayer(0, 0, boxSize.x, boxSize.z, 'x'); // TOP LEVEL - + + // DISABLE PWA IF EXIST + document.querySelector('#pwa-install').classList.remove('display'); + + // REMOVE NEW RECORD SHARE + recordShare.classList.remove('display'); } + // ============================================= // WORLD DEFINE world = new CANNON.World(); @@ -221,14 +266,18 @@ window.addEventListener('load', ()=>{ camera.lookAt(0, 0, 0); // RENDER SCENE - renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer = new THREE.WebGLRenderer( + { + antialias: true, + // preserveDrawingBuffer: true + }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.render(scene, camera); + renderer.domElement.id = "Stackblock"; // DISPLAY document.body.appendChild(renderer.domElement); - //===================================== /*=============================== = GAME CORE ================================*/ @@ -242,6 +291,9 @@ window.addEventListener('load', ()=>{ // ANIMATION FRAME 60fps renderer.setAnimationLoop(animation); GAME_.active = true; // ACTIVE GAME + + // CHANGE BEST_SCORE TO SCORE + scoreTab.querySelector('.score').innerHTML = "SCORE"; }else{ reset(); // WHEN IT ISN'T THE FIRST TIME, ONLY HAVE TO RESET THE WORLD } @@ -253,8 +305,6 @@ window.addEventListener('load', ()=>{ // DISABLE RSULT AND DISPLAY POINTS resultTabDom.classList.toggle('disable'); pointDom.classList.toggle('active'); - // DISABLE PWA IF EXIST - document.querySelector('#pwa-install').classList.remove('display'); // DISABLE NEW RECORD if(GAME_.newRecord){ @@ -397,20 +447,41 @@ window.addEventListener('load', ()=>{ playConfetti(); // PARTY YEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH!!!!!!!!!!!! // UPDATE LOCALSTORAGE - GAME_.bestResult = GAME_.score; - window.localStorage.setItem('bestResult', GAME_.score); + if (!GAME_.botMode) { + GAME_.bestResult = GAME_.score; + window.localStorage.setItem('bestResult', GAME_.score); + } // SHOW NEWRECORD scoreTab.classList.add('new'); // ACTIVE RECORD MODE GAME_.newRecord = true; + + if (GAME_.screenshot.service) { + // CALLBACK + GAME_.screenshot.callback = img => { + recordShare.replaceChild(img, recordShare.querySelector('img')); + // SHOW ANIMATION: SHAKY SHAKY SHAKIRA + recordShare.classList.add('display'); + } + GAME_.screenshot.enable = true; + } } + + // ACTIVE ZOOMOUT + if (GAME_.zoomOut.service) + GAME_.zoomOut.enable = true; + let dismissed = localStorage.getItem("installDismissed") ?? false; // Better than localStorage.getItem("installDismissed") || false // CHECK PWA INSTALLATION (1.0.4 version) - if (enableDownload) { + if (enableDownload && (dismissed == "false" || !dismissed)){ document.querySelector('#pwa-install').classList.add('display'); + if(GAME_.gamesPlayed > 0){ + document.querySelector('#pwa-dismiss-btn').classList.add('display'); + } } + GAME_.gamesPlayed++; } } } @@ -441,21 +512,109 @@ window.addEventListener('load', ()=>{ // FINISH LAST BOX ANIMATION stackBoxArr[stackBoxArr.length-1].threejs.position.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.position); // EXTRACT ALL DATA TO THREEJS stackBoxArr[stackBoxArr.length-1].threejs.quaternion.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.quaternion); // EXTRACT ALL DATA TO THREEJS + + // CHECK IF ZOOM OUT IS ENABLED + if (GAME_.zoomOut.enable) { + cameraPos.size -= 0.02; + refreshCameraView(); + + GAME_.zoomOut.frames ++; + + if (GAME_.zoomOut.frames >= 20) { + GAME_.zoomOut.enable = false; + GAME_.zoomOut.frames = 0; + GAME_.zoomOut.finished = true; + } + } } updatePhisics(); renderer.render(scene, camera); + + // CHECK IF CLIENT REQUESTED TO TAKE A SCREENSHOT + // This section of code must be executed after renderer.render(scene, camera) to avoid buffer cleaning + // Once the canvas(webgl2) buffer has been cleaned, it can not take screenshot anymore. + if (GAME_.screenshot.enable) { + // TAKE THE SCREENSHOT AFTER 5 FRAMES FOR MORE PRECISE IMAGE STATUS + if(GAME_.screenshot.frames >= 5) { + // TAKE THE SCREENSHOT AFTER ZOOMOUT, NOT BEFORE OR IN PROCESS + if(GAME_.zoomOut.finished) { + // CREATE A EXTRA CANVAS + DrawCanvasCopy(renderer.domElement, + ctx => { + // BG + ctx.fillStyle = "rgba(0, 0, 0, 0.2)"; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + // TEXT + ctx.fillStyle = "#ffffff"; + ctx.font = '32px monospace'; + + var textString = "NEW RECORD", + textWidth = ctx.measureText(textString).width; + + + ctx.fillText(textString , (ctx.canvas.width/2) - (textWidth / 2), ctx.canvas.height / 2 - 100); + + // RECORD + ctx.font = 'bold 64px monospace'; + + textString = GAME_.score; + textWidth = ctx.measureText(textString).width; + + ctx.fillText(textString , (ctx.canvas.width/2) - (textWidth / 2), ctx.canvas.height/ 2 + 50); + + if (GAME_.botMode) { + ctx.save(); + ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2 + 50); + ctx.rotate(-Math.PI/8); + // Text + var fontsize = 64; + ctx.fillStyle = 'rgba(255, 0, 0, 0.6)'; + ctx.font = `bold ${fontsize}px monospace`; + var lineHeight = fontsize * 1.286, padding = 10; + textString = "FAKE" + textWidth = ctx.measureText(textString).width; + ctx.fillText(textString, -(textWidth / 2), 0); + // Rect + ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)'; + ctx.lineWidth = 5; + ctx.strokeRect(-(textWidth/2 + padding), -(lineHeight/2 + padding), textWidth + padding*2, lineHeight/2 + padding*1.5); + ctx.restore(); + } + }, + blob => { + const img = document.createElement("img"); + const url = URL.createObjectURL(blob); + + img.onload = () => { + // no longer need to read the blob so it is revoked + URL.revokeObjectURL(url); + }; + + img.src = url; + + // SAVE BLOB + GAME_.screenshot.blob = blob; + + GAME_.screenshot.callback(img); + }); + + // DISABLE OPTION, IF THE PROCESS HAS SUCCESS OF FAILED SHOULD DISABLE. + // OTHERWISE WITH THE DELAY OF THE CALLBACK THE FUNCTION WILL BE CALLED MULTIPLE TIMES + GAME_.screenshot.enable = false; + GAME_.screenshot.frames = 0; + } + } else { + GAME_.screenshot.frames++; + } + } } let supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints; let eventType = supportsTouch ? 'touchstart' : 'mousedown'; - window.addEventListener(eventType, e => { - if (!e.target.className.split(' ').some((c) => { return /pwa-.*/.test(c); })) { - fncStart(); - } - }); // ADD FNC + eventLayer.addEventListener(eventType, fncStart); // ADD FNC let keyFree = true; // PC Version @@ -478,6 +637,14 @@ window.addEventListener('load', ()=>{ keyFree = true; } }); + + // SHARE RECORD + recordShare.addEventListener(eventType, async () => { + if( ! await Blob2Share(GAME_.screenshot.blob)){ + Blob2Download(GAME_.screenshot.blob); + } + + }) }); @@ -490,28 +657,33 @@ function playConfetti(min, max){ // ALPHABOT v1.0 INTERNAL BOT FOR TESTING function playBot(precision, timer, output=true){ //PRECISION BETWEEN 0 TO 1 + GAME_.botMode = true; let botTimer = setInterval(()=>{ - const lastLayer = stackBoxArr[stackBoxArr.length -1]; - const previousLayer = stackBoxArr[stackBoxArr.length -2]; - - // LAST LAYER DIRECTION - let lastDirection = lastLayer.direction; - - // CALCULATE OUTBOX - let delta = lastLayer.threejs.position[lastDirection] - previousLayer.threejs.position[lastDirection] // !NOTE: THE BOTH BOX MUST BE CALCULATED WITH THE SAME DIRECTION - let alpha = Math.abs(delta); // GET POSITIVE NUM - - // CALCULATE OUTBOX WIDTH DEPTH - let outbox = (lastDirection === "x")? lastLayer.width : lastLayer.depth; - let inbox = outbox - alpha; - - const boxRelation = inbox / outbox; // 0 to 1 - if(boxRelation >= (precision?precision:0.95)){ - fncStart(); - if(output){ - console.log(boxRelation); // OUTPUT + try { + const lastLayer = stackBoxArr[stackBoxArr.length -1]; + const previousLayer = stackBoxArr[stackBoxArr.length -2]; + + // LAST LAYER DIRECTION + let lastDirection = lastLayer.direction; + + // CALCULATE OUTBOX + let delta = lastLayer.threejs.position[lastDirection] - previousLayer.threejs.position[lastDirection] // !NOTE: THE BOTH BOX MUST BE CALCULATED WITH THE SAME DIRECTION + let alpha = Math.abs(delta); // GET POSITIVE NUM + + // CALCULATE OUTBOX WIDTH DEPTH + let outbox = (lastDirection === "x")? lastLayer.width : lastLayer.depth; + let inbox = outbox - alpha; + + const boxRelation = inbox / outbox; // 0 to 1 + if(boxRelation >= (precision?precision:0.95)){ + fncStart(); + if(output){ + console.log(boxRelation); // OUTPUT + } } + } catch (e) { + // BY DEFAULT START GAME + fncStart(); } - }, timer?timer:20); } \ No newline at end of file diff --git a/js/script.min.js b/js/script.min.js index 0597117..ec83069 100644 --- a/js/script.min.js +++ b/js/script.min.js @@ -1 +1 @@ -const log=console.log;let GAME_={status:!1,end:!1,active:!1,score:0,combo:0,bestResult:window.localStorage.getItem("bestResult")?window.localStorage.getItem("bestResult"):0,newRecord:!1,designPalette:0};const colorDesign=[[30,70,50],[120,80,60],[224,68,62],[251,50,60],[339,62,48],[231,50,47]],boxSize={x:3,y:1,z:3,range:10},c_width=10,cameraPos={width:10,height:10*(window.innerHeight/window.innerWidth),near:1,far:100,size:window.innerWidth>700?1:2};let world,scene,camera,renderer,fncStart,stackBoxArr=[],outBoxArr=[];function playConfetti(e,t){party.confetti(party.Rect.fromScreen(),{count:party.variation.range(e||60,t||80)})}function playBot(e,t,o=!0){setInterval(()=>{let t=stackBoxArr[stackBoxArr.length-1],r=stackBoxArr[stackBoxArr.length-2],s=t.direction,n=t.threejs.position[s]-r.threejs.position[s],i="x"===s?t.width:t.depth,a=(i-Math.abs(n))/i;a>=(e||.95)&&(fncStart(),o&&console.log(a))},t||20)}window.addEventListener("load",()=>{let e=document.querySelector(".result-area"),t=document.querySelector(".point-result"),o=document.querySelector(".score-tab"),r=document.querySelector(".combo-strike"),s=document.querySelector(".combo-extra-point");function n(e){scene.background=new THREE.Color(h(colorDesign[GAME_.designPalette][0]+120+stackBoxArr.length,colorDesign[GAME_.designPalette][1],colorDesign[GAME_.designPalette][2]))}function i(e){t.innerHTML=e}function a(e,t,o,r){let s=boxSize.y*(stackBoxArr.length-1),n=d(e,s,t,o,r,!0);outBoxArr.push(n)}function c(){world.step(1/60),outBoxArr.forEach(e=>{e.threejs.position.copy(e.cannonjs.position),e.threejs.quaternion.copy(e.cannonjs.quaternion)})}function l(e,t,o,r,s){let n=boxSize.y*stackBoxArr.length,i=d(e,n,t,o,r,!1);i.direction=s,i.positive=!0,stackBoxArr.push(i)}function d(e,t,o,r,s,n=!1){let i=n?(stackBoxArr.length-1)*4:4*stackBoxArr.length,a=new THREE.Color(h(colorDesign[GAME_.designPalette][0]+i,colorDesign[GAME_.designPalette][1],colorDesign[GAME_.designPalette][2])),c=new THREE.BoxGeometry(r,boxSize.y,s),l=new THREE.MeshLambertMaterial({color:a}),d=new THREE.Mesh(c,l);d.position.set(e,t,o);let $=new CANNON.Box(new CANNON.Vec3(r/2,boxSize.y/2,s/2)),x=new CANNON.Body({mass:n?5:0,shape:$});return x.position.set(e,t,o),world.addBody(x),scene.add(d),{threejs:d,geometry:c,material:l,cannonjs:x,x:e,y:t,z:o,width:r,depth:s}}function h(e,t,o){o=o>101?100:o,o/=100;let r=t*Math.min(o,1-o)/100,s=t=>{let s=(t+e/30)%12,n=o-r*Math.max(Math.min(s-3,9-s,1),-1);return Math.round(255*n).toString(16).padStart(2,"0")};return`#${s(0)}${s(8)}${s(4)}`}function $(){GAME_.designPalette=Math.floor(Math.random()*colorDesign.length),camera.position.set(4,4,4),camera.lookAt(0,0,0),stackBoxArr.forEach(e=>{scene.remove(e.threejs),e.geometry.dispose(),e.material.dispose(),world.removeBody(e.cannonjs)}),outBoxArr.forEach(e=>{scene.remove(e.threejs),e.geometry.dispose(),e.material.dispose(),world.removeBody(e.cannonjs)}),stackBoxArr=[],outBoxArr=[],GAME_.score=0,GAME_.combo=0,n(),l(0,0,boxSize.x,boxSize.z,"x")}(world=new CANNON.World).gravity.set(0,-9.8,0),world.broadphase=new CANNON.NaiveBroadphase,world.solver.interations=40,i(GAME_.bestResult),scene=new THREE.Scene,n(),l(0,0,boxSize.x,boxSize.z,"x");let x=new THREE.AmbientLight(16777215,.6);scene.add(x);let p=new THREE.DirectionalLight(16777215,.6);function A(){if(GAME_.end)stackBoxArr[stackBoxArr.length-1].threejs.position.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.position),stackBoxArr[stackBoxArr.length-1].threejs.quaternion.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.quaternion);else{let e=stackBoxArr[stackBoxArr.length-1],t=e.positive?.15:-.15;e.threejs.position[e.direction]>=boxSize.range-5&&(e.positive=!1),e.threejs.position[e.direction]<=-(boxSize.range-5)&&(e.positive=!0),e.threejs.position[e.direction]+=t,e.cannonjs.position[e.direction]+=t,camera.position.y{if(GAME_.status){let c,d,h=stackBoxArr[stackBoxArr.length-1],x=stackBoxArr[stackBoxArr.length-2],p=h.direction,g=h.threejs.position[p]-x.threejs.position[p],m=Math.abs(g),u="x"===p?h.width:h.depth,_=u-m;if(_>0){let w=_/u;m<.2?([9,19,29,39,49].includes(GAME_.combo)&&(s.classList.add("active"),setTimeout(()=>{s.classList.remove("active")},1100),GAME_.score+=10),GAME_.combo+=1,r.innerHTML=`x${GAME_.combo}`,r.classList.add("open"),setTimeout(()=>{r.classList.remove("open")},700)):GAME_.combo&&(GAME_.combo=0),c="x"===p?_:h.width,d="z"===p?_:h.depth,h.width=c,h.depth=d,h.threejs.scale[p]=w,h.threejs.position[p]-=g/2,h.cannonjs.position[p]-=g/2;let y=new CANNON.Box(new CANNON.Vec3(c/2,boxSize.y/2,d/2));h.cannonjs.shapes=[],h.cannonjs.addShape(y);let b=Math.sign(g)*(_/2+m/2),B={x:"x"===p?h.threejs.position.x+b:h.threejs.position.x,z:"z"===p?h.threejs.position.z+b:h.threejs.position.z,width:"x"===p?m:c,depth:"z"===p?m:d};a(B.x,B.z,B.width,B.depth);let f="x"===p?h.threejs.position.x:-(boxSize.range-1),E;l(f,"z"===p?h.threejs.position.z:-(boxSize.range-1),c,d,"x"===p?"z":"x"),GAME_.score++,n(),i(GAME_.score)}else{GAME_.end=!0,world.remove(h.cannonjs);let M=new CANNON.Box(new CANNON.Vec3(h.width/2,boxSize.y/2,h.depth/2)),k=5,z=new CANNON.Body({mass:k,shape:M});z.position.set(h.threejs.position.x,h.threejs.position.y,h.threejs.position.z),world.addBody(z),h.cannonjs=z,e.classList.toggle("disable"),t.classList.toggle("active"),GAME_.status=!1,GAME_.score>GAME_.bestResult&&(playConfetti(),GAME_.bestResult=GAME_.score,window.localStorage.setItem("bestResult",GAME_.score),o.classList.add("new"),GAME_.newRecord=!0),enableDownload&&document.querySelector("#pwa-install").classList.add("display")}}else GAME_.active?$():(renderer.setAnimationLoop(A),GAME_.active=!0),i(GAME_.score),l(0,0,boxSize.x,boxSize.z,"z"),e.classList.toggle("disable"),t.classList.toggle("active"),document.querySelector("#pwa-install").classList.remove("display"),GAME_.newRecord&&(GAME_.newRecord=!1,o.classList.remove("new")),GAME_.status=!0,GAME_.end=!1};let g="ontouchstart"in window||navigator.msMaxTouchPoints?"touchstart":"mousedown";window.addEventListener(g,e=>{e.target.className.split(" ").some(e=>/pwa-.*/.test(e))||fncStart()});let m=!0;window.addEventListener("keydown",function(e){(32===e.keyCode||40===e.keyCode||" "===e.key)&&(e.preventDefault(),m&&(m=!1,fncStart()))}),window.addEventListener("keyup",function(e){(32===e.keyCode||40===e.keyCode||" "===e.key)&&(m=!0)})}); \ No newline at end of file +let GAME_={status:!1,end:!1,active:!1,score:0,combo:0,gamesPlayed:0,bestResult:window.localStorage.getItem("bestResult")?window.localStorage.getItem("bestResult"):0,newRecord:!1,designPalette:0,screenshot:{service:!0,enable:!1,frames:0,callback:null,blob:null},botMode:!1,zoomOut:{service:!0,enable:!1,frames:0,finished:!1}};const colorDesign=[[30,70,50],[120,80,60],[224,68,62],[251,50,60],[339,62,48],[231,50,47],[165,30,68]],boxSize={x:3,y:1,z:3,range:10},c_width=10,cameraPos={width:10,height:window.innerHeight/window.innerWidth*10,near:1,far:100,size:window.innerWidth>700?1:2};let world,scene,camera,renderer,fncStart,stackBoxArr=[],outBoxArr=[];function playConfetti(e,t){party.confetti(party.Rect.fromScreen(),{count:party.variation.range(e||60,t||80)})}function playBot(e,t,o=!0){GAME_.botMode=!0;setInterval(()=>{try{const t=stackBoxArr[stackBoxArr.length-1],s=stackBoxArr[stackBoxArr.length-2];let n=t.direction,r=t.threejs.position[n]-s.threejs.position[n],a=Math.abs(r),i="x"===n?t.width:t.depth;const c=(i-a)/i;c>=(e||.95)&&(fncStart(),o&&console.log(c))}catch(e){fncStart()}},t||20)}window.addEventListener("load",()=>{let e=document.querySelector(".result-area"),t=document.querySelector(".point-result"),o=document.querySelector(".score-tab"),s=document.querySelector(".combo-strike"),n=document.querySelector(".combo-extra-point"),r=document.querySelector("#click-event"),a=document.querySelector("#record-share");function i(e){scene.background=new THREE.Color(h(colorDesign[GAME_.designPalette][0]+120+stackBoxArr.length,colorDesign[GAME_.designPalette][1],colorDesign[GAME_.designPalette][2]))}function c(e){t.innerHTML=e}function l(e,t,o,s,n){const r=d(e,boxSize.y*stackBoxArr.length,t,o,s,!1);r.direction=n,r.positive=!0,stackBoxArr.push(r)}function d(e,t,o,s,n,r=!1){let a=r?4*(stackBoxArr.length-1):4*stackBoxArr.length;const i=new THREE.Color(h(colorDesign[GAME_.designPalette][0]+a,colorDesign[GAME_.designPalette][1],colorDesign[GAME_.designPalette][2])),c=new THREE.BoxGeometry(s,boxSize.y,n),l=new THREE.MeshLambertMaterial({color:i}),d=new THREE.Mesh(c,l);d.position.set(e,t,o);const m=new CANNON.Box(new CANNON.Vec3(s/2,boxSize.y/2,n/2));let A=r?5:0;const E=new CANNON.Body({mass:A,shape:m});return E.position.set(e,t,o),world.addBody(E),scene.add(d),{threejs:d,geometry:c,material:l,cannonjs:E,x:e,y:t,z:o,width:s,depth:n}}function h(e,t,o){o=o>101?100:o,o/=100;const s=t*Math.min(o,1-o)/100,n=t=>{const n=(t+e/30)%12,r=o-s*Math.max(Math.min(n-3,9-n,1),-1);return Math.round(255*r).toString(16).padStart(2,"0")};return`#${n(0)}${n(8)}${n(4)}`}function m(){camera.right=cameraPos.width/cameraPos.size,camera.left=cameraPos.width/-cameraPos.size,camera.top=cameraPos.height/cameraPos.size,camera.bottom=cameraPos.height/-cameraPos.size,camera.updateProjectionMatrix()}(world=new CANNON.World).gravity.set(0,-9.8,0),world.broadphase=new CANNON.NaiveBroadphase,world.solver.interations=40,c(GAME_.bestResult),scene=new THREE.Scene,i(),l(0,0,boxSize.x,boxSize.z,"x");const A=new THREE.AmbientLight(16777215,.6);scene.add(A);const E=new THREE.DirectionalLight(16777215,.6);function p(){if(GAME_.end)stackBoxArr[stackBoxArr.length-1].threejs.position.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.position),stackBoxArr[stackBoxArr.length-1].threejs.quaternion.copy(stackBoxArr[stackBoxArr.length-1].cannonjs.quaternion),GAME_.zoomOut.enable&&(cameraPos.size-=.02,m(),GAME_.zoomOut.frames++,GAME_.zoomOut.frames>=20&&(GAME_.zoomOut.enable=!1,GAME_.zoomOut.frames=0,GAME_.zoomOut.finished=!0));else{const e=stackBoxArr[stackBoxArr.length-1],t=e.positive?.15:-.15;e.threejs.position[e.direction]>=boxSize.range-5&&(e.positive=!1),e.threejs.position[e.direction]<=-(boxSize.range-5)&&(e.positive=!0),e.threejs.position[e.direction]+=t,e.cannonjs.position[e.direction]+=t,camera.position.y{e.threejs.position.copy(e.cannonjs.position),e.threejs.quaternion.copy(e.cannonjs.quaternion)}),renderer.render(scene,camera),GAME_.screenshot.enable&&(GAME_.screenshot.frames>=5?GAME_.zoomOut.finished&&(DrawCanvasCopy(renderer.domElement,e=>{e.fillStyle="rgba(0, 0, 0, 0.2)",e.fillRect(0,0,e.canvas.width,e.canvas.height),e.fillStyle="#ffffff",e.font="32px monospace";var t="NEW RECORD",o=e.measureText(t).width;if(e.fillText(t,e.canvas.width/2-o/2,e.canvas.height/2-100),e.font="bold 64px monospace",t=GAME_.score,o=e.measureText(t).width,e.fillText(t,e.canvas.width/2-o/2,e.canvas.height/2+50),GAME_.botMode){e.save(),e.translate(e.canvas.width/2,e.canvas.height/2+50),e.rotate(-Math.PI/8);e.fillStyle="rgba(255, 0, 0, 0.6)",e.font="bold 64px monospace";t="FAKE",o=e.measureText(t).width,e.fillText(t,-o/2,0),e.strokeStyle="rgba(255, 0, 0, 0.5)",e.lineWidth=5,e.strokeRect(-(o/2+10),-51.152,o+20,56.152),e.restore()}},e=>{const t=document.createElement("img"),o=URL.createObjectURL(e);t.onload=(()=>{URL.revokeObjectURL(o)}),t.src=o,GAME_.screenshot.blob=e,GAME_.screenshot.callback(t)}),GAME_.screenshot.enable=!1,GAME_.screenshot.frames=0):GAME_.screenshot.frames++)}E.position.set(10,20,0),scene.add(E),(camera=new THREE.OrthographicCamera(cameraPos.width/-cameraPos.size,cameraPos.width/cameraPos.size,cameraPos.height/cameraPos.size,cameraPos.height/-cameraPos.size,cameraPos.near,cameraPos.far)).position.set(4,4,4),camera.lookAt(0,0,0),(renderer=new THREE.WebGLRenderer({antialias:!0})).setSize(window.innerWidth,window.innerHeight),renderer.render(scene,camera),renderer.domElement.id="Stackblock",document.body.appendChild(renderer.domElement),fncStart=(()=>{if(GAME_.status){let r,h;const m=stackBoxArr[stackBoxArr.length-1],A=stackBoxArr[stackBoxArr.length-2];let E=m.direction,p=m.threejs.position[E]-A.threejs.position[E],u=Math.abs(p),w="x"===E?m.width:m.depth,x=w-u;if(x>0){const e=x/w;u<.2?([9,19,29,39,49].includes(GAME_.combo)&&(n.classList.add("active"),setTimeout(()=>{n.classList.remove("active")},1100),GAME_.score+=10),GAME_.combo+=1,s.innerHTML=`x${GAME_.combo}`,s.classList.add("open"),setTimeout(()=>{s.classList.remove("open")},700)):GAME_.combo&&(GAME_.combo=0),r="x"===E?x:m.width,h="z"===E?x:m.depth,m.width=r,m.depth=h,m.threejs.scale[E]=e,m.threejs.position[E]-=p/2,m.cannonjs.position[E]-=p/2;const t=new CANNON.Box(new CANNON.Vec3(r/2,boxSize.y/2,h/2));if(m.cannonjs.shapes=[],m.cannonjs.addShape(t),u>=.01){let e=Math.sign(p)*(x/2+u/2);const t={x:"x"===E?m.threejs.position.x+e:m.threejs.position.x,z:"z"===E?m.threejs.position.z+e:m.threejs.position.z,width:"x"===E?u:r,depth:"z"===E?u:h};!function(e,t,o,s){const n=d(e,boxSize.y*(stackBoxArr.length-1),t,o,s,!0);outBoxArr.push(n)}(t.x,t.z,t.width,t.depth)}l("x"===E?m.threejs.position.x:-(boxSize.range-1),"z"===E?m.threejs.position.z:-(boxSize.range-1),r,h,"x"===E?"z":"x"),GAME_.score++,i(),c(GAME_.score)}else{GAME_.end=!0,world.remove(m.cannonjs);const s=new CANNON.Box(new CANNON.Vec3(m.width/2,boxSize.y/2,m.depth/2));let n=5;const r=new CANNON.Body({mass:n,shape:s});r.position.set(m.threejs.position.x,m.threejs.position.y,m.threejs.position.z),world.addBody(r),m.cannonjs=r,e.classList.toggle("disable"),t.classList.toggle("active"),GAME_.status=!1,GAME_.score>GAME_.bestResult&&(playConfetti(),GAME_.botMode||(GAME_.bestResult=GAME_.score,window.localStorage.setItem("bestResult",GAME_.score)),o.classList.add("new"),GAME_.newRecord=!0,GAME_.screenshot.service&&(GAME_.screenshot.callback=(e=>{a.replaceChild(e,a.querySelector("img")),a.classList.add("display")}),GAME_.screenshot.enable=!0)),GAME_.zoomOut.service&&(GAME_.zoomOut.enable=!0);let i=localStorage.getItem("installDismissed")||!1;!enableDownload||"false"!=i&&i||(document.querySelector("#pwa-install").classList.add("display"),GAME_.gamesPlayed>0&&document.querySelector("#pwa-dismiss-btn").classList.add("display")),GAME_.gamesPlayed++}}else GAME_.active?(GAME_.designPalette=Math.floor(Math.random()*colorDesign.length),cameraPos.size=window.innerWidth>700?1:2,m(),camera.position.set(4,4,4),camera.lookAt(0,0,0),GAME_.zoomOut.finished=!1,stackBoxArr.forEach(e=>{scene.remove(e.threejs),e.geometry.dispose(),e.material.dispose(),world.removeBody(e.cannonjs)}),outBoxArr.forEach(e=>{scene.remove(e.threejs),e.geometry.dispose(),e.material.dispose(),world.removeBody(e.cannonjs)}),stackBoxArr=[],outBoxArr=[],GAME_.score=0,GAME_.combo=0,i(),l(0,0,boxSize.x,boxSize.z,"x"),document.querySelector("#pwa-install").classList.remove("display"),a.classList.remove("display")):(renderer.setAnimationLoop(p),GAME_.active=!0,o.querySelector(".score").innerHTML="SCORE"),c(GAME_.score),l(0,0,boxSize.x,boxSize.z,"z"),e.classList.toggle("disable"),t.classList.toggle("active"),GAME_.newRecord&&(GAME_.newRecord=!1,o.classList.remove("new")),GAME_.status=!0,GAME_.end=!1});let u="ontouchstart"in window||navigator.msMaxTouchPoints?"touchstart":"mousedown";r.addEventListener(u,fncStart);let w=!0;window.addEventListener("keydown",function(e){32!==e.keyCode&&40!==e.keyCode&&" "!==e.key||(e.preventDefault(),w&&(w=!1,fncStart()))}),window.addEventListener("keyup",function(e){32!==e.keyCode&&40!==e.keyCode&&" "!==e.key||(w=!0)}),a.addEventListener(u,async()=>{await Blob2Share(GAME_.screenshot.blob)||Blob2Download(GAME_.screenshot.blob)})}); \ No newline at end of file diff --git a/manifest.json b/manifest.json index a02e8a2..a2ed15f 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "StackBlock.io", "short_name": "StackBlock", "description": "Easy and funny .io game.", - "start_url": "./index.html", + "start_url": "./index.html?fullscreen=true&standalone=true", "display": "standalone", "orientation": "portrait", "background_color": "#f5f5f5", diff --git a/scss/style.scss b/scss/style.scss index 60da193..930a7c6 100644 --- a/scss/style.scss +++ b/scss/style.scss @@ -13,6 +13,12 @@ -webkit-tap-highlight-color: transparent; } +html { + // Safe area to show the website upper the notch area (show real full-screen) + min-height: calc(100% + env(safe-area-inset-top)) !important; + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left) !important; +} + body{ font-family: 'Source Code Pro', monospace; text-align: center; @@ -124,18 +130,26 @@ body{ } } + #click-event { + width: 100vw; + height: 100vh; + z-index: 2; + position: fixed; + top: 0; + left: 0; + } #pwa-install { $image-size: 60px; // position: fixed; top: 30px; left: 50%; - transform: translateX(-50%) translateY(-100%); + transform: translateX(-50%) translateY(calc(-100% - 30px)); background-color: white; - z-index: 999; + z-index: 2; padding: 10px; border-radius: 5px; - display: none; + display: flex; justify-content: space-between; grid-gap: 20px; width: 250px; @@ -143,7 +157,6 @@ body{ flex-wrap: nowrap; overflow: hidden; &.display { - display: flex; // scroll down animation animation: scrollDown .3s ease forwards; } @@ -170,7 +183,8 @@ body{ } .pwa-install-button { display: flex; - justify-content: end; + justify-content: space-between; + align-items: end; button { // Minimal design for install button border: none; @@ -187,6 +201,74 @@ body{ background-color: #5fc581; } } + a { + visibility: hidden; + color: rgb(114, 114, 114); + text-decoration: underline; + font-size: 10px; + margin-bottom: 5px; + &:hover{ + color:rgb(74, 74, 74); + } + &.display { + visibility: visible; + } + } + } + } + + #record-share { + $gadget-size: 35vw; + // + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 99; + border-radius: 5px; + overflow: hidden; + background-color: white; + transition: .5s; + display: none; + &.display { + display: block; + animation: jumpShaking 0.5s; + } + &:hover { + transform: translate(-50%, -50%) rotate(-5deg) scale(1.1); + } + * { + color: black; + } + + img { + width: $gadget-size; + height: $gadget-size; + min-width: 140px; + min-height: 140px; + max-width: 200px; + max-height: 200px; + object-fit: cover; + } + // Dektop make it smaller + @media screen and (max-height: 700px) { + img { + width: calc($gadget-size * 0.8); + height: calc($gadget-size * 0.8); + max-height: 180px; + min-width: 180px; + } + } + // Mobile + @media (hover: none) { + &.display { + animation: jumpShakingTouch 0.5s forwards; + } + } + small { + display: block; + padding: 10px; + font-size: 10px; } } } @@ -215,4 +297,23 @@ body{ 100% { transform: translateX(-50%) translateY(0%); } -} \ No newline at end of file +} + +@keyframes jumpShaking { + 0% { transform: translate(-50%, -50%) } + 25% { transform: translate(-50%, calc(-50% - 9px)) } + 35% { transform: translate(-50%, calc(-50% - 9px)) rotate(-5deg) } + 55% { transform: translate(-50%, calc(-50% - 9px)) rotate(5deg) } + 65% { transform: translate(-50%, calc(-50% - 9px)) rotate(-5deg) } + 75% { transform: translate(-50%, calc(-50% - 9px)) rotate(5deg) } + 100% { transform: translate(-50%, -50%)} +} +@keyframes jumpShakingTouch { + 0% { transform: translate(-50%, -50%) } + 25% { transform: translate(-50%, calc(-50% - 9px)) } + 35% { transform: translate(-50%, calc(-50% - 9px)) rotate(-5deg) } + 55% { transform: translate(-50%, calc(-50% - 9px)) rotate(5deg) } + 65% { transform: translate(-50%, calc(-50% - 9px)) rotate(-5deg) } + 75% { transform: translate(-50%, calc(-50% - 9px)) rotate(5deg) } + 100% { transform: translate(-50%, -50%) rotate(-5deg) scale(1.1)} +} diff --git a/sw.js b/sw.js index 4ebc6bf..96f09fd 100644 --- a/sw.js +++ b/sw.js @@ -1,5 +1,5 @@ //GET VERSION -const CACHE_VERSION = "1.0.5"; +const CACHE_VERSION = "1.0.6"; const CURRENT_CACHE = `sbio-v${CACHE_VERSION}`; let filesToCache = [ "./manifest.json", @@ -13,7 +13,8 @@ let filesToCache = [ "https://cdn.jsdelivr.net/npm/party-js@latest/bundle/party.min.js", "./js/script.min.js", `./sw.js?v=${CACHE_VERSION}`, - "./js/pwa.min.js", + "./js/pwa/pwa.min.js", + // "./js/lib/png2share.min.js", "./index.html", "./", ];