dostum bana post tekrarlamayan veya benzer postları tek tek göstermeyen ve kullanıcı etkileşimine bağlı olarak dinamik akış değiştiren kişiye özel akışı olan instagram benzeri bir sosyal medya uygulaması kodlar mısın?
Browse files- README.md +8 -5
- components/footer.js +98 -0
- components/navbar.js +96 -0
- index.html +116 -19
- script.js +366 -0
- style.css +108 -19
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: FlowFlex - Personalized Social Stream 🌊
|
| 3 |
+
colorFrom: yellow
|
| 4 |
+
colorTo: blue
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
components/footer.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
margin-top: auto;
|
| 10 |
+
}
|
| 11 |
+
footer {
|
| 12 |
+
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
| 13 |
+
border-top: 1px solid #e2e8f0;
|
| 14 |
+
}
|
| 15 |
+
.footer-link {
|
| 16 |
+
transition: color 0.2s ease;
|
| 17 |
+
}
|
| 18 |
+
.footer-link:hover {
|
| 19 |
+
color: #0ea5e9;
|
| 20 |
+
}
|
| 21 |
+
</style>
|
| 22 |
+
<footer class="bg-gray-50 border-t border-gray-200 mt-12">
|
| 23 |
+
<div class="container mx-auto px-4 py-8">
|
| 24 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 25 |
+
<!-- Company Info -->
|
| 26 |
+
<div class="col-span-1">
|
| 27 |
+
<div class="flex items-center space-x-2 mb-4">
|
| 28 |
+
<div class="w-6 h-6 bg-gradient-to-r from-primary-500 to-secondary-500 rounded flex items-center justify-center">
|
| 29 |
+
<i data-feather="zap" class="w-3 h-3 text-white"></i>
|
| 30 |
+
</div>
|
| 31 |
+
<span class="text-lg font-bold text-gray-900">FlowFlex</span>
|
| 32 |
+
<p class="text-gray-600 text-sm mt-2">Your personalized social stream without the repetition.</p>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<!-- Quick Links -->
|
| 36 |
+
<div>
|
| 37 |
+
<h4 class="font-semibold text-gray-900 mb-4">Quick Links</h4>
|
| 38 |
+
<ul class="space-y-2">
|
| 39 |
+
<li><a href="/about" class="footer-link text-gray-600 text-sm hover:text-primary-500">About</a></li>
|
| 40 |
+
<li><a href="/careers" class="footer-link text-gray-600 text-sm hover:text-primary-500">Careers</a></li>
|
| 41 |
+
<li><a href="/press" class="footer-link text-gray-600 text-sm hover:text-primary-500">Press</a></li>
|
| 42 |
+
<li><a href="/blog" class="footer-link text-gray-600 text-sm hover:text-primary-500">Blog</a></li>
|
| 43 |
+
<li><a href="/help" class="footer-link text-gray-600 text-sm hover:text-primary-500">Help</a></li>
|
| 44 |
+
</ul>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<!-- Legal -->
|
| 48 |
+
<div>
|
| 49 |
+
<h4 class="font-semibold text-gray-900 mb-4">Legal</h4>
|
| 50 |
+
<ul class="space-y-2">
|
| 51 |
+
<li><a href="/privacy" class="footer-link text-gray-600 text-sm hover:text-primary-500">Privacy</a></li>
|
| 52 |
+
<li><a href="/terms" class="footer-link text-gray-600 text-sm hover:text-primary-500">Terms</a></li>
|
| 53 |
+
<li><a href="/cookies" class="footer-link text-gray-600 text-sm hover:text-primary-500">Cookies</a></li>
|
| 54 |
+
<li><a href="/safety" class="footer-link text-gray-600 text-sm hover:text-primary-500">Safety</a></li>
|
| 55 |
+
<li><a href="/guidelines" class="footer-link text-gray-600 text-sm hover:text-primary-500">Guidelines</a></li>
|
| 56 |
+
</ul>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<!-- Social -->
|
| 60 |
+
<div>
|
| 61 |
+
<h4 class="font-semibold text-gray-900 mb-4">Connect</h4>
|
| 62 |
+
<div class="flex space-x-4">
|
| 63 |
+
<a href="#" class="footer-link text-gray-600 hover:text-primary-500">
|
| 64 |
+
<i data-feather="twitter" class="w-5 h-5"></i>
|
| 65 |
+
</a>
|
| 66 |
+
<a href="#" class="footer-link text-gray-600 hover:text-primary-500">
|
| 67 |
+
<i data-feather="instagram" class="w-5 h-5"></i>
|
| 68 |
+
</a>
|
| 69 |
+
<a href="#" class="footer-link text-gray-600 hover:text-primary-500">
|
| 70 |
+
<i data-feather="facebook" class="w-5 h-5"></i>
|
| 71 |
+
</a>
|
| 72 |
+
<a href="#" class="footer-link text-gray-600 hover:text-primary-500">
|
| 73 |
+
<i data-feather="github" class="w-5 h-5"></i>
|
| 74 |
+
</a>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<!-- Bottom Section -->
|
| 80 |
+
<div class="border-t border-gray-200 mt-8 pt-6">
|
| 81 |
+
<div class="flex flex-col md:flex-row justify-between items-center">
|
| 82 |
+
<p class="text-gray-500 text-sm">© 2024 FlowFlex. All rights reserved.</p>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</footer>
|
| 87 |
+
`;
|
| 88 |
+
|
| 89 |
+
// Initialize Feather Icons for shadow DOM
|
| 90 |
+
setTimeout(() => {
|
| 91 |
+
if (this.shadowRoot.querySelector('[data-feather]')) {
|
| 92 |
+
feather.replace({}, this.shadowRoot);
|
| 93 |
+
}
|
| 94 |
+
}, 100);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
customElements.define('custom-footer', CustomFooter);
|
components/navbar.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
position: sticky;
|
| 10 |
+
top: 0;
|
| 11 |
+
z-index: 40;
|
| 12 |
+
}
|
| 13 |
+
nav {
|
| 14 |
+
background: rgba(255, 255, 255, 0.95);
|
| 15 |
+
backdrop-filter: blur(8px);
|
| 16 |
+
border-bottom: 1px solid #e5e7eb;
|
| 17 |
+
}
|
| 18 |
+
.nav-link {
|
| 19 |
+
transition: all 0.2s ease;
|
| 20 |
+
}
|
| 21 |
+
.nav-link:hover {
|
| 22 |
+
color: #0ea5e9;
|
| 23 |
+
}
|
| 24 |
+
.search-input:focus {
|
| 25 |
+
outline: none;
|
| 26 |
+
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2);
|
| 27 |
+
}
|
| 28 |
+
@media (max-width: 768px) {
|
| 29 |
+
.nav-items {
|
| 30 |
+
display: none;
|
| 31 |
+
}
|
| 32 |
+
.mobile-menu {
|
| 33 |
+
display: block;
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
</style>
|
| 37 |
+
<nav class="bg-white/95 backdrop-blur-sm border-b border-gray-200">
|
| 38 |
+
<div class="container mx-auto px-4 py-3">
|
| 39 |
+
<div class="flex items-center justify-between">
|
| 40 |
+
<!-- Logo -->
|
| 41 |
+
<a href="/" class="flex items-center space-x-2">
|
| 42 |
+
<div class="w-8 h-8 bg-gradient-to-r from-primary-500 to-secondary-500 rounded-lg flex items-center justify-center">
|
| 43 |
+
<i data-feather="zap" class="w-4 h-4 text-white"></i>
|
| 44 |
+
</div>
|
| 45 |
+
<span class="text-xl font-bold text-gray-900">FlowFlex</span>
|
| 46 |
+
</a>
|
| 47 |
+
|
| 48 |
+
<!-- Search Bar -->
|
| 49 |
+
<div class="flex-1 max-w-md mx-8">
|
| 50 |
+
<div class="relative">
|
| 51 |
+
<input type="text"
|
| 52 |
+
placeholder="Search posts, people, tags..."
|
| 53 |
+
class="search-input w-full px-4 py-2 bg-gray-100 rounded-full text-sm focus:bg-white focus:ring-2 focus:ring-primary-200 transition-all duration-200">
|
| 54 |
+
<i data-feather="search" class="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400">
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<!-- Navigation Items -->
|
| 59 |
+
<div class="nav-items flex items-center space-x-6">
|
| 60 |
+
<a href="/" class="nav-link flex flex-col items-center text-primary-500">
|
| 61 |
+
<i data-feather="home" class="w-5 h-5"></i>
|
| 62 |
+
</a>
|
| 63 |
+
<a href="/discover" class="nav-link flex flex-col items-center text-gray-600 hover:text-primary-500">
|
| 64 |
+
<i data-feather="compass" class="w-5 h-5"></i>
|
| 65 |
+
</a>
|
| 66 |
+
<a href="/messages" class="nav-link flex flex-col items-center text-gray-600 hover:text-primary-500">
|
| 67 |
+
<i data-feather="message-square" class="w-5 h-5"></i>
|
| 68 |
+
</a>
|
| 69 |
+
<a href="/notifications" class="nav-link flex flex-col items-center text-gray-600 hover:text-primary-500">
|
| 70 |
+
<i data-feather="bell" class="w-5 h-5"></i>
|
| 71 |
+
</a>
|
| 72 |
+
<a href="/profile" class="flex items-center space-x-2">
|
| 73 |
+
<img src="http://static.photos/people/32x32/1" alt="Profile" class="w-8 h-8 rounded-full border-2 border-primary-500">
|
| 74 |
+
</a>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<!-- Mobile Menu Button -->
|
| 78 |
+
<button class="mobile-menu hidden md:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors duration-200">
|
| 79 |
+
<i data-feather="menu" class="w-5 h-5 text-gray-600"></i>
|
| 80 |
+
</button>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</nav>
|
| 84 |
+
`;
|
| 85 |
+
|
| 86 |
+
// Initialize Feather Icons for shadow DOM
|
| 87 |
+
setTimeout(() => {
|
| 88 |
+
if (this.shadowRoot.querySelector('[data-feather]')) {
|
| 89 |
+
feather.replace({}, this.shadowRoot);
|
| 90 |
+
}
|
| 91 |
+
}, 100);
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
customElements.define('custom-navbar', CustomNavbar);
|
index.html
CHANGED
|
@@ -1,19 +1,116 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FlowFlex - Your Personalized Social Stream</title>
|
| 7 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 8 |
+
<link rel="stylesheet" href="style.css">
|
| 9 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 12 |
+
<script>
|
| 13 |
+
tailwind.config = {
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
primary: {
|
| 18 |
+
50: '#f0f9ff',
|
| 19 |
+
100: '#e0f2fe',
|
| 20 |
+
200: '#bae6fd',
|
| 21 |
+
300: '#7dd3fc',
|
| 22 |
+
400: '#38bdf8',
|
| 23 |
+
500: '#0ea5e9',
|
| 24 |
+
600: '#0284c7',
|
| 25 |
+
700: '#0369a1',
|
| 26 |
+
800: '#075985',
|
| 27 |
+
900: '#0c4a6e',
|
| 28 |
+
},
|
| 29 |
+
secondary: {
|
| 30 |
+
50: '#fdf4ff',
|
| 31 |
+
100: '#fae8ff',
|
| 32 |
+
200: '#f5d0fe',
|
| 33 |
+
300: '#f0abfc',
|
| 34 |
+
400: '#e879f9',
|
| 35 |
+
500: '#d946ef',
|
| 36 |
+
600: '#c026d3',
|
| 37 |
+
700: '#a21caf',
|
| 38 |
+
800: '#86198f',
|
| 39 |
+
900: '#701a75',
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
</script>
|
| 46 |
+
</head>
|
| 47 |
+
<body class="bg-gray-50 min-h-screen">
|
| 48 |
+
<custom-navbar></custom-navbar>
|
| 49 |
+
|
| 50 |
+
<main class="container mx-auto px-4 py-6 max-w-6xl">
|
| 51 |
+
<!-- Feed Filter Controls -->
|
| 52 |
+
<div class="mb-6 bg-white rounded-xl shadow-sm p-4">
|
| 53 |
+
<div class="flex flex-wrap gap-4 items-center justify-between">
|
| 54 |
+
<h2 class="text-xl font-semibold text-gray-800">Your Personalized Feed</h2>
|
| 55 |
+
<div class="flex flex-wrap gap-2">
|
| 56 |
+
<button class="feed-filter-btn active px-4 py-2 rounded-full bg-primary-500 text-white text-sm font-medium transition-all duration-200 hover:bg-primary-600" data-filter="trending">
|
| 57 |
+
<i data-feather="trending-up" class="w-4 h-4 mr-2"></i>
|
| 58 |
+
Trending
|
| 59 |
+
</button>
|
| 60 |
+
<button class="feed-filter-btn px-4 py-2 rounded-full bg-gray-200 text-gray-700 text-sm font-medium transition-all duration-200 hover:bg-gray-300" data-filter="following">
|
| 61 |
+
<i data-feather="users" class="w-4 h-4 mr-2"></i>
|
| 62 |
+
Following
|
| 63 |
+
</button>
|
| 64 |
+
<button class="feed-filter-btn px-4 py-2 rounded-full bg-gray-200 text-gray-700 text-sm font-medium transition-all duration-200 hover:bg-gray-300" data-filter="discover">
|
| 65 |
+
<i data-feather="compass" class="w-4 h-4 mr-2"></i>
|
| 66 |
+
Discover
|
| 67 |
+
</button>
|
| 68 |
+
<button class="feed-filter-btn px-4 py-2 rounded-full bg-gray-200 text-gray-700 text-sm font-medium transition-all duration-200 hover:bg-gray-300" data-filter="personalized">
|
| 69 |
+
<i data-feather="sparkles" class="w-4 h-4 mr-2"></i>
|
| 70 |
+
For You
|
| 71 |
+
</button>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Feed Content -->
|
| 77 |
+
<div id="feed-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 78 |
+
<!-- Posts will be dynamically loaded here -->
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<!-- Loading State -->
|
| 82 |
+
<div id="loading" class="text-center py-8">
|
| 83 |
+
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500 mx-auto"></div>
|
| 84 |
+
<p class="text-gray-600 mt-2">Loading your personalized feed...</p>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<!-- Empty State -->
|
| 88 |
+
<div id="empty-state" class="hidden text-center py-12">
|
| 89 |
+
<div class="max-w-md mx-auto">
|
| 90 |
+
<i data-feather="inbox" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
| 91 |
+
<h3 class="text-lg font-medium text-gray-900 mb-2">No posts yet</h3>
|
| 92 |
+
<p class="text-gray-600 mb-6">Start following people or explore trending content to see posts here.</p>
|
| 93 |
+
<a href="/discover" class="inline-flex items-center px-6 py-3 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors duration-200">
|
| 94 |
+
<i data-feather="compass" class="w-4 h-4 mr-2"></i>
|
| 95 |
+
Explore Content
|
| 96 |
+
</a>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
</main>
|
| 100 |
+
|
| 101 |
+
<custom-footer></custom-footer>
|
| 102 |
+
|
| 103 |
+
<!-- Web Components -->
|
| 104 |
+
<script src="components/navbar.js"></script>
|
| 105 |
+
<script src="components/footer.js"></script>
|
| 106 |
+
|
| 107 |
+
<!-- Main Script -->
|
| 108 |
+
<script src="script.js"></script>
|
| 109 |
+
|
| 110 |
+
<!-- Initialize Feather Icons -->
|
| 111 |
+
<script>
|
| 112 |
+
feather.replace();
|
| 113 |
+
</script>
|
| 114 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 115 |
+
</body>
|
| 116 |
+
</html>
|
script.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// FlowFlex - Main JavaScript
|
| 2 |
+
class FlowFlexApp {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.currentFeed = 'trending';
|
| 5 |
+
this.posts = [];
|
| 6 |
+
this.seenPosts = new Set();
|
| 7 |
+
this.userPreferences = this.getUserPreferences();
|
| 8 |
+
this.isLoading = false;
|
| 9 |
+
this.page = 1;
|
| 10 |
+
|
| 11 |
+
this.init();
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
init() {
|
| 15 |
+
this.bindEvents();
|
| 16 |
+
this.loadInitialFeed();
|
| 17 |
+
this.setupIntersectionObserver();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
bindEvents() {
|
| 21 |
+
// Feed filter buttons
|
| 22 |
+
document.querySelectorAll('.feed-filter-btn').forEach(btn => {
|
| 23 |
+
btn.addEventListener('click', (e) => {
|
| 24 |
+
this.switchFeed(e.target.closest('.feed-filter-btn').dataset.filter);
|
| 25 |
+
});
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
// Like button functionality
|
| 29 |
+
document.addEventListener('click', (e) => {
|
| 30 |
+
if (e.target.closest('.like-btn')) {
|
| 31 |
+
this.handleLike(e.target.closest('.like-btn'));
|
| 32 |
+
}
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
// Save post functionality
|
| 36 |
+
document.addEventListener('click', (e) => {
|
| 37 |
+
if (e.target.closest('.save-btn')) {
|
| 38 |
+
this.handleSave(e.target.closest('.save-btn'));
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
// Post interaction tracking
|
| 43 |
+
document.addEventListener('click', (e) => {
|
| 44 |
+
const postCard = e.target.closest('.post-card');
|
| 45 |
+
if (postCard) {
|
| 46 |
+
this.trackPostInteraction(postCard.dataset.postId);
|
| 47 |
+
}
|
| 48 |
+
});
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
switchFeed(feedType) {
|
| 52 |
+
// Update active button
|
| 53 |
+
document.querySelectorAll('.feed-filter-btn').forEach(btn => {
|
| 54 |
+
btn.classList.remove('active', 'bg-primary-500', 'text-white');
|
| 55 |
+
btn.classList.add('bg-gray-200', 'text-gray-700');
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
const activeBtn = document.querySelector(`[data-filter="${feedType}"]`);
|
| 59 |
+
activeBtn.classList.remove('bg-gray-200', 'text-gray-700');
|
| 60 |
+
activeBtn.classList.add('active', 'bg-primary-500', 'text-white');
|
| 61 |
+
|
| 62 |
+
this.currentFeed = feedType;
|
| 63 |
+
this.page = 1;
|
| 64 |
+
this.posts = [];
|
| 65 |
+
this.loadFeed();
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
async loadInitialFeed() {
|
| 69 |
+
await this.loadFeed();
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
async loadFeed() {
|
| 73 |
+
if (this.isLoading) return;
|
| 74 |
+
|
| 75 |
+
this.isLoading = true;
|
| 76 |
+
this.showLoading();
|
| 77 |
+
|
| 78 |
+
try {
|
| 79 |
+
// Simulate API call with timeout
|
| 80 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 81 |
+
|
| 82 |
+
const newPosts = await this.fetchPosts(this.currentFeed, this.page);
|
| 83 |
+
const filteredPosts = this.filterDuplicatePosts(newPosts);
|
| 84 |
+
|
| 85 |
+
if (filteredPosts.length > 0) {
|
| 86 |
+
this.posts = [...this.posts, ...filteredPosts];
|
| 87 |
+
this.renderPosts(filteredPosts);
|
| 88 |
+
this.page++;
|
| 89 |
+
} else {
|
| 90 |
+
this.showEmptyState();
|
| 91 |
+
}
|
| 92 |
+
} catch (error) {
|
| 93 |
+
console.error('Error loading feed:', error);
|
| 94 |
+
this.showError('Failed to load posts. Please try again.');
|
| 95 |
+
} finally {
|
| 96 |
+
this.isLoading = false;
|
| 97 |
+
this.hideLoading();
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
async fetchPosts(feedType, page = 1) {
|
| 102 |
+
// Using Unsplash API for demo posts (real implementation would use your backend)
|
| 103 |
+
const accessKey = 'YOUR_UNSPLASH_ACCESS_KEY'; // Replace with actual key
|
| 104 |
+
const endpoints = {
|
| 105 |
+
trending: `https://api.unsplash.com/photos?page=${page}&per_page=9&order_by=popular`,
|
| 106 |
+
following: `https://api.unsplash.com/photos?page=${page}&per_page=9&order_by=latest`,
|
| 107 |
+
discover: `https://api.unsplash.com/photos/random?count=9`,
|
| 108 |
+
personalized: `https://api.unsplash.com/photos?page=${page}&per_page=9`
|
| 109 |
+
};
|
| 110 |
+
|
| 111 |
+
// For demo purposes, we'll use mock data
|
| 112 |
+
return this.generateMockPosts(9);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
generateMockPosts(count) {
|
| 116 |
+
const categories = ['nature', 'technology', 'travel', 'food', 'architecture', 'people'];
|
| 117 |
+
const users = [
|
| 118 |
+
{ name: 'Alex Johnson', username: 'alexj', followers: 1243 },
|
| 119 |
+
{ name: 'Sarah Miller', username: 'sarahm', followers: 856 },
|
| 120 |
+
{ name: 'Mike Chen', username: 'mikec', followers: 2107 },
|
| 121 |
+
{ name: 'Emma Davis', username: 'emmad', followers: 932 },
|
| 122 |
+
{ name: 'James Wilson', username: 'jamesw', followers: 1541 }
|
| 123 |
+
];
|
| 124 |
+
|
| 125 |
+
return Array.from({ length: count }, (_, i) => {
|
| 126 |
+
const user = users[Math.floor(Math.random() * users.length)];
|
| 127 |
+
const category = categories[Math.floor(Math.random() * categories.length)];
|
| 128 |
+
const postId = `post_${Date.now()}_${i}`;
|
| 129 |
+
|
| 130 |
+
return {
|
| 131 |
+
id: postId,
|
| 132 |
+
user: user,
|
| 133 |
+
image: `http://static.photos/${category}/640x360/${i + 1}`,
|
| 134 |
+
caption: this.generateMockCaption(category),
|
| 135 |
+
likes: Math.floor(Math.random() * 1000),
|
| 136 |
+
comments: Math.floor(Math.random() * 50),
|
| 137 |
+
shares: Math.floor(Math.random() * 20),
|
| 138 |
+
timestamp: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000), // Within last 7 days
|
| 139 |
+
category: category,
|
| 140 |
+
engagement: Math.random() * 100
|
| 141 |
+
};
|
| 142 |
+
});
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
generateMockCaption(category) {
|
| 146 |
+
const captions = {
|
| 147 |
+
nature: ['Beautiful sunset at the beach! 🌅', 'Morning hike in the mountains 🏔️', 'Peaceful forest walk 🌲'],
|
| 148 |
+
technology: ['Working on some exciting new projects! 💻', 'Tech conference was amazing! 🚀', 'Latest gadget unboxing 📱'],
|
| 149 |
+
travel: ['Exploring new places! ✈️', 'Cultural experience of a lifetime 🌍', 'Travel dreams coming true 🗺️'],
|
| 150 |
+
food: ['Delicious homemade meal! 🍽️', 'Food photography session 📸', 'Trying out new recipes 👨🍳'],
|
| 151 |
+
architecture: ['Modern architecture never fails to impress! 🏛️', 'Historical building tour 🏰', 'Architectural marvels 🏗️'],
|
| 152 |
+
people: ['Great time with friends! 👥', 'Community event was fantastic! 🎉', 'Networking and making connections 🤝']
|
| 153 |
+
};
|
| 154 |
+
|
| 155 |
+
const categoryCaptions = captions[category] || ['Great day! 😊'];
|
| 156 |
+
return categoryCaptions[Math.floor(Math.random() * categoryCaptions.length)];
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
filterDuplicatePosts(newPosts) {
|
| 160 |
+
return newPosts.filter(post => !this.seenPosts.has(post.id));
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
renderPosts(posts) {
|
| 164 |
+
const feedContainer = document.getElementById('feed-container');
|
| 165 |
+
const emptyState = document.getElementById('empty-state');
|
| 166 |
+
|
| 167 |
+
if (posts.length === 0 && this.posts.length === 0) {
|
| 168 |
+
this.showEmptyState();
|
| 169 |
+
return;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
emptyState.classList.add('hidden');
|
| 173 |
+
|
| 174 |
+
posts.forEach(post => {
|
| 175 |
+
this.seenPosts.add(post.id);
|
| 176 |
+
const postElement = this.createPostElement(post);
|
| 177 |
+
feedContainer.appendChild(postElement);
|
| 178 |
+
});
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
createPostElement(post) {
|
| 182 |
+
const postDiv = document.createElement('div');
|
| 183 |
+
postDiv.className = 'post-card bg-white rounded-xl shadow-sm overflow-hidden fade-in';
|
| 184 |
+
postDiv.dataset.postId = post.id;
|
| 185 |
+
|
| 186 |
+
const timeAgo = this.getTimeAgo(post.timestamp);
|
| 187 |
+
|
| 188 |
+
postDiv.innerHTML = `
|
| 189 |
+
<div class="relative">
|
| 190 |
+
<img src="${post.image}" alt="Post image" class="w-full h-48 object-cover">
|
| 191 |
+
<button class="save-btn absolute top-3 right-3 p-2 bg-white/80 backdrop-blur-sm rounded-full hover:bg-white transition-colors duration-200">
|
| 192 |
+
<i data-feather="bookmark" class="w-4 h-4 text-gray-600"></i>
|
| 193 |
+
</button>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="p-4">
|
| 196 |
+
<div class="flex items-center justify-between mb-3">
|
| 197 |
+
<div class="flex items-center space-x-3">
|
| 198 |
+
<img src="http://static.photos/people/40x40/${Math.floor(Math.random() * 100)}" alt="${post.user.name}" class="w-8 h-8 rounded-full">
|
| 199 |
+
<div>
|
| 200 |
+
<h4 class="font-semibold text-gray-900 text-sm">${post.user.name}</h4>
|
| 201 |
+
<p class="text-gray-500 text-xs">@${post.user.username}</p>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
<span class="text-xs text-gray-400">${timeAgo}</span>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<p class="text-gray-700 mb-4 text-sm">${post.caption}</p>
|
| 208 |
+
|
| 209 |
+
<div class="flex items-center justify-between text-gray-500">
|
| 210 |
+
<div class="flex items-center space-x-4">
|
| 211 |
+
<button class="like-btn flex items-center space-x-1 text-sm hover:text-red-500 transition-colors duration-200">
|
| 212 |
+
<i data-feather="heart" class="w-4 h-4"></i>
|
| 213 |
+
<span>${post.likes}</span>
|
| 214 |
+
</button>
|
| 215 |
+
<button class="flex items-center space-x-1 text-sm hover:text-blue-500 transition-colors duration-200">
|
| 216 |
+
<i data-feather="message-circle" class="w-4 h-4"></i>
|
| 217 |
+
<span>${post.comments}</span>
|
| 218 |
+
</button>
|
| 219 |
+
<button class="flex items-center space-x-1 text-sm hover:text-green-500 transition-colors duration-200">
|
| 220 |
+
<i data-feather="share-2" class="w-4 h-4"></i>
|
| 221 |
+
<span>${post.shares}</span>
|
| 222 |
+
</button>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
`;
|
| 226 |
+
|
| 227 |
+
return postDiv;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
handleLike(likeBtn) {
|
| 231 |
+
const heartIcon = likeBtn.querySelector('i');
|
| 232 |
+
const likesCount = likeBtn.querySelector('span');
|
| 233 |
+
|
| 234 |
+
likeBtn.classList.add('like-animation');
|
| 235 |
+
setTimeout(() => likeBtn.classList.remove('like-animation'), 600);
|
| 236 |
+
|
| 237 |
+
const isLiked = heartIcon.style.fill === 'currentColor';
|
| 238 |
+
const currentLikes = parseInt(likesCount.textContent);
|
| 239 |
+
|
| 240 |
+
if (isLiked) {
|
| 241 |
+
heartIcon.style.fill = 'none';
|
| 242 |
+
likesCount.textContent = currentLikes - 1;
|
| 243 |
+
likesCount.style.color = '';
|
| 244 |
+
} else {
|
| 245 |
+
heartIcon.style.fill = 'currentColor';
|
| 246 |
+
likesCount.textContent = currentLikes + 1;
|
| 247 |
+
likesCount.style.color = '#ef4444';
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
handleSave(saveBtn) {
|
| 252 |
+
const bookmarkIcon = saveBtn.querySelector('i');
|
| 253 |
+
const isSaved = bookmarkIcon.style.fill === 'currentColor';
|
| 254 |
+
|
| 255 |
+
if (isSaved) {
|
| 256 |
+
bookmarkIcon.style.fill = 'none';
|
| 257 |
+
} else {
|
| 258 |
+
bookmarkIcon.style.fill = 'currentColor';
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
trackPostInteraction(postId) {
|
| 263 |
+
// Track user engagement for personalization
|
| 264 |
+
const post = this.posts.find(p => p.id === postId);
|
| 265 |
+
if (post) {
|
| 266 |
+
this.updateUserPreferences(post);
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
updateUserPreferences(post) {
|
| 271 |
+
if (!this.userPreferences.engagedCategories) {
|
| 272 |
+
this.userPreferences.engagedCategories = {};
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
this.userPreferences.engagedCategories[post.category] =
|
| 276 |
+
(this.userPreferences.engagedCategories[post.category] || 0) + 1;
|
| 277 |
+
|
| 278 |
+
localStorage.setItem('flowflex_preferences', JSON.stringify(this.userPreferences));
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
getUserPreferences() {
|
| 282 |
+
const stored = localStorage.getItem('flowflex_preferences');
|
| 283 |
+
return stored ? JSON.parse(stored) : {
|
| 284 |
+
engagedCategories: {},
|
| 285 |
+
preferredFeed: 'trending',
|
| 286 |
+
blockedUsers: []
|
| 287 |
+
};
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
getTimeAgo(timestamp) {
|
| 291 |
+
const now = new Date();
|
| 292 |
+
const diff = now - new Date(timestamp);
|
| 293 |
+
const minutes = Math.floor(diff / 60000);
|
| 294 |
+
const hours = Math.floor(diff / 3600000);
|
| 295 |
+
const days = Math.floor(diff / 86400000);
|
| 296 |
+
|
| 297 |
+
if (days > 0) return `${days}d ago`;
|
| 298 |
+
if (hours > 0) return `${hours}h ago`;
|
| 299 |
+
return `${minutes}m ago`;
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
showLoading() {
|
| 303 |
+
document.getElementById('loading').classList.remove('hidden');
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
hideLoading() {
|
| 307 |
+
document.getElementById('loading').classList.add('hidden');
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
showEmptyState() {
|
| 311 |
+
document.getElementById('feed-container').innerHTML = '';
|
| 312 |
+
document.getElementById('empty-state').classList.remove('hidden');
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
showError(message) {
|
| 316 |
+
// Simple error notification
|
| 317 |
+
const errorDiv = document.createElement('div');
|
| 318 |
+
errorDiv.className = 'fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
|
| 319 |
+
errorDiv.textContent = message;
|
| 320 |
+
document.body.appendChild(errorDiv);
|
| 321 |
+
|
| 322 |
+
setTimeout(() => {
|
| 323 |
+
errorDiv.remove();
|
| 324 |
+
}, 3000);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
setupIntersectionObserver() {
|
| 328 |
+
const observer = new IntersectionObserver((entries) => {
|
| 329 |
+
entries.forEach(entry => {
|
| 330 |
+
if (entry.isIntersecting && !this.isLoading) {
|
| 331 |
+
this.loadFeed();
|
| 332 |
+
}
|
| 333 |
+
}, {
|
| 334 |
+
rootMargin: '100px'
|
| 335 |
+
});
|
| 336 |
+
|
| 337 |
+
observer.observe(document.getElementById('loading'));
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
// Initialize the app when DOM is loaded
|
| 342 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 343 |
+
new FlowFlexApp();
|
| 344 |
+
});
|
| 345 |
+
|
| 346 |
+
// Utility function for API calls
|
| 347 |
+
async function apiCall(url, options = {}) {
|
| 348 |
+
try {
|
| 349 |
+
const response = await fetch(url, {
|
| 350 |
+
headers: {
|
| 351 |
+
'Authorization': 'Client-ID YOUR_UNSPLASH_ACCESS_KEY',
|
| 352 |
+
'Accept-Version': 'v1'
|
| 353 |
+
},
|
| 354 |
+
...options
|
| 355 |
+
});
|
| 356 |
+
|
| 357 |
+
if (!response.ok) {
|
| 358 |
+
throw new Error(`API call failed: ${response.status}`);
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
return await response.json();
|
| 362 |
+
} catch (error) {
|
| 363 |
+
console.error('API call error:', error);
|
| 364 |
+
throw error;
|
| 365 |
+
}
|
| 366 |
+
}
|
style.css
CHANGED
|
@@ -1,28 +1,117 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Custom CSS for FlowFlex */
|
| 2 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 3 |
+
|
| 4 |
+
* {
|
| 5 |
+
font-family: 'Inter', sans-serif;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
/* Smooth scrolling */
|
| 9 |
+
html {
|
| 10 |
+
scroll-behavior: smooth;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
/* Custom scrollbar */
|
| 14 |
+
::-webkit-scrollbar {
|
| 15 |
+
width: 6px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
::-webkit-scrollbar-track {
|
| 19 |
+
background: #f1f1f1;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
::-webkit-scrollbar-thumb {
|
| 23 |
+
background: #c1c1c1;
|
| 24 |
+
border-radius: 3px;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
::-webkit-scrollbar-thumb:hover {
|
| 28 |
+
background: #a8a8a8;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* Post card animations */
|
| 32 |
+
.post-card {
|
| 33 |
+
transition: all 0.3s ease;
|
| 34 |
+
transform: translateY(0);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.post-card:hover {
|
| 38 |
+
transform: translateY(-4px);
|
| 39 |
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* Like animation */
|
| 43 |
+
.like-animation {
|
| 44 |
+
animation: like-pulse 0.6s ease;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
@keyframes like-pulse {
|
| 48 |
+
0% { transform: scale(1); }
|
| 49 |
+
50% { transform: scale(1.2); }
|
| 50 |
+
100% { transform: scale(1); }
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/* Feed filter active state */
|
| 54 |
+
.feed-filter-btn.active {
|
| 55 |
+
box-shadow: 0 4px 6px -1px rgba(14, 165, 233, 0.3);
|
| 56 |
}
|
| 57 |
|
| 58 |
+
/* Loading skeleton animation */
|
| 59 |
+
.skeleton {
|
| 60 |
+
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
| 61 |
+
background-size: 200% 100%;
|
| 62 |
+
animation: loading 1.5s infinite;
|
| 63 |
}
|
| 64 |
|
| 65 |
+
@keyframes loading {
|
| 66 |
+
0% {
|
| 67 |
+
background-position: 200% 0;
|
| 68 |
+
}
|
| 69 |
+
100% {
|
| 70 |
+
background-position: -200% 0;
|
| 71 |
+
}
|
| 72 |
}
|
| 73 |
|
| 74 |
+
/* Fade in animation for new content */
|
| 75 |
+
.fade-in {
|
| 76 |
+
animation: fadeIn 0.5s ease-in;
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
|
| 79 |
+
@keyframes fadeIn {
|
| 80 |
+
from {
|
| 81 |
+
opacity: 0;
|
| 82 |
+
transform: translateY(20px);
|
| 83 |
+
}
|
| 84 |
+
to {
|
| 85 |
+
opacity: 1;
|
| 86 |
+
transform: translateY(0);
|
| 87 |
+
}
|
| 88 |
}
|
| 89 |
+
|
| 90 |
+
/* Mobile responsive adjustments */
|
| 91 |
+
@media (max-width: 768px) {
|
| 92 |
+
.post-card {
|
| 93 |
+
margin-bottom: 1rem;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.feed-filter-btn {
|
| 97 |
+
font-size: 0.75rem;
|
| 98 |
+
padding: 0.5rem 1rem;
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/* Dark mode support */
|
| 103 |
+
@media (prefers-color-scheme: dark) {
|
| 104 |
+
.dark-mode-auto {
|
| 105 |
+
background-color: #1a202c;
|
| 106 |
+
color: #e2e8f0;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/* Custom gradient backgrounds */
|
| 111 |
+
.gradient-bg-primary {
|
| 112 |
+
background: linear-gradient(135deg, #0ea5e9 0%, #d946ef 100%);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.gradient-bg-secondary {
|
| 116 |
+
background: linear-gradient(135deg, #d946ef 0%, #0ea5e9 100%);
|
| 117 |
+
}
|