Website: quick launch slides the camera in; no white flash
The camera now behaves as the page to the left of the first one: a swipe right pushes the home screen (wallpaper included) to the right while the camera follows the finger in from the left edge, and finishes the slide when the finger lets go. Before, the home screen rubber-banded and the camera was revealed by a clip from the edge. Same in the scroll story, the playground gesture and its demo button; in the playground a short swipe springs back, and a swipe left slides the camera out again. The white flash at the end of the camera chapter was a shutter flash over the whole screen; it read as a glitch while scrolling into the next chapter. The shutter button now presses in instead. Checked in Chrome: the camera chapters stepped through in 0.02 steps show no brightness spike; the playground swipe in, swipe back, short swipe and demo button; no console errors or sideways scrolling at 1440 x 900 and 390 x 844. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d273543070
commit
89fa6ef6ca
2 files changed
+51
-32
No files matched your search
+49
-28
@@ -161,7 +161,6 @@
|
||||
<div class="world"><div class="wall"></div>
|
||||
<div class="home"><div class="pages">${pages}</div><div class="pagedots">${dots}</div></div>
|
||||
</div>
|
||||
<div class="glow"></div>
|
||||
<div class="fscrim"></div>
|
||||
<div class="fcard"><div class="fcard__grid"></div><div class="fcard__title"></div></div>
|
||||
<div class="drawer"><div class="drawer__in">
|
||||
@@ -189,7 +188,6 @@
|
||||
<div class="cam__modes"><span>Video</span><span class="on">Photo</span><span>Portrait</span></div>
|
||||
<div class="cam__bar"><i class="cam__thumb"></i><i class="cam__shutter"></i><i class="cam__flip">${gl('flip')}</i></div>
|
||||
<button type="button" class="cam__close" tabindex="-1" aria-label="Close camera">×</button>
|
||||
<i class="cam__flash"></i>
|
||||
</div>
|
||||
<div class="sbar"><span class="sbar__time">9:41</span><span class="sbar__icons"><span class="sbar__sig"><i></i><i></i><i></i><i></i></span><span class="sbar__bat"></span></span></div>
|
||||
<i class="notch"></i>
|
||||
@@ -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) {
|
||||
|
||||
+2
-4
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user