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 @@
@@ -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; }