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

53
.gitignore vendored Normal file
View File

@@ -0,0 +1,53 @@
# Node.js dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json
yarn.lock
# Build output
public/build/
# macOS specific files
.DS_Store
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Docker related
.docker/
*.log
# Editor directories and files
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Svelte related
.svelte-kit/
# Environment variables
.env
.env.local
.env.development
.env.test
.env.production

131
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,131 @@
# Logo Gallery - Developer Documentation
This document provides instructions for developers working on the Logo Gallery project.
## Project Overview
Logo Gallery is a web application that displays a collection of company and brand logos with the following features:
- Grid and list views of logos
- Search functionality
- Direct URL copying for each logo
- Download capability
- Responsive design
The project is built with:
- Svelte for the front-end UI
- Node.js for script automation
- Docker for containerization
- GitHub Pages for deployment
## Development Environment Setup
### Prerequisites
- Docker and Docker Compose
- Git
No local Node.js installation is required as all operations run inside Docker containers.
### Getting Started
1. Clone the repository:
```
git clone https://github.com/yourusername/logos.git
cd logos
```
2. Build and start the application:
```
make build
make start
```
3. Access the application at http://localhost:5005
## Common Development Tasks
### Adding New Logos
1. Add logo files (SVG or PNG preferred) to the `public/logos/` directory
2. Scan the logos directory and update the data file:
```
make scan-logos-dev
```
(This runs `npm run scan-logos` inside the dev container)
3. The application will automatically rebuild with the new logos
### Modifying the UI
1. Edit files in the `src/` directory
2. The changes will require a rebuild:
```
make rebuild
```
### Running Custom Commands
To run any npm or shell command inside the Docker container:
```
make run CMD="your-command-here"
```
Examples:
- List logo files: `make run CMD="ls -la public/assets/logos"`
- Run a specific npm script: `make run CMD="npm run some-script"`
## Project Structure
```
logos/
├── public/ # Static assets
│ ├── assets/
│ │ └── logos/ # Logo files (SVG, PNG)
│ ├── data/ # JSON data files
│ ├── build/ # Compiled JS/CSS (generated)
│ └── global.css # Global styles
├── src/ # Application source code
│ ├── components/ # Svelte components
│ ├── App.svelte # Main app component
│ └── main.js # App entry point
├── scripts/ # Utility scripts
├── Dockerfile # Docker configuration
├── compose.yml # Docker Compose configuration
├── Makefile # Development commands
└── README.md # Project overview
```
## Deployment to GitHub Pages
To deploy the application to GitHub Pages:
1. Build the application:
```
make build
```
2. The `public/` directory contains all files needed for deployment
3. Push the contents of the `public/` directory to the `gh-pages` branch of your repository
## Available Make Commands
Run `make help` to see all available commands:
- `make build` - Build the Docker container
- `make start` - Start the application
- `make stop` - Stop the application
- `make restart` - Restart the application
- `make logs` - View the application logs
- `make run CMD=<cmd>` - Run a command in the container
- `make generate-logos` - Generate logos.json from assets directory
- `make clean` - Clean up build artifacts
- `make rebuild` - Completely rebuild from scratch
## Troubleshooting
If you encounter issues:
1. Check the logs: `make logs`
2. Try a complete rebuild: `make rebuild`
3. Ensure the Docker service is running
4. Verify your logo files are in the correct format and location

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM node:slim
# Update package index and upgrade packages to reduce vulnerabilities
RUN apt-get update && apt-get upgrade -y
WORKDIR /app
COPY package*.json ./
RUN npm install
# Copy all source files except those in .dockerignore
COPY . .
# Create necessary directories for the build
RUN mkdir -p public/build
RUN mkdir -p public/data
# Build the application
RUN npm run build
EXPOSE 5000
EXPOSE 35729
# Start the server
CMD ["npm", "run", "start"]

15
Dockerfile.dev Normal file
View File

@@ -0,0 +1,15 @@
FROM node:slim
WORKDIR /app
COPY package*.json ./
RUN [ -f package-lock.json ] || touch package-lock.json
RUN npm install
# Only copy minimal files for initial build, source will be mounted
COPY public/index.html public/global.css ./public/
EXPOSE 5000
EXPOSE 35729
CMD ["npm", "run", "dev"]

81
Makefile Normal file
View File

@@ -0,0 +1,81 @@
# Logo Gallery Project Makefile
# Configuration
DOCKER_COMPOSE = docker compose
CONTAINER_NAME = logo-gallery
PORT = 5005
DEV_PORT = 5006
# Main targets
.PHONY: all build start stop restart logs clean scan-logos-dev help dev
all: build start
# Development mode with hot reloading
dev:
docker compose -f compose.dev.yml up --build
# Build the Docker container
build:
@echo "Building the Logo Gallery container..."
$(DOCKER_COMPOSE) build
# Start the application in the background
start:
@echo "Starting Logo Gallery application on port $(PORT)..."
$(DOCKER_COMPOSE) up -d
@echo "Application is running at http://localhost:$(PORT)"
# Stop the application
stop:
@echo "Stopping Logo Gallery application..."
$(DOCKER_COMPOSE) down
# Restart the application
restart: stop start
# View the application logs
logs:
@echo "Showing application logs (press Ctrl+C to exit)..."
$(DOCKER_COMPOSE) logs -f
# Run a command inside the container
# Usage: make run CMD="npm run build"
run:
@echo "Running command in container: $(CMD)"
$(DOCKER_COMPOSE) run --rm $(CONTAINER_NAME) $(CMD)
# Scan logos.json from files in the logos directory (for dev mode)
scan-logos-dev:
@echo "Scanning logos directory and updating logos.json for development..."
$(DOCKER_COMPOSE) -f compose.dev.yml run --rm logo-gallery-dev npm run scan-logos
@echo "Logos have been updated - refresh the browser to see changes"
# Clean up build artifacts and temporary files
clean:
@echo "Cleaning up build artifacts and temporary files..."
$(DOCKER_COMPOSE) down
docker builder prune -f
# Complete rebuild from scratch
rebuild:
@echo "Performing complete rebuild..."
$(DOCKER_COMPOSE) down
docker builder prune -f
$(DOCKER_COMPOSE) build --no-cache
$(DOCKER_COMPOSE) up -d
@echo "Rebuild complete. Application is running at http://localhost:$(PORT)"
# Display help information
help:
@echo "Logo Gallery Makefile commands:"
@echo " make build - Build the Docker container"
@echo " make start - Start the application (http://localhost:$(PORT))"
@echo " make stop - Stop the application"
@echo " make restart - Restart the application"
@echo " make logs - View the application logs"
@echo " make run CMD=<cmd> - Run a command in the container"
@echo " make scan-logos-dev - Scan logos.json from assets directory"
@echo " make clean - Clean up build artifacts"
@echo " make rebuild - Completely rebuild from scratch"
@echo " make help - Display this help information"

96
README.md Normal file
View File

@@ -0,0 +1,96 @@
# Logo Gallery
A collection of company and brand logos hosted on GitHub Pages. This project provides an easy way to access and use various brand logos in SVG and PNG formats with a user-friendly interface.
## Features
- Browse logos in grid or list view
- Search functionality to find specific logos
- Copy direct URL to any logo with one click
- Download logos directly
- Responsive design that works on mobile and desktop
## Project Setup
### Running with Docker (Recommended)
The easiest way to run the project locally is using Docker, which doesn't require installing Node.js or npm packages directly on your system:
1. Clone this repository:
```
git clone https://github.com/yourusername/logos.git
cd logos
```
2. Start the Docker container:
```
docker-compose up
```
The application will be available at http://localhost:5000 with live reloading enabled.
### Running Manually (Alternative)
If you prefer to run the project without Docker:
1. Clone this repository:
```
git clone https://github.com/yourusername/logos.git
cd logos
```
2. Install dependencies:
```
npm install
```
3. Start the development server:
```
npm run dev
```
4. Build for production deployment:
```
npm run build
```
## Deploying to GitHub Pages
1. Build the project:
```
npm run build
```
2. Push the contents to your GitHub repository's `gh-pages` branch.
## Adding New Logos
To add new logos to the collection:
1. Add the logo file (SVG or PNG) to the `public/logos/` directory
2. Run the logo scan script:
```
make scan-logos-dev
```
(This runs `npm run scan-logos` inside the dev container)
3. The application will automatically rebuild with the new logos
## Directory Structure
```
logos/
├── public/ # Static assets
│ ├── assets/
│ │ └── logos/ # Logo files
│ └── global.css # Global styles
├── src/ # Application source code
│ ├── components/ # Svelte components
│ ├── data/ # Data files
│ ├── App.svelte # Main app component
│ └── main.js # App entry point
└── index.html # HTML entry point
```
## License
This project is MIT licensed. Please note that the logos themselves are property of their respective owners and should be used according to their brand guidelines.

20
compose.dev.yml Normal file
View File

@@ -0,0 +1,20 @@
services:
logo-gallery-dev:
build:
context: .
dockerfile: Dockerfile.dev
container_name: logo-gallery-dev
ports:
- "5006:5000"
- "35729:35729"
volumes:
- ./src:/app/src
- ./public:/app/public
- ./rollup.config.js:/app/rollup.config.js
- ./package.json:/app/package.json
- ./scripts:/app/scripts
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
- HOST=0.0.0.0
command: npm run dev

10
compose.yml Normal file
View File

@@ -0,0 +1,10 @@
services:
logo-gallery:
build: .
container_name: logo-gallery
ports:
- "5005:5000" # App port
- "35729:35729" # LiveReload port
environment:
- NODE_ENV=development
- HOST=0.0.0.0

24
package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "logo-gallery",
"version": "1.0.0",
"description": "A collection of company and brand logos accessible via GitHub Pages",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --host 0.0.0.0 --dev --single",
"scan-logos": "node scripts/scanLogos.js"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^1.0.0"
}
}

56
public/data/logos.json Normal file
View File

@@ -0,0 +1,56 @@
[
{
"name": "Apple (black)",
"path": "logos/apple_black.svg",
"format": "SVG",
"disable": false
},
{
"name": "ATB",
"path": "logos/atb.svg",
"format": "SVG",
"disable": false
},
{
"name": "Binance",
"path": "logos/binance.svg",
"format": "SVG",
"disable": false
},
{
"name": "Dalnoboy Service",
"path": "logos/dalnoboy-service.svg",
"format": "SVG",
"disable": false
},
{
"name": "Google",
"path": "logos/google.svg",
"format": "SVG",
"disable": false
},
{
"name": "Privatbank",
"path": "logos/privatbank.png",
"format": "PNG",
"disable": false
},
{
"name": "Pumb",
"path": "logos/pumb.png",
"format": "PNG",
"disable": false
},
{
"name": "Roomerin",
"path": "logos/roomerin.svg",
"format": "SVG",
"disable": false
},
{
"name": "Shkafnik",
"path": "logos/shkafnik.png",
"format": "PNG",
"disable": false
}
]

102
public/global.css Normal file
View File

@@ -0,0 +1,102 @@
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--background-color: #f8f9fa;
--card-background: #ffffff;
--text-color: #333333;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
button {
cursor: pointer;
padding: 0.5rem 1rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
font-size: 0.9rem;
transition: background-color 0.2s;
}
button:hover {
background-color: #2980b9;
}
.view-toggle {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.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;
}
.copy-btn {
background-color: var(--secondary-color);
margin-right: 0.5rem;
}
.download-btn {
background-color: #27ae60;
}
/* Direct logo image size constraints that will work with any component structure */
div.logo-image {
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
position: relative;
overflow: hidden;
}
div.logo-image img {
max-width: 80%;
max-height: 80%;
width: auto;
height: auto;
object-fit: contain;
}
/* Grid specific */
.logo-grid .logo-item .logo-image {
height: 160px;
width: 100%;
}
/* List specific */
.logo-list .logo-item .logo-image {
width: 120px;
min-width: 120px;
height: 100px;
border-right: 1px solid #eee;
}

14
public/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logo Gallery</title>
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="build/bundle.css">
</head>
<body>
<div id="app"></div>
<script src="build/bundle.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 1100 1100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><path d="M695.579,184.597c18.506,-22.393 33.973,-49.186 43.976,-78.123c10.015,-28.955 14.535,-60.049 11.135,-91.073c-26.66,1.075 -56.128,9.969 -83.708,24.178c-27.568,14.218 -53.264,33.758 -72.385,56.136c-17.138,19.821 -33.225,45.599 -44.007,74.022c-10.781,28.414 -16.259,59.488 -12.21,89.89c29.729,2.308 59.775,-5.251 87.016,-18.978c27.243,-13.733 51.673,-33.644 70.183,-56.052Z"/><path d="M900.01,745.451c35.18,33.47 70.36,46.979 71.14,47.319c-0.59,1.891 -6.15,20.9 -17.96,48.77c-11.8,27.88 -29.83,64.62 -55.38,101.95c-22.08,32.27 -44.58,64.5 -70.58,88.8c-26.02,24.32 -55.52,40.73 -91.634,41.4c-35.476,0.65 -58.913,-9.86 -83.622,-20.55c-24.716,-10.68 -50.703,-21.52 -91.26,-21.52c-40.548,-0 -67.156,10.18 -92.028,20.7c-24.874,10.51 -47.998,21.35 -81.589,22.68c-34.841,1.32 -65.535,-16.14 -93.082,-41.96c-27.559,-25.82 -51.961,-60.01 -74.218,-92.16c-45.502,-65.79 -85.641,-158.719 -102.729,-255.487c-17.086,-96.758 -11.131,-197.342 35.566,-278.41c23.192,-40.264 55.508,-73.131 93.276,-96.107c37.77,-22.975 80.994,-36.059 125.981,-36.71c34.228,-0.649 67.49,10.865 97.422,22.537c29.938,11.677 56.546,23.519 77.467,23.519c20.906,-0 50.986,-14.242 86.163,-27.436c35.167,-13.188 75.419,-25.336 116.687,-21.15c17.28,0.716 50.16,4.211 86.51,18.909c36.34,14.706 76.16,40.624 107.3,86.2c-2.51,1.555 -31.44,18.448 -59.59,51.704c-78.5,92.761 -72.3,232.832 16.16,317.002Z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

31
public/logos/atb.svg Normal file
View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X7 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="150mm" height="150mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 15000 15000"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:none}
.fil2 {fill:#FEFEFE}
.fil0 {fill:#256BB1}
.fil3 {fill:#C32328;fill-rule:nonzero}
]]>
</style>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<rect class="fil0" x="1290" y="1290" width="12421" height="12421" rx="1402" ry="1402"/>
<polygon class="fil1" points="0,0 15000,0 15000,15000 0,15000 "/>
<path class="fil2" d="M7500 1942c3070,0 5559,2488 5559,5558 0,3070 -2489,5559 -5559,5559 -3070,0 -5558,-2489 -5558,-5559 0,-3070 2488,-5558 5558,-5558z"/>
<polygon class="fil3" points="5306,6127 6005,6127 6005,8866 5306,8866 5306,8496 4055,8496 4757,7826 5306,7826 5306,7100 3364,8929 3364,7963 "/>
<polygon class="fil3" points="6770,6848 6196,6848 6196,6131 8703,6131 7949,6848 7523,6848 7523,8869 6770,8869 "/>
<path class="fil3" d="M8878 6790l1400 0 694 -659 -2094 0 -695 659 0 2079 1594 0c100,-1 194,-25 281,-71 88,-47 165,-110 231,-191 67,-80 119,-171 157,-273 37,-103 57,-211 57,-323 0,-127 -19,-243 -57,-346 -37,-103 -89,-191 -155,-264 -67,-74 -144,-131 -233,-170 -88,-40 -182,-60 -285,-60l-744 0 0 658 607 0c55,1 97,19 127,53 30,34 46,79 46,136 0,37 -7,70 -21,99 -13,29 -33,51 -59,68 -26,17 -57,25 -93,25l-758 0 0 -1420z"/>
<path class="fil0" d="M11102 4153l-504 1419c-1008,-1431 -2885,-1024 -5214,344 1612,-1637 3479,-2365 5718,-1763z"/>
<path class="fil0" d="M3898 10847l504 -1419c1008,1431 2885,1024 5214,-344 -1612,1637 -3479,2365 -5718,1763z"/>
<path class="fil0" d="M10696 3734c-3574,-3372 -9861,617 -7759,5609 132,-3725 4861,-6340 7759,-5609z"/>
<path class="fil0" d="M4304 11266c3574,3372 9861,-617 7759,-5609 -132,3725 -4861,6340 -7759,5609z"/>
<path class="fil0" d="M2575 905l9850 0c919,0 1670,751 1670,1670l0 9850c0,919 -751,1670 -1670,1670l-9850 0c-919,0 -1670,-751 -1670,-1670l0 -9850c0,-919 751,-1670 1670,-1670zm-46 -153l9942 0c977,0 1777,800 1777,1777l0 9942c0,977 -800,1777 -1777,1777l-9942 0c-977,0 -1777,-800 -1777,-1777l0 -9942c0,-977 800,-1777 1777,-1777z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
public/logos/binance.svg Normal file
View File

@@ -0,0 +1 @@
<svg viewBox="0 0 126.61 126.61" xmlns="http://www.w3.org/2000/svg"><g fill="#f3ba2f"><path d="m38.73 53.2 24.59-24.58 24.6 24.6 14.3-14.31-38.9-38.91-38.9 38.9z"/><path d="m0 63.31 14.3-14.31 14.31 14.31-14.31 14.3z"/><path d="m38.73 73.41 24.59 24.59 24.6-24.6 14.31 14.29-38.9 38.91-38.91-38.88z"/><path d="m98 63.31 14.3-14.31 14.31 14.3-14.31 14.32z"/><path d="m77.83 63.3-14.51-14.52-10.73 10.73-1.24 1.23-2.54 2.54 14.51 14.5 14.51-14.47z"/></g></svg>

After

Width:  |  Height:  |  Size: 458 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

8
public/logos/google.svg Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="272" height="92" viewBox="0 0 272 92">
<path fill="#EA4335" d="M115.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18C71.25 34.32 81.24 25 93.5 25s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44S80.99 39.2 80.99 47.18c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z"/>
<path fill="#FBBC05" d="M163.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18c0-12.85 9.99-22.18 22.25-22.18s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44s-12.51 5.46-12.51 13.44c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z"/>
<path fill="#4285F4" d="M209.75 26.34v39.82c0 16.38-9.66 23.07-21.08 23.07-10.75 0-17.22-7.19-19.66-13.07l8.48-3.53c1.51 3.61 5.21 7.87 11.17 7.87 7.31 0 11.84-4.51 11.84-13v-3.19h-.34c-2.18 2.69-6.38 5.04-11.68 5.04-11.09 0-21.25-9.66-21.25-22.09 0-12.52 10.16-22.26 21.25-22.26 5.29 0 9.49 2.35 11.68 4.96h.34v-3.61h9.25zm-8.56 20.92c0-7.81-5.21-13.52-11.84-13.52-6.72 0-12.35 5.71-12.35 13.52 0 7.73 5.63 13.36 12.35 13.36 6.63 0 11.84-5.63 11.84-13.36z"/>
<path fill="#34A853" d="M225 3v65h-9.5V3h9.5z"/>
<path fill="#EA4335" d="M262.02 54.48l7.56 5.04c-2.44 3.61-8.32 9.83-18.48 9.83-12.6 0-22.01-9.74-22.01-22.18 0-13.19 9.49-22.18 20.92-22.18 11.51 0 17.14 9.16 18.98 14.11l1.01 2.52-29.65 12.28c2.27 4.45 5.8 6.72 10.75 6.72 4.96 0 8.4-2.44 10.92-6.14zm-23.27-7.98l19.82-8.23c-1.09-2.77-4.37-4.7-8.23-4.7-4.95 0-11.84 4.37-11.59 12.93z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/logos/privatbank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
public/logos/pumb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/logos/shkafnik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

78
rollup.config.js Normal file
View File

@@ -0,0 +1,78 @@
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
compilerOptions: {
// enable run-time checks when not in production
dev: !production
},
// Force Svelte to emit CSS for components
emitCss: true
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: 'bundle.css' }),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

107
scripts/scanLogos.js Normal file
View File

@@ -0,0 +1,107 @@
// This file has been renamed and updated. See scanLogos.js in the same directory for the new script.
const fs = require('fs');
const path = require('path');
// Configuration
const logosDir = path.join(__dirname, '../public/logos');
const outputFile = path.join(__dirname, '../public/data/logos.json');
// Get file extension without the dot
function getFileExtension(filename) {
return path.extname(filename).slice(1).toUpperCase();
}
// Get file name without extension
function getBaseName(filename) {
return path.basename(filename, path.extname(filename));
}
// Convert filename to readable name (replace hyphens with spaces, capitalize words)
function formatName(filename) {
return getBaseName(filename)
.split(/[-_]/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}
// Scan directory and update logo objects
function scanLogos() {
console.log(`Scanning logos directory: ${logosDir}`);
let existing = [];
if (fs.existsSync(outputFile)) {
try {
existing = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
} catch (e) {
console.error('Could not parse existing logos.json:', e);
}
}
const existingMap = new Map();
for (const item of existing) {
existingMap.set(item.path, item);
}
try {
if (!fs.existsSync(logosDir)) {
console.error(`Directory does not exist: ${logosDir}`);
return [];
}
const files = fs.readdirSync(logosDir);
// Filter for image files (svg, png, jpg, jpeg)
const logoFiles = files.filter(file =>
/\.(svg|png|jpg|jpeg)$/i.test(file)
);
console.log(`Found ${logoFiles.length} logo files`);
// Create logo objects
const logos = logoFiles.map(file => {
const format = getFileExtension(file);
const logoPath = `logos/${file}`;
const existingItem = existingMap.get(logoPath);
if (existingItem) {
// Preserve name and disable, update format/path
return {
...existingItem,
path: logoPath,
format: format,
disable: typeof existingItem.disable === 'boolean' ? existingItem.disable : false
};
} else {
// New logo
return {
name: formatName(file),
path: logoPath,
format: format,
disable: false
};
}
});
return logos;
} catch (error) {
console.error('Error scanning logos directory:', error);
return [];
}
}
// Save logos data to JSON file
function saveLogosToJson(logos) {
try {
const data = JSON.stringify(logos, null, 2);
fs.writeFileSync(outputFile, data);
console.log(`Successfully wrote ${logos.length} logos to ${outputFile}`);
} catch (error) {
console.error('Error writing logos data to file:', error);
}
}
// Main function
function main() {
const logos = scanLogos();
saveLogosToJson(logos);
}
// Run the script
main();

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;