feat: apply new branding logo across app and pwa assets
BIN
public/favicon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 21 KiB |
BIN
public/images/icons/home.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 7.3 KiB |
BIN
public/images/icons/library.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
public/images/logostripstream-white.png
Normal file
|
After Width: | Height: | Size: 889 KiB |
BIN
public/images/logostripstream.png
Normal file
|
After Width: | Height: | Size: 895 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 5.8 MiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 2.2 MiB |
@@ -23,7 +23,12 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: "Segoe UI", "SF Pro Text", -apple-system, BlinkMacSystemFont, sans-serif;
|
font-family:
|
||||||
|
"Segoe UI",
|
||||||
|
"SF Pro Text",
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
sans-serif;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -60,7 +65,12 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
linear-gradient(112deg, rgba(79, 70, 229, 0.24) 0%, rgba(6, 182, 212, 0.2) 30%, transparent 56%),
|
linear-gradient(
|
||||||
|
112deg,
|
||||||
|
rgba(79, 70, 229, 0.24) 0%,
|
||||||
|
rgba(6, 182, 212, 0.2) 30%,
|
||||||
|
transparent 56%
|
||||||
|
),
|
||||||
linear-gradient(248deg, rgba(244, 114, 182, 0.16) 0%, transparent 46%),
|
linear-gradient(248deg, rgba(244, 114, 182, 0.16) 0%, transparent 46%),
|
||||||
repeating-linear-gradient(135deg, rgba(226, 232, 240, 0.03) 0 1px, transparent 1px 11px);
|
repeating-linear-gradient(135deg, rgba(226, 232, 240, 0.03) 0 1px, transparent 1px 11px);
|
||||||
}
|
}
|
||||||
@@ -82,6 +92,15 @@
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
width: 2.4rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 0 20px rgba(34, 211, 238, 0.35);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.menu-btn {
|
.menu-btn {
|
||||||
width: 2.25rem;
|
width: 2.25rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
@@ -170,7 +189,12 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
linear-gradient(160deg, rgba(79, 70, 229, 0.12) 0%, rgba(6, 182, 212, 0.08) 32%, transparent 58%),
|
linear-gradient(
|
||||||
|
160deg,
|
||||||
|
rgba(79, 70, 229, 0.12) 0%,
|
||||||
|
rgba(6, 182, 212, 0.08) 32%,
|
||||||
|
transparent 58%
|
||||||
|
),
|
||||||
linear-gradient(332deg, rgba(244, 114, 182, 0.06) 0%, transparent 42%),
|
linear-gradient(332deg, rgba(244, 114, 182, 0.06) 0%, transparent 42%),
|
||||||
repeating-linear-gradient(135deg, rgba(226, 232, 240, 0.02) 0 1px, transparent 1px 11px);
|
repeating-linear-gradient(135deg, rgba(226, 232, 240, 0.02) 0 1px, transparent 1px 11px);
|
||||||
}
|
}
|
||||||
@@ -327,8 +351,9 @@
|
|||||||
<div class="header-inner">
|
<div class="header-inner">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<button class="menu-btn" id="sidebar-toggle" type="button" aria-label="Menu">☰</button>
|
<button class="menu-btn" id="sidebar-toggle" type="button" aria-label="Menu">☰</button>
|
||||||
|
<img class="brand-logo" src="/images/logostripstream.png" alt="StripStream logo" />
|
||||||
<div>
|
<div>
|
||||||
<div class="brand-title">STRIPSTREAM</div>
|
<div class="brand-title">StripStream</div>
|
||||||
<div class="brand-subtitle">comic reader</div>
|
<div class="brand-subtitle">comic reader</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -358,16 +383,16 @@
|
|||||||
<div class="status">● Hors ligne</div>
|
<div class="status">● Hors ligne</div>
|
||||||
<h1>Cette page n'est pas encore disponible hors ligne.</h1>
|
<h1>Cette page n'est pas encore disponible hors ligne.</h1>
|
||||||
<p>
|
<p>
|
||||||
Tu peux continuer a naviguer sur les pages deja consultees. Cette route sera
|
Tu peux continuer a naviguer sur les pages deja consultees. Cette route sera disponible
|
||||||
disponible hors ligne apres une visite en ligne.
|
hors ligne apres une visite en ligne.
|
||||||
</p>
|
</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-secondary" onclick="window.history.back()">Retour</button>
|
<button class="btn btn-secondary" onclick="window.history.back()">Retour</button>
|
||||||
<button class="btn btn-primary" onclick="window.location.reload()">Reessayer</button>
|
<button class="btn btn-primary" onclick="window.location.reload()">Reessayer</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
Astuce: visite d'abord Accueil, Bibliotheques, Series et pages de lecture quand tu es
|
Astuce: visite d'abord Accueil, Bibliotheques, Series et pages de lecture quand tu es en
|
||||||
en ligne.
|
ligne.
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">StripStream - interface hors ligne</div>
|
<div class="footer">StripStream - interface hors ligne</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ const fs = require("fs").promises;
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
||||||
const inputSvg = path.join(__dirname, "../public/favicon.svg");
|
const sourceLogo = path.join(__dirname, "../public/images/logostripstream.png");
|
||||||
const inputAppleSvg = path.join(__dirname, "../public/apple-icon.svg");
|
|
||||||
const outputDir = path.join(__dirname, "../public/images/icons");
|
const outputDir = path.join(__dirname, "../public/images/icons");
|
||||||
const screenshotsDir = path.join(__dirname, "../public/images/screenshots");
|
const screenshotsDir = path.join(__dirname, "../public/images/screenshots");
|
||||||
const splashDir = path.join(__dirname, "../public/images/splash");
|
const splashDir = path.join(__dirname, "../public/images/splash");
|
||||||
|
const faviconPath = path.join(__dirname, "../public/favicon.png");
|
||||||
|
|
||||||
// Configuration des splashscreens pour différents appareils
|
// Configuration des splashscreens pour différents appareils
|
||||||
const splashScreens = [
|
const splashScreens = [
|
||||||
@@ -24,35 +24,23 @@ const splashScreens = [
|
|||||||
async function generateSplashScreens() {
|
async function generateSplashScreens() {
|
||||||
await fs.mkdir(splashDir, { recursive: true });
|
await fs.mkdir(splashDir, { recursive: true });
|
||||||
|
|
||||||
// Créer le SVG de base pour la splashscreen avec le même style que le favicon
|
|
||||||
const splashSvg = `
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="100" height="100" fill="#4F46E5"/>
|
|
||||||
<g transform="translate(25, 20) scale(1.5)">
|
|
||||||
<!-- Lettre S stylisée -->
|
|
||||||
<path
|
|
||||||
d="M21 12C21 10.3431 19.6569 9 18 9H14C12.3431 9 11 10.3431 11 12V12.5C11 14.1569 12.3431 15.5 14 15.5H18C19.6569 15.5 21 16.8431 21 18.5V19C21 20.6569 19.6569 22 18 22H14C12.3431 22 11 20.6569 11 19"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<!-- Points décoratifs -->
|
|
||||||
<circle cx="11" cy="24" r="1.5" fill="white"/>
|
|
||||||
<circle cx="21" cy="8" r="1.5" fill="white"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
for (const screen of splashScreens) {
|
for (const screen of splashScreens) {
|
||||||
const outputPath = path.join(splashDir, `splash-${screen.width}x${screen.height}.png`);
|
const outputPath = path.join(splashDir, `splash-${screen.width}x${screen.height}.png`);
|
||||||
|
const darkOverlay = Buffer.from(
|
||||||
|
`<svg width="${screen.width}" height="${screen.height}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="100%" height="100%" fill="rgba(4, 8, 20, 0.22)" />
|
||||||
|
</svg>`
|
||||||
|
);
|
||||||
|
|
||||||
await sharp(Buffer.from(splashSvg))
|
await sharp(sourceLogo)
|
||||||
.resize(screen.width, screen.height, {
|
.resize(screen.width, screen.height, {
|
||||||
fit: "contain",
|
fit: "cover",
|
||||||
background: "#4F46E5",
|
position: "center",
|
||||||
|
})
|
||||||
|
.composite([{ input: darkOverlay, blend: "over" }])
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
})
|
})
|
||||||
.png()
|
|
||||||
.toFile(outputPath);
|
.toFile(outputPath);
|
||||||
|
|
||||||
console.log(`✓ Splashscreen ${screen.name} (${screen.width}x${screen.height}) générée`);
|
console.log(`✓ Splashscreen ${screen.name} (${screen.width}x${screen.height}) générée`);
|
||||||
@@ -61,17 +49,19 @@ async function generateSplashScreens() {
|
|||||||
|
|
||||||
async function generateIcons() {
|
async function generateIcons() {
|
||||||
try {
|
try {
|
||||||
|
await fs.access(sourceLogo);
|
||||||
|
|
||||||
// Créer les dossiers de sortie s'ils n'existent pas
|
// Créer les dossiers de sortie s'ils n'existent pas
|
||||||
await fs.mkdir(outputDir, { recursive: true });
|
await fs.mkdir(outputDir, { recursive: true });
|
||||||
await fs.mkdir(screenshotsDir, { recursive: true });
|
await fs.mkdir(screenshotsDir, { recursive: true });
|
||||||
|
|
||||||
// Générer les icônes Android (avec bords arrondis)
|
// Générer les icônes Android
|
||||||
for (const size of sizes) {
|
for (const size of sizes) {
|
||||||
const outputPath = path.join(outputDir, `icon-${size}x${size}.png`);
|
const outputPath = path.join(outputDir, `icon-${size}x${size}.png`);
|
||||||
|
|
||||||
await sharp(inputSvg)
|
await sharp(sourceLogo)
|
||||||
.resize(size, size, {
|
.resize(size, size, {
|
||||||
fit: "contain",
|
fit: "cover",
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
|
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
|
||||||
})
|
})
|
||||||
.png({
|
.png({
|
||||||
@@ -88,9 +78,9 @@ async function generateIcons() {
|
|||||||
for (const size of appleSizes) {
|
for (const size of appleSizes) {
|
||||||
const outputPath = path.join(outputDir, `apple-icon-${size}x${size}.png`);
|
const outputPath = path.join(outputDir, `apple-icon-${size}x${size}.png`);
|
||||||
|
|
||||||
await sharp(inputAppleSvg)
|
await sharp(sourceLogo)
|
||||||
.resize(size, size, {
|
.resize(size, size, {
|
||||||
fit: "contain",
|
fit: "cover",
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
|
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
|
||||||
})
|
})
|
||||||
.png({
|
.png({
|
||||||
@@ -102,26 +92,25 @@ async function generateIcons() {
|
|||||||
console.log(`✓ Icône Apple ${size}x${size} générée`);
|
console.log(`✓ Icône Apple ${size}x${size} générée`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Générer le favicon principal utilisé par Next metadata
|
||||||
|
await sharp(sourceLogo)
|
||||||
|
.resize(64, 64, {
|
||||||
|
fit: "cover",
|
||||||
|
})
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
|
palette: true,
|
||||||
|
})
|
||||||
|
.toFile(faviconPath);
|
||||||
|
|
||||||
|
console.log("✓ Favicon principal généré");
|
||||||
|
|
||||||
// Générer les icônes de raccourcis
|
// Générer les icônes de raccourcis
|
||||||
const shortcutIcons = [
|
const shortcutIcons = ["home", "library"];
|
||||||
{ name: "home", icon: "Home" },
|
|
||||||
{ name: "library", icon: "Library" },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const shortcut of shortcutIcons) {
|
for (const shortcut of shortcutIcons) {
|
||||||
const outputPath = path.join(outputDir, `${shortcut.name}.png`);
|
const outputPath = path.join(outputDir, `${shortcut}.png`);
|
||||||
|
await sharp(sourceLogo)
|
||||||
// Créer une image carrée avec fond indigo et icône blanche
|
|
||||||
const svg = `
|
|
||||||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="96" height="96" rx="20" fill="#4F46E5"/>
|
|
||||||
<path d="${getIconPath(
|
|
||||||
shortcut.icon
|
|
||||||
)}" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sharp(Buffer.from(svg))
|
|
||||||
.resize(96, 96)
|
.resize(96, 96)
|
||||||
.png({
|
.png({
|
||||||
compressionLevel: 9,
|
compressionLevel: 9,
|
||||||
@@ -129,7 +118,7 @@ async function generateIcons() {
|
|||||||
})
|
})
|
||||||
.toFile(outputPath);
|
.toFile(outputPath);
|
||||||
|
|
||||||
console.log(`✓ Icône de raccourci ${shortcut.name} générée`);
|
console.log(`✓ Icône de raccourci ${shortcut} générée`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Générer les screenshots de démonstration
|
// Générer les screenshots de démonstration
|
||||||
@@ -166,14 +155,4 @@ async function generateIcons() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction helper pour obtenir les chemins SVG des icônes
|
|
||||||
function getIconPath(iconName) {
|
|
||||||
const paths = {
|
|
||||||
Home: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6",
|
|
||||||
Library:
|
|
||||||
"M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 19.5A2.5 2.5 0 0 0 6.5 22H20M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20",
|
|
||||||
};
|
|
||||||
return paths[iconName] || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
generateIcons();
|
generateIcons();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function AccountPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<div className="container mx-auto px-4 py-8">
|
||||||
<div className="max-w-4xl mx-auto space-y-8">
|
<div className="mx-auto max-w-4xl space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">Mon compte</h1>
|
<h1 className="text-3xl font-bold">Mon compte</h1>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export const metadata: Metadata = {
|
|||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{
|
{
|
||||||
url: "/favicon.svg",
|
url: "/favicon.png",
|
||||||
type: "image/svg+xml",
|
type: "image/png",
|
||||||
},
|
},
|
||||||
{ url: "/images/icons/icon-72x72.png", sizes: "72x72", type: "image/png" },
|
{ url: "/images/icons/icon-72x72.png", sizes: "72x72", type: "image/png" },
|
||||||
{ url: "/images/icons/icon-96x96.png", sizes: "96x96", type: "image/png" },
|
{ url: "/images/icons/icon-96x96.png", sizes: "96x96", type: "image/png" },
|
||||||
@@ -176,7 +176,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<I18nProvider locale={locale}>
|
<I18nProvider locale={locale}>
|
||||||
<PreferencesProvider initialPreferences={preferences}>
|
<PreferencesProvider initialPreferences={preferences}>
|
||||||
<ClientLayout initialLibraries={libraries} initialFavorites={favorites} userIsAdmin={userIsAdmin}>
|
<ClientLayout
|
||||||
|
initialLibraries={libraries}
|
||||||
|
initialFavorites={favorites}
|
||||||
|
userIsAdmin={userIsAdmin}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ClientLayout>
|
</ClientLayout>
|
||||||
</PreferencesProvider>
|
</PreferencesProvider>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
const SHOW_DELAY_MS = 140;
|
const SHOW_DELAY_MS = 140;
|
||||||
|
|
||||||
@@ -22,8 +23,22 @@ export default function AppLoading() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex w-full max-w-sm flex-col items-center gap-6 text-center">
|
<div className="flex w-full max-w-sm flex-col items-center gap-6 text-center">
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<p className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-xl font-bold uppercase tracking-[0.12em] text-transparent">
|
<Image
|
||||||
|
src="/images/logostripstream.png"
|
||||||
|
alt="StripStream Logo"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="mx-auto hidden h-20 w-20 rounded-xl object-cover dark:block"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src="/images/logostripstream-white.png"
|
||||||
|
alt="StripStream Logo"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="mx-auto h-20 w-20 rounded-xl object-cover dark:hidden"
|
||||||
|
/>
|
||||||
|
<p className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-xl font-bold tracking-[0.08em] text-transparent">
|
||||||
StripStream
|
StripStream
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">Chargement de votre bibliotheque...</p>
|
<p className="text-sm text-muted-foreground">Chargement de votre bibliotheque...</p>
|
||||||
|
|||||||
@@ -43,21 +43,21 @@ export function LoginContent({ searchParams }: LoginContentProps) {
|
|||||||
transition={{ duration: 0.6, delay: 0.2 }}
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
className="relative z-20 flex items-center text-lg font-medium"
|
className="relative z-20 flex items-center text-lg font-medium"
|
||||||
>
|
>
|
||||||
<motion.svg
|
<motion.img
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
src="/images/logostripstream.png"
|
||||||
viewBox="0 0 24 24"
|
alt="StripStream Logo"
|
||||||
fill="none"
|
className="mr-3 hidden h-9 w-9 rounded-md object-cover dark:block"
|
||||||
stroke="currentColor"
|
whileHover={{ scale: 1.08, rotate: -3 }}
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
className="mr-2 h-6 w-6"
|
|
||||||
whileHover={{ rotate: 180 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
/>
|
||||||
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
|
<motion.img
|
||||||
</motion.svg>
|
src="/images/logostripstream-white.png"
|
||||||
<span className="text-2xl font-bold bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
|
alt="StripStream Logo"
|
||||||
|
className="mr-3 h-9 w-9 rounded-md object-cover dark:hidden"
|
||||||
|
whileHover={{ scale: 1.08, rotate: -3 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
/>
|
||||||
|
<span className="text-2xl font-bold bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-transparent">
|
||||||
StripStream
|
StripStream
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -83,12 +83,20 @@ export function LoginContent({ searchParams }: LoginContentProps) {
|
|||||||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||||
<div className="flex flex-col items-center space-y-4 text-center">
|
<div className="flex flex-col items-center space-y-4 text-center">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute -inset-1 bg-gradient-to-r from-[#4F46E5] to-[#6366F1] rounded-full opacity-75 blur-md animate-pulse"></div>
|
<div className="relative bg-gradient-to-br from-white to-gray-100 dark:from-slate-800 dark:to-slate-900 rounded-full shadow-xl overflow-hidden w-32 h-32 flex items-center justify-center">
|
||||||
<div className="relative bg-gradient-to-br from-white to-gray-100 dark:from-slate-800 dark:to-slate-900 rounded-full shadow-xl overflow-hidden w-24 h-24 flex items-center justify-center">
|
|
||||||
<motion.img
|
<motion.img
|
||||||
src="/images/icons/apple-icon-180x180.png"
|
src="/images/logostripstream.png"
|
||||||
alt="StripStream Logo"
|
alt="StripStream Logo"
|
||||||
className="w-[100%] h-[100%] object-cover"
|
className="hidden h-[100%] w-[100%] object-cover dark:block"
|
||||||
|
initial={{ scale: 1.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
/>
|
||||||
|
<motion.img
|
||||||
|
src="/images/logostripstream-white.png"
|
||||||
|
alt="StripStream Logo"
|
||||||
|
className="h-[100%] w-[100%] object-cover dark:hidden"
|
||||||
initial={{ scale: 1.8, opacity: 0 }}
|
initial={{ scale: 1.8, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
@@ -99,7 +107,7 @@ export function LoginContent({ searchParams }: LoginContentProps) {
|
|||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ y: -20 }}
|
initial={{ y: -20 }}
|
||||||
animate={{ y: 0 }}
|
animate={{ y: 0 }}
|
||||||
className="text-3xl font-bold tracking-tight bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-gray-300 bg-clip-text text-transparent"
|
className="text-3xl font-bold tracking-tight bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-transparent"
|
||||||
>
|
>
|
||||||
{t("login.title")}
|
{t("login.title")}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { RefreshButton } from "@/components/library/RefreshButton";
|
|||||||
import { PullToRefreshIndicator } from "@/components/common/PullToRefreshIndicator";
|
import { PullToRefreshIndicator } from "@/components/common/PullToRefreshIndicator";
|
||||||
import { usePullToRefresh } from "@/hooks/usePullToRefresh";
|
import { usePullToRefresh } from "@/hooks/usePullToRefresh";
|
||||||
import { revalidateForRefresh } from "@/app/actions/refresh";
|
import { revalidateForRefresh } from "@/app/actions/refresh";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
interface HomeClientWrapperProps {
|
interface HomeClientWrapperProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -48,7 +49,25 @@ export function HomeClientWrapper({ children }: HomeClientWrapperProps) {
|
|||||||
isHiding={pullToRefresh.isHiding}
|
isHiding={pullToRefresh.isHiding}
|
||||||
/>
|
/>
|
||||||
<main className="relative isolate overflow-hidden">
|
<main className="relative isolate overflow-hidden">
|
||||||
<div className="container mx-auto space-y-6 px-4 pb-8 pt-3">
|
<div className="pointer-events-none absolute inset-0 z-0 flex items-start justify-center pt-10">
|
||||||
|
<Image
|
||||||
|
src="/images/logostripstream.png"
|
||||||
|
alt=""
|
||||||
|
width={600}
|
||||||
|
height={600}
|
||||||
|
aria-hidden
|
||||||
|
className="hidden h-auto w-[min(78vw,600px)] opacity-[0.08] saturate-125 dark:block"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src="/images/logostripstream-white.png"
|
||||||
|
alt=""
|
||||||
|
width={600}
|
||||||
|
height={600}
|
||||||
|
aria-hidden
|
||||||
|
className="h-auto w-[min(78vw,600px)] opacity-[0.1] saturate-125 dark:hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="container relative z-10 mx-auto space-y-6 px-4 pb-8 pt-3">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<RefreshButton libraryId="home" refreshLibrary={handleRefresh} />
|
<RefreshButton libraryId="home" refreshLibrary={handleRefresh} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export function Header({
|
|||||||
|
|
||||||
<div className="mr-2 flex items-center md:mr-4">
|
<div className="mr-2 flex items-center md:mr-4">
|
||||||
<a className="mr-2 flex items-center md:mr-6" href="/">
|
<a className="mr-2 flex items-center md:mr-6" href="/">
|
||||||
<span className="inline-flex bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-sm font-bold uppercase tracking-[0.1em] text-transparent sm:hidden">
|
<span className="inline-flex bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-sm font-bold tracking-[0.06em] text-transparent sm:hidden">
|
||||||
StripStream
|
Strip
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden sm:inline-flex flex-col leading-none">
|
<span className="hidden sm:inline-flex flex-col leading-none">
|
||||||
<span className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-lg font-bold tracking-[0.1em] text-transparent">
|
<span className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-lg font-bold tracking-[0.08em] text-transparent">
|
||||||
STRIPSTREAM
|
StripStream
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 text-[10px] font-medium uppercase tracking-[0.28em] text-foreground/70">
|
<span className="mt-1 text-[10px] font-medium uppercase tracking-[0.22em] text-foreground/70">
|
||||||
comic reader
|
comic reader
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -157,8 +157,18 @@ export function Sidebar({
|
|||||||
id="sidebar"
|
id="sidebar"
|
||||||
>
|
>
|
||||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(160deg,hsl(var(--primary)/0.12)_0%,hsl(192_85%_55%/0.08)_32%,transparent_58%),linear-gradient(332deg,hsl(338_82%_62%/0.06)_0%,transparent_42%),repeating-linear-gradient(135deg,hsl(var(--foreground)/0.02)_0_1px,transparent_1px_11px)]" />
|
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(160deg,hsl(var(--primary)/0.12)_0%,hsl(192_85%_55%/0.08)_32%,transparent_58%),linear-gradient(332deg,hsl(338_82%_62%/0.06)_0%,transparent_42%),repeating-linear-gradient(135deg,hsl(var(--foreground)/0.02)_0_1px,transparent_1px_11px)]" />
|
||||||
|
<div className="pointer-events-none absolute inset-0 z-0">
|
||||||
|
<div
|
||||||
|
className="hidden h-full w-full bg-center bg-no-repeat opacity-[0.1] [background-size:260%] dark:block"
|
||||||
|
style={{ backgroundImage: "url('/images/logostripstream.png')" }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="h-full w-full bg-center bg-no-repeat opacity-[0.12] [background-size:260%] dark:hidden"
|
||||||
|
style={{ backgroundImage: "url('/images/logostripstream-white.png')" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative flex-1 space-y-4 overflow-y-auto px-3 py-4">
|
<div className="relative z-10 flex-1 space-y-4 overflow-y-auto px-3 py-4">
|
||||||
<div className="rounded-xl border border-border/50 bg-background/30 p-2">
|
<div className="rounded-xl border border-border/50 bg-background/30 p-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h2 className="mb-2 px-3 text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
<h2 className="mb-2 px-3 text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function ClientSettings({ initialConfig, initialLibraries }: ClientSettin
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<div className="container mx-auto px-4 py-8">
|
||||||
<div className="max-w-4xl mx-auto space-y-8">
|
<div className="mx-auto max-w-4xl space-y-8">
|
||||||
<h1 className="text-3xl font-bold">{t("settings.title")}</h1>
|
<h1 className="text-3xl font-bold">{t("settings.title")}</h1>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||||
|
|||||||