feat: Initialize Svelte application with logo gallery functionality

- Add Rollup configuration for building and serving the application.
- Implement logo scanning script to generate logos.json from logo files.
- Create main App component to manage logo display and search functionality.
- Develop LogoGrid and LogoList components for different viewing modes.
- Add LogoModal component for logo preview with details.
- Implement URL copying and logo downloading features.
- Style components for improved user experience and responsiveness.
This commit is contained in:
sHa
2025-04-27 16:55:23 +03:00
commit 9c3024c61d
28 changed files with 1468 additions and 0 deletions

191
src/App.svelte Normal file
View File

@@ -0,0 +1,191 @@
<script>
import { onMount } from 'svelte';
import LogoGrid from './components/LogoGrid.svelte';
import LogoList from './components/LogoList.svelte';
let viewMode = 'grid'; // 'grid' or 'list'
let searchQuery = '';
let logos = [];
let filteredLogos = [];
// Load logos from JSON file with cache busting
onMount(async () => {
try {
// Add timestamp as cache-busting query parameter
const timestamp = new Date().getTime();
const response = await fetch(`data/logos.json?t=${timestamp}`, {
// Force reload from server, don't use cache
cache: 'no-cache',
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
});
if (response.ok) {
logos = await response.json();
filteredLogos = logos;
console.log('Loaded logos:', logos.length, 'at', new Date().toLocaleTimeString());
} else {
console.error('Failed to load logos data', response.status);
}
} catch (error) {
console.error('Error loading logos:', error);
}
});
$: {
filteredLogos = logos.filter(logo =>
logo.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}
function setGridView() {
console.log('Setting view mode to: grid');
viewMode = 'grid';
}
function setListView() {
console.log('Setting view mode to: list');
viewMode = 'list';
}
function copyUrl(logoPath) {
const url = `${window.location.origin}/${logoPath}`;
navigator.clipboard.writeText(url)
.then(() => {
alert('URL copied to clipboard!');
})
.catch(err => {
console.error('Failed to copy URL: ', err);
});
}
function downloadLogo(logoPath, logoName) {
const link = document.createElement('a');
link.href = logoPath;
link.download = logoName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</script>
<main class="container">
<header>
<h1>Logo Gallery</h1>
<p>Collection of company and brand logos for your projects</p>
<div class="search-bar">
<input
type="text"
placeholder="Search logos..."
bind:value={searchQuery}
/>
</div>
<div class="view-toggle">
<button
class:active={viewMode === 'grid'}
on:click={setGridView}
>
Grid View
</button>
<button
class:active={viewMode === 'list'}
on:click={setListView}
>
List View
</button>
</div>
<!-- Debug info to show current view mode -->
<div style="margin-bottom: 10px; font-size: 0.8rem; color: #666;">
Current view: {viewMode}
</div>
</header>
<div class="logos-container">
{#if viewMode === 'grid'}
<LogoGrid
logos={filteredLogos}
onCopy={copyUrl}
onDownload={downloadLogo}
/>
{:else}
<LogoList
logos={filteredLogos}
onCopy={copyUrl}
onDownload={downloadLogo}
/>
{/if}
</div>
<footer>
<p>© {new Date().getFullYear()} Logo Gallery. All logos are property of their respective owners.</p>
</footer>
</main>
<style>
header {
margin-bottom: 2rem;
}
h1 {
color: var(--secondary-color);
margin-bottom: 0.5rem;
}
p {
margin-bottom: 1.5rem;
}
footer {
margin-top: 3rem;
text-align: center;
font-size: 0.9rem;
color: #666;
}
.view-toggle {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.view-toggle button {
padding: 0.5rem 1rem;
background-color: #e0e0e0;
color: var(--text-color);
border: none;
border-radius: 4px;
font-size: 0.9rem;
transition: all 0.2s;
}
.active {
background-color: var(--secondary-color) !important;
color: white !important;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
.search-bar {
margin-bottom: 1.5rem;
width: 100%;
max-width: 500px;
}
.search-bar input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.logos-container {
width: 100%;
}
</style>

View File

@@ -0,0 +1,121 @@
<script>
import LogoModal from './LogoModal.svelte';
export let logos = [];
export let onCopy;
export let onDownload;
let showModal = false;
let selectedLogo = null;
function openPreview(logo) {
selectedLogo = logo;
showModal = true;
}
function closeModal() {
showModal = false;
}
</script>
<LogoModal show={showModal} logo={selectedLogo} on:close={closeModal} />
<div class="logo-grid">
{#each logos as logo}
<div class="logo-card">
<div class="logo-image"
role="button"
tabindex="0"
aria-label="Preview {logo.name}"
on:click={() => openPreview(logo)}
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && openPreview(logo)}
style="cursor:pointer;"
>
<img src={logo.path} alt={logo.name} />
</div>
<div class="logo-info">
<h3>{logo.name}</h3>
<p>Format: {logo.format}</p>
<div class="logo-actions">
<button class="copy-btn" on:click={() => onCopy(logo.path)}>
Copy URL
</button>
<button class="download-btn" on:click={() => onDownload(logo.path, logo.name)}>
Download
</button>
</div>
</div>
</div>
{:else}
<p class="no-results">No logos found matching your search criteria.</p>
{/each}
</div>
<style>
.logo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.logo-card {
background: var(--card-background);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.logo-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.logo-image {
height: 160px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background-color: #f5f5f5;
position: relative;
overflow: hidden;
cursor: pointer;
}
.logo-image img {
max-width: 80%;
max-height: 80%;
width: auto !important;
height: auto !important;
object-fit: contain;
object-position: center;
}
.logo-info {
padding: 1rem;
}
.logo-info h3 {
margin-bottom: 0.5rem;
color: var(--secondary-color);
}
.logo-info p {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
.logo-actions {
display: flex;
}
.no-results {
grid-column: 1 / -1;
text-align: center;
padding: 2rem;
color: #666;
}
</style>

View File

@@ -0,0 +1,150 @@
<script>
import LogoModal from './LogoModal.svelte';
export let logos = [];
export let onCopy;
export let onDownload;
let showModal = false;
let selectedLogo = null;
function openPreview(logo) {
selectedLogo = logo;
showModal = true;
}
function closeModal() {
showModal = false;
}
</script>
<LogoModal show={showModal} logo={selectedLogo} on:close={closeModal} />
<div class="logo-list">
{#each logos as logo}
<div class="logo-item">
<div class="logo-image"
role="button"
tabindex="0"
aria-label="Preview {logo.name}"
on:click={() => openPreview(logo)}
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && openPreview(logo)}
style="cursor:pointer;"
>
<img src={logo.path} alt={logo.name} />
</div>
<div class="logo-info">
<h3>{logo.name}</h3>
<p>Format: {logo.format}</p>
</div>
<div class="logo-actions">
<button class="copy-btn" on:click={() => onCopy(logo.path)}>
Copy URL
</button>
<button class="download-btn" on:click={() => onDownload(logo.path, logo.name)}>
Download
</button>
</div>
</div>
{:else}
<p class="no-results">No logos found matching your search criteria.</p>
{/each}
</div>
<style>
.logo-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.logo-item {
display: flex;
align-items: center;
background: var(--card-background);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
width: 100%;
}
.logo-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.logo-image {
width: 120px;
min-width: 120px;
height: 100px;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
position: relative;
overflow: hidden;
border-right: 1px solid #eee;
cursor: pointer;
}
.logo-image img {
max-width: 80%;
max-height: 80%;
width: auto !important;
height: auto !important;
object-fit: contain;
}
.logo-info {
flex-grow: 1;
padding: 1rem;
}
.logo-info h3 {
margin-bottom: 0.5rem;
color: var(--secondary-color);
font-size: 1.2rem;
}
.logo-info p {
font-size: 0.9rem;
color: #666;
}
.logo-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
min-width: 120px;
border-left: 1px solid #eee;
}
.logo-actions button {
padding: 0.4rem 0.8rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
text-align: center;
transition: background-color 0.2s;
}
.copy-btn {
background-color: #f0f0f0;
color: #333;
}
.download-btn {
background-color: var(--secondary-color);
color: white;
}
.no-results {
text-align: center;
padding: 2rem;
color: #666;
}
</style>

View File

@@ -0,0 +1,143 @@
<script>
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
export let show = false;
export let logo = null;
const dispatch = createEventDispatcher();
function close() {
dispatch('close');
}
function handleKeydown(event) {
if (event.key === 'Escape') {
close();
}
}
onMount(() => {
document.addEventListener('keydown', handleKeydown);
});
onDestroy(() => {
document.removeEventListener('keydown', handleKeydown);
});
</script>
{#if show && logo}
<div class="modal-backdrop"
role="dialog"
aria-modal="true"
>
<div class="modal-content">
<div class="modal-header">
<h2>{logo.name}</h2>
<button class="close-btn" on:click={close} aria-label="Close preview">×</button>
</div>
<div class="modal-body">
<div class="preview-container"
role="button"
tabindex="0"
aria-label="Close preview"
on:click={close}
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && close()}
style="cursor:pointer;"
>
<img src={logo.path} alt={logo.name} />
</div>
<div class="logo-details">
<p><strong>Format:</strong> {logo.format}</p>
<p><strong>Path:</strong> {logo.path}</p>
</div>
</div>
</div>
</div>
{/if}
<style>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(3px);
}
.modal-content {
background-color: white;
border-radius: 8px;
width: 90%;
max-width: 900px;
max-height: 90vh;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.modal-header h2 {
margin: 0;
color: var(--secondary-color);
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
}
.close-btn:hover {
color: #333;
}
.modal-body {
padding: 1rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
border-radius: 4px;
padding: 2rem;
min-height: 300px;
}
.preview-container img {
max-width: 100%;
max-height: 60vh;
object-fit: contain;
}
.logo-details {
padding: 1rem;
background-color: #f9f9f9;
border-radius: 4px;
}
.logo-details p {
margin-bottom: 0.5rem;
}
</style>

7
src/main.js Normal file
View File

@@ -0,0 +1,7 @@
import App from './App.svelte';
const app = new App({
target: document.getElementById('app') || document.body
});
export default app;