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:
@@ -34,6 +34,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
||||
<Link className="znacka" href="/">
|
||||
Reiner <span>zbraně a střelivo</span>
|
||||
</Link>
|
||||
<div className="terc" aria-hidden="true" />
|
||||
<nav>
|
||||
<Link href="/">Nabídka</Link>
|
||||
{stranky.map((stranka) => (
|
||||
@@ -46,6 +47,9 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
||||
|
||||
<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">
|
||||
<p>© {new Date().getFullYear()} Martin Reiner</p>
|
||||
<p>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
--barva-plocha: #f5f5f4;
|
||||
--barva-linka: #e2e0dd;
|
||||
--barva-akcent: #7a5c3e;
|
||||
--barva-silueta: #dcd7d0;
|
||||
--sirka: 1100px;
|
||||
--radius: 6px;
|
||||
}
|
||||
@@ -22,6 +23,7 @@
|
||||
--barva-plocha: #1e1e1e;
|
||||
--barva-linka: #2e2e2e;
|
||||
--barva-akcent: #c9a227;
|
||||
--barva-silueta: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +98,53 @@ a {
|
||||
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 {
|
||||
max-width: var(--sirka);
|
||||
margin: 4rem auto 0;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem 1.25rem 3rem;
|
||||
border-top: 1px solid var(--barva-linka);
|
||||
font-size: 0.85rem;
|
||||
color: var(--barva-tlumena);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { vytvorSlug } from '../lib/slug'
|
||||
|
||||
/** Statické stránky — o nás, kontakt, otevírací doba. */
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
@@ -25,12 +27,18 @@ export const Pages: CollectionConfig = {
|
||||
name: 'slug',
|
||||
label: 'URL adresa',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
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 { vytvorSlug } from '../lib/slug'
|
||||
|
||||
/**
|
||||
* 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é.',
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
({ value, data }) => value || slugify(data?.nazev ?? ''),
|
||||
],
|
||||
beforeValidate: [({ value, data }) => vytvorSlug(value || 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;
|
||||
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?: {
|
||||
root: {
|
||||
type: string;
|
||||
|
||||
Reference in New Issue
Block a user