Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions main/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Кошачий Pinterest</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<nav class="navigation">
<button id="show-all" class="nav-btn active">Все котики</button>
<button id="show-fav" class="nav-btn">Любимые котики</button>
</nav>
</header>

<main class="container">
<div id="cats-grid" class="cats-grid">
</div>
<div id="loader" class="loader">...загружаем еще котиков...</div>
</main>

<script src="script.js"></script>
</body>
</html>
121 changes: 121 additions & 0 deletions main/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
const API_KEY = 'live_NkfkwgsT5jyDaTq14WAYKqJ0EOJPTadssh4xMRuDVNc8xlJmRw4yhC1WgemOJoMs';
const grid = document.getElementById('cats-grid');
const loader = document.getElementById('loader');
let currentPage = 0;
let currentView = 'all';

async function fetchCats(page) {
try {
const response = await fetch(`https://api.thecatapi.com/v1/images/search?limit=15&page=${page}&order=DESC`, {
headers: { 'x-api-key': API_KEY }
});
return await response.json();
} catch (e) {
console.error("Ошибка загрузки:", e);
return [];
}
}

function renderCat(cat, isFavorite) {
const card = document.createElement('div');
card.className = 'cat-card';

card.innerHTML = `
<img src="${cat.url}" class="cat-image" alt="cat">
<button class="fav-btn ${isFavorite ? 'active' : ''}">
<svg class="heart-icon" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M16 28.5L14.1 26.7C7.2 20.4 2.7 16.3 2.7 11.2C2.7 7.1 5.9 3.9 10 3.9C12.3 3.9 14.5 5 16 6.7C17.5 5 19.7 3.9 22 3.9C26.1 3.9 29.3 7.1 29.3 11.2C29.3 16.3 24.8 20.4 17.9 26.7L16 28.5Z"/>
</svg>
</button>
`;

const btn = card.querySelector('.fav-btn');
btn.onclick = (e) => {
e.stopPropagation();
toggleFavorite(cat);
btn.classList.toggle('active');

if (currentView === 'fav' && !btn.classList.contains('active')) {
card.remove();
}
};

grid.appendChild(card);
}

function toggleFavorite(cat) {
let favorites = JSON.parse(localStorage.getItem('favCats')) || [];
const index = favorites.findIndex(f => f.id === cat.id);

if (index === -1) {
favorites.push(cat);
} else {
favorites.splice(index, 1);
}
localStorage.setItem('favCats', JSON.stringify(favorites));
}

async function init() {
const cats = await fetchCats(0);
const favorites = JSON.parse(localStorage.getItem('favCats')) || [];

cats.forEach(cat => {
const isFav = favorites.some(f => f.id === cat.id);
renderCat(cat, isFav);
});
}

document.getElementById('show-fav').onclick = function() {
currentView = 'fav';
document.getElementById('show-all').classList.remove('active');
this.classList.add('active');

loader.classList.add('hidden');

grid.innerHTML = '';
const favorites = JSON.parse(localStorage.getItem('favCats')) || [];
favorites.forEach(cat => renderCat(cat, true));
};

document.getElementById('show-all').onclick = function() {
currentView = 'all';
document.getElementById('show-fav').classList.remove('active');
this.classList.add('active');

loader.classList.remove('hidden');

grid.innerHTML = '';
init();
};

init();

let isLoading = false;

const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && currentView === 'all' && !isLoading) {
loadMoreCats();
}
}, {
rootMargin: '200px',
threshold: 0.1
});

observer.observe(document.querySelector('#loader'));

async function loadMoreCats() {
isLoading = true;
currentPage++;

const newCats = await fetchCats(currentPage);
const favorites = JSON.parse(localStorage.getItem('favCats')) || [];

if (newCats.length > 0) {
newCats.forEach(cat => {
const isFav = favorites.some(f => f.id === cat.id);
renderCat(cat, isFav);
});
}

isLoading = false;
}
152 changes: 152 additions & 0 deletions main/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
body {
margin: 0;
font-family: 'Roboto', sans-serif;
background-color: #fff;
}

.header {
background-color: #2196f3;
padding: 0 50px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 100;
}

.navigation {
display: flex;
gap: 20px;
}

.nav-btn {
background: none;
border: none;
color: rgba(255, 255, 255, 0.7);
padding: 20px;
cursor: pointer;
font-size: 16px;
}

.nav-btn.active {
color: #fff;
background-color: #1e88e5;
}

.container {
padding: 40px;
}

.cats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
gap: 48px;
}

.cat-card {
position: relative;
width: 225px;
height: 225px;
transition: transform 0.2s;
overflow: hidden;
}

.cat-card:hover {
transform: scale(1.1);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}

.cat-image {
width: 100%;
height: 100%;
object-fit: cover;
}

.fav-btn {
position: absolute;
bottom: 15px;
right: 15px;
background: none;
border: none;
cursor: pointer;
width: 40px;
height: 40px;
background-image: url('heart-empty.svg');
background-size: contain;
opacity: 0;
transition: opacity 0.3s;
}

.cat-card:hover .fav-btn {
opacity: 1;
}

.fav-btn.active {
background-image: url('heart-filled.svg');
opacity: 1;
}

.cat-card {
position: relative;
width: 225px;
height: 225px;
cursor: pointer;
transition: all 0.3s ease;
}

.cat-card:hover {
transform: scale(1.1);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}

.fav-btn {
position: absolute;
bottom: 15px;
right: 15px;
background: none;
border: none;
padding: 0;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease, transform 0.2s ease;
}

.cat-card:hover .fav-btn {
opacity: 1;
}

.heart-icon {
width: 40px;
height: 40px;
fill: none;
stroke: #FF4207;
stroke-width: 2px;
transition: fill 0.2s, stroke 0.2s;
}

.fav-btn:hover .heart-icon {
fill: #FF4207;
transform: scale(1.1);
}

.fav-btn.active .heart-icon {
fill: #F00;
stroke: #F00;
opacity: 1;
}

.fav-btn.active {
opacity: 1;
}

.loader {
text-align: center;
padding: 20px;
width: 100%;
font-size: 16px;
margin-top: 20px;
display: block;
}

.hidden {
display: none !important;
}