Přidat siluetu lesa a terč do vzhledu, normalizovat slug
Grafika: - silueta lesa přes celou šířku nad patičkou - terč v hlavičce mezi značkou a navigací, s červeným středem Obojí je vložené jako CSS maska. Zdrojová PNG byla černá kresba na bílém bez alfa kanálu, takže by z nich koukal bílý obdélník a v tmavém motivu by to bylo nepoužitelné. Převedená maska nese jen průhlednost a barvu řídí CSS. Skyline se tím zmenšil z 856 kB na 245 kB. Assety patří do public/, ne do media/ — media je v produkci Docker volume mimo image, takže by tam soubory nebyly. Slug se navíc normalizuje sdíleným helperem: vedoucí lomítko vyrábělo odkaz //kontakt, což prohlížeč čte jako jméno cizího serveru. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,10 @@ verzování [sémantické](https://semver.org/lang/cs/).
|
|||||||
než je `NEXT_PUBLIC_SERVER_URL` (typicky po IP v LAN). Bez ní Payload
|
než je `NEXT_PUBLIC_SERVER_URL` (typicky po IP v LAN). Bez ní Payload
|
||||||
odmítal veškeré zápisy včetně nahrávání obrázků chybou 403.
|
odmítal veškeré zápisy včetně nahrávání obrázků chybou 403.
|
||||||
|
|
||||||
|
- Grafika: silueta lesa přes celou šířku nad patičkou a terč v hlavičce
|
||||||
|
mezi značkou a navigací. Obojí je vloženo jako CSS maska, takže se barví
|
||||||
|
podle motivu.
|
||||||
|
|
||||||
### Opraveno
|
### Opraveno
|
||||||
|
|
||||||
- Adresáře `data/` a `media/` jsou v repu přes `.gitkeep`. SQLite si adresář
|
- Adresáře `data/` a `media/` jsou v repu přes `.gitkeep`. SQLite si adresář
|
||||||
@@ -35,3 +39,6 @@ verzování [sémantické](https://semver.org/lang/cs/).
|
|||||||
- Stránky založené v administraci se nikde neobjevily. Navigace byla napsaná
|
- Stránky založené v administraci se nikde neobjevily. Navigace byla napsaná
|
||||||
natvrdo a odkazovala na `/kontakt`, i když stránka s tím slugem nemusela
|
natvrdo a odkazovala na `/kontakt`, i když stránka s tím slugem nemusela
|
||||||
existovat (odtud 404). Menu se teď skládá z kolekce Pages.
|
existovat (odtud 404). Menu se teď skládá z kolekce Pages.
|
||||||
|
- Slug se u stránek i položek normalizuje — vedoucí lomítko (`/kontakt`)
|
||||||
|
vyrábělo odkaz `href="//kontakt"`, který prohlížeč čte jako jméno cizího
|
||||||
|
serveru.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -34,6 +34,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
|||||||
<Link className="znacka" href="/">
|
<Link className="znacka" href="/">
|
||||||
Reiner <span>zbraně a střelivo</span>
|
Reiner <span>zbraně a střelivo</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
<div className="terc" aria-hidden="true" />
|
||||||
<nav>
|
<nav>
|
||||||
<Link href="/">Nabídka</Link>
|
<Link href="/">Nabídka</Link>
|
||||||
{stranky.map((stranka) => (
|
{stranky.map((stranka) => (
|
||||||
@@ -46,6 +47,9 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|
||||||
|
{/* Silueta lesa uzavírá stránku — přes celou šířku, nad patičkou. */}
|
||||||
|
<div className="skyline" aria-hidden="true" />
|
||||||
|
|
||||||
<footer className="paticka">
|
<footer className="paticka">
|
||||||
<p>© {new Date().getFullYear()} Martin Reiner</p>
|
<p>© {new Date().getFullYear()} Martin Reiner</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
--barva-plocha: #f5f5f4;
|
--barva-plocha: #f5f5f4;
|
||||||
--barva-linka: #e2e0dd;
|
--barva-linka: #e2e0dd;
|
||||||
--barva-akcent: #7a5c3e;
|
--barva-akcent: #7a5c3e;
|
||||||
|
--barva-silueta: #dcd7d0;
|
||||||
--sirka: 1100px;
|
--sirka: 1100px;
|
||||||
--radius: 6px;
|
--radius: 6px;
|
||||||
}
|
}
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
--barva-plocha: #1e1e1e;
|
--barva-plocha: #1e1e1e;
|
||||||
--barva-linka: #2e2e2e;
|
--barva-linka: #2e2e2e;
|
||||||
--barva-akcent: #c9a227;
|
--barva-akcent: #c9a227;
|
||||||
|
--barva-silueta: #262626;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,11 +98,53 @@ a {
|
|||||||
color: var(--barva-text);
|
color: var(--barva-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Terč a silueta lesa jsou vloženy jako CSS maska, ne jako <img>. Zdrojové
|
||||||
|
* PNG nesou jen průhlednost, barvu určuje `background-color` — díky tomu
|
||||||
|
* fungují ve světlém i tmavém motivu a nekouká z nich bílý obdélník.
|
||||||
|
*/
|
||||||
|
.terc {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
background-color: var(--barva-tlumena);
|
||||||
|
-webkit-mask: url('/terc.png') center / contain no-repeat;
|
||||||
|
mask: url('/terc.png') center / contain no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Červený střed je jediný barevný akcent v hlavičce — maska ho neumí
|
||||||
|
přenést, tak ho doplníme. */
|
||||||
|
.terc::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #c0392b;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skyline {
|
||||||
|
height: clamp(60px, 9vw, 120px);
|
||||||
|
margin-top: 4rem;
|
||||||
|
background-color: var(--barva-silueta);
|
||||||
|
-webkit-mask: url('/skyline.png') bottom center / cover no-repeat;
|
||||||
|
mask: url('/skyline.png') bottom center / cover no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
.terc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.paticka {
|
.paticka {
|
||||||
max-width: var(--sirka);
|
max-width: var(--sirka);
|
||||||
margin: 4rem auto 0;
|
margin: 0 auto;
|
||||||
padding: 1.5rem 1.25rem 3rem;
|
padding: 1.5rem 1.25rem 3rem;
|
||||||
border-top: 1px solid var(--barva-linka);
|
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--barva-tlumena);
|
color: var(--barva-tlumena);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { vytvorSlug } from '../lib/slug'
|
||||||
|
|
||||||
/** Statické stránky — o nás, kontakt, otevírací doba. */
|
/** Statické stránky — o nás, kontakt, otevírací doba. */
|
||||||
export const Pages: CollectionConfig = {
|
export const Pages: CollectionConfig = {
|
||||||
slug: 'pages',
|
slug: 'pages',
|
||||||
@@ -25,12 +27,18 @@ export const Pages: CollectionConfig = {
|
|||||||
name: 'slug',
|
name: 'slug',
|
||||||
label: 'URL adresa',
|
label: 'URL adresa',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
|
||||||
unique: true,
|
unique: true,
|
||||||
index: true,
|
index: true,
|
||||||
admin: {
|
admin: {
|
||||||
position: 'sidebar',
|
position: 'sidebar',
|
||||||
description: 'Např. `kontakt` → /kontakt',
|
description:
|
||||||
|
'Vyplní se samo z názvu, pokud necháte prázdné. Bez lomítek a diakritiky — ' +
|
||||||
|
'z „Otevírací doba" vznikne /oteviraci-doba.',
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
// Normalizuje i to, co uživatel napíše ručně — hlavně vede lomítko,
|
||||||
|
// které by vyrobilo odkaz na cizí server (`//kontakt`).
|
||||||
|
beforeValidate: [({ value, data }) => vytvorSlug(value || data?.nazev || '')],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { vytvorSlug } from '../lib/slug'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Položka nabídky — zbraň, střelivo, doplněk.
|
* Položka nabídky — zbraň, střelivo, doplněk.
|
||||||
*
|
*
|
||||||
@@ -41,9 +43,7 @@ export const Products: CollectionConfig = {
|
|||||||
description: 'Vyplní se samo z názvu, pokud necháte prázdné.',
|
description: 'Vyplní se samo z názvu, pokud necháte prázdné.',
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeValidate: [
|
beforeValidate: [({ value, data }) => vytvorSlug(value || data?.nazev || '')],
|
||||||
({ value, data }) => value || slugify(data?.nazev ?? ''),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -153,12 +153,3 @@ export const Products: CollectionConfig = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Diakritika pryč, mezery na pomlčky — `CZ 75 SP-01` → `cz-75-sp-01`. */
|
|
||||||
function slugify(text: string): string {
|
|
||||||
return text
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, '-')
|
|
||||||
.replace(/^-+|-+$/g, '')
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Převede text na URL adresu: `Otevírací doba` → `oteviraci-doba`.
|
||||||
|
*
|
||||||
|
* Snese i to, co člověk do pole běžně napíše — lomítka (`/kontakt`),
|
||||||
|
* diakritiku, velká písmena i mezery. Právě lomítko je zrádné: uložený
|
||||||
|
* slug `/kontakt` vyrobí odkaz `href="//kontakt"`, což prohlížeč přečte
|
||||||
|
* jako jméno cizího serveru, ne jako cestu na tomhle webu.
|
||||||
|
*/
|
||||||
|
export function vytvorSlug(text: string): string {
|
||||||
|
return text
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '')
|
||||||
|
}
|
||||||
@@ -212,9 +212,9 @@ export interface Page {
|
|||||||
id: number;
|
id: number;
|
||||||
nazev: string;
|
nazev: string;
|
||||||
/**
|
/**
|
||||||
* Např. `kontakt` → /kontakt
|
* Vyplní se samo z názvu, pokud necháte prázdné. Bez lomítek a diakritiky — z „Otevírací doba" vznikne /oteviraci-doba.
|
||||||
*/
|
*/
|
||||||
slug: string;
|
slug?: string | null;
|
||||||
obsah?: {
|
obsah?: {
|
||||||
root: {
|
root: {
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user