diff --git a/_static_server.js b/_static_server.js
new file mode 100644
index 0000000..2a924cb
--- /dev/null
+++ b/_static_server.js
@@ -0,0 +1,61 @@
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const root = __dirname;
+const port = process.argv[2] ? parseInt(process.argv[2], 10) : 8181;
+
+const mime = {
+ '.html': 'text/html; charset=utf-8',
+ '.js': 'text/javascript; charset=utf-8',
+ '.css': 'text/css; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.gif': 'image/gif',
+ '.svg': 'image/svg+xml',
+ '.webp': 'image/webp',
+ '.mp4': 'video/mp4',
+ '.woff': 'font/woff',
+ '.woff2': 'font/woff2',
+ '.ttf': 'font/ttf',
+ '.ico': 'image/x-icon',
+};
+
+const server = http.createServer((req, res) => {
+ let reqPath = decodeURIComponent(req.url.split('?')[0]);
+ if (reqPath === '/') reqPath = '/index.html';
+ let filePath = path.join(root, reqPath);
+
+ if (!filePath.startsWith(root)) {
+ res.writeHead(403);
+ res.end('Forbidden');
+ return;
+ }
+
+ fs.stat(filePath, (err, stats) => {
+ if (err) {
+ res.writeHead(404);
+ res.end('Not found: ' + reqPath);
+ return;
+ }
+ if (stats.isDirectory()) {
+ filePath = path.join(filePath, 'index.html');
+ }
+ fs.readFile(filePath, (err2, data) => {
+ if (err2) {
+ res.writeHead(404);
+ res.end('Not found: ' + reqPath);
+ return;
+ }
+ const ext = path.extname(filePath).toLowerCase();
+ res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream' });
+ res.end(data);
+ });
+ });
+});
+
+server.listen(port, () => {
+ console.log(`Static server running at http://localhost:${port}/`);
+});
diff --git a/assets/black-loafer.png b/assets/black-loafer.png
new file mode 100644
index 0000000..8e18125
Binary files /dev/null and b/assets/black-loafer.png differ
diff --git a/assets/car.svg b/assets/car.svg
new file mode 100644
index 0000000..82c091b
--- /dev/null
+++ b/assets/car.svg
@@ -0,0 +1,39 @@
+
diff --git a/assets/connected-systems/Shoe_trans.png b/assets/connected-systems/Shoe_trans.png
new file mode 100644
index 0000000..e88b395
Binary files /dev/null and b/assets/connected-systems/Shoe_trans.png differ
diff --git a/assets/connected-systems/banner.png b/assets/connected-systems/banner.png
new file mode 100644
index 0000000..9b793bb
Binary files /dev/null and b/assets/connected-systems/banner.png differ
diff --git a/assets/connected-systems/connected-branch-new.svg b/assets/connected-systems/connected-branch-new.svg
new file mode 100644
index 0000000..269ecf5
--- /dev/null
+++ b/assets/connected-systems/connected-branch-new.svg
@@ -0,0 +1,239 @@
+
diff --git a/assets/connected-systems/connected-branch.svg b/assets/connected-systems/connected-branch.svg
new file mode 100644
index 0000000..30c08c2
--- /dev/null
+++ b/assets/connected-systems/connected-branch.svg
@@ -0,0 +1,172 @@
+
diff --git a/assets/connected-systems/icon-donation.svg b/assets/connected-systems/icon-donation.svg
new file mode 100644
index 0000000..496a24d
--- /dev/null
+++ b/assets/connected-systems/icon-donation.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/connected-systems/icon-dot.svg b/assets/connected-systems/icon-dot.svg
new file mode 100644
index 0000000..3723c52
--- /dev/null
+++ b/assets/connected-systems/icon-dot.svg
@@ -0,0 +1,32 @@
+
diff --git a/assets/connected-systems/icon-marketplace.svg b/assets/connected-systems/icon-marketplace.svg
new file mode 100644
index 0000000..80a37e1
--- /dev/null
+++ b/assets/connected-systems/icon-marketplace.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/connected-systems/icon-recondition.svg b/assets/connected-systems/icon-recondition.svg
new file mode 100644
index 0000000..7b87291
--- /dev/null
+++ b/assets/connected-systems/icon-recondition.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/connected-systems/icon-recycling.svg b/assets/connected-systems/icon-recycling.svg
new file mode 100644
index 0000000..2622fb5
--- /dev/null
+++ b/assets/connected-systems/icon-recycling.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/connected-systems/icon-resale.svg b/assets/connected-systems/icon-resale.svg
new file mode 100644
index 0000000..19f2389
--- /dev/null
+++ b/assets/connected-systems/icon-resale.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/connected-systems/label-icon-condition.svg b/assets/connected-systems/label-icon-condition.svg
new file mode 100644
index 0000000..9488d8f
--- /dev/null
+++ b/assets/connected-systems/label-icon-condition.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/connected-systems/label-icon-market.svg b/assets/connected-systems/label-icon-market.svg
new file mode 100644
index 0000000..b738c73
--- /dev/null
+++ b/assets/connected-systems/label-icon-market.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/connected-systems/label-icon-resale.svg b/assets/connected-systems/label-icon-resale.svg
new file mode 100644
index 0000000..d7dcaf3
--- /dev/null
+++ b/assets/connected-systems/label-icon-resale.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/connected-systems/label-icon-value.svg b/assets/connected-systems/label-icon-value.svg
new file mode 100644
index 0000000..d43af05
--- /dev/null
+++ b/assets/connected-systems/label-icon-value.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/connected-systems/shoe-clean.png b/assets/connected-systems/shoe-clean.png
new file mode 100644
index 0000000..b804094
Binary files /dev/null and b/assets/connected-systems/shoe-clean.png differ
diff --git a/assets/connected-systems/shoe-new.png b/assets/connected-systems/shoe-new.png
new file mode 100644
index 0000000..df39dba
Binary files /dev/null and b/assets/connected-systems/shoe-new.png differ
diff --git a/assets/connected-systems/skiply-logo.png b/assets/connected-systems/skiply-logo.png
new file mode 100644
index 0000000..1151245
Binary files /dev/null and b/assets/connected-systems/skiply-logo.png differ
diff --git a/assets/red-loafer.png b/assets/red-loafer.png
new file mode 100644
index 0000000..08445be
Binary files /dev/null and b/assets/red-loafer.png differ
diff --git a/assets/road-path-1.svg b/assets/road-path-1.svg
new file mode 100644
index 0000000..1112b93
--- /dev/null
+++ b/assets/road-path-1.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/road-path-2.svg b/assets/road-path-2.svg
new file mode 100644
index 0000000..498cc82
--- /dev/null
+++ b/assets/road-path-2.svg
@@ -0,0 +1,33 @@
+
diff --git a/assets/road-path-3.svg b/assets/road-path-3.svg
new file mode 100644
index 0000000..b28bd41
--- /dev/null
+++ b/assets/road-path-3.svg
@@ -0,0 +1,20 @@
+
diff --git a/main/index.html b/main/index.html
new file mode 100644
index 0000000..4e6bebe
--- /dev/null
+++ b/main/index.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ Shoe Scanner – Product Showcase
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Red Statement Loafer
+
+
+
- MSRP
+ - $119
+
+
+
- SIZE
+ - USED
+
+
+
- CONDITION
+ - 13
+
+
+
- COLOR
+ - Red
+
+
+
- VALUE
+ - $34
+
+
+
- DEMAND
+ - Seasonal/niche
+
+
+
- CHANNEL
+ - Donation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
Top image of both shoes
+
+
Scanning process
+
+
1 of 2 Captured
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
Top image of both shoes
+
+
Scanning process
+
+
1 of 2 Captured
+
+
+
+
+
+
+
+
+
Black Classic Loafer
+
+
+
- MSRP
+ - $119
+
+
+
- SIZE
+ - USED
+
+
+
- CONDITION
+ - 10
+
+
+
- COLOR
+ - Black
+
+
+
- VALUE
+ - $91
+
+
+
- DEMAND
+ - Year-round
+
+
+
- CHANNEL
+ - Premium Resale
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/shoe-animation-gsap.js b/main/shoe-animation-gsap.js
new file mode 100644
index 0000000..cdc4eb8
--- /dev/null
+++ b/main/shoe-animation-gsap.js
@@ -0,0 +1,83 @@
+/* ================================================
+ Shoe Showcase – Interactive GSAP Animation
+ ================================================
+
+ - Uses GSAP for smooth easing and interpolation
+ ================================================ */
+
+document.addEventListener('DOMContentLoaded', () => {
+ const showcase = document.getElementById('showcase');
+ const bgRed = document.getElementById('bg-red');
+ const bgBlack = document.getElementById('bg-black');
+ const phoneRed = document.getElementById('phone-red');
+ const phoneBlack = document.getElementById('phone-black');
+ const contentLeft = document.getElementById('content-left');
+ const contentRight = document.getElementById('content-right');
+
+ function clamp(v, lo, hi) {
+ return Math.min(hi, Math.max(lo, v));
+ }
+
+ function lerp(a, b, t) {
+ return a + (b - a) * t;
+ }
+
+ function animateToRatio(ratio) {
+ // ratio is 0 (far left) to 1 (far right). Center is 0.5.
+
+ // 1. Calculate flex values for background panels
+ const redFlex = lerp(3.5, 0.3, ratio);
+ const blackFlex = lerp(0.3, 3.5, ratio);
+
+ // 2. Calculate bias for phones (+1 when red dominates, -1 when black dominates)
+ const bias = (0.5 - ratio) * 2;
+
+ const redScale = 1 + clamp(bias, 0, 1) * 0.3;
+ const redOpacity = clamp(1 + bias * 3, 0, 1);
+ const redShiftX = clamp(bias, 0, 1) * 60;
+
+ const blackScale = 1 + clamp(-bias, 0, 1) * 0.3;
+ const blackOpacity = clamp(1 - bias * 3, 0, 1);
+ const blackShiftX = clamp(-bias, 0, 1) * -60;
+
+ // 3. Animate using GSAP
+ // Using overwrite: "auto" ensures that new mouse movements smoothly override old animations
+ const animConfig = { duration: 0.5, ease: "power2.out", overwrite: "auto" };
+
+ gsap.to(bgRed, { flexGrow: redFlex, ...animConfig });
+ gsap.to(bgBlack, { flexGrow: blackFlex, ...animConfig });
+
+ gsap.to(phoneRed, {
+ scale: redScale,
+ x: redShiftX,
+ opacity: redOpacity,
+ ...animConfig
+ });
+
+ gsap.to(phoneBlack, {
+ scale: blackScale,
+ x: blackShiftX,
+ opacity: blackOpacity,
+ ...animConfig
+ });
+
+ gsap.to(contentLeft, { opacity: redOpacity, ...animConfig });
+ gsap.to(contentRight, { opacity: blackOpacity, ...animConfig });
+ }
+
+ // Set initial center state
+ gsap.set(bgRed, { flexGrow: 1 });
+ gsap.set(bgBlack, { flexGrow: 1 });
+
+ /* ---- Mouse tracking ---- */
+ showcase.addEventListener('mousemove', (e) => {
+ const rect = showcase.getBoundingClientRect();
+ const ratio = clamp((e.clientX - rect.left) / rect.width, 0, 1);
+ animateToRatio(ratio);
+ });
+
+ // Spring back to center on leave
+ showcase.addEventListener('mouseleave', () => {
+ animateToRatio(0.5);
+ });
+});
diff --git a/main/shoe-animation-vanilla.js b/main/shoe-animation-vanilla.js
new file mode 100644
index 0000000..32550cd
--- /dev/null
+++ b/main/shoe-animation-vanilla.js
@@ -0,0 +1,74 @@
+/* ================================================
+ Shoe Showcase – Interactive Mouse-Driven Animation
+ ================================================
+ - Background panels expand/contract based on mouse X
+ - Phone mockups fade in/out (only phones, text stays)
+ - On mouse leave → smoothly resets to 50/50
+ ================================================ */
+
+document.addEventListener('DOMContentLoaded', () => {
+ const showcase = document.getElementById('showcase');
+ const bgRed = document.getElementById('bg-red');
+ const bgBlack = document.getElementById('bg-black');
+ const phoneRed = document.getElementById('phone-red');
+ const phoneBlack = document.getElementById('phone-black');
+
+ let targetRatio = 0.5;
+ let currentRatio = 0.5;
+ const LERP_SPEED = 0.1;
+
+ function lerp(a, b, t) {
+ return a + (b - a) * t;
+ }
+
+ function clamp(v, lo, hi) {
+ return Math.min(hi, Math.max(lo, v));
+ }
+
+ /* ---- mouse tracking ---- */
+ showcase.addEventListener('mousemove', (e) => {
+ const rect = showcase.getBoundingClientRect();
+ targetRatio = clamp((e.clientX - rect.left) / rect.width, 0, 1);
+ });
+
+ showcase.addEventListener('mouseleave', () => {
+ targetRatio = 0.5;
+ });
+
+ /* ---- animation loop ---- */
+ function animate() {
+ currentRatio = lerp(currentRatio, targetRatio, LERP_SPEED);
+
+ const r = currentRatio;
+
+ /* 1. Background panel flex (full-width decorative) */
+ const redFlex = lerp(3.5, 0.3, r);
+ const blackFlex = lerp(0.3, 3.5, r);
+
+ bgRed.style.flex = redFlex;
+ bgBlack.style.flex = blackFlex;
+
+ /* 2. Phone mockup scale / opacity / translate */
+ const bias = (0.5 - r) * 2; // +1 red, -1 black
+
+ const redScale = 1 + clamp(bias, 0, 1) * 0.3;
+ const redOpacity = clamp(1 + bias * 3, 0, 1);
+ const redShiftX = clamp(bias, 0, 1) * 60;
+
+ const blackScale = 1 + clamp(-bias, 0, 1) * 0.3;
+ const blackOpacity = clamp(1 - bias * 3, 0, 1);
+ const blackShiftX = clamp(-bias, 0, 1) * -60;
+
+ phoneRed.style.transform = `scale(${redScale}) translateX(${redShiftX}px)`;
+ phoneRed.style.opacity = redOpacity;
+
+ phoneBlack.style.transform = `scale(${blackScale}) translateX(${blackShiftX}px)`;
+ phoneBlack.style.opacity = blackOpacity;
+
+ /* 3. Text always stays visible — no opacity changes */
+
+ requestAnimationFrame(animate);
+ }
+
+ animate();
+});
diff --git a/main/styles.css b/main/styles.css
new file mode 100644
index 0000000..bcca018
--- /dev/null
+++ b/main/styles.css
@@ -0,0 +1,426 @@
+/* ========================================
+ CSS Custom Properties & Reset
+ ======================================== */
+
+:root {
+ --color-red-bg: #6b0f1a;
+ --color-red-dark: #3d0a10;
+ --color-black-bg: #0e0e0e;
+ --color-white: #ffffff;
+ --color-white-dim: rgba(255, 255, 255, 0.35);
+ --color-capture-btn: #1a1f3d;
+ --color-progress-fill: #d4a017;
+
+ --font-display: 'Playfair Display', Georgia, serif;
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+
+ --phone-width: 190px;
+ --phone-height: 380px;
+ --phone-radius: 26px;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html {
+ font-size: 16px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-family: var(--font-body);
+ background: #000;
+ color: var(--color-white);
+ overflow-x: hidden;
+ margin: 0;
+}
+
+/* ========================================
+ Showcase Section – Full-width wrapper
+ ======================================== */
+
+.showcase {
+ position: relative;
+ width: 100%;
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
+
+/* ========================================
+ Background Panels – Decorative, full-width
+ ======================================== */
+
+.bg-panels {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ z-index: 0;
+}
+
+.bg-panel {
+ flex: 1;
+ will-change: flex;
+}
+
+.bg-panel--red {
+ background: linear-gradient(135deg, var(--color-red-bg) 0%, var(--color-red-dark) 100%);
+}
+
+.bg-panel--black {
+ background: linear-gradient(135deg, #1a1a1a 0%, var(--color-black-bg) 100%);
+}
+
+/* ========================================
+ Container – 1320px centered content
+ ======================================== */
+
+.container {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 60px 40px;
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ align-items: center;
+ gap: 30px;
+}
+
+/* ========================================
+ Content Columns (Left / Right text)
+ ======================================== */
+
+.content-col {
+ width: 100%;
+}
+
+.content-col--left {
+ text-align: left;
+}
+
+.content-col--right {
+ text-align: left;
+ justify-self: end;
+}
+
+.content-col__title {
+ font-family: var(--font-display);
+ font-weight: 500;
+ font-style: italic;
+ font-size: 1.85rem;
+ line-height: 1.2;
+ margin-bottom: 36px;
+ letter-spacing: -0.02em;
+ color: var(--color-white);
+}
+
+/* ========================================
+ Spec List
+ ======================================== */
+
+.spec-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.spec-list__row {
+ display: flex;
+ align-items: baseline;
+ padding: 9px 0;
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.spec-list__row:last-child {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.spec-list__label {
+ font-size: 0.65rem;
+ font-weight: 500;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--color-white-dim);
+ width: 90px;
+ flex-shrink: 0;
+}
+
+.spec-list__value {
+ font-size: 1rem;
+ font-weight: 500;
+ color: var(--color-white);
+ letter-spacing: -0.01em;
+}
+
+/* ========================================
+ Phone Mockups – Center column
+ ======================================== */
+
+.phones {
+ display: flex;
+ gap: 14px;
+ flex-shrink: 0;
+}
+
+.phone {
+ width: var(--phone-width);
+ height: var(--phone-height);
+ background: #ffffff;
+ border-radius: var(--phone-radius);
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ box-shadow:
+ 0 25px 60px rgba(0, 0, 0, 0.4),
+ 0 8px 20px rgba(0, 0, 0, 0.25),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ overflow: hidden;
+ will-change: transform, opacity;
+ transform-origin: center center;
+}
+
+/* Phone Notch */
+.phone__notch {
+ width: 80px;
+ height: 5px;
+ background: #1a1a1a;
+ border-radius: 10px;
+ margin: 0 auto 8px;
+}
+
+/* Phone Header */
+.phone__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 4px 6px;
+}
+
+.phone__icon {
+ width: 16px;
+ height: 16px;
+ color: #1a1a1a;
+ cursor: pointer;
+}
+
+/* Viewfinder */
+.phone__viewfinder {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 1 / 0.85;
+ background: #f0f0f0;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
+
+.phone__corners {
+ position: absolute;
+ inset: 12px;
+ pointer-events: none;
+}
+
+.corner {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+}
+
+.corner::before,
+.corner::after {
+ content: '';
+ position: absolute;
+ background: #555;
+}
+
+.corner--tl { top: 0; left: 0; }
+.corner--tl::before { width: 20px; height: 2px; top: 0; left: 0; }
+.corner--tl::after { width: 2px; height: 20px; top: 0; left: 0; }
+
+.corner--tr { top: 0; right: 0; }
+.corner--tr::before { width: 20px; height: 2px; top: 0; right: 0; }
+.corner--tr::after { width: 2px; height: 20px; top: 0; right: 0; }
+
+.corner--bl { bottom: 0; left: 0; }
+.corner--bl::before { width: 20px; height: 2px; bottom: 0; left: 0; }
+.corner--bl::after { width: 2px; height: 20px; bottom: 0; left: 0; }
+
+.corner--br { bottom: 0; right: 0; }
+.corner--br::before { width: 20px; height: 2px; bottom: 0; right: 0; }
+.corner--br::after { width: 2px; height: 20px; bottom: 0; right: 0; }
+
+.phone__shoe-img {
+ width: 85%;
+ height: 85%;
+ object-fit: contain;
+ position: relative;
+ z-index: 1;
+}
+
+/* Caption */
+.phone__caption {
+ font-size: 0.65rem;
+ color: #444;
+ text-align: center;
+ margin-top: 6px;
+ font-weight: 400;
+}
+
+/* Progress */
+.phone__progress {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 4px;
+ padding: 0 2px;
+}
+
+.phone__progress-text {
+ font-size: 0.5rem;
+ color: #888;
+ white-space: nowrap;
+}
+
+.phone__progress-bar {
+ flex: 1;
+ height: 3px;
+ background: #e0e0e0;
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.phone__progress-fill {
+ height: 100%;
+ background: var(--color-progress-fill);
+ border-radius: 3px;
+}
+
+.phone__progress-count {
+ font-size: 0.5rem;
+ color: #888;
+ white-space: nowrap;
+}
+
+/* Phone Buttons */
+.phone__btn {
+ display: block;
+ width: 100%;
+ border: none;
+ cursor: pointer;
+ font-family: var(--font-body);
+ font-weight: 500;
+ transition: transform 0.15s ease, opacity 0.15s ease;
+}
+
+.phone__btn:active {
+ transform: scale(0.97);
+}
+
+.phone__btn--capture {
+ background: var(--color-capture-btn);
+ color: #fff;
+ font-size: 0.72rem;
+ padding: 10px 0;
+ border-radius: 20px;
+ margin-top: 10px;
+ letter-spacing: 0.02em;
+}
+
+.phone__btn--capture:hover {
+ background: #252b55;
+}
+
+.phone__btn--skip {
+ background: transparent;
+ color: #555;
+ font-size: 0.68rem;
+ padding: 6px 0;
+ margin-top: 2px;
+}
+
+.phone__btn--skip:hover {
+ color: #222;
+}
+
+/* ========================================
+ Responsive
+ ======================================== */
+
+@media (max-width: 1100px) {
+ :root {
+ --phone-width: 190px;
+ --phone-height: 380px;
+ }
+
+ .content-col__title {
+ font-size: 1.5rem;
+ }
+
+ .container {
+ padding: 0 30px;
+ }
+
+ .content-col {
+ width: 220px;
+ }
+}
+
+@media (max-width: 900px) {
+ .container {
+ flex-direction: column;
+ padding: 60px 30px;
+ gap: 40px;
+ }
+
+ .content-col {
+ width: 100%;
+ max-width: 400px;
+ text-align: center;
+ }
+
+ .spec-list__row {
+ justify-content: center;
+ }
+
+ .phones {
+ justify-content: center;
+ }
+
+ .channel-icons {
+ align-items: center;
+ }
+}
+
+@media (max-width: 520px) {
+ :root {
+ --phone-width: 160px;
+ --phone-height: 340px;
+ }
+
+ .phones {
+ gap: 10px;
+ }
+
+ .content-col__title {
+ font-size: 1.3rem;
+ }
+
+ .spec-list__label {
+ width: 80px;
+ }
+}
diff --git a/skiply/ambient-float-check.png b/skiply/ambient-float-check.png
new file mode 100644
index 0000000..e5c5b67
Binary files /dev/null and b/skiply/ambient-float-check.png differ
diff --git a/skiply/ambient-t1.png b/skiply/ambient-t1.png
new file mode 100644
index 0000000..e8a0d58
Binary files /dev/null and b/skiply/ambient-t1.png differ
diff --git a/skiply/ambient-t2.png b/skiply/ambient-t2.png
new file mode 100644
index 0000000..59bf4b5
Binary files /dev/null and b/skiply/ambient-t2.png differ
diff --git a/skiply/beam-final.png b/skiply/beam-final.png
new file mode 100644
index 0000000..808bd20
Binary files /dev/null and b/skiply/beam-final.png differ
diff --git a/skiply/beam-t1.png b/skiply/beam-t1.png
new file mode 100644
index 0000000..3f75a09
Binary files /dev/null and b/skiply/beam-t1.png differ
diff --git a/skiply/beam-t2.png b/skiply/beam-t2.png
new file mode 100644
index 0000000..122a03d
Binary files /dev/null and b/skiply/beam-t2.png differ
diff --git a/skiply/beam-t3.png b/skiply/beam-t3.png
new file mode 100644
index 0000000..25b84df
Binary files /dev/null and b/skiply/beam-t3.png differ
diff --git a/skiply/beam-v2-rise-full.png b/skiply/beam-v2-rise-full.png
new file mode 100644
index 0000000..1b79c30
Binary files /dev/null and b/skiply/beam-v2-rise-full.png differ
diff --git a/skiply/beam-v2-rise.png b/skiply/beam-v2-rise.png
new file mode 100644
index 0000000..d41b610
Binary files /dev/null and b/skiply/beam-v2-rise.png differ
diff --git a/skiply/beam-v2-sweep-full.png b/skiply/beam-v2-sweep-full.png
new file mode 100644
index 0000000..83b7aab
Binary files /dev/null and b/skiply/beam-v2-sweep-full.png differ
diff --git a/skiply/beam-v2-sweep.png b/skiply/beam-v2-sweep.png
new file mode 100644
index 0000000..ad08d11
Binary files /dev/null and b/skiply/beam-v2-sweep.png differ
diff --git a/skiply/beam-v2-zoom-rise-base.png b/skiply/beam-v2-zoom-rise-base.png
new file mode 100644
index 0000000..c0708ec
Binary files /dev/null and b/skiply/beam-v2-zoom-rise-base.png differ
diff --git a/skiply/beam-v2-zoom-sweep-mid.png b/skiply/beam-v2-zoom-sweep-mid.png
new file mode 100644
index 0000000..55d61c4
Binary files /dev/null and b/skiply/beam-v2-zoom-sweep-mid.png differ
diff --git a/skiply/beam-v2-zoombase-rise-t028.png b/skiply/beam-v2-zoombase-rise-t028.png
new file mode 100644
index 0000000..e8c1bd1
Binary files /dev/null and b/skiply/beam-v2-zoombase-rise-t028.png differ
diff --git a/skiply/beam-v2-zoombase-rise-t045.png b/skiply/beam-v2-zoombase-rise-t045.png
new file mode 100644
index 0000000..7756479
Binary files /dev/null and b/skiply/beam-v2-zoombase-rise-t045.png differ
diff --git a/skiply/connector-final.png b/skiply/connector-final.png
new file mode 100644
index 0000000..181aa8a
Binary files /dev/null and b/skiply/connector-final.png differ
diff --git a/skiply/connector-t1.png b/skiply/connector-t1.png
new file mode 100644
index 0000000..84bf2a7
Binary files /dev/null and b/skiply/connector-t1.png differ
diff --git a/skiply/connector-t2.png b/skiply/connector-t2.png
new file mode 100644
index 0000000..6999812
Binary files /dev/null and b/skiply/connector-t2.png differ
diff --git a/skiply/final-check-full.png b/skiply/final-check-full.png
new file mode 100644
index 0000000..5b74a78
Binary files /dev/null and b/skiply/final-check-full.png differ
diff --git a/skiply/final-check-shoe.png b/skiply/final-check-shoe.png
new file mode 100644
index 0000000..b719f9e
Binary files /dev/null and b/skiply/final-check-shoe.png differ
diff --git a/skiply/h2-t1.png b/skiply/h2-t1.png
new file mode 100644
index 0000000..83117e4
Binary files /dev/null and b/skiply/h2-t1.png differ
diff --git a/skiply/h2-t2.png b/skiply/h2-t2.png
new file mode 100644
index 0000000..47d581f
Binary files /dev/null and b/skiply/h2-t2.png differ
diff --git a/skiply/h2-t3.png b/skiply/h2-t3.png
new file mode 100644
index 0000000..98adb79
Binary files /dev/null and b/skiply/h2-t3.png differ
diff --git a/skiply/h2-t4.png b/skiply/h2-t4.png
new file mode 100644
index 0000000..20c3cfe
Binary files /dev/null and b/skiply/h2-t4.png differ
diff --git a/skiply/horizon-final.png b/skiply/horizon-final.png
new file mode 100644
index 0000000..04b18c7
Binary files /dev/null and b/skiply/horizon-final.png differ
diff --git a/skiply/horizon-t1.png b/skiply/horizon-t1.png
new file mode 100644
index 0000000..f8b70f7
Binary files /dev/null and b/skiply/horizon-t1.png differ
diff --git a/skiply/horizon-t2.png b/skiply/horizon-t2.png
new file mode 100644
index 0000000..87ade26
Binary files /dev/null and b/skiply/horizon-t2.png differ
diff --git a/skiply/labels-t1.png b/skiply/labels-t1.png
new file mode 100644
index 0000000..15f17d2
Binary files /dev/null and b/skiply/labels-t1.png differ
diff --git a/skiply/labels-t2.png b/skiply/labels-t2.png
new file mode 100644
index 0000000..118a79d
Binary files /dev/null and b/skiply/labels-t2.png differ
diff --git a/skiply/labels-t3.png b/skiply/labels-t3.png
new file mode 100644
index 0000000..3f85b8d
Binary files /dev/null and b/skiply/labels-t3.png differ
diff --git a/skiply/labels-t4.png b/skiply/labels-t4.png
new file mode 100644
index 0000000..f39f532
Binary files /dev/null and b/skiply/labels-t4.png differ
diff --git a/skiply/no-logo-final.png b/skiply/no-logo-final.png
new file mode 100644
index 0000000..8ed46aa
Binary files /dev/null and b/skiply/no-logo-final.png differ
diff --git a/skiply/node-fix-final.png b/skiply/node-fix-final.png
new file mode 100644
index 0000000..e030c3f
Binary files /dev/null and b/skiply/node-fix-final.png differ
diff --git a/skiply/node-fix-mid1.png b/skiply/node-fix-mid1.png
new file mode 100644
index 0000000..6cfe450
Binary files /dev/null and b/skiply/node-fix-mid1.png differ
diff --git a/skiply/node-fix-mid2.png b/skiply/node-fix-mid2.png
new file mode 100644
index 0000000..46846d6
Binary files /dev/null and b/skiply/node-fix-mid2.png differ
diff --git a/skiply/path-animation-fade.js b/skiply/path-animation-fade.js
new file mode 100644
index 0000000..ec56701
--- /dev/null
+++ b/skiply/path-animation-fade.js
@@ -0,0 +1,221 @@
+/* =============================================
+ Skiply.ai — JavaScript
+ - Car travels along the exact road-path-1.svg path
+ - Back-and-forth loop using requestAnimationFrame
+ - Step icons pinned to real path coordinates
+ - Entry reveal animations via IntersectionObserver
+ ============================================= */
+
+'use strict';
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ /* ═══════════════════════════════════════════
+ SCREEN 1 — Car motion along road path
+ ═══════════════════════════════════════════ */
+
+ /* ═══════════════════════════════════════════
+ Reusable Car Animation Setup
+ ═══════════════════════════════════════════ */
+ function setupRoadAnimation(svgId, pathId, carId, mainAreaSelector, screenId, speed = 0.00016) {
+ const svgEl = document.getElementById(svgId);
+ const pathEl = document.getElementById(pathId);
+ const car = document.getElementById(carId);
+ const mainArea = document.querySelector(mainAreaSelector);
+ const screen = document.getElementById(screenId);
+
+ if (!pathEl || !car || !svgEl || !mainArea || !screen) {
+ console.warn(`Missing elements for ${screenId}:`, {svgEl, pathEl, car, mainArea, screen});
+ return;
+ }
+
+ const TOTAL_LEN = pathEl.getTotalLength();
+
+ function getSVGScale() {
+ const rect = svgEl.getBoundingClientRect();
+ const vbW = svgEl.viewBox.baseVal.width || 1005;
+ const vbH = svgEl.viewBox.baseVal.height || 650;
+
+ const scaleX = rect.width / vbW;
+ const scaleY = rect.height / vbH;
+ const scale = Math.min(scaleX, scaleY);
+
+ const offsetX = (rect.width - vbW * scale) / 2;
+ const offsetY = (rect.height - vbH * scale) / 2;
+ return { scale, offsetX, offsetY, rect };
+ }
+
+ let progress = 0;
+ const SPEED = speed;
+ let lastTime = null;
+ let rafId = null;
+ let running = false;
+ let looping = false;
+
+ function getAngle(t, delta = 0.002) {
+ const t1 = Math.max(0, t - delta);
+ const t2 = Math.min(1, t + delta);
+ const p1 = pathEl.getPointAtLength(t1 * TOTAL_LEN);
+ const p2 = pathEl.getPointAtLength(t2 * TOTAL_LEN);
+ return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI);
+ }
+
+ function placeCar(t) {
+ const { scale, offsetX, offsetY, rect } = getSVGScale();
+ const containerRect = mainArea.getBoundingClientRect();
+ const pt = pathEl.getPointAtLength(t * TOTAL_LEN);
+ const angle = getAngle(t);
+ const relX = (rect.left + offsetX + pt.x * scale) - containerRect.left;
+ const relY = (rect.top + offsetY + pt.y * scale) - containerRect.top;
+ car.style.left = `${relX}px`;
+ car.style.top = `${relY}px`;
+ car.style.transform = `translate(-50%, -50%) rotate(${angle}deg)`;
+ }
+
+ function doLoop() {
+ looping = true;
+ car.style.transition = 'opacity 0.25s ease';
+ car.style.opacity = '0';
+
+ setTimeout(() => {
+ progress = 0;
+ placeCar(0);
+ setTimeout(() => {
+ car.style.transition = 'opacity 0.3s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ looping = false;
+ lastTime = null;
+ rafId = requestAnimationFrame(animateCar);
+ }, 320);
+ }, 60);
+ }, 280);
+ }
+
+ function animateCar(timestamp) {
+ if (looping) return;
+ if (!lastTime) lastTime = timestamp;
+ const dt = Math.min(timestamp - lastTime, 50);
+ lastTime = timestamp;
+
+ progress += SPEED * dt;
+
+ if (progress >= 1) {
+ progress = 1;
+ placeCar(1);
+ cancelAnimationFrame(rafId);
+ doLoop();
+ return;
+ }
+
+ placeCar(progress);
+ rafId = requestAnimationFrame(animateCar);
+ }
+
+ function startCar() {
+ if (running) return;
+ running = true;
+ lastTime = null;
+ progress = 0;
+ looping = false;
+ placeCar(0);
+ car.style.transition = 'opacity 0.4s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ rafId = requestAnimationFrame(animateCar);
+ }, 420);
+ }
+
+ const ro = new ResizeObserver(() => {});
+ ro.observe(mainArea);
+
+
+
+ // Use GSAP ScrollTrigger for Fade In / Fade Out effect
+ if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
+ gsap.registerPlugin(ScrollTrigger);
+
+ // 1. Fade in the left side panel text
+ gsap.fromTo(document.querySelectorAll('#' + screenId + ' .side-panel > *'),
+ { opacity: 0, y: 20 },
+ {
+ opacity: 1,
+ y: 0,
+ duration: 0.6,
+ stagger: 0.15,
+ ease: "power2.out",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 60%",
+ end: "bottom 40%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 2. Stagger fade-in for SVG pointers (navy circles and white icons)
+ const pointers = Array.from(document.querySelectorAll('#' + screenId + ' svg circle, #' + screenId + ' svg path')).filter(el => {
+ const fill = el.getAttribute('fill');
+ if (!fill) return false;
+ const upper = fill.toUpperCase();
+ return upper === '#0A1A37' || upper === 'WHITE' || upper === '#FFFFFF';
+ });
+
+ gsap.fromTo(pointers,
+ { opacity: 0, scale: 0, transformOrigin: "50% 50%" },
+ {
+ opacity: 1,
+ scale: 1,
+ duration: 0.4,
+ stagger: 0.15,
+ ease: "back.out(1.5)",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 50%",
+ end: "bottom 20%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 3. Start car animation when screen is in view
+ ScrollTrigger.create({
+ trigger: screen,
+ start: "top 60%",
+ onEnter: startCar,
+ once: true
+ });
+ }
+ }
+
+ // Initialize Screen 1
+ setupRoadAnimation('road-svg', 'road-path', 'road-car', '.main-area--prob1', 'screen-prob1', 0.00008);
+
+ // Initialize Screen 2
+ setupRoadAnimation('road-svg-2', 'road-path-2', 'road-car-2', '.main-area--prob2', 'screen-prob2', 0.00016);
+
+
+
+
+
+ /* ═══════════════════════════════════════════
+ Screen 2 Fade In Effect (Original Smooth Version)
+ ═══════════════════════════════════════════ */
+ if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
+ gsap.fromTo('.screen--prob2',
+ { opacity: 0 },
+ {
+ opacity: 1,
+ ease: "none",
+ scrollTrigger: {
+ trigger: '.screen--prob2',
+ start: "top 75%",
+ end: "top 0%",
+ scrub: true
+ }
+ }
+ );
+ }
+});
diff --git a/skiply/path-animation-fullpage.js b/skiply/path-animation-fullpage.js
new file mode 100644
index 0000000..7b85313
--- /dev/null
+++ b/skiply/path-animation-fullpage.js
@@ -0,0 +1,215 @@
+/* =============================================
+ Skiply.ai — JavaScript
+ - Car travels along the exact road-path-1.svg path
+ - Back-and-forth loop using requestAnimationFrame
+ - Step icons pinned to real path coordinates
+ - Entry reveal animations via IntersectionObserver
+ ============================================= */
+
+'use strict';
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ /* ═══════════════════════════════════════════
+ SCREEN 1 — Car motion along road path
+ ═══════════════════════════════════════════ */
+
+ /* ═══════════════════════════════════════════
+ Reusable Car Animation Setup
+ ═══════════════════════════════════════════ */
+ function setupRoadAnimation(svgId, pathId, carId, mainAreaSelector, screenId, speed = 0.00016) {
+ const svgEl = document.getElementById(svgId);
+ const pathEl = document.getElementById(pathId);
+ const car = document.getElementById(carId);
+ const mainArea = document.querySelector(mainAreaSelector);
+ const screen = document.getElementById(screenId);
+
+ if (!pathEl || !car || !svgEl || !mainArea || !screen) {
+ console.warn(`Missing elements for ${screenId}:`, {svgEl, pathEl, car, mainArea, screen});
+ return;
+ }
+
+ const TOTAL_LEN = pathEl.getTotalLength();
+
+ function getSVGScale() {
+ const rect = svgEl.getBoundingClientRect();
+ const vbW = svgEl.viewBox.baseVal.width || 1005;
+ const vbH = svgEl.viewBox.baseVal.height || 650;
+
+ const scaleX = rect.width / vbW;
+ const scaleY = rect.height / vbH;
+ const scale = Math.min(scaleX, scaleY);
+
+ const offsetX = (rect.width - vbW * scale) / 2;
+ const offsetY = (rect.height - vbH * scale) / 2;
+ return { scale, offsetX, offsetY, rect };
+ }
+
+ let progress = 0;
+ const SPEED = speed;
+ let lastTime = null;
+ let rafId = null;
+ let running = false;
+ let looping = false;
+
+ function getAngle(t, delta = 0.002) {
+ const t1 = Math.max(0, t - delta);
+ const t2 = Math.min(1, t + delta);
+ const p1 = pathEl.getPointAtLength(t1 * TOTAL_LEN);
+ const p2 = pathEl.getPointAtLength(t2 * TOTAL_LEN);
+ return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI);
+ }
+
+ function placeCar(t) {
+ const { scale, offsetX, offsetY, rect } = getSVGScale();
+ const containerRect = mainArea.getBoundingClientRect();
+ const pt = pathEl.getPointAtLength(t * TOTAL_LEN);
+ const angle = getAngle(t);
+ const relX = (rect.left + offsetX + pt.x * scale) - containerRect.left;
+ const relY = (rect.top + offsetY + pt.y * scale) - containerRect.top;
+ car.style.left = `${relX}px`;
+ car.style.top = `${relY}px`;
+ car.style.transform = `translate(-50%, -50%) rotate(${angle}deg)`;
+ }
+
+ function doLoop() {
+ looping = true;
+ car.style.transition = 'opacity 0.25s ease';
+ car.style.opacity = '0';
+
+ setTimeout(() => {
+ progress = 0;
+ placeCar(0);
+ setTimeout(() => {
+ car.style.transition = 'opacity 0.3s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ looping = false;
+ lastTime = null;
+ rafId = requestAnimationFrame(animateCar);
+ }, 320);
+ }, 60);
+ }, 280);
+ }
+
+ function animateCar(timestamp) {
+ if (looping) return;
+ if (!lastTime) lastTime = timestamp;
+ const dt = Math.min(timestamp - lastTime, 50);
+ lastTime = timestamp;
+
+ progress += SPEED * dt;
+
+ if (progress >= 1) {
+ progress = 1;
+ placeCar(1);
+ cancelAnimationFrame(rafId);
+ doLoop();
+ return;
+ }
+
+ placeCar(progress);
+ rafId = requestAnimationFrame(animateCar);
+ }
+
+ function startCar() {
+ if (running) return;
+ running = true;
+ lastTime = null;
+ progress = 0;
+ looping = false;
+ placeCar(0);
+ car.style.transition = 'opacity 0.4s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ rafId = requestAnimationFrame(animateCar);
+ }, 420);
+ }
+
+ const ro = new ResizeObserver(() => {});
+ ro.observe(mainArea);
+
+
+
+ // Use GSAP ScrollTrigger for Fade In / Fade Out effect
+ if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
+ gsap.registerPlugin(ScrollTrigger);
+
+ // 1. Fade in the left side panel text
+ gsap.fromTo(document.querySelectorAll('#' + screenId + ' .side-panel > *'),
+ { opacity: 0, y: 20 },
+ {
+ opacity: 1,
+ y: 0,
+ duration: 0.6,
+ stagger: 0.15,
+ ease: "power2.out",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 60%",
+ end: "bottom 40%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 2. Stagger fade-in for SVG pointers (navy circles and white icons)
+ const pointers = Array.from(document.querySelectorAll('#' + screenId + ' svg circle, #' + screenId + ' svg path')).filter(el => {
+ const fill = el.getAttribute('fill');
+ if (!fill) return false;
+ const upper = fill.toUpperCase();
+ return upper === '#0A1A37' || upper === 'WHITE' || upper === '#FFFFFF';
+ });
+
+ gsap.fromTo(pointers,
+ { opacity: 0, scale: 0, transformOrigin: "50% 50%" },
+ {
+ opacity: 1,
+ scale: 1,
+ duration: 0.4,
+ stagger: 0.15,
+ ease: "back.out(1.5)",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 50%",
+ end: "bottom 20%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 3. Start car animation when screen is in view
+ ScrollTrigger.create({
+ trigger: screen,
+ start: "top 60%",
+ onEnter: startCar,
+ once: true
+ });
+ }
+ }
+
+ // Initialize Screen 1
+ setupRoadAnimation('road-svg', 'road-path', 'road-car', '.main-area--prob1', 'screen-prob1', 0.00008);
+
+ // Initialize Screen 2
+ setupRoadAnimation('road-svg-2', 'road-path-2', 'road-car-2', '.main-area--prob2', 'screen-prob2', 0.00016);
+
+
+
+
+
+ /* ═══════════════════════════════════════════
+ FullPage.js Initialization
+ ═══════════════════════════════════════════ */
+ if (typeof fullpage !== 'undefined') {
+ new fullpage('#fullpage', {
+ autoScrolling: true,
+ scrollBar: true, // IMPORTANT: keeps native scroll so GSAP ScrollTrigger still works!
+ scrollingSpeed: 700,
+ fitToSection: true,
+ fitToSectionDelay: 1000
+ });
+ }
+});
diff --git a/skiply/path-animation.js b/skiply/path-animation.js
new file mode 100644
index 0000000..993f178
--- /dev/null
+++ b/skiply/path-animation.js
@@ -0,0 +1,202 @@
+/* =============================================
+ Skiply.ai — JavaScript
+ - Car travels along the exact road-path-1.svg path
+ - Back-and-forth loop using requestAnimationFrame
+ - Step icons pinned to real path coordinates
+ - Entry reveal animations via IntersectionObserver
+ ============================================= */
+
+'use strict';
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ /* ═══════════════════════════════════════════
+ SCREEN 1 — Car motion along road path
+ ═══════════════════════════════════════════ */
+
+ /* ═══════════════════════════════════════════
+ Reusable Car Animation Setup
+ ═══════════════════════════════════════════ */
+ function setupRoadAnimation(svgId, pathId, carId, mainAreaSelector, screenId, speed = 0.00016) {
+ const svgEl = document.getElementById(svgId);
+ const pathEl = document.getElementById(pathId);
+ const car = document.getElementById(carId);
+ const mainArea = document.querySelector(mainAreaSelector);
+ const screen = document.getElementById(screenId);
+
+ if (!pathEl || !car || !svgEl || !mainArea || !screen) {
+ console.warn(`Missing elements for ${screenId}:`, {svgEl, pathEl, car, mainArea, screen});
+ return;
+ }
+
+ const TOTAL_LEN = pathEl.getTotalLength();
+
+ function getSVGScale() {
+ const rect = svgEl.getBoundingClientRect();
+ const vbW = svgEl.viewBox.baseVal.width || 1005;
+ const vbH = svgEl.viewBox.baseVal.height || 650;
+
+ const scaleX = rect.width / vbW;
+ const scaleY = rect.height / vbH;
+ const scale = Math.min(scaleX, scaleY);
+
+ const offsetX = (rect.width - vbW * scale) / 2;
+ const offsetY = (rect.height - vbH * scale) / 2;
+ return { scale, offsetX, offsetY, rect };
+ }
+
+ let progress = 0;
+ const SPEED = speed;
+ let lastTime = null;
+ let rafId = null;
+ let running = false;
+ let looping = false;
+
+ function getAngle(t, delta = 0.002) {
+ const t1 = Math.max(0, t - delta);
+ const t2 = Math.min(1, t + delta);
+ const p1 = pathEl.getPointAtLength(t1 * TOTAL_LEN);
+ const p2 = pathEl.getPointAtLength(t2 * TOTAL_LEN);
+ return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI);
+ }
+
+ function placeCar(t) {
+ const { scale, offsetX, offsetY, rect } = getSVGScale();
+ const containerRect = mainArea.getBoundingClientRect();
+ const pt = pathEl.getPointAtLength(t * TOTAL_LEN);
+ const angle = getAngle(t);
+ const relX = (rect.left + offsetX + pt.x * scale) - containerRect.left;
+ const relY = (rect.top + offsetY + pt.y * scale) - containerRect.top;
+ car.style.left = `${relX}px`;
+ car.style.top = `${relY}px`;
+ car.style.transform = `translate(-50%, -50%) rotate(${angle}deg)`;
+ }
+
+ function doLoop() {
+ looping = true;
+ car.style.transition = 'opacity 0.25s ease';
+ car.style.opacity = '0';
+
+ setTimeout(() => {
+ progress = 0;
+ placeCar(0);
+ setTimeout(() => {
+ car.style.transition = 'opacity 0.3s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ looping = false;
+ lastTime = null;
+ rafId = requestAnimationFrame(animateCar);
+ }, 320);
+ }, 60);
+ }, 280);
+ }
+
+ function animateCar(timestamp) {
+ if (looping) return;
+ if (!lastTime) lastTime = timestamp;
+ const dt = Math.min(timestamp - lastTime, 50);
+ lastTime = timestamp;
+
+ progress += SPEED * dt;
+
+ if (progress >= 1) {
+ progress = 1;
+ placeCar(1);
+ cancelAnimationFrame(rafId);
+ doLoop();
+ return;
+ }
+
+ placeCar(progress);
+ rafId = requestAnimationFrame(animateCar);
+ }
+
+ function startCar() {
+ if (running) return;
+ running = true;
+ lastTime = null;
+ progress = 0;
+ looping = false;
+ placeCar(0);
+ car.style.transition = 'opacity 0.4s ease';
+ car.style.opacity = '1';
+ setTimeout(() => {
+ car.style.transition = '';
+ rafId = requestAnimationFrame(animateCar);
+ }, 420);
+ }
+
+ const ro = new ResizeObserver(() => {});
+ ro.observe(mainArea);
+
+
+
+ // Use GSAP ScrollTrigger for Fade In / Fade Out effect
+ if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
+ gsap.registerPlugin(ScrollTrigger);
+
+ // 1. Fade in the left side panel text
+ gsap.fromTo(document.querySelectorAll('#' + screenId + ' .side-panel > *'),
+ { opacity: 0, y: 20 },
+ {
+ opacity: 1,
+ y: 0,
+ duration: 0.6,
+ stagger: 0.15,
+ ease: "power2.out",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 60%",
+ end: "bottom 40%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 2. Stagger fade-in for SVG pointers (navy circles and white icons)
+ const pointers = Array.from(document.querySelectorAll('#' + screenId + ' svg circle, #' + screenId + ' svg path')).filter(el => {
+ const fill = el.getAttribute('fill');
+ if (!fill) return false;
+ const upper = fill.toUpperCase();
+ return upper === '#0A1A37' || upper === 'WHITE' || upper === '#FFFFFF';
+ });
+
+ gsap.fromTo(pointers,
+ { opacity: 0, scale: 0, transformOrigin: "50% 50%" },
+ {
+ opacity: 1,
+ scale: 1,
+ duration: 0.4,
+ stagger: 0.15,
+ ease: "back.out(1.5)",
+ scrollTrigger: {
+ trigger: screen,
+ start: "top 50%",
+ end: "bottom 20%",
+ toggleActions: "play reverse play reverse"
+ }
+ }
+ );
+
+ // 3. Start car animation when screen is in view
+ ScrollTrigger.create({
+ trigger: screen,
+ start: "top 60%",
+ onEnter: startCar,
+ once: true
+ });
+ }
+ }
+
+ // Initialize Screen 1
+ setupRoadAnimation('road-svg', 'road-path', 'road-car', '.main-area--prob1', 'screen-prob1', 0.00008);
+
+ // Initialize Screen 2
+ setupRoadAnimation('road-svg-2', 'road-path-2', 'road-car-2', '.main-area--prob2', 'screen-prob2', 0.00016);
+
+
+
+
+});
diff --git a/skiply/routes-card-1440.png b/skiply/routes-card-1440.png
new file mode 100644
index 0000000..a607b03
Binary files /dev/null and b/skiply/routes-card-1440.png differ
diff --git a/skiply/routes-card-500.png b/skiply/routes-card-500.png
new file mode 100644
index 0000000..eadfdc3
Binary files /dev/null and b/skiply/routes-card-500.png differ
diff --git a/skiply/routes-card-800.png b/skiply/routes-card-800.png
new file mode 100644
index 0000000..5e5fb8e
Binary files /dev/null and b/skiply/routes-card-800.png differ
diff --git a/skiply/scan-anim-end.png b/skiply/scan-anim-end.png
new file mode 100644
index 0000000..d0b6c6e
Binary files /dev/null and b/skiply/scan-anim-end.png differ
diff --git a/skiply/scan-anim-fixed-crop.png b/skiply/scan-anim-fixed-crop.png
new file mode 100644
index 0000000..070c53c
Binary files /dev/null and b/skiply/scan-anim-fixed-crop.png differ
diff --git a/skiply/scan-anim-fixed.png b/skiply/scan-anim-fixed.png
new file mode 100644
index 0000000..a0521d2
Binary files /dev/null and b/skiply/scan-anim-fixed.png differ
diff --git a/skiply/scan-anim-mid.png b/skiply/scan-anim-mid.png
new file mode 100644
index 0000000..d0b6c6e
Binary files /dev/null and b/skiply/scan-anim-mid.png differ
diff --git a/skiply/screenshot-banner-crop.png b/skiply/screenshot-banner-crop.png
new file mode 100644
index 0000000..07b4430
Binary files /dev/null and b/skiply/screenshot-banner-crop.png differ
diff --git a/skiply/screenshot-connected-systems-1440-v2.png b/skiply/screenshot-connected-systems-1440-v2.png
new file mode 100644
index 0000000..4450099
Binary files /dev/null and b/skiply/screenshot-connected-systems-1440-v2.png differ
diff --git a/skiply/screenshot-connected-systems-1440.png b/skiply/screenshot-connected-systems-1440.png
new file mode 100644
index 0000000..361095c
Binary files /dev/null and b/skiply/screenshot-connected-systems-1440.png differ
diff --git a/skiply/skiply-connected-systems.css b/skiply/skiply-connected-systems.css
new file mode 100644
index 0000000..0ca3973
--- /dev/null
+++ b/skiply/skiply-connected-systems.css
@@ -0,0 +1,487 @@
+/* =============================================
+ Skiply.ai — Connected Systems section
+ Figma: SG-DEV
+ ============================================= */
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --navy-deep: #06152c;
+ --blue: #0060ef;
+ --text-white: #ffffff;
+ --text-dim: rgba(255, 255, 255, 0.65);
+ --line: rgba(255, 255, 255, 0.5);
+
+ --font-main: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+ --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
+
+ --content-w: 1320px;
+}
+
+html {
+ font-family: var(--font-main);
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background: var(--navy-deep);
+}
+
+/* ── Section shell ──────────────────────────── */
+.connected {
+ background: var(--navy-deep);
+ padding: 96px 32px 80px;
+}
+
+.connected__inner {
+ max-width: var(--content-w);
+ margin: 0 auto;
+}
+
+/* ── Heading ────────────────────────────────── */
+.connected__header {
+ text-align: center;
+ margin-bottom: 32px;
+}
+
+.connected__eyebrow {
+ font-size: 0.95rem;
+ font-weight: 400;
+ color: var(--text-dim);
+ margin-bottom: 12px;
+}
+
+.connected__title {
+ font-family: var(--font-display);
+ font-size: 2.6rem;
+ font-weight: 400;
+ line-height: 1.2;
+ color: var(--text-white);
+ max-width: 780px;
+ margin: 0 auto;
+}
+
+/* ── Banner / scan stage ─────────────────────── */
+.connected__banner {
+ max-width: 720px;
+ margin: 0 auto;
+}
+
+.shoe-stage {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 631/527;
+}
+
+.shoe-stage__glow {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 85%;
+ height: 85%;
+ transform: translate(-50%, -50%);
+ background: radial-gradient(ellipse, rgba(0, 96, 239, 0.35) 0%, rgba(0, 96, 239, 0) 70%);
+ filter: blur(20px);
+ animation: shoeGlowPulse 4s ease-in-out infinite;
+ pointer-events: none;
+}
+
+@keyframes shoeGlowPulse {
+
+ 0%,
+ 100% {
+ opacity: 0.7;
+ }
+
+ 50% {
+ opacity: 1;
+ }
+}
+
+.shoe-stage__img {
+ position: absolute;
+ z-index: 2;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ filter: drop-shadow(0 0 14px rgba(110, 165, 255, 0.8)) drop-shadow(0 0 36px rgba(70, 120, 255, 0.4));
+}
+
+/* Outer layer: a plain rectangular clip that reveals left-to-right in
+ step with the scan beam via --reveal (kept separate from the alpha
+ mask below — combining both in one multi-layer mask is unreliable
+ across engines). */
+.shoe-stage__mesh-clip {
+ --reveal: 0%;
+ position: absolute;
+ inset: 0;
+ clip-path: inset(0 calc(100% - var(--reveal)) 0 0);
+ pointer-events: none;
+}
+
+/* Inner layer: the wireframe grid, clipped to the shoe's own alpha
+ silhouette so it only ever paints on the shoe itself. */
+.shoe-stage__mesh {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ background-image:
+ repeating-linear-gradient(0deg, rgba(140, 190, 255, 0.4) 0 1px, transparent 1px 22px),
+ repeating-linear-gradient(90deg, rgba(140, 190, 255, 0.4) 0 1px, transparent 1px 22px);
+ mix-blend-mode: screen;
+ filter: drop-shadow(0 0 5px rgba(96, 160, 255, 0.6));
+ -webkit-mask-image: url(../assets/connected-systems/shoe-clean.png);
+ -webkit-mask-size: 100% 100%;
+ -webkit-mask-repeat: no-repeat;
+ mask-image: url(../assets/connected-systems/shoe-clean.png);
+ mask-size: 100% 100%;
+ mask-repeat: no-repeat;
+ mask-mode: alpha;
+ pointer-events: none;
+}
+
+/* Horizon beam: a persistent glowing line that appears to pass through
+ the shoe once the scan finishes, extending past its silhouette on
+ both sides. Grows outward from center. */
+
+.shoe-stage__horizon {
+ position: absolute;
+ left: -18%;
+ right: -18%;
+ top: 45%;
+ height: 3px;
+ margin-top: -1.5px;
+ opacity: 0;
+ transform: scaleX(0);
+ transform-origin: center;
+
+ pointer-events: none;
+
+ border-top: 2px solid;
+ border-image-slice: 2;
+ border-image-source: linear-gradient(90deg,
+ rgba(255, 255, 255, 0) 0%,
+ #fff 15.38%,
+ #fff 85.58%,
+ rgba(255, 255, 255, 0) 100%);
+
+ filter:
+ drop-shadow(0 0 4px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 12px rgba(110, 165, 255, 0.8)) drop-shadow(0 0 30px rgba(70, 120, 255, 0.5));
+}
+
+.shoe-stage__particles {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+}
+
+.scan-particle {
+ position: absolute;
+ width: 3px;
+ height: 3px;
+ border-radius: 50%;
+ background: #cfe2ff;
+ box-shadow: 0 0 6px 1px rgba(150, 195, 255, 0.9);
+ opacity: 0;
+}
+
+/* ── Ambient dust: a persistent field of dots, independent of the
+ scroll-triggered scan sequence, that keeps the banner feeling alive.
+ Color is a light cyan-white chosen for contrast against the dark
+ navy banner backdrop. ── */
+.shoe-stage__ambient {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.ambient-particle {
+ position: absolute;
+ border-radius: 50%;
+ background: #e4f0ff;
+ box-shadow: 0 0 5px 1.5px rgba(150, 195, 255, 0.85);
+ animation: ambientFloat var(--dur, 3.5s) ease-in-out infinite;
+ animation-delay: var(--delay, 0s);
+ animation-fill-mode: backwards;
+}
+
+@keyframes ambientFloat {
+
+ 0%,
+ 100% {
+ transform: translate(0, 0) scale(0.7);
+ opacity: 0.12;
+ }
+
+ 50% {
+ transform: translate(var(--dx, 8px), var(--dy, -14px)) scale(1.2);
+ opacity: 0.85;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .ambient-particle {
+ animation: none;
+ opacity: 0.4;
+ }
+}
+
+/* ── Scan labels: icon + two-line chip, matching Figma node 328:820 ── */
+.scan-label {
+ position: absolute;
+ z-index: 4;
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 0.85rem;
+ font-weight: 400;
+ color: rgba(255, 255, 255, 0.85);
+ border: 1px solid rgba(255, 255, 255, 0.35);
+ text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9), 0 0 10px rgba(0, 0, 0, 0.7);
+ opacity: 0;
+ white-space: nowrap;
+ border-radius: 7px;
+ background: rgba(255, 255, 255, 0.10);
+ padding: 10px 20px;
+ backdrop-filter: blur(3px);
+ -webkit-backdrop-filter: blur(3px);
+}
+
+.scan-label__icon {
+ width: 26px;
+ height: 26px;
+ flex-shrink: 0;
+ object-fit: contain;
+}
+
+.scan-label__text {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.3;
+}
+
+.scan-label__key {
+ font-size: 0.8rem;
+ font-weight: 400;
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.scan-label__value {
+ font-size: 1rem;
+ font-weight: 700;
+ color: var(--text-white);
+}
+
+.scan-label--condition {
+ left: -1%;
+ top: 0;
+}
+
+.scan-label--resale {
+ right: 2%;
+ top: 18%;
+}
+
+.scan-label--market {
+ left: 10%;
+ top: 66%;
+}
+
+.scan-label--value {
+ right: -4%;
+ top: 65%;
+}
+
+/* ── Light connector: carries the horizon beam into the tree ──── */
+.connected__flow {
+ position: relative;
+ width: 3px;
+ height: 56px;
+ margin: 0 auto;
+ overflow: visible;
+}
+
+.connected__flow-line {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(to bottom,
+ transparent,
+ rgba(150, 195, 255, 0.95) 25%,
+ rgba(150, 195, 255, 0.95) 75%,
+ transparent);
+ box-shadow: 0 0 12px 3px rgba(110, 165, 255, 0.7);
+ transform: scaleY(0);
+ transform-origin: top center;
+ opacity: 0;
+}
+
+.connected__flow-dot {
+ position: absolute;
+ left: 50%;
+ top: 0;
+ width: 8px;
+ height: 8px;
+ margin: -4px 0 0 -4px;
+ border-radius: 50%;
+ background: #eaf2ff;
+ box-shadow: 0 0 10px 3px #6ba8ff, 0 0 20px 8px #3f7bff;
+ opacity: 0;
+}
+
+/* ── Distribution tree ──────────────────────── */
+.connected__tree {
+ position: relative;
+ margin-top: -250px;
+}
+
+.connected__branches {
+ display: block;
+ width: 100%;
+ height: auto;
+ opacity: 0;
+}
+
+/* Each branch reveals top-to-bottom independently, staggered in JS,
+ so the five lines visibly draw in one after another. */
+.connected__branch-group {
+ clip-path: inset(0 0 100% 0);
+}
+
+.connected__node {
+ opacity: 0;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+/* ── Route pills ─────────────────────────────── */
+.connected__routes {
+ display: flex;
+ gap: 15px;
+ list-style: none;
+}
+
+.route {
+ flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ gap: 24px;
+ min-height: 150px;
+ padding: 40px 20px;
+ background: var(--blue);
+ border-radius: 16px;
+ transition: transform 0.4s ease;
+}
+
+.route.is-lit {
+ transform: translateY(-4px);
+}
+
+.route__badge {
+ flex-shrink: 0;
+ width: 52px;
+ height: 52px;
+ border-radius: 7px;
+ background: #ffffff;
+ border: 1px solid #e2dede;
+ box-shadow: 0 4px 4px rgba(0, 0, 0, 0.15), inset 1px -1px 4px rgba(0, 0, 0, 0.15);
+ transform: rotate(45deg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.route__badge img {
+ width: 24px;
+ height: 24px;
+ transform: rotate(-45deg);
+}
+
+.route__label {
+ font-size: 1.1rem;
+ font-weight: 400;
+ color: var(--text-white);
+}
+
+/* ═══════════════════════════════════════════════
+ Responsive
+ ═══════════════════════════════════════════════ */
+@media (max-width: 1024px) {
+ .connected__title {
+ font-size: 2.1rem;
+ }
+
+ .connected__routes {
+ flex-wrap: wrap;
+ }
+
+ .route {
+ flex: 1 1 calc(33.333% - 10px);
+ }
+}
+
+@media (max-width: 640px) {
+ .connected {
+ padding: 64px 20px 56px;
+ }
+
+ .connected__title {
+ font-size: 1.6rem;
+ }
+
+ .connected__eyebrow {
+ font-size: 0.8rem;
+ }
+
+ .connected__branches {
+ display: none;
+ }
+
+ .scan-label {
+ font-size: 0.7rem;
+ gap: 8px;
+ padding: 6px 12px;
+ }
+
+ .scan-label__icon {
+ width: 18px;
+ height: 18px;
+ }
+
+ .scan-label__key {
+ font-size: 0.65rem;
+ }
+
+ .scan-label__value {
+ font-size: 0.8rem;
+ }
+
+ .connected__routes {
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .route {
+ flex: 1 1 auto;
+ width: 100%;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ min-height: auto;
+ gap: 16px;
+ padding: 14px 20px;
+ }
+
+ .route__label {
+ font-size: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/skiply/skiply-connected-systems.html b/skiply/skiply-connected-systems.html
new file mode 100644
index 0000000..bd09f3f
--- /dev/null
+++ b/skiply/skiply-connected-systems.html
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+ Skiply.ai – Connected Systems
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+ Condition
+ New
+
+
+
+
+
+ Resale
+ In-Store
+
+
+
+
+
+ Market
+ High Demand
+
+
+
+
+
+ Value
+ $110
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skiply/skiply-connected-systems.js b/skiply/skiply-connected-systems.js
new file mode 100644
index 0000000..012316c
--- /dev/null
+++ b/skiply/skiply-connected-systems.js
@@ -0,0 +1,155 @@
+/* =============================================
+ Skiply.ai — Connected Systems section
+ Scan-the-shoe → distribute-to-tree motion sequence
+ ============================================= */
+
+'use strict';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const section = document.getElementById('connected-systems');
+ const shoeStage = document.getElementById('shoeStage');
+ const horizon = document.getElementById('scanHorizon');
+ const flowLine = document.querySelector('.connected__flow-line');
+ const flowDot = document.querySelector('.connected__flow-dot');
+ const mesh = document.getElementById('shoeMesh');
+ const meshClip = document.getElementById('meshClip');
+ const particlesHost = document.getElementById('scanParticles');
+ const labels = Array.from(document.querySelectorAll('.scan-label'));
+ const treeSvg = document.getElementById('treeSvg');
+ const branchGroups = Array.from(document.querySelectorAll('.connected__branch-group'));
+ const nodes = Array.from(document.querySelectorAll('.connected__node'));
+ const routes = Array.from(document.querySelectorAll('.route'));
+
+ if (!section || !shoeStage || typeof gsap === 'undefined') return;
+
+ const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+ /* ── Build scan particles ───────────────────── */
+ const PARTICLE_COUNT = 14;
+ const particles = [];
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
+ const p = document.createElement('span');
+ p.className = 'scan-particle';
+ const left = 8 + Math.random() * 84;
+ const top = 6 + Math.random() * 88;
+ const size = 2 + Math.random() * 3;
+ p.style.left = left + '%';
+ p.style.top = top + '%';
+ p.style.width = size + 'px';
+ p.style.height = size + 'px';
+ particlesHost.appendChild(p);
+ particles.push({ el: p, left });
+ }
+
+ /* ── Ambient dust: persistent, independent of the scroll timeline ── */
+ const ambientHost = document.getElementById('ambientParticles');
+ const AMBIENT_COUNT = 22;
+ for (let i = 0; i < AMBIENT_COUNT; i++) {
+ const p = document.createElement('span');
+ p.className = 'ambient-particle';
+ const size = 1.5 + Math.random() * 2;
+ p.style.left = Math.random() * 100 + '%';
+ p.style.top = Math.random() * 100 + '%';
+ p.style.width = size + 'px';
+ p.style.height = size + 'px';
+ p.style.setProperty('--dur', (2.5 + Math.random() * 3).toFixed(2) + 's');
+ p.style.setProperty('--delay', (Math.random() * 4).toFixed(2) + 's');
+ p.style.setProperty('--dx', (Math.random() * 20 - 10).toFixed(1) + 'px');
+ p.style.setProperty('--dy', (-6 - Math.random() * 16).toFixed(1) + 'px');
+ ambientHost.appendChild(p);
+ }
+
+ if (reduceMotion) {
+ gsap.set(shoeStage, { opacity: 1 });
+ gsap.set(mesh, { opacity: 0.75 });
+ gsap.set(meshClip, { '--reveal': '115%' });
+ gsap.set(labels, { opacity: 1, y: 0, scale: 1 });
+ gsap.set(particles.map((p) => p.el), { opacity: 0.6 });
+ gsap.set(horizon, { opacity: 0.85, scaleX: 1 });
+ gsap.set(flowLine, { opacity: 0.7, scaleY: 1 });
+ gsap.set(treeSvg, { opacity: 1 });
+ gsap.set(branchGroups, { clipPath: 'inset(0% 0% 0% 0%)' });
+ gsap.set(nodes, { opacity: 1, scale: 1 });
+ routes.forEach((el) => el.classList.add('is-lit'));
+ return;
+ }
+
+ gsap.set(shoeStage, { opacity: 0 });
+ gsap.set(labels, { opacity: 0, y: 10, scale: 0.9, transformOrigin: '50% 50%' });
+
+ function playIntro() {
+ const tl = gsap.timeline({ defaults: { ease: 'power2.out' } });
+
+ // 1. Stage fades up, ambient glow already pulsing via CSS
+ tl.to(shoeStage, { opacity: 1, duration: 0.5 }, 0);
+
+ // 2. The horizon line is the whole scan effect: it lights up and
+ // grows outward from center, passing through the shoe and
+ // extending past its silhouette on both sides. The wireframe
+ // mesh reveals in step with it.
+ tl.to(horizon, { opacity: 0.85, duration: 0.2 }, 0.3)
+ .to(horizon, { scaleX: 1, duration: 0.6, ease: 'power2.out' }, 0.3)
+ .to(mesh, { opacity: 0.75, duration: 0.2 }, 0.3)
+ .to(meshClip, { '--reveal': '115%', duration: 0.6, ease: 'power2.out' }, 0.3);
+
+ // 3. Particles twinkle in across the same window the horizon grows
+ particles.forEach(({ el, left }) => {
+ const t = 0.3 + (left / 100) * 0.6;
+ tl.to(el, { opacity: 1, duration: 0.25 }, t)
+ .to(el, { opacity: 0.45, y: -4, duration: 0.5 }, t + 0.25);
+ });
+
+ // 4. Annotation labels appear one at a time, each a distinct beat
+ labels.forEach((label, i) => {
+ tl.to(label, { opacity: 1, y: 0, scale: 1, duration: 0.4, ease: 'back.out(1.7)' }, 0.45 + i * 0.32);
+ });
+
+ // 5. The horizon line hands off into a connector that carries the
+ // light down from the shoe stage into the tree's attachment point.
+ tl.to(flowLine, { opacity: 0.7, scaleY: 1, duration: 0.4, ease: 'power1.out' }, 1.1)
+ .fromTo(flowDot, { opacity: 0, top: '0%' }, { opacity: 1, top: '100%', duration: 0.4, ease: 'power1.in' }, 1.1)
+ .to(flowDot, { opacity: 0, duration: 0.15 }, 1.5);
+
+ // 6. The tree becomes visible, then each branch draws itself in
+ // center-out: the middle line first, then the two adjacent, then
+ // the two outer ones — each followed by its node flashing and
+ // its pill lighting up as the light "arrives".
+ const centerIndex = Math.floor(branchGroups.length / 2);
+ const STEP = 0.22;
+ const BRANCH_DUR = 0.9;
+
+ tl.addLabel('branches', 1.5)
+ .to(treeSvg, { opacity: 1, duration: 0.3 }, 'branches');
+
+ branchGroups.forEach((group, i) => {
+ const pos = `branches+=${(Math.abs(i - centerIndex) * STEP).toFixed(2)}`;
+ tl.fromTo(group,
+ { clipPath: 'inset(0% 0% 100% 0%)' },
+ { clipPath: 'inset(0% 0% 0% 0%)', duration: BRANCH_DUR, ease: 'power2.out' },
+ pos
+ );
+ // Opacity reveal is permanent; the scale punch alone yoyos back down.
+ tl.to(nodes[i], { opacity: 1, duration: 0.15 }, `${pos}+=${BRANCH_DUR - 0.15}`);
+ tl.fromTo(nodes[i],
+ { scale: 1 },
+ { scale: 1.5, duration: 0.18, yoyo: true, repeat: 1, ease: 'power2.out' },
+ `${pos}+=${BRANCH_DUR - 0.15}`
+ );
+ tl.call(() => routes[i].classList.add('is-lit'), null, `${pos}+=${(BRANCH_DUR - 0.05).toFixed(2)}`);
+ });
+
+ return tl;
+ }
+
+ if (typeof ScrollTrigger !== 'undefined') {
+ gsap.registerPlugin(ScrollTrigger);
+ ScrollTrigger.create({
+ trigger: section,
+ start: 'top 70%',
+ once: true,
+ onEnter: playIntro,
+ });
+ } else {
+ playIntro();
+ }
+});
diff --git a/skiply/skiply-fade.css b/skiply/skiply-fade.css
new file mode 100644
index 0000000..58a6511
--- /dev/null
+++ b/skiply/skiply-fade.css
@@ -0,0 +1,653 @@
+/* =============================================
+ Skiply.ai — Styles
+ Figma: Skiply.ai / node 172-173
+ ============================================= */
+
+/* ── Keyframes ──────────────────────────────── */
+@keyframes truckBounce {
+ from {
+ transform: translate(-50%, -50%) translateY(0px);
+ }
+
+ to {
+ transform: translate(-50%, -50%) translateY(-8px);
+ }
+}
+
+@keyframes dashFlow {
+ to {
+ stroke-dashoffset: -48;
+ }
+}
+
+@keyframes pulseGlow {
+
+ 0%,
+ 100% {
+ filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.2));
+ }
+
+ 50% {
+ filter: drop-shadow(0 4px 20px rgba(100, 160, 240, 0.55));
+ }
+}
+
+/* ── Reset & Tokens ─────────────────────────── */
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --navy: #0a1a37;
+ --navy-mid: #0d2247;
+ --light-bg: #f6f6f6;
+ --road-blue: #b8c8e8;
+ --road-blue-dark: #8fa8d0;
+ --text-dark: #0a1a37;
+ --text-white: #ffffff;
+
+ --font-main: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+
+ /* Typography scale matching Figma */
+ --fs-intro: 2.2rem;
+ /* "From" / "We take only" */
+ --fs-number: 11rem;
+ /* "30" */
+ --fs-number-lg: 12.5rem;
+ /* "15" */
+ --fs-unit: 3.1rem;
+ /* "Days" / "Seconds" */
+ --fs-desc: 1.5rem;
+ /* sub-description */
+ --fs-step-title: 1.5rem;
+ /* "Receive", "Inspect" … */
+ --fs-step-desc: 1.05rem;
+ /* descriptor text */
+
+ --panel-w: 312px;
+ --screen-h: 769px;
+}
+
+html {
+ font-family: var(--font-main);
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background: var(--light-bg);
+ color: var(--text-white);
+ overflow-x: hidden;
+}
+
+/* ── Screen wrapper ─────────────────────────── */
+.screen {
+ position: sticky;
+ top: 0;
+ width: 1320px;
+ height: var(--screen-h);
+ margin: 0 auto;
+ display: flex;
+ overflow: hidden;
+}
+
+/* ── Side Panel ─────────────────────────────── */
+.side-panel {
+ flex-shrink: 0;
+ width: var(--panel-w);
+ height: 100%;
+ background: var(--navy);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 48px 40px 48px 36px;
+ position: relative;
+ z-index: 2;
+}
+
+.side-panel__intro {
+ font-size: var(--fs-intro);
+ font-weight: 400;
+ line-height: 1;
+ margin-bottom: 4px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__number {
+ font-size: var(--fs-number);
+ font-weight: 300;
+ line-height: 0.85;
+ letter-spacing: -0.04em;
+ margin-bottom: 2px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__number--lg {
+ font-size: var(--fs-number-lg);
+}
+
+.side-panel__unit {
+ font-size: var(--fs-unit);
+ font-weight: 400;
+ line-height: 1;
+ margin-bottom: 24px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__desc {
+ font-size: var(--fs-desc);
+ font-weight: 400;
+ line-height: 1.45;
+ color: rgba(255, 255, 255, 0.65);
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+/* ── Main Content Area ──────────────────────── */
+.main-area {
+ flex: 1;
+ height: 100%;
+ background: var(--light-bg);
+ position: relative;
+ overflow: hidden;
+}
+
+/* ── Background Decorative Blobs ────────────── */
+.bg-blobs {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.blob {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0.35;
+ filter: blur(60px);
+}
+
+.screen--prob1 .blob--1 {
+ width: 600px;
+ height: 500px;
+ background: radial-gradient(ellipse, #a8bfe8 0%, transparent 70%);
+ right: -100px;
+ top: -80px;
+}
+
+.screen--prob1 .blob--2 {
+ width: 400px;
+ height: 350px;
+ background: radial-gradient(ellipse, #c0d4f0 0%, transparent 70%);
+ left: 100px;
+ bottom: -60px;
+}
+
+.screen--prob1 .blob--3 {
+ width: 350px;
+ height: 300px;
+ background: radial-gradient(ellipse, #d4e4f8 0%, transparent 70%);
+ right: 200px;
+ bottom: 100px;
+}
+
+.screen--prob2 .blob--1 {
+ width: 550px;
+ height: 450px;
+ background: radial-gradient(ellipse, #b0c8ec 0%, transparent 70%);
+ right: -80px;
+ top: -60px;
+}
+
+.screen--prob2 .blob--2 {
+ width: 380px;
+ height: 300px;
+ background: radial-gradient(ellipse, #c8daf4 0%, transparent 70%);
+ left: 60px;
+ bottom: -40px;
+}
+
+.screen--prob2 .blob--3 {
+ width: 300px;
+ height: 260px;
+ background: radial-gradient(ellipse, #e0eaf8 0%, transparent 70%);
+ left: 250px;
+ top: 120px;
+}
+
+/* ── Background shoe image (Prob 2) ─────────── */
+.shoe-bg {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.shoe-bg__img {
+ position: absolute;
+ width: 180%;
+ max-width: none;
+ left: -10%;
+ top: -10%;
+ opacity: 0.18;
+ filter: blur(3px);
+ object-fit: cover;
+}
+
+/* ════════════════════════════════════════════
+ PROB 1 — Road / Journey Layout
+ ════════════════════════════════════════════ */
+.road-container {
+ position: absolute;
+ inset: 0;
+}
+
+/* SVG Road */
+.road-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.road-fill {
+ filter: drop-shadow(0 4px 12px rgba(100, 140, 200, 0.3));
+}
+
+.road-dashes {
+ animation: dashFlow 2s linear infinite;
+}
+
+.flow-dashes {
+ animation: dashFlow 2s linear infinite;
+}
+
+/* Removed SVG connector class */
+
+/* ── Road SVG fills the container vertically centred ── */
+.road-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* ── Moving Car (truck) ────────────────────── */
+.road-car {
+ position: absolute;
+ width: 88px;
+ height: 88px;
+ z-index: 10;
+ /* JS will set left/top via transform */
+ transform: translate(-50%, -50%);
+ pointer-events: none;
+ opacity: 0;
+ will-change: transform;
+ filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.25));
+}
+
+.road-car__img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* ── Step Icons ─────────────────────────────── */
+.step-icon {
+ position: absolute;
+ width: 71px;
+ height: 71px;
+ z-index: 5;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ transition: transform 0.2s ease;
+}
+
+.step-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.step-icon:hover {
+ transform: translate(-50%, -50%) scale(1.12);
+}
+
+/*
+ Icon positions anchored to the real path geometry.
+ Road SVG viewBox = 983 × 473, rendered inside 983×769 area.
+ xMidYMid meet => rendered SVG height = 473px, top offset = (769-473)/2 = 148px.
+
+ screen_top% = (svgY + 148) / 769 * 100
+ Receive : (384+148)/769 = 69.2%
+ Sort : (449+148)/769 = 77.6%
+ Inspect : (100+148)/769 = 32.3%
+ Hold : ( 24+148)/769 = 22.4%
+ Route : (178+148)/769 = 42.4%
+
+ JS will override these with precise pixel positions after render.
+ These are fallback values only.
+*/
+.step-icon--receive {
+ left: 11.3%;
+ top: 69.2%;
+}
+
+.step-icon--sort {
+ left: 36.7%;
+ top: 77.6%;
+}
+
+.step-icon--inspect {
+ left: 49.5%;
+ top: 32.3%;
+}
+
+.step-icon--hold {
+ left: 75.2%;
+ top: 22.4%;
+}
+
+.step-icon--route {
+ left: 87.7%;
+ top: 42.4%;
+}
+
+/* ── Step Labels ─────────────────────────────── */
+.step-label {
+ position: absolute;
+ z-index: 6;
+ opacity: 0;
+ transform: translateY(8px);
+}
+
+.step-label--inspect,
+.step-label--sort,
+.step-label--route {
+ padding-left: 12px;
+}
+
+.step-label::before {
+ content: "";
+ position: absolute;
+ opacity: 0.5;
+}
+
+.step-label--receive::before {
+ right: -60px;
+ bottom: 100%;
+ width: calc(100% + 60px);
+ height: 190px;
+ border-bottom: 1.5px solid var(--navy);
+ border-right: 1.5px solid var(--navy);
+ background-color: transparent;
+}
+
+.step-label--inspect::before {
+ left: 0;
+ top: -10px;
+ width: 1.5px;
+ height: 360px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label--sort::before {
+ left: 0;
+ bottom: 100%;
+ width: 1.5px;
+ height: 400px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label--hold::before {
+ right: -60px;
+ top: -10px;
+ width: calc(100% + 60px);
+ height: 230px;
+ border-top: 1.5px solid var(--navy);
+ border-right: 1.5px solid var(--navy);
+ background-color: transparent;
+}
+
+.step-label--route::before {
+ left: 0;
+ bottom: 100%;
+ width: 1.5px;
+ height: 300px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label__title {
+ display: block;
+ font-size: var(--fs-step-title);
+ font-weight: 600;
+ color: var(--text-dark);
+ line-height: 1;
+ margin-bottom: 6px;
+}
+
+.step-label__desc {
+ display: block;
+ font-size: var(--fs-step-desc);
+ font-weight: 400;
+ color: var(--text-dark);
+ line-height: 1.45;
+ max-width: 185px;
+}
+
+/*
+ Label positions (pre-computed, same logic as icons).
+ Bottom labels (receive, sort, route): icon top% + ~5.7% (44px/769)
+ Top labels (inspect, hold): icon top% - ~11.7% (90px/769)
+*/
+.step-label--receive {
+ left: 7%;
+ top: 74.9%;
+}
+
+/* 69.2 + 5.7 */
+.step-label--sort {
+ left: 33%;
+ top: 83.3%;
+}
+
+/* 77.6 + 5.7 */
+.step-label--inspect {
+ left: 46%;
+ top: 20.6%;
+}
+
+/* 32.3 - 11.7 */
+.step-label--hold {
+ left: 71%;
+ top: 10.7%;
+}
+
+/* 22.4 - 11.7 */
+.step-label--route {
+ left: 84%;
+ top: 48.1%;
+}
+
+/* 42.4 + 5.7 */
+
+/* ════════════════════════════════════════════
+ PROB 2 — Scan → Decision Flow Layout
+ ════════════════════════════════════════════ */
+.flow-container {
+ position: absolute;
+ inset: 0;
+}
+
+/* SVG Flow Path */
+.flow-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* ── Shoe icon in center ────────────────────── */
+.flow-shoe-icon {
+ position: absolute;
+ width: 96px;
+ height: 96px;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%) rotate(-2.69deg);
+ z-index: 8;
+ opacity: 0;
+ filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.15));
+}
+
+.flow-shoe-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* ── Flow Nodes ─────────────────────────────── */
+.flow-node {
+ position: absolute;
+ width: 71px;
+ height: 71px;
+ z-index: 7;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ transition: transform 0.2s ease;
+}
+
+.flow-node img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.flow-node:hover {
+ transform: translate(-50%, -50%) scale(1.12);
+}
+
+/* Positions (% within 983px × 769px main area) */
+.flow-node--scan {
+ left: 29%;
+ top: 61%;
+}
+
+.flow-node--decision {
+ left: 67%;
+ top: 37%;
+}
+
+/* ── Flow Labels ─────────────────────────────── */
+.flow-label {
+ position: absolute;
+ z-index: 8;
+ opacity: 0;
+ transform: translateY(8px);
+ padding-left: 12px;
+}
+
+.flow-label::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 1.5px;
+ background-color: var(--navy);
+ opacity: 0.5;
+}
+
+.flow-label--scan::before {
+ top: -45px;
+ height: 45px;
+}
+
+.flow-label--decision::before {
+ bottom: -45px;
+ height: 45px;
+}
+
+.flow-label--scan {
+ left: 22%;
+ top: 70%;
+}
+
+.flow-label--decision {
+ left: 60%;
+ top: 20%;
+}
+
+/* ── Spacing between two screens ─────────────── */
+.screen+.screen {
+ margin-top: 0;
+ border-top: 2px solid rgba(255, 255, 255, 0.06);
+}
+
+/* ═══════════════════════════════════════════════
+ Responsive – scale for smaller viewports
+ ═══════════════════════════════════════════════ */
+@media (max-width: 1340px) {
+ .screen {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 1320 / 769;
+ }
+
+ :root {
+ --fs-number: 8vw;
+ --fs-number-lg: 9vw;
+ --fs-unit: 2.2vw;
+ --fs-intro: 1.6vw;
+ --fs-desc: 1.1vw;
+ --fs-step-title: 1.1vw;
+ --fs-step-desc: 0.8vw;
+ --panel-w: 23.6%;
+ }
+}
+
+@media (max-width: 768px) {
+ .screen {
+ flex-direction: column;
+ height: auto;
+ aspect-ratio: unset;
+ width: 100%;
+ }
+
+ .side-panel {
+ width: 100%;
+ height: auto;
+ padding: 40px 32px;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .main-area {
+ min-height: 420px;
+ width: 100%;
+ }
+
+ :root {
+ --fs-number: 20vw;
+ --fs-number-lg: 22vw;
+ --fs-unit: 8vw;
+ --fs-intro: 5vw;
+ --fs-desc: 3.5vw;
+ --fs-step-title: 3vw;
+ --fs-step-desc: 2.2vw;
+ }
+}
+
+
+
+
+
diff --git a/skiply/skiply-fade.html b/skiply/skiply-fade.html
new file mode 100644
index 0000000..a274507
--- /dev/null
+++ b/skiply/skiply-fade.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+ Skiply.ai – Smarter Shoe Processing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+ Receive
+ Our team will get back to you promptly
+
+
+ Sort
+ Quickly route products to the right resale channel.
+
+
+ Inspect
+ Assess condition quickly and consistently.
+
+
+ Hold
+ Pause uncertain items for a closer review.
+
+
+ Route
+ Direct every pair to the right next step.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skiply/skiply-fullpage.html b/skiply/skiply-fullpage.html
new file mode 100644
index 0000000..c8d2ff4
--- /dev/null
+++ b/skiply/skiply-fullpage.html
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+ Skiply.ai – Smarter Shoe Processing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+ Receive
+ Our team will get back to you promptly
+
+
+ Sort
+ Quickly route products to the right resale channel.
+
+
+ Inspect
+ Assess condition quickly and consistently.
+
+
+ Hold
+ Pause uncertain items for a closer review.
+
+
+ Route
+ Direct every pair to the right next step.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/skiply/skiply.css b/skiply/skiply.css
new file mode 100644
index 0000000..58a6511
--- /dev/null
+++ b/skiply/skiply.css
@@ -0,0 +1,653 @@
+/* =============================================
+ Skiply.ai — Styles
+ Figma: Skiply.ai / node 172-173
+ ============================================= */
+
+/* ── Keyframes ──────────────────────────────── */
+@keyframes truckBounce {
+ from {
+ transform: translate(-50%, -50%) translateY(0px);
+ }
+
+ to {
+ transform: translate(-50%, -50%) translateY(-8px);
+ }
+}
+
+@keyframes dashFlow {
+ to {
+ stroke-dashoffset: -48;
+ }
+}
+
+@keyframes pulseGlow {
+
+ 0%,
+ 100% {
+ filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.2));
+ }
+
+ 50% {
+ filter: drop-shadow(0 4px 20px rgba(100, 160, 240, 0.55));
+ }
+}
+
+/* ── Reset & Tokens ─────────────────────────── */
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --navy: #0a1a37;
+ --navy-mid: #0d2247;
+ --light-bg: #f6f6f6;
+ --road-blue: #b8c8e8;
+ --road-blue-dark: #8fa8d0;
+ --text-dark: #0a1a37;
+ --text-white: #ffffff;
+
+ --font-main: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+
+ /* Typography scale matching Figma */
+ --fs-intro: 2.2rem;
+ /* "From" / "We take only" */
+ --fs-number: 11rem;
+ /* "30" */
+ --fs-number-lg: 12.5rem;
+ /* "15" */
+ --fs-unit: 3.1rem;
+ /* "Days" / "Seconds" */
+ --fs-desc: 1.5rem;
+ /* sub-description */
+ --fs-step-title: 1.5rem;
+ /* "Receive", "Inspect" … */
+ --fs-step-desc: 1.05rem;
+ /* descriptor text */
+
+ --panel-w: 312px;
+ --screen-h: 769px;
+}
+
+html {
+ font-family: var(--font-main);
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background: var(--light-bg);
+ color: var(--text-white);
+ overflow-x: hidden;
+}
+
+/* ── Screen wrapper ─────────────────────────── */
+.screen {
+ position: sticky;
+ top: 0;
+ width: 1320px;
+ height: var(--screen-h);
+ margin: 0 auto;
+ display: flex;
+ overflow: hidden;
+}
+
+/* ── Side Panel ─────────────────────────────── */
+.side-panel {
+ flex-shrink: 0;
+ width: var(--panel-w);
+ height: 100%;
+ background: var(--navy);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 48px 40px 48px 36px;
+ position: relative;
+ z-index: 2;
+}
+
+.side-panel__intro {
+ font-size: var(--fs-intro);
+ font-weight: 400;
+ line-height: 1;
+ margin-bottom: 4px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__number {
+ font-size: var(--fs-number);
+ font-weight: 300;
+ line-height: 0.85;
+ letter-spacing: -0.04em;
+ margin-bottom: 2px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__number--lg {
+ font-size: var(--fs-number-lg);
+}
+
+.side-panel__unit {
+ font-size: var(--fs-unit);
+ font-weight: 400;
+ line-height: 1;
+ margin-bottom: 24px;
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.side-panel__desc {
+ font-size: var(--fs-desc);
+ font-weight: 400;
+ line-height: 1.45;
+ color: rgba(255, 255, 255, 0.65);
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+/* ── Main Content Area ──────────────────────── */
+.main-area {
+ flex: 1;
+ height: 100%;
+ background: var(--light-bg);
+ position: relative;
+ overflow: hidden;
+}
+
+/* ── Background Decorative Blobs ────────────── */
+.bg-blobs {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.blob {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0.35;
+ filter: blur(60px);
+}
+
+.screen--prob1 .blob--1 {
+ width: 600px;
+ height: 500px;
+ background: radial-gradient(ellipse, #a8bfe8 0%, transparent 70%);
+ right: -100px;
+ top: -80px;
+}
+
+.screen--prob1 .blob--2 {
+ width: 400px;
+ height: 350px;
+ background: radial-gradient(ellipse, #c0d4f0 0%, transparent 70%);
+ left: 100px;
+ bottom: -60px;
+}
+
+.screen--prob1 .blob--3 {
+ width: 350px;
+ height: 300px;
+ background: radial-gradient(ellipse, #d4e4f8 0%, transparent 70%);
+ right: 200px;
+ bottom: 100px;
+}
+
+.screen--prob2 .blob--1 {
+ width: 550px;
+ height: 450px;
+ background: radial-gradient(ellipse, #b0c8ec 0%, transparent 70%);
+ right: -80px;
+ top: -60px;
+}
+
+.screen--prob2 .blob--2 {
+ width: 380px;
+ height: 300px;
+ background: radial-gradient(ellipse, #c8daf4 0%, transparent 70%);
+ left: 60px;
+ bottom: -40px;
+}
+
+.screen--prob2 .blob--3 {
+ width: 300px;
+ height: 260px;
+ background: radial-gradient(ellipse, #e0eaf8 0%, transparent 70%);
+ left: 250px;
+ top: 120px;
+}
+
+/* ── Background shoe image (Prob 2) ─────────── */
+.shoe-bg {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.shoe-bg__img {
+ position: absolute;
+ width: 180%;
+ max-width: none;
+ left: -10%;
+ top: -10%;
+ opacity: 0.18;
+ filter: blur(3px);
+ object-fit: cover;
+}
+
+/* ════════════════════════════════════════════
+ PROB 1 — Road / Journey Layout
+ ════════════════════════════════════════════ */
+.road-container {
+ position: absolute;
+ inset: 0;
+}
+
+/* SVG Road */
+.road-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.road-fill {
+ filter: drop-shadow(0 4px 12px rgba(100, 140, 200, 0.3));
+}
+
+.road-dashes {
+ animation: dashFlow 2s linear infinite;
+}
+
+.flow-dashes {
+ animation: dashFlow 2s linear infinite;
+}
+
+/* Removed SVG connector class */
+
+/* ── Road SVG fills the container vertically centred ── */
+.road-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* ── Moving Car (truck) ────────────────────── */
+.road-car {
+ position: absolute;
+ width: 88px;
+ height: 88px;
+ z-index: 10;
+ /* JS will set left/top via transform */
+ transform: translate(-50%, -50%);
+ pointer-events: none;
+ opacity: 0;
+ will-change: transform;
+ filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.25));
+}
+
+.road-car__img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* ── Step Icons ─────────────────────────────── */
+.step-icon {
+ position: absolute;
+ width: 71px;
+ height: 71px;
+ z-index: 5;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ transition: transform 0.2s ease;
+}
+
+.step-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.step-icon:hover {
+ transform: translate(-50%, -50%) scale(1.12);
+}
+
+/*
+ Icon positions anchored to the real path geometry.
+ Road SVG viewBox = 983 × 473, rendered inside 983×769 area.
+ xMidYMid meet => rendered SVG height = 473px, top offset = (769-473)/2 = 148px.
+
+ screen_top% = (svgY + 148) / 769 * 100
+ Receive : (384+148)/769 = 69.2%
+ Sort : (449+148)/769 = 77.6%
+ Inspect : (100+148)/769 = 32.3%
+ Hold : ( 24+148)/769 = 22.4%
+ Route : (178+148)/769 = 42.4%
+
+ JS will override these with precise pixel positions after render.
+ These are fallback values only.
+*/
+.step-icon--receive {
+ left: 11.3%;
+ top: 69.2%;
+}
+
+.step-icon--sort {
+ left: 36.7%;
+ top: 77.6%;
+}
+
+.step-icon--inspect {
+ left: 49.5%;
+ top: 32.3%;
+}
+
+.step-icon--hold {
+ left: 75.2%;
+ top: 22.4%;
+}
+
+.step-icon--route {
+ left: 87.7%;
+ top: 42.4%;
+}
+
+/* ── Step Labels ─────────────────────────────── */
+.step-label {
+ position: absolute;
+ z-index: 6;
+ opacity: 0;
+ transform: translateY(8px);
+}
+
+.step-label--inspect,
+.step-label--sort,
+.step-label--route {
+ padding-left: 12px;
+}
+
+.step-label::before {
+ content: "";
+ position: absolute;
+ opacity: 0.5;
+}
+
+.step-label--receive::before {
+ right: -60px;
+ bottom: 100%;
+ width: calc(100% + 60px);
+ height: 190px;
+ border-bottom: 1.5px solid var(--navy);
+ border-right: 1.5px solid var(--navy);
+ background-color: transparent;
+}
+
+.step-label--inspect::before {
+ left: 0;
+ top: -10px;
+ width: 1.5px;
+ height: 360px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label--sort::before {
+ left: 0;
+ bottom: 100%;
+ width: 1.5px;
+ height: 400px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label--hold::before {
+ right: -60px;
+ top: -10px;
+ width: calc(100% + 60px);
+ height: 230px;
+ border-top: 1.5px solid var(--navy);
+ border-right: 1.5px solid var(--navy);
+ background-color: transparent;
+}
+
+.step-label--route::before {
+ left: 0;
+ bottom: 100%;
+ width: 1.5px;
+ height: 300px;
+ background-color: var(--navy);
+ border: none;
+}
+
+.step-label__title {
+ display: block;
+ font-size: var(--fs-step-title);
+ font-weight: 600;
+ color: var(--text-dark);
+ line-height: 1;
+ margin-bottom: 6px;
+}
+
+.step-label__desc {
+ display: block;
+ font-size: var(--fs-step-desc);
+ font-weight: 400;
+ color: var(--text-dark);
+ line-height: 1.45;
+ max-width: 185px;
+}
+
+/*
+ Label positions (pre-computed, same logic as icons).
+ Bottom labels (receive, sort, route): icon top% + ~5.7% (44px/769)
+ Top labels (inspect, hold): icon top% - ~11.7% (90px/769)
+*/
+.step-label--receive {
+ left: 7%;
+ top: 74.9%;
+}
+
+/* 69.2 + 5.7 */
+.step-label--sort {
+ left: 33%;
+ top: 83.3%;
+}
+
+/* 77.6 + 5.7 */
+.step-label--inspect {
+ left: 46%;
+ top: 20.6%;
+}
+
+/* 32.3 - 11.7 */
+.step-label--hold {
+ left: 71%;
+ top: 10.7%;
+}
+
+/* 22.4 - 11.7 */
+.step-label--route {
+ left: 84%;
+ top: 48.1%;
+}
+
+/* 42.4 + 5.7 */
+
+/* ════════════════════════════════════════════
+ PROB 2 — Scan → Decision Flow Layout
+ ════════════════════════════════════════════ */
+.flow-container {
+ position: absolute;
+ inset: 0;
+}
+
+/* SVG Flow Path */
+.flow-svg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* ── Shoe icon in center ────────────────────── */
+.flow-shoe-icon {
+ position: absolute;
+ width: 96px;
+ height: 96px;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%) rotate(-2.69deg);
+ z-index: 8;
+ opacity: 0;
+ filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.15));
+}
+
+.flow-shoe-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* ── Flow Nodes ─────────────────────────────── */
+.flow-node {
+ position: absolute;
+ width: 71px;
+ height: 71px;
+ z-index: 7;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ transition: transform 0.2s ease;
+}
+
+.flow-node img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.flow-node:hover {
+ transform: translate(-50%, -50%) scale(1.12);
+}
+
+/* Positions (% within 983px × 769px main area) */
+.flow-node--scan {
+ left: 29%;
+ top: 61%;
+}
+
+.flow-node--decision {
+ left: 67%;
+ top: 37%;
+}
+
+/* ── Flow Labels ─────────────────────────────── */
+.flow-label {
+ position: absolute;
+ z-index: 8;
+ opacity: 0;
+ transform: translateY(8px);
+ padding-left: 12px;
+}
+
+.flow-label::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 1.5px;
+ background-color: var(--navy);
+ opacity: 0.5;
+}
+
+.flow-label--scan::before {
+ top: -45px;
+ height: 45px;
+}
+
+.flow-label--decision::before {
+ bottom: -45px;
+ height: 45px;
+}
+
+.flow-label--scan {
+ left: 22%;
+ top: 70%;
+}
+
+.flow-label--decision {
+ left: 60%;
+ top: 20%;
+}
+
+/* ── Spacing between two screens ─────────────── */
+.screen+.screen {
+ margin-top: 0;
+ border-top: 2px solid rgba(255, 255, 255, 0.06);
+}
+
+/* ═══════════════════════════════════════════════
+ Responsive – scale for smaller viewports
+ ═══════════════════════════════════════════════ */
+@media (max-width: 1340px) {
+ .screen {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 1320 / 769;
+ }
+
+ :root {
+ --fs-number: 8vw;
+ --fs-number-lg: 9vw;
+ --fs-unit: 2.2vw;
+ --fs-intro: 1.6vw;
+ --fs-desc: 1.1vw;
+ --fs-step-title: 1.1vw;
+ --fs-step-desc: 0.8vw;
+ --panel-w: 23.6%;
+ }
+}
+
+@media (max-width: 768px) {
+ .screen {
+ flex-direction: column;
+ height: auto;
+ aspect-ratio: unset;
+ width: 100%;
+ }
+
+ .side-panel {
+ width: 100%;
+ height: auto;
+ padding: 40px 32px;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .main-area {
+ min-height: 420px;
+ width: 100%;
+ }
+
+ :root {
+ --fs-number: 20vw;
+ --fs-number-lg: 22vw;
+ --fs-unit: 8vw;
+ --fs-intro: 5vw;
+ --fs-desc: 3.5vw;
+ --fs-step-title: 3vw;
+ --fs-step-desc: 2.2vw;
+ }
+}
+
+
+
+
+
diff --git a/skiply/skiply.html b/skiply/skiply.html
new file mode 100644
index 0000000..6e3c00b
--- /dev/null
+++ b/skiply/skiply.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+ Skiply.ai – Smarter Shoe Processing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+ Receive
+ Our team will get back to you promptly
+
+
+ Sort
+ Quickly route products to the right resale channel.
+
+
+ Inspect
+ Assess condition quickly and consistently.
+
+
+ Hold
+ Pause uncertain items for a closer review.
+
+
+ Route
+ Direct every pair to the right next step.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skiply/tree-final-crop.png b/skiply/tree-final-crop.png
new file mode 100644
index 0000000..fdcaa6b
Binary files /dev/null and b/skiply/tree-final-crop.png differ
diff --git a/skiply/tree-final.png b/skiply/tree-final.png
new file mode 100644
index 0000000..58aa8aa
Binary files /dev/null and b/skiply/tree-final.png differ
diff --git a/skiply/tree-progressive-t1.png b/skiply/tree-progressive-t1.png
new file mode 100644
index 0000000..57d4f19
Binary files /dev/null and b/skiply/tree-progressive-t1.png differ
diff --git a/skiply/tree-progressive-t2.png b/skiply/tree-progressive-t2.png
new file mode 100644
index 0000000..c8e198d
Binary files /dev/null and b/skiply/tree-progressive-t2.png differ
diff --git a/skiply/tree-progressive-t3.png b/skiply/tree-progressive-t3.png
new file mode 100644
index 0000000..eb1f155
Binary files /dev/null and b/skiply/tree-progressive-t3.png differ
diff --git a/skiply/tree-progressive-t4.png b/skiply/tree-progressive-t4.png
new file mode 100644
index 0000000..3f34280
Binary files /dev/null and b/skiply/tree-progressive-t4.png differ