From 595a890704020c967628a2e08438132e48a9d65d Mon Sep 17 00:00:00 2001 From: travis Date: Thu, 2 Apr 2026 16:12:40 -0400 Subject: [PATCH] Update --- css/style.css | 23 +++-- index.html | 3 +- js/main.js | 228 +++++++++++++++++++++++++++++++------------------- 3 files changed, 160 insertions(+), 94 deletions(-) diff --git a/css/style.css b/css/style.css index e76cf26..d65b758 100755 --- a/css/style.css +++ b/css/style.css @@ -4,7 +4,9 @@ html, body { height: 100%; + height: 100dvh; min-height: 100%; + min-height: 100dvh; overflow: hidden; margin: 0; padding: 0; @@ -12,7 +14,6 @@ html, body { body { background-color: #0e0e0e; - margin: 0; display: flex; justify-content: center; align-items: center; @@ -52,6 +53,7 @@ body { margin: 0; width: 100%; height: 100%; + min-height: 100%; display: block; position: relative; background: #111; @@ -123,17 +125,17 @@ h1 { a { color: #ddd; transition: color 100ms ease; - height: 20px; + display: flex; + align-items: center; + justify-content: center; + min-width: 44px; + min-height: 44px; } a:hover { color: #eee; } -p { - margin: 0; -} - svg { width: 20px; height: 20px; @@ -152,7 +154,14 @@ svg { @media screen and (max-width: 430px) { .card { - height: 450px; + width: 85%; + height: auto; + aspect-ratio: 3 / 4; + max-height: 70dvh; + } + + .links span { + font-size: 0.85rem; } } diff --git a/index.html b/index.html index 87bca54..42c78b8 100755 --- a/index.html +++ b/index.html @@ -2,7 +2,6 @@ - Travis Heinström @@ -51,7 +50,7 @@ diff --git a/js/main.js b/js/main.js index f292116..af229bb 100644 --- a/js/main.js +++ b/js/main.js @@ -1,7 +1,6 @@ (function (window) { "use strict"; - // Helper vars and functions. function extend(a, b) { for (var key in b) { if (b.hasOwnProperty(key)) { @@ -11,26 +10,6 @@ return a; } - function getMousePos(e) { - var posx = 0, - posy = 0; - if (!e) var e = window.event; - if (e.pageX || e.pageY) { - posx = e.pageX; - posy = e.pageY; - } else if (e.clientX || e.clientY) { - posx = - e.clientX + - document.body.scrollLeft + - document.documentElement.scrollLeft; - posy = - e.clientY + - document.body.scrollTop + - document.documentElement.scrollTop; - } - return { x: posx, y: posy }; - } - /** * TiltFx obj. */ @@ -73,7 +52,9 @@ this.DOM.animatable.shine = this.DOM.el.querySelector( ".card__deco--shine > div" ); + this._gyroActive = false; this._initEvents(); + this._initGyro(); }; /** @@ -89,75 +70,67 @@ }; this.mousemoveFn = function (ev) { + if (self._gyroActive) return; requestAnimationFrame(function () { - self._layout(ev); + var bounds = self.DOM.el.getBoundingClientRect(); + self._applyTilt( + ev.clientX - bounds.left, + ev.clientY - bounds.top, + bounds.width, + bounds.height + ); }); }; - this.mouseleaveFn = function (ev) { + this.mouseleaveFn = function () { requestAnimationFrame(function () { - for (var key in self.DOM.animatable) { - if (self.options.movement[key] == undefined) { - continue; - } - anime({ - targets: self.DOM.animatable[key], - duration: - self.options.movement[key].reverseAnimation != undefined - ? self.options.movement[key].reverseAnimation.duration || 0 - : 1, - easing: - self.options.movement[key].reverseAnimation != undefined - ? self.options.movement[key].reverseAnimation.easing || "linear" - : "linear", - elasticity: - self.options.movement[key].reverseAnimation != undefined - ? self.options.movement[key].reverseAnimation.elasticity || null - : null, - scaleX: 1, - scaleY: 1, - scaleZ: 1, - translateX: 0, - translateY: 0, - translateZ: 0, - rotateX: 0, - rotateY: 0, - rotateZ: 0, - }); - } + self._animateReset(); }); }; this.debounceLeaveFn = function (e) { e.stopPropagation(); clearTimeout(self.timeout); - self.timeout = setTimeout(() => { - self.mouseleaveFn(); + self.timeout = setTimeout(function () { + self._animateReset(); }, 25); }; + // Touch handlers + this.touchstartFn = function () { + if (self._gyroActive) return; + for (var key in self.DOM.animatable) { + anime.remove(self.DOM.animatable[key]); + } + }; + + this.touchmoveFn = function (ev) { + if (self._gyroActive) return; + var touch = ev.touches[0]; + if (!touch) return; + requestAnimationFrame(function () { + var bounds = self.DOM.el.getBoundingClientRect(); + self._applyTilt( + touch.clientX - bounds.left, + touch.clientY - bounds.top, + bounds.width, + bounds.height + ); + }); + }; + this.DOM.el.addEventListener("mousemove", this.mousemoveFn); this.DOM.el.addEventListener("mouseleave", this.mouseleaveFn); this.DOM.el.addEventListener("mouseenter", this.mouseenterFn); + this.DOM.el.addEventListener("touchstart", this.touchstartFn, { passive: true }); + this.DOM.el.addEventListener("touchmove", this.touchmoveFn, { passive: true }); this.DOM.el.addEventListener("touchend", this.debounceLeaveFn); }; - TiltFx.prototype._layout = function (ev) { - // Mouse position relative to the document. - var mousepos = getMousePos(ev), - // Document scrolls. - docScrolls = { - left: document.body.scrollLeft + document.documentElement.scrollLeft, - top: document.body.scrollTop + document.documentElement.scrollTop, - }, - bounds = this.DOM.el.getBoundingClientRect(), - // Mouse position relative to the main element (this.DOM.el). - relmousepos = { - x: mousepos.x - bounds.left - docScrolls.left, - y: mousepos.y - bounds.top - docScrolls.top, - }; - - // Movement settings for the animatable elements. + /** + * Apply tilt from a relative position within the card. + */ + TiltFx.prototype._applyTilt = function (relX, relY, width, height) { for (var key in this.DOM.animatable) { if ( this.DOM.animatable[key] == undefined || @@ -166,13 +139,9 @@ continue; } var t = - this.options.movement[key] != undefined - ? this.options.movement[key].translation || { x: 0, y: 0, z: 0 } - : { x: 0, y: 0, z: 0 }, + this.options.movement[key].translation || { x: 0, y: 0, z: 0 }, r = - this.options.movement[key] != undefined - ? this.options.movement[key].rotation || { x: 0, y: 0, z: 0 } - : { x: 0, y: 0, z: 0 }, + this.options.movement[key].rotation || { x: 0, y: 0, z: 0 }, setRange = function (obj) { for (var k in obj) { if (obj[k] == undefined) { @@ -188,20 +157,18 @@ var transforms = { translation: { - x: ((t.x[1] - t.x[0]) / bounds.width) * relmousepos.x + t.x[0], - y: ((t.y[1] - t.y[0]) / bounds.height) * relmousepos.y + t.y[0], - z: ((t.z[1] - t.z[0]) / bounds.height) * relmousepos.y + t.z[0], + x: ((t.x[1] - t.x[0]) / width) * relX + t.x[0], + y: ((t.y[1] - t.y[0]) / height) * relY + t.y[0], + z: ((t.z[1] - t.z[0]) / height) * relY + t.z[0], }, rotation: { - x: ((r.x[1] - r.x[0]) / bounds.height) * relmousepos.y + r.x[0], - y: ((r.y[1] - r.y[0]) / bounds.width) * relmousepos.x + r.y[0], - z: ((r.z[1] - r.z[0]) / bounds.width) * relmousepos.x + r.z[0], + x: ((r.x[1] - r.x[0]) / height) * relY + r.x[0], + y: ((r.y[1] - r.y[0]) / width) * relX + r.y[0], + z: ((r.z[1] - r.z[0]) / width) * relX + r.z[0], }, }; - this.DOM.animatable[key].style.WebkitTransform = this.DOM.animatable[ - key - ].style.transform = + this.DOM.animatable[key].style.transform = "translateX(" + transforms.translation.x + "px) translateY(" + @@ -218,5 +185,96 @@ } }; + /** + * Animate back to resting position. + */ + TiltFx.prototype._animateReset = function () { + for (var key in this.DOM.animatable) { + if (this.options.movement[key] == undefined) { + continue; + } + anime({ + targets: this.DOM.animatable[key], + duration: + this.options.movement[key].reverseAnimation != undefined + ? this.options.movement[key].reverseAnimation.duration || 0 + : 1, + easing: + this.options.movement[key].reverseAnimation != undefined + ? this.options.movement[key].reverseAnimation.easing || "linear" + : "linear", + elasticity: + this.options.movement[key].reverseAnimation != undefined + ? this.options.movement[key].reverseAnimation.elasticity || null + : null, + scaleX: 1, + scaleY: 1, + scaleZ: 1, + translateX: 0, + translateY: 0, + translateZ: 0, + rotateX: 0, + rotateY: 0, + rotateZ: 0, + }); + } + }; + + /** + * Init gyroscope / device orientation tilt. + * On iOS 13+ this requires a user gesture to request permission. + */ + TiltFx.prototype._initGyro = function () { + var self = this; + + if (!window.DeviceOrientationEvent) return; + + // iOS 13+ requires explicit permission request — skip those devices + if (typeof DeviceOrientationEvent.requestPermission === "function") return; + + // On Android and other devices, test if events actually fire + var testHandler = function (e) { + if (e.gamma !== null && e.beta !== null) { + self._bindGyro(); + } + window.removeEventListener("deviceorientation", testHandler); + }; + window.addEventListener("deviceorientation", testHandler); + }; + + TiltFx.prototype._bindGyro = function () { + var self = this; + this._gyroActive = true; + + // Stop any in-progress touch/mouse animations + for (var key in this.DOM.animatable) { + anime.remove(this.DOM.animatable[key]); + } + + window.addEventListener("deviceorientation", function (e) { + requestAnimationFrame(function () { + // beta: front-back tilt (-180 to 180), gamma: left-right tilt (-90 to 90) + var beta = e.beta || 0; + var gamma = e.gamma || 0; + + // Clamp to reasonable range + beta = Math.max(-30, Math.min(30, beta)); + gamma = Math.max(-30, Math.min(30, gamma)); + + // Normalize to 0-1 range (centered at 0 degrees = 0.5) + var normX = (gamma + 30) / 60; + var normY = (beta + 30) / 60; + + var bounds = self.DOM.el.getBoundingClientRect(); + self._applyTilt( + normX * bounds.width, + normY * bounds.height, + bounds.width, + bounds.height + ); + }); + }); + }; + window.TiltFx = TiltFx; })(window);