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
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
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
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
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
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
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
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
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
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
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
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
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>
+1
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
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
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
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+78
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
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
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>
+121
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>
+150
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>
+143
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
View File
@@ -0,0 +1,7 @@
import App from './App.svelte';
const app = new App({
target: document.getElementById('app') || document.body
});
export default app;