diff --git a/WEBSITE/script.js b/WEBSITE/script.js index bc7b587696..22901745ec 100644 --- a/WEBSITE/script.js +++ b/WEBSITE/script.js @@ -161,7 +161,6 @@
${pages}
${dots}
-
@@ -189,7 +188,6 @@
VideoPhotoPortrait
${gl('flip')}
-
9:41
@@ -209,8 +207,8 @@ folderId: 'media', pull: 0, // px the pages are dragged sideways page: 0, - camera: 0, - flash: 0, + camera: 0, // the camera is the page left of the first one: 0 home, 1 camera fully in + shutter: 0, sheet: 0, align: 0, // 0 left, .5 center, 1 right weight: DEFAULT_WEIGHT, @@ -246,12 +244,12 @@ this.el = q('.phone'); this.screen = q('.screen'); this.r = { - world: q('.world'), home: q('.home'), pages: q('.pages'), dots: qa('.pagedots i'), glow: q('.glow'), + world: q('.world'), home: q('.home'), pages: q('.pages'), dots: qa('.pagedots i'), fscrim: q('.fscrim'), fcard: q('.fcard'), fgrid: q('.fcard__grid'), ftitle: q('.fcard__title'), drawer: q('.drawer'), drawerIn: q('.drawer__in'), list: q('.list'), alpha: q('.alpha'), letters: qa('.alpha__l'), bubble: q('.bubble'), menu: q('.menu'), menuHide: q('.mi--hide'), snack: q('.snack'), snackT: q('.snack__t'), sheet: q('.sheet'), segAlign: qa('[data-seg="align"] span'), segWeight: qa('[data-seg="weight"] span'), - cam: q('.cam'), flash: q('.cam__flash'), finger: q('.finger'), ring: q('.finger__ring'), + cam: q('.cam'), shutter: q('.cam__shutter'), finger: q('.finger'), ring: q('.finger__ring'), clockLines: qa('.clk__line > span'), clockTime: q('.clk__time'), time: q('[data-time]'), date: q('[data-date]'), stime: q('.sbar__time'), pics: qa('.pic'), rows: new Map(qa('.row').map((el) => [el.dataset.row, el])), @@ -371,13 +369,16 @@ const d = s.drawer; const f = s.folder; - // Wallpaper and home share one layer, so a folder blurs both. - css(r.world, 'transform', f > 0.001 ? `scale(${(1 - 0.035 * f).toFixed(4)})` : ''); + // Wallpaper and home share one layer, so a folder blurs both, and the camera, the page to + // the left of the first one, pushes both to the right as it slides in. + const cam = s.camera; + const slide = cam > 0.0005 ? `translateX(${(cam * W).toFixed(1)}px)` : ''; + const shrink = f > 0.001 ? `scale(${(1 - 0.035 * f).toFixed(4)})` : ''; + css(r.world, 'transform', `${slide} ${shrink}`.trim()); css(r.world, 'filter', f > 0.001 ? `blur(${(f * 16).toFixed(2)}px)` : ''); css(r.home, 'opacity', (1 - d).toFixed(3)); css(r.home, 'transform', d > 0.001 ? `translateY(${(-26 * d).toFixed(1)}px) scale(${(1 - 0.05 * d).toFixed(4)})` : ''); css(r.pages, 'transform', `translateX(${(-s.page * W + s.pull).toFixed(1)}px)`); - css(r.glow, 'opacity', clamp(s.pull / 70).toFixed(3)); const page = Math.round(clamp(s.page - s.pull / W, 0, HOME.length - 1)); r.dots.forEach((dot, i) => dot.classList.toggle('on', i === page)); @@ -435,10 +436,10 @@ css(r.snack, 'transform', `translateY(${((1 - s.snack) * 26).toFixed(1)}px)`); if (r.snackT.textContent !== s.snackText) r.snackT.textContent = s.snackText; - // Camera, revealed from the left edge. - css(r.cam, 'visibility', s.camera > 0.001 ? 'visible' : 'hidden'); - css(r.cam, 'clipPath', `inset(0 ${((1 - s.camera) * 100).toFixed(2)}% 0 0)`); - css(r.flash, 'opacity', s.flash.toFixed(3)); + // Camera, sliding in from the left with the home screen. + css(r.cam, 'visibility', cam > 0.0005 ? 'visible' : 'hidden'); + css(r.cam, 'transform', `translateX(${((cam - 1) * W).toFixed(1)}px)`); + css(r.shutter, 'transform', s.shutter > 0.5 ? 'scale(.86)' : ''); // Clock options sheet and the clock itself. css(r.sheet, 'transform', `translateY(${((1 - s.sheet) * 105).toFixed(2)}%)`); @@ -529,16 +530,17 @@ s.folder = E(t, 0.33, 0.62); break; } - case 4: { // close the folder, pull past the first page, camera + case 4: { // close the folder, swipe right: the camera slides in as the page to the left s.folder = 1 - E(t, 0, 0.15); const drag = E(t, 0.26, 0.55); - F.x = lerp(70, 300, drag); + const reach = 230 / W; // how far the finger drags the screen before letting go + F.x = lerp(70, 70 + 230, drag); F.y = 420; F.o = seg(t, 0.18, 0.25) * (1 - seg(t, 0.55, 0.62)); F.press = t > 0.25 && t < 0.56 ? 1 : 0; - s.pull = rubber(drag * 230) * (1 - E(t, 0.56, 0.7)); - s.camera = E(t, 0.56, 0.82); - s.flash = seg(t, 0.9, 0.92) * (1 - seg(t, 0.93, 0.99)); + // The screen follows the finger, then finishes the slide on its own once it lets go. + s.camera = reach * drag + (1 - reach) * E(t, 0.55, 0.74); + s.shutter = t > 0.86 && t < 0.92 ? 1 : 0; break; } case 5: { // close the camera, hold the clock, restyle it @@ -738,10 +740,10 @@ } const closeFolder = () => to({ folder: 0 }, 300); function launchCamera() { - say('Quick launch. Swipe past the first page and your camera is right there.'); - return to({ pull: 0, page: 0, camera: 1 }, 480, outCubic); + say('Quick launch. Swipe past the first page and your camera is right there. Swipe left to go back.'); + return to({ pull: 0, page: 0, camera: 1 }, 420, outCubic); } - const closeCamera = () => to({ camera: 0 }, 320).then(() => say('Back home. Try the playground buttons, or just play.')); + const closeCamera = () => to({ camera: 0 }, 400).then(() => say('Back home. Try the playground buttons, or just play.')); function openMenu(name) { s.held = name; const ry = ph.rowY(name, s.list); @@ -857,7 +859,7 @@ fingerAt(70, 420); await to({ 'finger.o': 1 }, 160); set({ 'finger.press': 1 }); - await to({ 'finger.x': 300, pull: rubber(230) }, 560); + await to({ 'finger.x': 300, camera: 230 / W }, 560); set({ 'finger.press': 0 }); fingerOff(); if (!alive()) return; @@ -984,11 +986,21 @@ if (!gest.dir && gest.moved) gest.dir = Math.abs(dy) > Math.abs(dx) ? 'v' : 'h'; if (gest.dir === 'v') s.drawer = clamp(-dy / 280); if (gest.dir === 'h') { - const atStart = s.page < 0.5 && dx > 0; - const atEnd = s.page > HOME.length - 1.5 && dx < 0; - s.pull = atStart || atEnd ? rubber(dx) : dx; + if (s.page < 0.5 && dx > 0) { + // Past the first page: the camera follows the finger in from the left. + s.camera = clamp(dx / W); + s.pull = 0; + } else { + const atEnd = s.page > HOME.length - 1.5 && dx < 0; + s.pull = atEnd ? rubber(dx) : dx; + s.camera = 0; + } } break; + case 'camera': + if (!gest.dir && gest.moved) gest.dir = Math.abs(dy) > Math.abs(dx) ? 'v' : 'h'; + if (gest.dir === 'h') s.camera = clamp(1 + Math.min(0, dx) / W); + break; default: break; } @@ -1021,7 +1033,14 @@ if (dy < -36) closeAll().then(() => say('Home. Swipe up from the bottom edge works from anywhere.')); break; case 'camera': - if ((tap && g.target.closest('.cam__close')) || dy < -60) closeCamera(); + if (g.dir === 'h') { + if (s.camera < 0.8) closeCamera(); + else to({ camera: 1 }, 220, outCubic); + } else if ((tap && g.target.closest('.cam__close')) || dy < -60) closeCamera(); + else if (tap) { + set({ shutter: 1 }); + setTimeout(() => set({ shutter: 0 }), 140); + } break; case 'menu': if (tap && g.target.closest('.mi--hide')) hideApp(s.held); @@ -1070,8 +1089,10 @@ if (s.drawer > 0.3) openDrawer(); else to({ drawer: 0 }, 220); } else if (g.dir === 'h') { - if (s.page < 0.5 && dx > 70) launchCamera(); - else if (s.page < 0.5 && dx < -70) to({ page: 1, pull: 0 }, 380).then(() => say('Page two. Swipe back, or past the last page.')); + if (s.page < 0.5 && dx > 0) { + if (s.camera > 0.2) launchCamera(); + else to({ camera: 0 }, 240, outCubic); + } else if (s.page < 0.5 && dx < -70) to({ page: 1, pull: 0 }, 380).then(() => say('Page two. Swipe back, or past the last page.')); else if (s.page > 0.5 && dx > 70) to({ page: 0, pull: 0 }, 380).then(() => say(HELLO)); else to({ pull: 0 }, 260, outCubic); } else if (tap) { diff --git a/WEBSITE/styles.css b/WEBSITE/styles.css index 6cf527731c..e342363c3a 100644 --- a/WEBSITE/styles.css +++ b/WEBSITE/styles.css @@ -609,7 +609,6 @@ section { position: relative; } .pagedots { position: absolute; left: 0; right: 0; top: 748px; display: flex; justify-content: center; gap: 8px; } .pagedots i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, .45); transition: background .2s, width .2s; } .pagedots i.on { background: #fff; width: 18px; border-radius: 4px; } -.glow { position: absolute; top: 0; bottom: 0; left: -80px; width: 160px; background: radial-gradient(closest-side, rgba(118, 250, 252, .85), rgba(29, 219, 253, .25) 55%, transparent); opacity: 0; pointer-events: none; z-index: 5; } .fscrim { position: absolute; inset: 0; background: #000; opacity: 0; pointer-events: none; z-index: 10; } .fcard { @@ -662,7 +661,7 @@ section { position: relative; } .seg span:first-child { border-left: 0; } .seg span.on { background: #ddd5ff; font-weight: 700; } -.cam { position: absolute; inset: 0; background: #000; z-index: 25; visibility: hidden; clip-path: inset(0 100% 0 0); } +.cam { position: absolute; inset: 0; background: #000; z-index: 25; visibility: hidden; transform: translateX(-100%); } .cam__view { position: absolute; left: 0; right: 0; top: 52px; height: 560px; overflow: hidden; background: @@ -674,12 +673,11 @@ section { position: relative; } .cam__hill--2 { height: 30%; background: #120a36; clip-path: polygon(0 30%, 25% 12%, 48% 40%, 70% 20%, 100% 44%, 100% 100%, 0 100%); } .cam__grid { position: absolute; inset: 0; background: linear-gradient(to right, transparent 33%, rgba(255, 255, 255, .35) 33%, rgba(255, 255, 255, .35) calc(33% + 1px), transparent calc(33% + 1px), transparent 66%, rgba(255, 255, 255, .35) 66%, rgba(255, 255, 255, .35) calc(66% + 1px), transparent calc(66% + 1px)), linear-gradient(to bottom, transparent 33%, rgba(255, 255, 255, .35) 33%, rgba(255, 255, 255, .35) calc(33% + 1px), transparent calc(33% + 1px), transparent 66%, rgba(255, 255, 255, .35) 66%, rgba(255, 255, 255, .35) calc(66% + 1px), transparent calc(66% + 1px)); } .cam__focus { position: absolute; left: 50%; top: 50%; width: 76px; height: 76px; margin: -38px; border: 2px solid #ffe066; border-radius: 6px; } -.cam__flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; } .cam__modes { position: absolute; left: 0; right: 0; top: 632px; display: flex; justify-content: center; gap: 26px; font: 600 13px/1 var(--ui); letter-spacing: .08em; text-transform: uppercase; color: #9d9ab8; } .cam__modes .on { color: var(--ice); } .cam__bar { position: absolute; left: 0; right: 0; top: 684px; display: flex; align-items: center; justify-content: space-between; padding: 0 44px; } .cam__thumb { width: 50px; height: 50px; border-radius: 12px; background: linear-gradient(145deg, #ff9b6a, #4b2ac9); border: 2px solid #fff; } -.cam__shutter { width: 78px; height: 78px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 5px #000, 0 0 0 9px #fff; } +.cam__shutter { width: 78px; height: 78px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 5px #000, 0 0 0 9px #fff; transition: transform .12s ease; } .cam__flip { display: grid; place-items: center; width: 50px; height: 50px; border-radius: 50%; background: #26243a; } .cam__flip .gl { width: 24px; height: 24px; color: #fff; } .cam__close { position: absolute; top: 58px; right: 16px; width: 40px; height: 40px; display: grid; place-items: center; border: 0; border-radius: 50%; background: rgba(0, 0, 0, .45); color: #fff; font: 400 26px/1 var(--ui); cursor: pointer; }