// Global scope let pane; let presentTransition = { duration: 600, opacity: 0, translateY: 280, prespective: 250, rotateX: 65, scale: 0.3 }; let destroyTransition = { duration: 600, opacity: 0, translateY: 280, prespective: 250, rotateX: 65, scale: 0.3 }; // DOM ready window.onload = async function () { await document.querySelector('ion-app').componentOnReady(); drawGraphCanvas(); window.addEventListener('resize', drawGraphCanvas); // Calculator let price = '23,165.82'; let amount = document.querySelector('#amount'); let submitButton = document.querySelector('#submitButton'); let estPrice = document.querySelector('#estPrice'); let estBtc = document.querySelector('#estBtc'); let swipeCancel = document.querySelector('#swipeCancel'); let sendingOrder = document.querySelector('#sendingOrder'); // Cupertino-Pane is magic panes for PWA interfaces // npm i cupertino-pane pane = new CupertinoPane('cupertino-pane', { fitHeight: true, inverse: true, bottomClose: true, showDraggable: false, bottomOffset: 120, upperThanTop: true, dragBy: ['.backdrop', '.cupertino-pane-wrapper .pane'], backdrop: true, backdropOpacity: 0, events: { onWillPresent: () => { amount.value = '$1'; estPrice.value = `$${price}`; estBtc.value = '0.0000431670 BTC'; submitButton.removeAttribute('disabled'); }, onWillDismiss: () => { document.body.classList.remove('pane-visible'); }, onDidDismiss: () => { sendingOrder.classList.add('hidden'); swipeCancel.classList.remove('hidden'); } } }); // Rewrite and customize some transitions to make // pane moved by touchmove events with // custom perspective transitions pane.transitions.setPaneElTransform = (params) => setPaneElTransform(params); // Set caret amount.addEventListener('ionFocus', () => { let val = amount.value; amount.value = '$1 '; setTimeout(() => amount.value = val); }); // calculate value amount.addEventListener('ionChange', (val) => { val = val.detail.value; val = val.replace(/\D/g,''); amount.value = `$${val}`; if (!val) { submitButton.setAttribute('disabled', ''); estBtc.value = '-'; } else { submitButton.removeAttribute('disabled'); estBtc.value = `${(val / parseInt(price.replace(',', ''))).toFixed(10)} BTC`; } }); }; // rewrite basic transition function function setPaneElTransform(params) { let customY = destroyTransition.translateY; let customAngle = destroyTransition.rotateX; let customPerspective = destroyTransition.prespective; let customOpacity = destroyTransition.opacity; let customScale = destroyTransition.scale; let path = pane.breakpoints.topper - customY; let factor = -1 * (100 - (params.translateY - (customY - path)) * 100 / path); factor = factor / 100; if (params.type === 'move') { if (params.translateY < customY) { return; } pane.paneEl.style.setProperty( 'transform', `translateY(${params.translateY}px) translateZ(1000px) perspective(${customPerspective + ((1000 - customPerspective) * factor)}px) rotateX(${customAngle - (customAngle * factor)}deg) scale(${customScale + ((1 - customScale) * factor)})` ); pane.paneEl.style.setProperty('opacity', `${customOpacity + ((1 - customOpacity) * factor)}`); return; } if (params.type === 'destroy') { pane.paneEl.style.setProperty( 'transform', `translateY(${customY}px) translateZ(1000px) perspective(${customPerspective}px) rotateX(${customAngle}deg) scale(${customScale})` ); pane.paneEl.style.setProperty('opacity', `${customOpacity}`); return; } if (params.type === 'end') { pane.paneEl.style.setProperty('opacity', '1'); } pane.paneEl.style.transform = `translateY(${params.translateY}px) translateZ(1000px)`; } async function toggleTheme() { document.body.classList.toggle('primary'); document.body.classList.toggle('secondary'); drawGraphCanvas(); } async function showDialog(buy) { document.body.classList.add('pane-visible'); await pane.present({ animate: true, transition: { duration: presentTransition.duration, from: { opacity: presentTransition.opacity, transform: `translateY(${presentTransition.translateY}px) translateZ(1000px) perspective(${presentTransition.prespective}px) rotateX(${presentTransition.rotateX}deg) scale(${presentTransition.scale})` }, to: { opacity: 1 } } }); } async function submit() { submitButton.setAttribute('disabled', ''); swipeCancel.classList.add('hidden'); sendingOrder.classList.remove('hidden'); await new Promise(resolve => setTimeout(resolve, 2000)); await pane.destroy({ animate: true, transition: { duration: destroyTransition.duration, to: { opacity: destroyTransition.opacity, transform: `translateY(${destroyTransition.translateY}px) translateZ(1000px) perspective(${destroyTransition.prespective}px) rotateX(${destroyTransition.rotateX}deg) scale(${destroyTransition.scale})` } } }); } async function drawGraphCanvas() { let height = (window.innerHeight * 0.76) - 160; // let height = 480; let color = getComputedStyle(document.body).getPropertyValue('--ion-color-secondary'); let canvas = document.querySelector('canvas'); canvas.width = window.innerWidth; canvas.height = height; let context = canvas.getContext('2d'); document.querySelector('.graph-container').appendChild(canvas); // declare graph start and end let GRAPH_TOP = 0; let GRAPH_BOTTOM = height; let GRAPH_LEFT = 0; let GRAPH_RIGHT = 275; let GRAPH_HEIGHT = height; let GRAPH_WIDTH = window.innerWidth > 390 ? 390 : window.innerWidth; context.clearRect( 0, 0, GRAPH_WIDTH + 50, GRAPH_HEIGHT + 50 ); // Bitcoin two weeks raw data let dataArr = [ 23.8584, 23.3082, 23.3444, 23.1198, 23.3082, 22.6133, 23.4476, 22.5935, 22.6121, 22.8222, 23.2145, 22.4387, 22.8208, 22.9887, 23.6237, 22.6986, 23.6342, 23.7742, 24.6053, 23.5218, 23.7743, 23.8502, 24.3400, 23.4514, 23.8500, 22.9577, 24.1905, 22.6116, 22.9583, 21.2480, 23.0278, 21.0475, 21.2487, 21.3016, 21.3223, 20.7373, 21.3019, 22.5823, 22.6533, 21.2754, 22.5821, 22.4498, 22.9879, 22.2813, 22.4604, 22.6836, 22.9910, 21.9717, 22.6752, 23.1500, 23.7414, 22.5242, 23.1530, 23.2199, 23.4038, 22.3602, 23.2152, 23.4120, 24.2580, 22.9444, 23.4102, 22.5293, 23.7573, 21.5818, 22.5258, 20.7856, 22.7149, 20.7706, 20.7856, 21.2098, 21.6544, 20.7552, 21.2099, 20.8252, 21.5613, 20.4844, 20.8251, 20.5861, 21.1781, 20.3934, 20.5860, 20.2500, 20.8622, 19.6649, 20.2500, 19.3316, 20.2508, 18.9422, 19.3309, 19.9632, 20.0517, 19.2796, 19.9632, 20.8472, 20.8550, 19.8970, ].reverse(); let smallest = Math.min(...dataArr); dataArr = dataArr.map(item => item - smallest); let largest = Math.max(...dataArr); let arrayLen = dataArr.length; context.beginPath(); context.lineJoin = "round"; context.strokeStyle = color; context.lineWidth = 2; context.shadowColor = color; context.shadowBlur = 10; context.shadowOffsetX = 2; context.shadowOffsetY = 2; // add first point in the graph context.moveTo( GRAPH_LEFT, ( GRAPH_HEIGHT - dataArr[ 0 ] / largest * GRAPH_HEIGHT ) + GRAPH_TOP ); // loop over data and add points for(let i = 1; i < arrayLen; i++ ){ context.lineTo( GRAPH_RIGHT / arrayLen * i + GRAPH_LEFT, ( GRAPH_HEIGHT - dataArr[ i ] / largest * GRAPH_HEIGHT ) + GRAPH_TOP ); } context.stroke(); }
<html mode="ios"> <head> <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> </head> <body class="dark primary"> <ion-app> <ion-content scroll-y="false"> <!-- Texts --> <div class="top-content hide-on-present"> <div class="titles"> <h1>$23,165.82</h1> <h2 class="result">+$49.56 (0.32%)</h2> <h2 class="name">Bitcoin</h2> </div> <!-- Thanks GUI CHALLENGE for button idea --> <button class="theme-toggle" onclick="toggleTheme();" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite"> <svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24"> <mask class="moon" id="moon-mask"> <rect x="0" y="0" width="100%" height="100%" fill="white" /> <circle cx="24" cy="10" r="6" fill="black" /> </mask> <circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" /> <g class="sun-beams" stroke="currentColor"> <line x1="12" y1="1" x2="12" y2="3" /> <line x1="12" y1="21" x2="12" y2="23" /> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /> <line x1="1" y1="12" x2="3" y2="12" /> <line x1="21" y1="12" x2="23" y2="12" /> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /> </g> </svg> </button> </div> <!-- Synthwave --> <section class="synthwave"> <div class="fade-overlay"></div> <div class="synth-wrapper"> <div class="background"></div> <div class="grid"></div> </div> </section> <!-- Bottom texts --> <div class="bottom-content show-on-present"> <div class="titles"> <h1 id="swipeCancel">Swipe up to cancel</h1> <h1 id="sendingOrder" class="sending-order hidden">Sending order...</h1> </div> </div> <!-- Pane --> <cupertino-pane> <ion-list> <ion-item class="amount"> <ion-label>Amount in USD</ion-label> <ion-input id="amount" class="ion-text-right" type="text"></ion-input> </ion-item> <ion-item> <ion-label>Est Price</ion-label> <ion-input class="ion-text-right" id="estPrice" readonly type="text"></ion-input> </ion-item> <ion-item> <ion-label>Est Btc</ion-label> <ion-input class="ion-text-right" id="estBtc" readonly type="text"></ion-input> </ion-item> </ion-list> <div class="description"> <ion-text>Order Summary</ion-text> <small>You are placing an order to buy Bitcoin. Amount purchased may vary due to market volatility. Cryptocurrencies are not securities and are not FOIC or SIPC insured.</small> </div> <ion-button id="submitButton" color="primary" expand="full" onclick="submit();">Submit</ion-button> </cupertino-pane> </ion-content> <!-- Graph --> <div class="graph-container hide-on-present"> <canvas></canvas> <div class="signal"></div> </div> <!-- Footer --> <ion-footer translucent class="hide-on-present"> <ion-toolbar> <div class="buttons"> <ion-button color="primary" onclick="showDialog(true);" expand="full">Buy</ion-button> <ion-button color="secondary" onclick="showDialog(false);" expand="full">Sell</ion-button> </div> </ion-toolbar> </ion-footer> </ion-app> </body> </html>
body.dark { --ion-background-color: #080317; --ion-background-color-rgb: rgb(8 3 23); --ion-toolbar-background: #1f1f1f; --hide-delay: 400ms; } body.primary { --ion-color-primary: #ff69b4; --ion-color-primary-rgb: 255, 107, 181; --ion-color-primary-contrast: #333333; --ion-color-primary-contrast-rgb: 51, 51, 51; --ion-color-primary-shade: #f3499e; --ion-color-primary-tint: #fa71b5; --ion-color-secondary: #add65c; --ion-color-secondary-rgb: 173, 214, 92; --ion-color-secondary-contrast: #333333; --ion-color-secondary-contrast-rgb: 51, 51, 51; --ion-color-secondary-shade: #a4d63e; --ion-color-secondary-tint: #b5da6b; } body.secondary { --ion-color-primary: #add65c; --ion-color-primary-rgb: 173, 214, 92; --ion-color-primary-contrast: #333333; --ion-color-primary-contrast-rgb: 51, 51, 51; --ion-color-primary-shade: #a4d63e; --ion-color-primary-tint: #b5da6b; --ion-color-secondary: #ff69b4; --ion-color-secondary-rgb: 255, 107, 181; --ion-color-secondary-contrast: #333333; --ion-color-secondary-contrast-rgb: 51, 51, 51; --ion-color-secondary-shade: #f3499e; --ion-color-secondary-tint: #fa71b5; } .hide-on-present { opacity: 1; transition: opacity var(--hide-delay) ease; } .show-on-present { opacity: 0; transition: opacity var(--hide-delay) ease; } body.pane-visible .hide-on-present { opacity: 0; } body.pane-visible .show-on-present { opacity: 1; } /* Synthwave styles */ .fade-overlay { position: absolute; bottom: 183px; width: 100vw; height: 100px; z-index: 1; background: linear-gradient(180deg, rgba(8,3,23,1) 0%, rgba(8,3,23,0.8) 37%, rgba(8,3,23,0) 100%); } section.synthwave { position: absolute; bottom: 0px; height: 270px; overflow: visible; z-index: 1; } section.synthwave div.synth-wrapper { height: 100vh; width: 140vw; margin-left: -20vw; perspective: 470px; filter: saturate(0.9); opacity: 0.9; } section.synthwave .background { width: 100%; height: 292px; position: absolute; background: rgba(var(--ion-color-primary-rgb), 0.23); } section.synthwave .grid { background-size: 40px 50px; background-image: linear-gradient(to right, var(--ion-color-primary) 2px, transparent 1px), linear-gradient(to bottom, var(--ion-color-primary) 2px, transparent 1px); height: inherit; transform: rotateX(35deg) scale(0.8); transform-origin: top center; animation: 1.8s linear infinite wave; background-position-y: 0px; } @keyframes wave { to { background-position-y: -50px; } } /* Bottom toolbar */ ion-footer { position: absolute; bottom: 0; } ion-toolbar { --padding-top: 20px; --padding-bottom: 30px; --padding-start: 10px; --padding-end: 10px; } ion-toolbar .buttons { display: flex; max-width: 390px; gap: 10px; margin: auto; } ion-toolbar ion-button { width: 100%; height: 44px; font-weight: 700; margin: auto; } ion-toolbar ion-button::part(native) { border-radius: 30px; } /* Prices texts */ .top-content { position: absolute; top: 20px; width: calc(100% - 40px); max-width: 390px; left: 0; right: 0; margin-left: auto; margin-right: auto; height: 190px; justify-content: space-between; display: flex; flex-direction: column; z-index: 1; } .titles h1 { font-size: 35px; color: #f4f5f6; text-shadow: 1px 1px #1a1b20; margin-bottom: 10px; } .titles h2 { font-size: 17px; font-weight: 500; color: #f4f5f6; margin-top: 0; } .titles h2.result { color: var(--ion-color-secondary); } .titles h2.name { font-size: 20px; } /* Bottom text */ .bottom-content { position: absolute; bottom: calc(100vh * 0.255 - 70px); width: calc(100% - 40px); max-width: 390px; left: 0; right: 0; text-align: center; margin-left: auto; margin-right: auto; justify-content: space-between; flex-direction: column; z-index: 1; } .bottom-content h1 { font-size: 25px; opacity: 1; transition: opacity var(--hide-delay) ease; position: absolute; left: 0; right: 0; } .bottom-content h1.hidden { opacity: 0; } /* Graph */ .graph-container { position: absolute; z-index: 10; width: 100%; max-width: 390px; left: 0; right: 0; margin-left: auto; margin-right: auto; transform: translateZ(1px); bottom: calc(100vh * 0.22); } .graph-container .signal { position: absolute; top: 12%; left: 268px; width: 8px; height: 8px; border-radius: 50%; background: var(--ion-color-secondary); } .graph-container .signal::before { content: ""; width: 8px; height: 8px; position: absolute; border-radius: 50%; z-index: -1; opacity: 1; transform: scale(1); background-color: rgba(var(--ion-color-secondary-rgb), 1); animation: 1.8s linear infinite signal; } @keyframes signal { to { transform: scale(5); opacity: 0; } } /* Cupertino Pane */ .cupertino-pane-wrapper .pane { border-radius: 10px !important; background: var(--ion-background-color); box-shadow: 0px 0px 40px 0px rgba(var(--ion-color-primary-rgb), 0.3); } cupertino-pane { display: none; } cupertino-pane ion-list ion-item { --border-color: rgba(var(--ion-color-primary-rgb), 0.2); --padding-end: 20px; --padding-start: 20px; --inner-border-width: 0 0 2px 0; } cupertino-pane ion-list ion-item ion-label { font-size: 14px !important; font-weight: 600 !important; color: #ffffff !important; margin-bottom: 22px; margin-top: 22px; } cupertino-pane ion-list ion-item ion-input { --color: #ffffff !important; font-size: 14px !important; font-weight: 600 !important; --padding-end: 0 !important; } cupertino-pane ion-list ion-item.amount ion-input { font-size: 20px !important; } cupertino-pane ion-list ion-item.amount ion-label { color: var(--ion-color-primary) !important; font-size: 15px !important; } cupertino-pane ion-button { margin-left: 20px !important; margin-top: 30px !important; margin-right: 20px !important; margin-bottom: 30px !important; height: 50px; font-weight: 700; margin: auto; } cupertino-pane ion-button::part(native) { border-radius: 30px; } cupertino-pane .description { margin-top: 30px; margin-left: 20px; margin-right: 20px; } cupertino-pane .description ion-text { display: block; color: #9e9e9e; font-size: 14px; } cupertino-pane .description small { color: #9e9e9e; display: block; font-size: 12px; margin-top: 8px; line-height: 18px; } /* Theme switcher */ @import"https://unpkg.com/open-props/easings.min.css"; .sun-and-moon>:is(.moon, .sun, .sun-beams) { transform-origin: center center } .sun-and-moon>:is(.moon, .sun) { fill: var(--icon-fill) } .sun-and-moon>.sun-beams { stroke: var(--icon-fill); stroke-width: 2px } body.primary .sun-and-moon>.sun { transform: scale(1.75) } body.primary .sun-and-moon>.sun-beams { opacity: 0 } body.primary .sun-and-moon>.moon>circle { transform: translate(-7px) } @supports (cx: 1) { body.primary .sun-and-moon>.moon>circle { transform: translate(0); cx: 17 } } @media (prefers-reduced-motion: no-preference) { .sun-and-moon>.sun { transition: transform .5s var(--ease-elastic-3) } .sun-and-moon>.sun-beams { transition: transform .5s var(--ease-elastic-4), opacity .5s var(--ease-3) } .sun-and-moon .moon>circle { transition: transform .25s var(--ease-out-5) } @supports (cx: 1) { .sun-and-moon .moon>circle { transition: cx .25s var(--ease-out-5) } } body.primary .sun-and-moon>.sun { transform: scale(1.75); transition-timing-function: var(--ease-3); transition-duration: .25s } body.primary .sun-and-moon>.sun-beams { transform: rotate(-25deg); transition-duration: .15s } body.primary .sun-and-moon>.moon>circle { transition-delay: .25s; transition-duration: .5s } } .theme-toggle { --size: 26px; --icon-fill: rgba(var(--ion-color-secondary-rgb), 0.7); background: none; border: none; padding: 0; inline-size: var(--size); block-size: var(--size); aspect-ratio: 1; border-radius: 50%; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; outline-offset: 5px; position: absolute; right: 0px; top: 30px; } .theme-toggle>svg { inline-size: 100%; block-size: 100%; stroke-linecap: round }