VibeCoderzVibeCoderz
Все промпты
Gesture First Navigation System UI Preview
mobile appnavigationlayout

Gesture First Navigation System

Система навигации для мобильных приложений, основанная на жестах. Уменьшает видимый интерфейс, переключает разделы свайпами для погружения.

Автор: Shirley LouЖивое превью

Prompt

# Gesture First Navigation System

Here is a reference implementation:

~~~html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Gesture Navigation System</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
    <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Manrope', sans-serif;
            overscroll-behavior: none;
            background-color: #f3f4f6;
        }
        /* Hide scrollbars for clean look */
        .no-scrollbar::-webkit-scrollbar { display: none; }
        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
        
        /* Physics-based transitions */
        .snap-transition {
            transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
        }

        /* Animation for gesture hints */
        @keyframes hint-bounce-x {
            0%, 100% { transform: translateX(0); opacity: 0; }
            20% { opacity: 0.6; }
            50% { transform: translateX(-12px); opacity: 0; }
        }
        @keyframes hint-bounce-y {
            0%, 100% { transform: translateY(0); opacity: 0; }
            20% { opacity: 0.6; }
            50% { transform: translateY(12px); opacity: 0; }
        }
        .hint-x { animation: hint-bounce-x 2.5s ease-in-out infinite; }
        .hint-y { animation: hint-bounce-y 2.5s ease-in-out infinite 1s; }

        /* Glass morphism */
        .glass {
            background: rgba(255, 255, 255, 0.8);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
        }
    </style>
</head>
<body>
    <!-- Main App Container -->
    <!-- touch-action: pan-y allows vertical scroll on children but we capture horizontal -->
    <div id="app" class="w-full h-screen bg-white overflow-hidden relative text-gray-900 shadow-2xl mx-auto" style="touch-action: none; max-width: 100%;">
        
        <!-- TOP ACTION LAYER (Hidden initially) -->
        <div id="action-layer" class="absolute inset-0 bg-gray-900 z-50 transform -translate-y-full text-white pt-14 pb-[34px] px-6 flex flex-col justify-between shadow-2xl" style="will-change: transform;">
            <div class="opacity-0 transition-opacity duration-500 delay-100" id="action-content">
                <div class="flex items-center justify-between mb-12">
                    <h2 class="text-3xl font-light tracking-tight">Quick Actions</h2>
                    <button id="close-actions" class="w-12 h-12 flex items-center justify-center bg-white/10 rounded-full hover:bg-white/20 active:scale-95 transition-all">
                        <iconify-icon icon="lucide:x" class="text-2xl"></iconify-icon>
                    </button>
                </div>
                
                <div class="grid grid-cols-2 gap-4">
                    <button class="h-32 bg-white/5 rounded-2xl flex flex-col items-center justify-center gap-3 hover:bg-white/10 active:scale-95 transition-all group">
                        <iconify-icon icon="lucide:filter" class="text-3xl text-gray-400 group-hover:text-white transition-colors"></iconify-icon>
                        <span class="font-medium text-sm text-gray-300">Filter Feed</span>
                    </button>
                    <button class="h-32 bg-white/5 rounded-2xl flex flex-col items-center justify-center gap-3 hover:bg-white/10 active:scale-95 transition-all group">
                        <iconify-icon icon="lucide:search" class="text-3xl text-gray-400 group-hover:text-white transition-colors"></iconify-icon>
                        <span class="font-medium text-sm text-gray-300">Search</span>
                    </button>
                    <button class="h-32 bg-white/5 rounded-2xl flex flex-col items-center justify-center gap-3 hover:bg-white/10 active:scale-95 transition-all group">
                        <iconify-icon icon="lucide:bell" class="text-3xl text-gray-400 group-hover:text-white transition-colors"></iconify-icon>
                        <span class="font-medium text-sm text-gray-300">Notifications</span>
                    </button>
                    <button class="h-32 bg-white/5 rounded-2xl flex flex-col items-center justify-center gap-3 hover:bg-white/10 active:scale-95 transition-all group">
                        <iconify-icon icon="lucide:settings-2" class="text-3xl text-gray-400 group-hover:text-white transition-colors"></iconify-icon>
                        <span class="font-medium text-sm text-gray-300">Preferences</span>
                    </button>
                </div>
            </div>
            
            <div class="flex flex-col items-center gap-2 pb-8 opacity-50">
                <iconify-icon icon="lucide:chevron-up" class="text-2xl animate-bounce"></iconify-icon>
                <span class="text-xs font-mono uppercase tracking-widest">Push up to close</span>
            </div>
        </div>

        <!-- CONTENT WRAPPER (Horizontal Slider) -->
        <div id="content-wrapper" class="flex h-full w-[300%] relative z-10" style="will-change: transform;">
            
            <!-- PAGE 1: FEED -->
            <div class="w-1/3 h-full pt-14 pb-[34px] px-6 flex flex-col bg-white overflow-hidden">
                <header class="shrink-0 mb-8">
                    <div class="flex justify-between items-center">
                        <span class="text-[10px] font-bold tracking-[0.2em] text-gray-400 uppercase">01 / 03</span>
                        <div class="w-2 h-2 rounded-full bg-red-500"></div>
                    </div>
                    <h1 class="text-4xl font-light mt-4 leading-tight tracking-tight">Daily<br>Insight</h1>
                </header>
                
                <div class="flex-1 overflow-y-auto no-scrollbar pb-10 space-y-8 scroll-container">
                    <article class="group">
                        <div class="w-full aspect-[16/10] bg-gray-100 mb-4 rounded-lg overflow-hidden relative">
                            <img src="https://images.unsplash.com/photo-1600607686527-6fb886090705?q=80&w=600&auto=format&fit=crop" class="w-full h-full object-cover opacity-90 group-hover:scale-105 transition-transform duration-700" alt="content">
                            <div class="absolute top-3 right-3 bg-white/90 backdrop-blur px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide">New</div>
                        </div>
                        <div class="flex justify-between items-start">
                            <h3 class="text-xl font-medium leading-snug w-3/4">Minimalism in the age of chaos</h3>
                            <iconify-icon icon="lucide:arrow-up-right" class="text-xl text-gray-400"></iconify-icon>
                        </div>
                        <p class="text-gray-500 text-sm mt-2 leading-relaxed line-clamp-2">How reducing visual noise can amplify digital experiences and improve user focus.</p>
                    </article>

                    <article class="group">
                        <div class="w-full aspect-[16/10] bg-gray-100 mb-4 rounded-lg overflow-hidden relative">
                            <img src="https://images.unsplash.com/photo-1506784983877-45594efa4cbe?q=80&w=600&auto=format&fit=crop" class="w-full h-full object-cover opacity-90 group-hover:scale-105 transition-transform duration-700" alt="content">
                        </div>
                        <div class="flex justify-between items-start">
                            <h3 class="text-xl font-medium leading-snug w-3/4">Natural forms</h3>
                            <iconify-icon icon="lucide:arrow-up-right" class="text-xl text-gray-400"></iconify-icon>
                        </div>
                        <p class="text-gray-500 text-sm mt-2 leading-relaxed line-clamp-2">Exploring organic shapes in rigid grid systems.</p>
                    </article>
                </div>
            </div>

            <!-- PAGE 2: EXPLORE -->
            <div class="w-1/3 h-full pt-14 pb-[34px] px-6 flex flex-col bg-gray-50 overflow-hidden">
                <header class="shrink-0 mb-8">
                    <div class="flex justify-between items-center">
                        <span class="text-[10px] font-bold tracking-[0.2em] text-gray-400 uppercase">02 / 03</span>
                    </div>
                    <h1 class="text-4xl font-light mt-4 leading-tight tracking-tight">Explore<br>Topics</h1>
                </header>

                <div class="flex-1 overflow-y-auto no-scrollbar pb-10 scroll-container">
                    <!-- Masonry-ish Grid -->
                    <div class="flex gap-4 mb-4">
                        <div class="w-1/2 aspect-[3/4] bg-white p-4 flex flex-col justify-between rounded-lg shadow-sm active:scale-95 transition-transform">
                            <iconify-icon icon="lucide:camera" class="text-2xl text-gray-300"></iconify-icon>
                            <span class="text-sm font-medium">Photography</span>
                        </div>
                        <div class="w-1/2 flex flex-col gap-4">
                             <div class="flex-1 bg-gray-900 p-4 flex flex-col justify-between rounded-lg shadow-sm text-white active:scale-95 transition-transform">
                                <iconify-icon icon="lucide:music" class="text-2xl text-gray-600"></iconify-icon>
                                <span class="text-sm font-medium">Music</span>
                            </div>
                             <div class="flex-1 bg-white p-4 flex flex-col justify-between rounded-lg shadow-sm active:scale-95 transition-transform">
                                <iconify-icon icon="lucide:pen-tool" class="text-2xl text-gray-300"></iconify-icon>
                                <span class="text-sm font-medium">Design</span>
                            </div>
                        </div>
                    </div>
                    
                    <div class="w-full aspect-video bg-white mb-4 rounded-lg p-6 flex flex-col justify-center gap-2 shadow-sm active:scale-95 transition-transform">
                        <span class="text-xs font-bold text-blue-600 uppercase tracking-widest">Featured Collection</span>
                        <h3 class="text-2xl font-light">Interface Dynamics</h3>
                        <p class="text-sm text-gray-400">12 curated items</p>
                    </div>

                    <div class="flex gap-4">
                        <div class="w-1/2 aspect-square bg-gray-200 p-4 flex flex-col justify-between rounded-lg active:scale-95 transition-transform">
                            <iconify-icon icon="lucide:code-2" class="text-2xl text-gray-500"></iconify-icon>
                            <span class="text-sm font-medium">Dev</span>
                        </div>
                        <div class="w-1/2 aspect-square bg-white p-4 flex flex-col justify-between rounded-lg shadow-sm active:scale-95 transition-transform">
                            <iconify-icon icon="lucide:globe" class="text-2xl text-gray-300"></iconify-icon>
                            <span class="text-sm font-medium">Web</span>
                        </div>
                    </div>
                </div>
            </div>

            <!-- PAGE 3: PROFILE -->
            <div class="w-1/3 h-full pt-14 pb-[34px] px-6 flex flex-col bg-white overflow-hidden">
                <header class="shrink-0 mb-8 flex justify-between items-start">
                    <div>
                        <span class="text-[10px] font-bold tracking-[0.2em] text-gray-400 uppercase">03 / 03</span>
                        <h1 class="text-4xl font-light mt-4 leading-tight tracking-tight">User<br>Profile</h1>
                    </div>
                    <div class="w-14 h-14 rounded-full bg-gray-100 overflow-hidden ring-4 ring-gray-50">
                        <img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=200&auto=format&fit=crop" class="w-full h-full object-cover grayscale" alt="profile">
                    </div>
                </header>

                <div class="flex gap-12 mb-10 shrink-0">
                    <div>
                        <div class="text-2xl font-light tabular-nums">842</div>
                        <div class="text-[10px] text-gray-400 uppercase tracking-widest mt-1">Following</div>
                    </div>
                    <div>
                        <div class="text-2xl font-light tabular-nums">12.5k</div>
                        <div class="text-[10px] text-gray-400 uppercase tracking-widest mt-1">Followers</div>
                    </div>
                </div>

                <div class="flex-1 border-t border-gray-100 pt-6 overflow-y-auto no-scrollbar scroll-container">
                    <h3 class="text-xs font-bold uppercase tracking-widest mb-6 text-gray-400">Saved Lists</h3>
                    <div class="space-y-3">
                        <div class="group flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors rounded-lg cursor-pointer">
                            <div class="flex items-center gap-4">
                                <div class="w-10 h-10 bg-white rounded flex items-center justify-center text-gray-400">
                                    <iconify-icon icon="lucide:bookmark"></iconify-icon>
                                </div>
                                <span class="font-medium">Reading List</span>
                            </div>
                            <span class="text-gray-400 text-xs font-mono">12</span>
                        </div>
                        
                        <div class="group flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors rounded-lg cursor-pointer">
                            <div class="flex items-center gap-4">
                                <div class="w-10 h-10 bg-white rounded flex items-center justify-center text-gray-400">
                                    <iconify-icon icon="lucide:heart"></iconify-icon>
                                </div>
                                <span class="font-medium">Liked Posts</span>
                            </div>
                            <span class="text-gray-400 text-xs font-mono">148</span>
                        </div>

                         <div class="group flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors rounded-lg cursor-pointer">
                            <div class="flex items-center gap-4">
                                <div class="w-10 h-10 bg-white rounded flex items-center justify-center text-gray-400">
                                    <iconify-icon icon="lucide:archive"></iconify-icon>
                                </div>
                                <span class="font-medium">Archive</span>
                            </div>
                            <span class="text-gray-400 text-xs font-mono">4</span>
                        </div>
                    </div>
                </div>
            </div>

        </div>

        <!-- OVERLAYS & CONTROLS -->
        
        <!-- Pagination Indicators -->
        <div class="absolute bottom-8 left-0 w-full flex justify-center gap-4 z-30 pointer-events-none">
            <div id="dot-0" class="h-1 rounded-full bg-gray-900 transition-all duration-300 w-8"></div>
            <div id="dot-1" class="h-1 rounded-full bg-gray-200 transition-all duration-300 w-2"></div>
            <div id="dot-2" class="h-1 rounded-full bg-gray-200 transition-all duration-300 w-2"></div>
        </div>

        <!-- Initial Gesture Hints -->
        <div id="hints" class="absolute inset-0 pointer-events-none z-40 transition-opacity duration-700">
            <div class="absolute top-1/2 right-6 -translate-y-1/2 flex flex-col items-end opacity-0 hint-x">
                <iconify-icon icon="lucide:arrow-left" class="text-3xl text-gray-400"></iconify-icon>
            </div>
            <div class="absolute top-14 left-0 w-full flex justify-center opacity-0 hint-y">
                <div class="flex flex-col items-center gap-2">
                    <div class="w-12 h-1 bg-gray-200 rounded-full"></div>
                    <span class="text-[10px] uppercase tracking-widest text-gray-400 font-medium">Pull Down</span>
                </div>
            </div>
        </div>

        <!-- Depth Shadows -->
        <div class="absolute top-0 bottom-0 left-0 w-8 bg-gradient-to-r from-black/5 to-transparent pointer-events-none z-20"></div>
        <div class="absolute top-0 bottom-0 right-0 w-8 bg-gradient-to-l from-black/5 to-transparent pointer-events-none z-20"></div>

    </div>

    <script>
        /* 
           GESTURE NAVIGATION CORE 
           Handles horizontal swipes for pages and vertical pull for action layer.
        */
        
        // State
        let state = {
            page: 0,
            isDragging: false,
            startX: 0,
            startY: 0,
            direction: null, // 'horizontal' or 'vertical'
            isActionOpen: false
        };

        // Configuration
        const config = {
            snapThreshold: window.innerWidth * 0.25,
            verticalThreshold: 120,
            dragResistance: 0.3
        };

        // DOM Elements
        const els = {
            app: document.getElementById('app'),
            content: document.getElementById('content-wrapper'),
            actions: document.getElementById('action-layer'),
            actionContent: document.getElementById('action-content'),
            closeBtn: document.getElementById('close-actions'),
            hints: document.getElementById('hints'),
            dots: [0, 1, 2].map(i => document.getElementById(`dot-${i}`))
        };

        // Lifecycle & Hints
        setTimeout(() => {
            if (els.hints) {
                els.hints.querySelectorAll('div').forEach(el => el.classList.remove('opacity-0'));
            }
        }, 500);

        const removeHints = () => {
            const hintsEl = els.hints;
            if(hintsEl) {
                hintsEl.style.opacity = '0';
                setTimeout(() => hintsEl.remove(), 700);
                els.hints = null;
            }
        };

        // UI Updates
        const updatePagination = () => {
            els.dots.forEach((dot, idx) => {
                if (idx === state.page) {
                    dot.className = 'h-1 rounded-full bg-gray-900 transition-all duration-300 w-8';
                } else {
                    dot.className = 'h-1 rounded-full bg-gray-200 transition-all duration-300 w-2';
                }
            });
        };

        const updateActionLayerOpacity = (percent) => {
            // Fade in content as we pull down
            const opacity = Math.min(Math.max(percent, 0), 1);
            els.actionContent.style.opacity = opacity;
        };

        // Input Handlers
        const onPointerDown = (e) => {
            removeHints();
            state.isDragging = true;
            state.startX = e.clientX;
            state.startY = e.clientY;
            state.direction = null;
            
            // Disable transitions for direct 1:1 movement
            els.content.classList.remove('snap-transition');
            els.actions.classList.remove('snap-transition');
        };

        const onPointerMove = (e) => {
            if (!state.isDragging) return;

            const dx = e.clientX - state.startX;
            const dy = e.clientY - state.startY;

            // Determine Gesture Direction (Lock)
            if (!state.direction) {
                if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 5) state.direction = 'horizontal';
                else if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 5) state.direction = 'vertical';
            }

            if (state.direction === 'horizontal') {
                if (state.isActionOpen) return; // Lock horiz swipe if actions open
                
                // Prevent default browser nav actions
                if (e.cancelable) e.preventDefault();

                const baseTranslate = -state.page * els.app.clientWidth;
                let moveX = baseTranslate + dx;
                
                // Resistance at edges
                if ((state.page === 0 && dx > 0) || (state.page === 2 && dx < 0)) {
                    moveX = baseTranslate + (dx * config.dragResistance);
                }
                
                els.content.style.transform = `translateX(${moveX}px)`;

            } else if (state.direction === 'vertical') {
                // Logic for Pull Down Actions
                
                // Only trigger pull down if we are at top of scroll or action layer is open
                // For this demo, we assume pull down works from header area mostly, but we'll allow it generally
                // if the intention is clearly DOWN and we are at top.
                
                // Simple check: if dragging DOWN and not open, OR dragging UP and open
                if (!state.isActionOpen && dy > 0) {
                     // Opening
                     const moveY = -els.app.clientHeight + dy;
                     // Only move if we are actually pulling down
                     if (moveY < 0) {
                         els.actions.style.transform = `translateY(${moveY}px)`;
                         updateActionLayerOpacity(dy / (els.app.clientHeight * 0.5));
                     }
                } else if (state.isActionOpen) {
                    // Closing (dragging up)
                    let moveY = dy;
                    if (moveY > 0) moveY *= 0.2; // Resistance if pulling down more
                    els.actions.style.transform = `translateY(${moveY}px)`;
                }
            }
        };

        const onPointerUp = (e) => {
            if (!state.isDragging) return;
            state.isDragging = false;
            
            // Restore smooth physics
            els.content.classList.add('snap-transition');
            els.actions.classList.add('snap-transition');

            const dx = e.clientX - state.startX;
            const dy = e.clientY - state.startY;

            if (state.direction === 'horizontal') {
                if (Math.abs(dx) > config.snapThreshold) {
                    if (dx > 0 && state.page > 0) state.page--;
                    else if (dx < 0 && state.page < 2) state.page++;
                }
                els.content.style.transform = `translateX(-${state.page * (100 / 3)}%)`;
                updatePagination();

            } else if (state.direction === 'vertical') {
                if (state.isActionOpen) {
                    // Close if dragged up significantly
                    if (dy < -config.verticalThreshold) {
                        state.isActionOpen = false;
                        els.actions.style.transform = 'translateY(-100%)';
                        els.actionContent.style.opacity = '0';
                    } else {
                        els.actions.style.transform = 'translateY(0)'; // Snap back open
                    }
                } else {
                    // Open if dragged down significantly
                    if (dy > config.verticalThreshold) {
                        state.isActionOpen = true;
                        els.actions.style.transform = 'translateY(0)';
                        els.actionContent.style.opacity = '1';
                    } else {
                        els.actions.style.transform = 'translateY(-100%)'; // Snap back closed
                        els.actionContent.style.opacity = '0';
                    }
                }
            }
            
            state.direction = null;
        };

        // Bind Events
        els.app.addEventListener('pointerdown', onPointerDown);
        els.app.addEventListener('pointermove', onPointerMove);
        els.app.addEventListener('pointerup', onPointerUp);
        els.app.addEventListener('pointerleave', onPointerUp);

        // Accessibility / Mouse Fallbacks
        els.closeBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            state.isActionOpen = false;
            els.actions.classList.add('snap-transition');
            els.actions.style.transform = 'translateY(-100%)';
            els.actionContent.style.opacity = '0';
        });

    </script>
</body>
</html>
~~~
Все промпты