Compare commits
	
		
			No commits in common. "6649cd6e23bb08638355ee26085c2869bead5858" and "42aaa21703171c3da140c0667127da1cb8fb4835" have entirely different histories.
		
	
	
		
			6649cd6e23
			...
			42aaa21703
		
	
		
					 7 changed files with 42 additions and 162 deletions
				
			
		
							
								
								
									
										196
									
								
								setup.sh
									
										
									
									
									
								
							
							
						
						
									
										196
									
								
								setup.sh
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -589,7 +589,7 @@ def export_pdf():
 | 
			
		|||
                img = Paragraph('', styles['Normal'])
 | 
			
		||||
        elif game.url and 'gog.com' in game.url:
 | 
			
		||||
            try:
 | 
			
		||||
                img_path = os.path.join(app.root_path, 'static', 'gog_logo.webp')
 | 
			
		||||
                img_path = os.path.join(app.root_path, 'static', 'gog_logo.png')
 | 
			
		||||
                img = Image(img_path, width=3*cm, height=img_height)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                img = Paragraph('', styles['Normal'])
 | 
			
		||||
| 
						 | 
				
			
			@ -821,11 +821,10 @@ SHELL ["/bin/bash", "-c"]
 | 
			
		|||
 | 
			
		||||
RUN apt-get update && apt-get install -y --no-install-recommends wget \
 | 
			
		||||
    && mkdir -p /app/static \
 | 
			
		||||
    && wget -O /app/static/logo.webp "https://drop.nocadmin.net/logo.webp" \
 | 
			
		||||
    && wget -O /app/static/logo_small.webp "https://drop.nocadmin.net/logo_small.webp" \
 | 
			
		||||
    && wget -O /app/static/forgejo.webp "https://drop.nocadmin.net/forgejo.webp" \
 | 
			
		||||
    && wget -O /app/static/gog_logo.webp "https://drop.nocadmin.net/gog_logo.webp" \
 | 
			
		||||
    && wget -O /app/static/logo_small_maskable.webp  "https://drop.nocadmin.net/logo_small_maskable.webp" \
 | 
			
		||||
    && wget -O /app/static/logo.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo.png" \
 | 
			
		||||
    && wget -O /app/static/logo_small.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo_small.png" \
 | 
			
		||||
    && wget -O /app/static/forgejo.svg "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/forgejo.svg" \
 | 
			
		||||
    && wget -O /app/static/gog_logo.png "https://git.nocci.it/nocci/GameKeyManager/raw/branch/dev/steam-gift-manager/static/gog_logo.png" \
 | 
			
		||||
    && rm -rf /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
RUN mkdir -p /app/data && \
 | 
			
		||||
| 
						 | 
				
			
			@ -943,81 +942,6 @@ echo "✅ Database migration completed!"
 | 
			
		|||
SCRIPT_END
 | 
			
		||||
chmod +x ../upgrade.sh
 | 
			
		||||
 | 
			
		||||
# Manifest for PWA
 | 
			
		||||
cat <<MANIFEST_END > static/manifest.json
 | 
			
		||||
{
 | 
			
		||||
  "id": "/",
 | 
			
		||||
  "name": "Game Key Manager",
 | 
			
		||||
  "short_name": "GameKeys",
 | 
			
		||||
  "start_url": "/",
 | 
			
		||||
  "display": "standalone",
 | 
			
		||||
  "background_color": "#212529",
 | 
			
		||||
  "theme_color": "#212529",
 | 
			
		||||
  "description": "Manage Steam/GOG keys easily!",
 | 
			
		||||
  "orientation": "any",
 | 
			
		||||
  "launch_handler": {
 | 
			
		||||
    "client_mode": "navigate-existing"
 | 
			
		||||
  },
 | 
			
		||||
  "icons": [
 | 
			
		||||
    {
 | 
			
		||||
      "src": "/static/logo_small.webp",
 | 
			
		||||
      "sizes": "192x192",
 | 
			
		||||
      "type": "image/webp",
 | 
			
		||||
      "purpose": "any"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "src": "/static/logo_small_maskable.webp",
 | 
			
		||||
      "sizes": "192x192",
 | 
			
		||||
      "type": "image/webp",
 | 
			
		||||
      "purpose": "maskable"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "src": "/static/logo.webp",
 | 
			
		||||
      "sizes": "512x512",
 | 
			
		||||
      "type": "image/webp",
 | 
			
		||||
      "purpose": "any maskable"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
MANIFEST_END
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Service Worker
 | 
			
		||||
cat <<SW_END > static/serviceworker.js
 | 
			
		||||
const CACHE_NAME = 'game-key-manager-v1';
 | 
			
		||||
const ASSETS = [
 | 
			
		||||
  '/',
 | 
			
		||||
  '/static/style.css',
 | 
			
		||||
  '/static/logo.webp',
 | 
			
		||||
  '/static/logo_small.webp',
 | 
			
		||||
  '/static/gog_logo.webp',
 | 
			
		||||
  '/static/forgejo.webp'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
self.addEventListener('install', (event) => {
 | 
			
		||||
  event.waitUntil(
 | 
			
		||||
    caches.open(CACHE_NAME)
 | 
			
		||||
      .then(cache => cache.addAll(ASSETS))
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
self.addEventListener('fetch', (event) => {
 | 
			
		||||
  event.respondWith(
 | 
			
		||||
    caches.match(event.request)
 | 
			
		||||
      .then(cachedResponse => cachedResponse || fetch(event.request))
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
self.addEventListener('activate', (event) => {
 | 
			
		||||
  event.waitUntil(
 | 
			
		||||
    caches.keys().then(keys => Promise.all(
 | 
			
		||||
      keys.filter(key => key !== CACHE_NAME)
 | 
			
		||||
        .map(key => caches.delete(key))
 | 
			
		||||
    ))
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
SW_END
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 9. Templates
 | 
			
		||||
mkdir -p templates static
 | 
			
		||||
| 
						 | 
				
			
			@ -1025,36 +949,27 @@ mkdir -p templates static
 | 
			
		|||
# Base Template
 | 
			
		||||
cat <<HTML_END > templates/base.html
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="{{ get_locale() if get_locale() in ['en', 'de'] else 'en' }}" data-bs-theme="{{ theme }}">
 | 
			
		||||
<html lang="{{ get_locale() }}" data-bs-theme="{{ theme }}">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <meta name="csrf-token" content="{{ csrf_token() }}">
 | 
			
		||||
    <meta name="description" content="Manage your Steam and GOG keys efficiently. Track redemption dates, share games, and export lists.">
 | 
			
		||||
    <meta name="theme-color" content="#212529">
 | 
			
		||||
    <title>{{ _('Game Key Manager') }}</title>
 | 
			
		||||
    <!-- PWA Manifest -->
 | 
			
		||||
    <link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
 | 
			
		||||
    <!-- Preload Bootstrap CSS for better LCP -->
 | 
			
		||||
    <link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
 | 
			
		||||
    <noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"></noscript>
 | 
			
		||||
    <!-- Critical CSS (Above-the-Fold) kann hier inline ergänzt werden -->
 | 
			
		||||
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <a class="navbar-brand d-flex align-items-center gap-2" href="/">
 | 
			
		||||
                <img src="{{ url_for('static', filename='logo_small.webp') }}" alt="Logo" width="150" height="116" style="object-fit:contain; border-radius:8px;">
 | 
			
		||||
                <span>Game Key Manager</span>
 | 
			
		||||
                 <img src="{{ url_for('static', filename='logo_small.png') }}" alt="Logo" width="150" height="116" style="object-fit:contain; border-radius:8px;">
 | 
			
		||||
                 <span>Game Key Manager</span>
 | 
			
		||||
            </a>
 | 
			
		||||
            <div class="d-flex align-items-center gap-3">
 | 
			
		||||
                <form class="d-flex" action="{{ url_for('index') }}" method="GET">
 | 
			
		||||
                    <label for="searchInput" class="visually-hidden">{{ _('Search') }}</label>
 | 
			
		||||
                    <input class="form-control me-2" 
 | 
			
		||||
                           type="search" 
 | 
			
		||||
                           name="q"
 | 
			
		||||
                           id="searchInput"
 | 
			
		||||
                           placeholder="{{ _('Search') }}"
 | 
			
		||||
                           value="{{ search_query }}">
 | 
			
		||||
                    <button class="btn btn-outline-success" type="submit">🔍</button>
 | 
			
		||||
| 
						 | 
				
			
			@ -1066,6 +981,7 @@ cat <<HTML_END > templates/base.html
 | 
			
		|||
                    <label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="dropdown ms-3">
 | 
			
		||||
                    <!-- DEBUG: Current locale {{ get_locale() }} -->
 | 
			
		||||
                    <div hidden id="locale-debug" data-locale="{{ get_locale() }}"></div>
 | 
			
		||||
                    <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
 | 
			
		||||
                        {% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %}
 | 
			
		||||
| 
						 | 
				
			
			@ -1076,12 +992,12 @@ cat <<HTML_END > templates/base.html
 | 
			
		|||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% if current_user.is_authenticated %}
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Password') }}</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                             <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Password') }}</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                             <a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -1101,30 +1017,16 @@ cat <<HTML_END > templates/base.html
 | 
			
		|||
    </div>
 | 
			
		||||
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
    // Service Worker Registration for PWA
 | 
			
		||||
    if ('serviceWorker' in navigator) {
 | 
			
		||||
      window.addEventListener('load', () => {
 | 
			
		||||
        navigator.serviceWorker.register('{{ url_for("static", filename="serviceworker.js") }}', {scope: '/'})
 | 
			
		||||
          .then(registration => {
 | 
			
		||||
            console.log('ServiceWorker registered:', registration.scope);
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => {
 | 
			
		||||
            console.log('ServiceWorker registration failed:', error);
 | 
			
		||||
          });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // Dark Mode Switch
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        const toggle = document.getElementById('darkModeSwitch')
 | 
			
		||||
        const html = document.documentElement
 | 
			
		||||
        if (toggle) {
 | 
			
		||||
            toggle.addEventListener('change', function() {
 | 
			
		||||
                const theme = this.checked ? 'dark' : 'light'
 | 
			
		||||
                fetch('/set-theme/' + theme)
 | 
			
		||||
                    .then(() => html.setAttribute('data-bs-theme', theme))
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
        
 | 
			
		||||
        toggle.addEventListener('change', function() {
 | 
			
		||||
            const theme = this.checked ? 'dark' : 'light'
 | 
			
		||||
            fetch('/set-theme/' + theme)
 | 
			
		||||
                .then(() => html.setAttribute('data-bs-theme', theme))
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
    </script>
 | 
			
		||||
{% include "footer.html" %}
 | 
			
		||||
</body>
 | 
			
		||||
| 
						 | 
				
			
			@ -1166,17 +1068,10 @@ cat <<HTML_END > templates/index.html
 | 
			
		|||
                <td>
 | 
			
		||||
                {% if game.steam_appid %}
 | 
			
		||||
                <img src="https://cdn.cloudflare.steamstatic.com/steam/apps/{{ game.steam_appid }}/header.jpg"
 | 
			
		||||
                     alt="Steam Header" 
 | 
			
		||||
                     class="game-cover"
 | 
			
		||||
                     {% if loop.first %}fetchpriority="high"{% endif %}
 | 
			
		||||
                     width="368" 
 | 
			
		||||
                     height="172">
 | 
			
		||||
                     alt="Steam Header" class="game-cover">
 | 
			
		||||
                {% elif game.url and 'gog.com' in game.url %}
 | 
			
		||||
                <img src="{{ url_for('static', filename='gog_logo.webp') }}"
 | 
			
		||||
                     alt="GOG Logo" 
 | 
			
		||||
                     class="game-cover"
 | 
			
		||||
                     width="368"
 | 
			
		||||
                     height="172">
 | 
			
		||||
                <img src="{{ url_for('static', filename='gog_logo.png') }}"
 | 
			
		||||
                     alt="GOG Logo" class="game-cover">
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ game.name }}</td>
 | 
			
		||||
| 
						 | 
				
			
			@ -1261,7 +1156,7 @@ cat <<HTML_END > templates/login.html
 | 
			
		|||
    <div class="col-md-6">
 | 
			
		||||
        <div class="card shadow-sm">
 | 
			
		||||
            <div class="card-body text-center">
 | 
			
		||||
                <img src="{{ url_for('static', filename='logo.webp') }}" alt="Logo" width="266" height="206" class="mb-4" style="object-fit:contain;">
 | 
			
		||||
                <img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="266" height="206" class="mb-4" style="object-fit:contain;">
 | 
			
		||||
                <h2 class="card-title mb-4">{{ _('Login') }}</h2>
 | 
			
		||||
                <form method="POST">
 | 
			
		||||
                    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
			
		||||
| 
						 | 
				
			
			@ -1584,7 +1479,7 @@ cat <<HTML_END > templates/footer.html
 | 
			
		|||
    </div>
 | 
			
		||||
    <div class="mb-2">
 | 
			
		||||
      <a href="https://git.nocci.it/nocci/GiftGamesDB" target="_blank" rel="noopener">
 | 
			
		||||
        <img src="{{ url_for('static', filename='forgejo.webp') }}" alt="forgejo" width="20" style="vertical-align:middle;margin-right:4px;">
 | 
			
		||||
        <img src="{{ url_for('static', filename='forgejo.svg') }}" alt="forgejo" width="20" style="vertical-align:middle;margin-right:4px;">
 | 
			
		||||
        find the source code on my Forgejo
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -1676,53 +1571,38 @@ body {
 | 
			
		|||
    transition: width 0.2s, height 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsive Cover Images */
 | 
			
		||||
.game-cover {
 | 
			
		||||
    width: 368px;
 | 
			
		||||
    height: 172px;
 | 
			
		||||
    object-fit: contain;
 | 
			
		||||
    background: #222;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsiv für kleinere Bildschirme */
 | 
			
		||||
@media (max-width: 1200px) {
 | 
			
		||||
    .game-cover {
 | 
			
		||||
        width: 260px;
 | 
			
		||||
        height: 122px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 992px) {
 | 
			
		||||
@media (max-width: 900px) {
 | 
			
		||||
    .game-cover {
 | 
			
		||||
        width: 180px;
 | 
			
		||||
        height: 84px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
@media (max-width: 600px) {
 | 
			
		||||
    .game-cover {
 | 
			
		||||
        width: 120px;
 | 
			
		||||
        height: 56px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 576px) {
 | 
			
		||||
@media (max-width: 400px) {
 | 
			
		||||
    .game-cover {
 | 
			
		||||
        width: 90px;
 | 
			
		||||
        height: 42px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Accessibility Improvements */
 | 
			
		||||
.visually-hidden {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    width: 1px;
 | 
			
		||||
    height: 1px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: -1px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    clip: rect(0, 0, 0, 0);
 | 
			
		||||
    border: 0;
 | 
			
		||||
img.game-cover {
 | 
			
		||||
    width: 368px;
 | 
			
		||||
    height: 172px;
 | 
			
		||||
    object-fit: contain;
 | 
			
		||||
    background: #222;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CSS_END
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 740 B  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 6.8 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 8.1 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 5.9 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 20 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 2.5 KiB  | 
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue