@press2ai/theme-specialist-glossy 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/ai.ts +1 -1
- package/src/styles/base.ts +0 -33
- package/src/styles/index.ts +12 -10
- package/src/templates/catalog-grid.ts +18 -0
- package/src/templates/catalog.ts +10 -83
- package/src/templates/category-nav.ts +20 -0
- package/src/templates/helpers.ts +4 -0
- package/src/templates/hero.ts +72 -1
- package/src/templates/index.ts +6 -2
- package/src/templates/layout.ts +39 -1
- package/src/templates/pagination.ts +31 -0
- package/src/templates/profile-article.ts +23 -0
- package/src/templates/profile-card.ts +27 -0
- package/src/templates/stat-bar.ts +31 -0
- package/src/{styles → templates}/steps.ts +9 -1
- package/src/styles/_shared.ts +0 -1
- package/src/styles/catalog-grid.ts +0 -4
- package/src/styles/category-nav.ts +0 -8
- package/src/styles/footer.ts +0 -23
- package/src/styles/header.ts +0 -11
- package/src/styles/hero.ts +0 -38
- package/src/styles/pagination.ts +0 -8
- package/src/styles/profile-article.ts +0 -19
- package/src/styles/profile-card.ts +0 -22
- package/src/styles/stat-bar.ts +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@press2ai/theme-specialist-glossy",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Classless, AI-first glossy theme. Framework-agnostic templates (Hono, Astro, raw HTML). Semantic HTML, Schema.org microdata, JSON-LD — built for LLM crawlers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/ai.ts
CHANGED
package/src/styles/base.ts
CHANGED
|
@@ -33,19 +33,6 @@ body {
|
|
|
33
33
|
letter-spacing: -.011em;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/* --- HEADER --- */
|
|
37
|
-
body > header {
|
|
38
|
-
position: sticky; top: 0; z-index: 50;
|
|
39
|
-
background: rgba(255,255,255,.82); backdrop-filter: saturate(180%) blur(16px);
|
|
40
|
-
border-bottom: 1px solid var(--border);
|
|
41
|
-
}
|
|
42
|
-
body > header nav {
|
|
43
|
-
max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
44
|
-
height: 48px; display: flex; align-items: center; justify-content: space-between;
|
|
45
|
-
}
|
|
46
|
-
body > header nav a { color: var(--fg-muted); font-size: 13px; font-weight: 500; text-decoration: none; }
|
|
47
|
-
body > header nav a strong { font-size: 1rem; font-weight: 700; color: var(--fg); }
|
|
48
|
-
|
|
49
36
|
/* --- MAIN (container) --- */
|
|
50
37
|
main { max-width: var(--container); margin-inline: auto; padding-inline: var(--pad); }
|
|
51
38
|
main > * + * { margin-top: var(--g5); }
|
|
@@ -72,26 +59,6 @@ label { display: block; font-size: 14px; font-weight: 600; margin-bottom: var(--
|
|
|
72
59
|
button, input[type="submit"] { background: var(--accent); color: var(--surface); border: none; border-radius: var(--pill); padding: var(--g2) var(--g4); font-size: 14px; font-weight: 600; font-family: inherit; cursor: pointer; transition: all var(--ease); }
|
|
73
60
|
button:hover, input[type="submit"]:hover { opacity: .85; }
|
|
74
61
|
|
|
75
|
-
/* --- FOOTER --- */
|
|
76
|
-
body > footer {
|
|
77
|
-
border-top: 1px solid var(--border); background: var(--surface);
|
|
78
|
-
padding: var(--g5) 0 var(--g4); margin-top: var(--g6);
|
|
79
|
-
}
|
|
80
|
-
body > footer > small {
|
|
81
|
-
display: block; max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
82
|
-
color: var(--fg-faint); font-size: .8rem;
|
|
83
|
-
}
|
|
84
|
-
body > footer a { color: var(--fg-muted); transition: color var(--ease); text-decoration: none; }
|
|
85
|
-
body > footer a:hover { color: var(--accent); }
|
|
86
|
-
body > footer > small > nav {
|
|
87
|
-
display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--g4); margin-bottom: var(--g4);
|
|
88
|
-
}
|
|
89
|
-
body > footer > small > nav section { all: unset; display: block; }
|
|
90
|
-
body > footer > small > nav strong { display: block; font-size: .8rem; font-weight: 600; color: var(--fg); text-transform: uppercase; letter-spacing: .06em; margin-bottom: var(--g2); }
|
|
91
|
-
body > footer > small > nav p { font-size: .8rem; line-height: 1.6; color: var(--fg-faint); margin: 0; max-width: 36ch; }
|
|
92
|
-
body > footer > small > nav a { display: block; font-size: .8rem; line-height: 2; }
|
|
93
|
-
body > footer > small > p { padding-top: var(--g3); border-top: 1px solid var(--border); }
|
|
94
|
-
|
|
95
62
|
/* --- RESPONSIVE --- */
|
|
96
63
|
@media (max-width: 640px) {
|
|
97
64
|
:root { --pad: var(--g2); }
|
package/src/styles/index.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
// CSS for each component lives next to its HTML in `../templates/`.
|
|
2
|
+
// This file is the dispatcher that the consumer calls via `composeCss('hero', ...)`.
|
|
3
|
+
|
|
1
4
|
import { css as base } from './base.ts';
|
|
2
|
-
import { css as header } from './header.ts';
|
|
3
|
-
import { css as footer } from './footer.ts';
|
|
4
|
-
import { css as hero } from './hero.ts';
|
|
5
|
-
import { css as statBar } from './stat-bar.ts';
|
|
6
|
-
import { css as categoryNav } from './category-nav.ts';
|
|
7
|
-
import { css as steps } from './steps.ts';
|
|
8
|
-
import { css as catalogGrid } from './catalog-grid.ts';
|
|
9
|
-
import { css as profileCard } from './profile-card.ts';
|
|
10
|
-
import { css as profileArticle } from './profile-article.ts';
|
|
11
|
-
import { css as pagination } from './pagination.ts';
|
|
12
5
|
import { css as forms } from './forms.ts';
|
|
6
|
+
import { headerCss as header, footerCss as footer } from '../templates/layout.ts';
|
|
7
|
+
import { css as hero } from '../templates/hero.ts';
|
|
8
|
+
import { css as statBar } from '../templates/stat-bar.ts';
|
|
9
|
+
import { css as categoryNav } from '../templates/category-nav.ts';
|
|
10
|
+
import { css as steps } from '../templates/steps.ts';
|
|
11
|
+
import { css as catalogGrid } from '../templates/catalog-grid.ts';
|
|
12
|
+
import { css as profileCard } from '../templates/profile-card.ts';
|
|
13
|
+
import { css as profileArticle } from '../templates/profile-article.ts';
|
|
14
|
+
import { css as pagination } from '../templates/pagination.ts';
|
|
13
15
|
|
|
14
16
|
const components: Record<string, string> = {
|
|
15
17
|
hero,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { esc } from './helpers.ts';
|
|
2
|
+
|
|
3
|
+
// Selector matches `<section>` whose direct child is the article cards rendered
|
|
4
|
+
// by `profileCard()`. Cards themselves are styled by profile-card.ts using the
|
|
5
|
+
// same selector head — the two files share this invariant.
|
|
6
|
+
const R = 'main > section:has(> article[itemscope])';
|
|
7
|
+
|
|
8
|
+
export const css = `${R} { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--g3); }
|
|
9
|
+
${R} > h2 { grid-column: 1 / -1; }
|
|
10
|
+
@media (max-width: 640px) { ${R} { grid-template-columns: 1fr; } }`;
|
|
11
|
+
|
|
12
|
+
export function catalogGrid(p: { title: string; filters?: string; cards: string }): string {
|
|
13
|
+
return `<section>
|
|
14
|
+
<h2>${esc(p.title)}</h2>
|
|
15
|
+
${p.filters ?? ''}
|
|
16
|
+
${p.cards}
|
|
17
|
+
</section>`;
|
|
18
|
+
}
|
package/src/templates/catalog.ts
CHANGED
|
@@ -1,83 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function catalogHero(p: CatalogHeroProps): string {
|
|
14
|
-
const badge = p.badge ? `<p><small>${esc(p.badge)}</small></p>` : '';
|
|
15
|
-
const subtitle = p.subtitle ? `<p>${esc(p.subtitle)}</p>` : '';
|
|
16
|
-
const search = p.searchAction !== undefined ? `<form role="search" action="${esc(p.searchAction ?? '/')}" method="get">
|
|
17
|
-
<input type="search" name="q" placeholder="${esc(p.searchPlaceholder ?? 'Szukaj...')}" value="${esc(p.searchValue ?? '')}" />
|
|
18
|
-
<button type="submit">Szukaj</button>
|
|
19
|
-
</form>` : '';
|
|
20
|
-
const ctas = p.ctas?.length ? `<nav>${p.ctas.map(
|
|
21
|
-
c => `<a href="${esc(c.href)}">${esc(c.label)}</a>`
|
|
22
|
-
).join('\n')}</nav>` : '';
|
|
23
|
-
return `<hgroup>
|
|
24
|
-
${badge}
|
|
25
|
-
<h1>${p.title}</h1>
|
|
26
|
-
${subtitle}
|
|
27
|
-
${search}
|
|
28
|
-
${ctas}
|
|
29
|
-
</hgroup>`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const STAT_ICONS: Record<string, string> = {
|
|
33
|
-
people: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
|
|
34
|
-
city: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/><path d="M9 9v.01M9 12v.01M9 15v.01M9 18v.01"/></svg>',
|
|
35
|
-
free: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export function statBar(items: { value: string; label: string; icon?: string }[], summary?: string): string {
|
|
39
|
-
const divs = items.map(i => {
|
|
40
|
-
const icon = i.icon && STAT_ICONS[i.icon] ? `<span>${STAT_ICONS[i.icon]}</span>` : '';
|
|
41
|
-
return `<div>${icon}<dt>${esc(i.value)}</dt><dd>${esc(i.label)}</dd></div>`;
|
|
42
|
-
}).join('\n');
|
|
43
|
-
const summaryP = summary ? `<p>${esc(summary)}</p>` : '';
|
|
44
|
-
return `<section aria-label="Statystyki">${summaryP}<dl aria-hidden="true">\n${divs}\n</dl></section>`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function categoryNav(items: { href: string; label: string }[], ariaLabel = 'Kategorie'): string {
|
|
48
|
-
if (!items.length) return '';
|
|
49
|
-
const links = items.map(i => `<a href="${esc(i.href)}">${esc(i.label)}</a>`).join('');
|
|
50
|
-
return `<nav aria-label="${esc(ariaLabel)}">${links}</nav>`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function steps(title: string, items: { title: string; description: string }[]): string {
|
|
54
|
-
const lis = items.map(i => `<li><strong>${esc(i.title)}</strong><span>${esc(i.description)}</span></li>`).join('\n');
|
|
55
|
-
return `<section>\n<h2>${esc(title)}</h2>\n<ol>\n${lis}\n</ol>\n</section>`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function catalogGrid(p: { title: string; filters?: string; cards: string }): string {
|
|
59
|
-
return `<section>
|
|
60
|
-
<h2>${esc(p.title)}</h2>
|
|
61
|
-
${p.filters ?? ''}
|
|
62
|
-
${p.cards}
|
|
63
|
-
</section>`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export interface PaginationProps {
|
|
68
|
-
current: number;
|
|
69
|
-
total: number;
|
|
70
|
-
baseHref?: string;
|
|
71
|
-
extraParams?: string;
|
|
72
|
-
prevLabel?: string;
|
|
73
|
-
nextLabel?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function pagination(p: PaginationProps): string {
|
|
77
|
-
if (p.total <= 1) return '';
|
|
78
|
-
const base = p.baseHref ?? '/';
|
|
79
|
-
const extra = p.extraParams ?? '';
|
|
80
|
-
const prev = p.current > 1 ? `<a href="${esc(base)}?p=${p.current - 1}${extra}">${esc(p.prevLabel ?? '\u2190 Poprzednia')}</a>` : '';
|
|
81
|
-
const next = p.current < p.total ? `<a href="${esc(base)}?p=${p.current + 1}${extra}">${esc(p.nextLabel ?? 'Następna \u2192')}</a>` : '';
|
|
82
|
-
return `<nav><p>${prev} Strona ${p.current} z ${p.total} ${next}</p></nav>`;
|
|
83
|
-
}
|
|
1
|
+
// Public entry point — kept for backwards compatibility with anything importing
|
|
2
|
+
// `@press2ai/theme-specialist-glossy/catalog`. The functions live in their own
|
|
3
|
+
// files now (each co-located with its CSS); this file just re-exports them.
|
|
4
|
+
|
|
5
|
+
export { catalogHero, type CatalogHeroProps } from './hero.ts';
|
|
6
|
+
export { statBar } from './stat-bar.ts';
|
|
7
|
+
export { categoryNav } from './category-nav.ts';
|
|
8
|
+
export { steps } from './steps.ts';
|
|
9
|
+
export { catalogGrid } from './catalog-grid.ts';
|
|
10
|
+
export { pagination, type PaginationProps } from './pagination.ts';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { esc } from './helpers.ts';
|
|
2
|
+
|
|
3
|
+
// Selector matches `<nav>` direct child of `main` that contains `<a>` — i.e.
|
|
4
|
+
// the bare list-of-anchors nav rendered below. Hero's CTA <nav> sits inside
|
|
5
|
+
// <hgroup>, not <main>, so it is not affected.
|
|
6
|
+
const R = 'main > nav:has(> a)';
|
|
7
|
+
|
|
8
|
+
export const css = `${R} { display: flex; flex-wrap: wrap; gap: var(--g1); }
|
|
9
|
+
${R} > a {
|
|
10
|
+
display: inline-flex; align-items: center; height: 32px; padding: 0 var(--g2);
|
|
11
|
+
border-radius: var(--pill); font-size: .8rem; font-weight: 500;
|
|
12
|
+
background: var(--surface); border: 1px solid var(--border); color: var(--fg-muted); transition: all var(--ease);
|
|
13
|
+
}
|
|
14
|
+
${R} > a:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }`;
|
|
15
|
+
|
|
16
|
+
export function categoryNav(items: { href: string; label: string }[], ariaLabel = 'Kategorie'): string {
|
|
17
|
+
if (!items.length) return '';
|
|
18
|
+
const links = items.map(i => `<a href="${esc(i.href)}">${esc(i.label)}</a>`).join('');
|
|
19
|
+
return `<nav aria-label="${esc(ariaLabel)}">${links}</nav>`;
|
|
20
|
+
}
|
package/src/templates/helpers.ts
CHANGED
|
@@ -8,3 +8,7 @@ export function esc(s: string | undefined | null): string {
|
|
|
8
8
|
export function fullName(p: Profile): string {
|
|
9
9
|
return `${p.firstName} ${p.lastName}`;
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
// Full-bleed escape from the centered `main` container — so a section can paint
|
|
13
|
+
// edge-to-edge while its content stays aligned to var(--container).
|
|
14
|
+
export const BREAKOUT = `margin-inline: calc(50% - 50vw); padding-inline: var(--pad);`;
|
package/src/templates/hero.ts
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
import type { Profile } from '../schema.ts';
|
|
2
|
-
import { esc, fullName } from './helpers.ts';
|
|
2
|
+
import { esc, fullName, BREAKOUT } from './helpers.ts';
|
|
3
|
+
|
|
4
|
+
// Both `hero(profile)` and `catalogHero(props)` render the SAME outer element
|
|
5
|
+
// `<hgroup>` and share this CSS. If you split them visually, give one a
|
|
6
|
+
// distinguishing wrapper/attribute and update the selector below.
|
|
7
|
+
const R = 'main > hgroup';
|
|
8
|
+
|
|
9
|
+
export const css = `${R} {
|
|
10
|
+
${BREAKOUT} text-align: center; overflow: hidden; position: relative; isolation: isolate;
|
|
11
|
+
padding-block: var(--g6) var(--g5);
|
|
12
|
+
background: radial-gradient(ellipse 70% 50% at 20% 30%, var(--accent-glow) 0%, transparent 60%), radial-gradient(ellipse 50% 60% at 80% 20%, rgba(13,148,136,.12) 0%, transparent 55%), linear-gradient(180deg, var(--surface) 0%, var(--bg) 100%);
|
|
13
|
+
border-bottom: 1px solid var(--border);
|
|
14
|
+
}
|
|
15
|
+
${R}::before {
|
|
16
|
+
content: ''; position: absolute; inset: 0;
|
|
17
|
+
background-image: linear-gradient(rgba(124,92,252,.04) 1px, transparent 1px), linear-gradient(90deg, rgba(124,92,252,.04) 1px, transparent 1px);
|
|
18
|
+
background-size: 48px 48px; mask-image: radial-gradient(ellipse 60% 50% at 50% 40%, black 0%, transparent 70%); z-index: -1;
|
|
19
|
+
}
|
|
20
|
+
${R} > * { position: relative; }
|
|
21
|
+
${R} > p:first-child { display: inline-flex; margin-bottom: var(--g4); }
|
|
22
|
+
${R} > p:first-child small {
|
|
23
|
+
display: inline-flex; align-items: center; gap: var(--g1);
|
|
24
|
+
background: rgba(255,255,255,.7); backdrop-filter: blur(8px);
|
|
25
|
+
padding: var(--g1) var(--g2); border: 1px solid var(--border); border-radius: var(--pill);
|
|
26
|
+
font-weight: 600; color: var(--teal); box-shadow: var(--shadow);
|
|
27
|
+
}
|
|
28
|
+
${R} > p:first-child small::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #00d24a; box-shadow: 0 0 0 3px rgba(0,210,74,.2); }
|
|
29
|
+
${R} h1 { max-width: 18ch; margin: 0 auto var(--g3); }
|
|
30
|
+
${R} p { font-size: 1.25rem; line-height: 1.55; max-width: 38rem; margin: 0 auto var(--g3); }
|
|
31
|
+
form[role="search"] {
|
|
32
|
+
display: flex; max-width: 495px; height: 48px; margin: var(--g4) auto 0;
|
|
33
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--pill);
|
|
34
|
+
overflow: hidden; box-shadow: var(--shadow-md); transition: all var(--ease);
|
|
35
|
+
}
|
|
36
|
+
form[role="search"]:focus-within { border-color: var(--accent); box-shadow: 0 4px 16px var(--accent-glow); }
|
|
37
|
+
form[role="search"] input { flex: 1; min-width: 0; border: none; background: transparent; color: var(--fg); padding: 0 var(--g3); font-size: 1rem; font-family: var(--font); outline: none; }
|
|
38
|
+
form[role="search"] input::placeholder { color: var(--fg-faint); }
|
|
39
|
+
form[role="search"] button { flex-shrink: 0; height: 100%; border: none; background: var(--accent); color: var(--surface); padding: 0 var(--g4); font-size: 1rem; font-weight: 600; font-family: var(--font); cursor: pointer; border-radius: 0; }
|
|
40
|
+
form[role="search"] button:hover { opacity: .85; }
|
|
41
|
+
@media (max-width: 640px) {
|
|
42
|
+
${R} { padding-block: var(--g5) var(--g4); }
|
|
43
|
+
form[role="search"] { height: 40px; }
|
|
44
|
+
}`;
|
|
3
45
|
|
|
4
46
|
export function hero(p: Profile): string {
|
|
5
47
|
const name = fullName(p);
|
|
@@ -20,3 +62,32 @@ ${badge}
|
|
|
20
62
|
${ctaBlock}
|
|
21
63
|
</hgroup>`;
|
|
22
64
|
}
|
|
65
|
+
|
|
66
|
+
export interface CatalogHeroProps {
|
|
67
|
+
badge?: string;
|
|
68
|
+
title: string;
|
|
69
|
+
subtitle?: string;
|
|
70
|
+
searchAction?: string;
|
|
71
|
+
searchPlaceholder?: string;
|
|
72
|
+
searchValue?: string;
|
|
73
|
+
ctas?: { label: string; href: string }[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function catalogHero(p: CatalogHeroProps): string {
|
|
77
|
+
const badge = p.badge ? `<p><small>${esc(p.badge)}</small></p>` : '';
|
|
78
|
+
const subtitle = p.subtitle ? `<p>${esc(p.subtitle)}</p>` : '';
|
|
79
|
+
const search = p.searchAction !== undefined ? `<form role="search" action="${esc(p.searchAction ?? '/')}" method="get">
|
|
80
|
+
<input type="search" name="q" placeholder="${esc(p.searchPlaceholder ?? 'Szukaj...')}" value="${esc(p.searchValue ?? '')}" />
|
|
81
|
+
<button type="submit">Szukaj</button>
|
|
82
|
+
</form>` : '';
|
|
83
|
+
const ctas = p.ctas?.length ? `<nav>${p.ctas.map(
|
|
84
|
+
c => `<a href="${esc(c.href)}">${esc(c.label)}</a>`
|
|
85
|
+
).join('\n')}</nav>` : '';
|
|
86
|
+
return `<hgroup>
|
|
87
|
+
${badge}
|
|
88
|
+
<h1>${p.title}</h1>
|
|
89
|
+
${subtitle}
|
|
90
|
+
${search}
|
|
91
|
+
${ctas}
|
|
92
|
+
</hgroup>`;
|
|
93
|
+
}
|
package/src/templates/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { layout, type LayoutProps } from './layout.ts';
|
|
2
|
-
export { hero } from './hero.ts';
|
|
2
|
+
export { hero, catalogHero, type CatalogHeroProps } from './hero.ts';
|
|
3
3
|
export { profileCard } from './profile-card.ts';
|
|
4
4
|
export { profileArticle } from './profile-article.ts';
|
|
5
|
-
export {
|
|
5
|
+
export { statBar } from './stat-bar.ts';
|
|
6
|
+
export { categoryNav } from './category-nav.ts';
|
|
7
|
+
export { steps } from './steps.ts';
|
|
8
|
+
export { catalogGrid } from './catalog-grid.ts';
|
|
9
|
+
export { pagination, type PaginationProps } from './pagination.ts';
|
|
6
10
|
export { esc, fullName } from './helpers.ts';
|
package/src/templates/layout.ts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import { esc } from './helpers.ts';
|
|
2
2
|
|
|
3
|
+
// Header/footer CSS lives here because the matching HTML is built inline by
|
|
4
|
+
// `layout()` below — `body > header > nav` and `body > footer > small`.
|
|
5
|
+
// Change the markup, change the selector. Don't split these apart again.
|
|
6
|
+
|
|
7
|
+
export const headerCss = `body > header {
|
|
8
|
+
position: sticky; top: 0; z-index: 50;
|
|
9
|
+
background: rgba(255,255,255,.82); backdrop-filter: saturate(180%) blur(16px);
|
|
10
|
+
border-bottom: 1px solid var(--border);
|
|
11
|
+
}
|
|
12
|
+
body > header nav {
|
|
13
|
+
max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
14
|
+
height: 48px; display: flex; align-items: center; justify-content: space-between;
|
|
15
|
+
}
|
|
16
|
+
body > header nav a { color: var(--fg-muted); font-size: 13px; font-weight: 500; text-decoration: none; }
|
|
17
|
+
body > header nav a strong { font-size: 1rem; font-weight: 700; color: var(--fg); }`;
|
|
18
|
+
|
|
19
|
+
export const footerCss = `body > footer {
|
|
20
|
+
border-top: 1px solid var(--border); background: var(--surface);
|
|
21
|
+
padding: var(--g5) 0 var(--g4); margin-top: var(--g6);
|
|
22
|
+
}
|
|
23
|
+
body > footer > small {
|
|
24
|
+
display: block; max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
25
|
+
color: var(--fg-faint); font-size: .8rem;
|
|
26
|
+
}
|
|
27
|
+
body > footer a { color: var(--fg-muted); transition: color var(--ease); text-decoration: none; }
|
|
28
|
+
body > footer a:hover { color: var(--accent); }
|
|
29
|
+
body > footer > small > nav {
|
|
30
|
+
display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--g4); margin-bottom: var(--g4);
|
|
31
|
+
}
|
|
32
|
+
body > footer > small > nav section { all: unset; display: block; }
|
|
33
|
+
body > footer > small > nav strong { display: block; font-size: .8rem; font-weight: 600; color: var(--fg); text-transform: uppercase; letter-spacing: .06em; margin-bottom: var(--g2); }
|
|
34
|
+
body > footer > small > nav p { font-size: .8rem; line-height: 1.6; color: var(--fg-faint); margin: 0; max-width: 36ch; }
|
|
35
|
+
body > footer > small > nav a { display: block; font-size: .8rem; line-height: 2; }
|
|
36
|
+
body > footer > small > p { padding-top: var(--g3); border-top: 1px solid var(--border); }
|
|
37
|
+
@media (max-width: 640px) {
|
|
38
|
+
body > footer > small > nav { grid-template-columns: 1fr; gap: var(--g3); }
|
|
39
|
+
}`;
|
|
40
|
+
|
|
3
41
|
export interface LayoutProps {
|
|
4
42
|
title: string;
|
|
5
43
|
description?: string;
|
|
@@ -12,7 +50,7 @@ export interface LayoutProps {
|
|
|
12
50
|
footerContent?: string;
|
|
13
51
|
}
|
|
14
52
|
|
|
15
|
-
const THEME_VERSION = '3.0.
|
|
53
|
+
const THEME_VERSION = '3.0.2';
|
|
16
54
|
|
|
17
55
|
export function layout(props: LayoutProps, body: string): string {
|
|
18
56
|
const {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { esc } from './helpers.ts';
|
|
2
|
+
|
|
3
|
+
// Selector matches `<nav>` containing `<p>` — the shape of `pagination()`.
|
|
4
|
+
// category-nav uses `<nav>` containing `<a>` directly, not `<p>`, so they don't collide.
|
|
5
|
+
const R = 'main > nav:has(> p)';
|
|
6
|
+
|
|
7
|
+
export const css = `${R} > p { display: flex; align-items: center; justify-content: center; gap: var(--g3); font-size: 1rem; color: var(--fg-faint); }
|
|
8
|
+
${R} a {
|
|
9
|
+
height: 32px; display: inline-flex; align-items: center; padding: 0 var(--g2);
|
|
10
|
+
border-radius: var(--pill); background: var(--surface); border: 1px solid var(--border);
|
|
11
|
+
font-size: .8rem; font-weight: 500; transition: all var(--ease); text-decoration: none;
|
|
12
|
+
}
|
|
13
|
+
${R} a:hover { border-color: var(--accent); color: var(--accent); }`;
|
|
14
|
+
|
|
15
|
+
export interface PaginationProps {
|
|
16
|
+
current: number;
|
|
17
|
+
total: number;
|
|
18
|
+
baseHref?: string;
|
|
19
|
+
extraParams?: string;
|
|
20
|
+
prevLabel?: string;
|
|
21
|
+
nextLabel?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function pagination(p: PaginationProps): string {
|
|
25
|
+
if (p.total <= 1) return '';
|
|
26
|
+
const base = p.baseHref ?? '/';
|
|
27
|
+
const extra = p.extraParams ?? '';
|
|
28
|
+
const prev = p.current > 1 ? `<a href="${esc(base)}?p=${p.current - 1}${extra}">${esc(p.prevLabel ?? '\u2190 Poprzednia')}</a>` : '';
|
|
29
|
+
const next = p.current < p.total ? `<a href="${esc(base)}?p=${p.current + 1}${extra}">${esc(p.nextLabel ?? 'Następna \u2192')}</a>` : '';
|
|
30
|
+
return `<nav><p>${prev} Strona ${p.current} z ${p.total} ${next}</p></nav>`;
|
|
31
|
+
}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import type { Profile } from '../schema.ts';
|
|
2
2
|
import { esc, fullName } from './helpers.ts';
|
|
3
3
|
|
|
4
|
+
// Selector root MUST match the outer element below (`<article itemscope>` inside <main>).
|
|
5
|
+
// If you change the HTML root, change R — and vice versa. Keep them in this file together.
|
|
6
|
+
const R = 'main > article[itemscope]';
|
|
7
|
+
|
|
8
|
+
export const css = `${R} { max-width: 667px; }
|
|
9
|
+
${R} > header { padding-bottom: var(--g4); margin-bottom: var(--g4); border-bottom: 2px solid var(--fg); }
|
|
10
|
+
${R} > header h1 { font-size: 2.441rem; font-weight: 800; letter-spacing: -.04em; line-height: 1.05; margin-bottom: var(--g1); }
|
|
11
|
+
${R} > header p { font-size: 1.25rem; color: var(--fg-muted); margin: 0; }
|
|
12
|
+
${R} > header small { display: block; margin-top: var(--g1); font-size: .8rem; color: var(--teal); font-weight: 600; text-transform: uppercase; letter-spacing: .08em; }
|
|
13
|
+
${R} address { font-style: normal; font-size: 1rem; color: var(--fg-muted); padding: var(--g2) 0; margin-bottom: var(--g3); border-bottom: 1px solid var(--border); }
|
|
14
|
+
${R} section { margin: var(--g4) 0; padding-top: var(--g3); border-top: 1px solid var(--border); }
|
|
15
|
+
${R} section h2 { font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; margin: 0 0 var(--g2); }
|
|
16
|
+
${R} ul { list-style: none; display: flex; flex-wrap: wrap; gap: var(--g1); }
|
|
17
|
+
${R} ul li { font-size: .8rem; font-weight: 500; color: var(--fg-muted); }
|
|
18
|
+
${R} ul li:not(:last-child)::after { content: ','; }
|
|
19
|
+
${R} dl { display: grid; grid-template-columns: 8rem 1fr; gap: var(--g1) var(--g3); }
|
|
20
|
+
${R} dt { font-size: .8rem; color: var(--fg-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
|
|
21
|
+
${R} dd { margin: 0; font-size: 1rem; }
|
|
22
|
+
@media (max-width: 640px) {
|
|
23
|
+
${R} dl { grid-template-columns: 1fr; gap: 2px 0; }
|
|
24
|
+
${R} dt { margin-top: var(--g2); }
|
|
25
|
+
}`;
|
|
26
|
+
|
|
4
27
|
export function profileArticle(p: Profile): string {
|
|
5
28
|
const name = fullName(p);
|
|
6
29
|
const socials = Object.entries(p.social ?? {}).filter(([, v]) => v);
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import type { Profile } from '../schema.ts';
|
|
2
2
|
import { esc, fullName } from './helpers.ts';
|
|
3
3
|
|
|
4
|
+
// Card sits inside catalog grid: `<section><h2>...</h2><article>...</article>...</section>`.
|
|
5
|
+
// Selector targets the article only when wrapped by such a section — keep that
|
|
6
|
+
// shape in sync with `catalogGrid()` in catalog-grid.ts.
|
|
7
|
+
const R = 'main > section:has(> article[itemscope]) > article[itemscope]';
|
|
8
|
+
|
|
9
|
+
export const css = `${R} {
|
|
10
|
+
background: var(--card); border: 1px solid var(--border); border-radius: var(--r-lg);
|
|
11
|
+
padding: var(--g3); display: flex; flex-direction: column;
|
|
12
|
+
box-shadow: var(--shadow); transition: all var(--ease);
|
|
13
|
+
}
|
|
14
|
+
${R}:hover { transform: translateY(-2px); box-shadow: var(--shadow-up); border-color: var(--border-strong); }
|
|
15
|
+
${R} > div[aria-hidden] {
|
|
16
|
+
width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
|
17
|
+
font-size: .8rem; font-weight: 700; color: var(--surface); background: var(--accent); margin-bottom: var(--g2);
|
|
18
|
+
}
|
|
19
|
+
${R} header { margin-bottom: var(--g1); }
|
|
20
|
+
${R} h2 { font-size: 1rem; font-weight: 600; line-height: 1.3; margin: 0 0 2px; }
|
|
21
|
+
${R} h2 a { color: var(--fg); text-decoration: none; }
|
|
22
|
+
${R} h2 a:hover { color: var(--accent); }
|
|
23
|
+
${R} header p { font-size: .8rem; color: var(--fg-faint); margin: 0; }
|
|
24
|
+
${R} header p:has([itemprop="address"]) { display: inline-flex; align-items: center; gap: 4px; margin-top: 2px; }
|
|
25
|
+
${R} header p:has([itemprop="address"])::before { content: ''; width: 3px; height: 3px; border-radius: 50%; background: var(--fg-faint); }
|
|
26
|
+
${R} ul { list-style: none; display: flex; flex-wrap: wrap; gap: 4px; margin-top: var(--g1); }
|
|
27
|
+
${R} ul li { background: var(--accent-soft); color: var(--accent); padding: 1px var(--g1); border-radius: var(--pill); font-size: .7rem; font-weight: 500; }
|
|
28
|
+
${R} > a:last-child { display: inline-flex; margin-top: auto; padding-top: var(--g2); font-size: .8rem; font-weight: 600; color: var(--accent); }
|
|
29
|
+
${R} > a:last-child:hover { color: var(--fg); }`;
|
|
30
|
+
|
|
4
31
|
export function profileCard(p: Profile, href: string): string {
|
|
5
32
|
const name = fullName(p);
|
|
6
33
|
const initials = `${(p.firstName?.[0] ?? '').toUpperCase()}${(p.lastName?.[0] ?? '').toUpperCase()}`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { esc } from './helpers.ts';
|
|
2
|
+
|
|
3
|
+
// Selector matches `<section>` whose direct child is `<dl>` — that's how
|
|
4
|
+
// `statBar()` below renders. If you change the wrapper, update the selector.
|
|
5
|
+
const R = 'main > section:has(> dl)';
|
|
6
|
+
|
|
7
|
+
export const css = `${R} > dl { display: flex; justify-content: center; gap: var(--g5); }
|
|
8
|
+
${R} > dl div { text-align: center; }
|
|
9
|
+
${R} > dl div > span:has(> svg) {
|
|
10
|
+
display: flex; align-items: center; justify-content: center; width: 36px; height: 36px;
|
|
11
|
+
margin: 0 auto var(--g1); background: var(--accent-soft); border-radius: 50%; color: var(--accent);
|
|
12
|
+
}
|
|
13
|
+
${R} > dl div > span:has(> svg) > svg { width: 18px; height: 18px; }
|
|
14
|
+
${R} > dl dt { font-size: 1.563rem; font-weight: 800; line-height: 1.2; color: var(--accent); }
|
|
15
|
+
${R} > dl dd { margin: 2px 0 0; font-size: .8rem; color: var(--fg-faint); font-weight: 500; text-transform: uppercase; letter-spacing: .06em; }
|
|
16
|
+
@media (max-width: 640px) { ${R} > dl { gap: var(--g3); } }`;
|
|
17
|
+
|
|
18
|
+
const STAT_ICONS: Record<string, string> = {
|
|
19
|
+
people: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
|
|
20
|
+
city: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/><path d="M9 9v.01M9 12v.01M9 15v.01M9 18v.01"/></svg>',
|
|
21
|
+
free: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function statBar(items: { value: string; label: string; icon?: string }[], summary?: string): string {
|
|
25
|
+
const divs = items.map(i => {
|
|
26
|
+
const icon = i.icon && STAT_ICONS[i.icon] ? `<span>${STAT_ICONS[i.icon]}</span>` : '';
|
|
27
|
+
return `<div>${icon}<dt>${esc(i.value)}</dt><dd>${esc(i.label)}</dd></div>`;
|
|
28
|
+
}).join('\n');
|
|
29
|
+
const summaryP = summary ? `<p>${esc(summary)}</p>` : '';
|
|
30
|
+
return `<section aria-label="Statystyki">${summaryP}<dl aria-hidden="true">\n${divs}\n</dl></section>`;
|
|
31
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { BREAKOUT } from './
|
|
1
|
+
import { esc, BREAKOUT } from './helpers.ts';
|
|
2
|
+
|
|
3
|
+
// Selector matches `<section>` containing `<ol>` — that's the shape of `steps()` below.
|
|
2
4
|
const R = 'main > section:has(> ol)';
|
|
5
|
+
|
|
3
6
|
export const css = `${R} {
|
|
4
7
|
${BREAKOUT} background: var(--surface); padding-block: var(--g5);
|
|
5
8
|
border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
|
|
@@ -22,3 +25,8 @@ ${R} > ol > li::before {
|
|
|
22
25
|
${R} > ol > li strong { display: block; font-size: 1rem; margin-bottom: 4px; }
|
|
23
26
|
${R} > ol > li span { font-size: .8rem; line-height: 1.5; color: var(--fg-faint); }
|
|
24
27
|
@media (max-width: 640px) { ${R} > ol { grid-template-columns: 1fr; } }`;
|
|
28
|
+
|
|
29
|
+
export function steps(title: string, items: { title: string; description: string }[]): string {
|
|
30
|
+
const lis = items.map(i => `<li><strong>${esc(i.title)}</strong><span>${esc(i.description)}</span></li>`).join('\n');
|
|
31
|
+
return `<section>\n<h2>${esc(title)}</h2>\n<ol>\n${lis}\n</ol>\n</section>`;
|
|
32
|
+
}
|
package/src/styles/_shared.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const BREAKOUT = `margin-inline: calc(50% - 50vw); padding-inline: var(--pad);`;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
const R = 'main > section:has(> article[itemscope])';
|
|
2
|
-
export const css = `${R} { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--g3); }
|
|
3
|
-
${R} > h2 { grid-column: 1 / -1; }
|
|
4
|
-
@media (max-width: 640px) { ${R} { grid-template-columns: 1fr; } }`;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const R = 'main > nav:has(> a)';
|
|
2
|
-
export const css = `${R} { display: flex; flex-wrap: wrap; gap: var(--g1); }
|
|
3
|
-
${R} > a {
|
|
4
|
-
display: inline-flex; align-items: center; height: 32px; padding: 0 var(--g2);
|
|
5
|
-
border-radius: var(--pill); font-size: .8rem; font-weight: 500;
|
|
6
|
-
background: var(--surface); border: 1px solid var(--border); color: var(--fg-muted); transition: all var(--ease);
|
|
7
|
-
}
|
|
8
|
-
${R} > a:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }`;
|
package/src/styles/footer.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export const css = `body > footer {
|
|
2
|
-
border-top: 1px solid var(--line); background: var(--white); padding: var(--g5) 0 var(--g4);
|
|
3
|
-
}
|
|
4
|
-
body > footer > small {
|
|
5
|
-
display: block; max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
6
|
-
color: var(--ink-3); font-size: var(--t-xs);
|
|
7
|
-
}
|
|
8
|
-
body > footer a { color: var(--ink-2); transition: color var(--ease); }
|
|
9
|
-
body > footer a:hover { color: var(--violet); }
|
|
10
|
-
body > footer > small > nav {
|
|
11
|
-
display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--g4); margin-bottom: var(--g4);
|
|
12
|
-
}
|
|
13
|
-
body > footer > small > nav section { all: unset; display: block; }
|
|
14
|
-
body > footer > small > nav strong {
|
|
15
|
-
display: block; font-size: var(--t-xs); font-weight: 600; color: var(--ink);
|
|
16
|
-
text-transform: uppercase; letter-spacing: .06em; margin-bottom: var(--g2);
|
|
17
|
-
}
|
|
18
|
-
body > footer > small > nav p { font-size: var(--t-xs); line-height: 1.6; color: var(--ink-3); margin: 0; max-width: 36ch; }
|
|
19
|
-
body > footer > small > nav a { display: block; font-size: var(--t-xs); line-height: 2; }
|
|
20
|
-
body > footer > small > p { padding-top: var(--g3); border-top: 1px solid var(--line); }
|
|
21
|
-
@media (max-width: 640px) {
|
|
22
|
-
body > footer > small > nav { grid-template-columns: 1fr; gap: var(--g3); }
|
|
23
|
-
}`;
|
package/src/styles/header.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const css = `body > header {
|
|
2
|
-
position: sticky; top: 0; z-index: 50;
|
|
3
|
-
background: rgba(255,255,255,.82); backdrop-filter: saturate(180%) blur(16px);
|
|
4
|
-
border-bottom: 1px solid var(--line);
|
|
5
|
-
}
|
|
6
|
-
body > header nav {
|
|
7
|
-
max-width: var(--container); margin-inline: auto; padding-inline: var(--pad);
|
|
8
|
-
height: 48px; display: flex; align-items: center; justify-content: space-between;
|
|
9
|
-
}
|
|
10
|
-
body > header nav a { color: var(--ink-2); font-size: var(--t-sm); font-weight: 500; }
|
|
11
|
-
body > header nav a strong { font-size: var(--t-md); font-weight: 700; color: var(--ink); }`;
|
package/src/styles/hero.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { BREAKOUT } from './_shared.ts';
|
|
2
|
-
const R = 'main > hgroup';
|
|
3
|
-
export const css = `${R} {
|
|
4
|
-
${BREAKOUT} text-align: center; overflow: hidden; position: relative; isolation: isolate;
|
|
5
|
-
padding-block: var(--g6) var(--g5);
|
|
6
|
-
background: radial-gradient(ellipse 70% 50% at 20% 30%, var(--accent-glow) 0%, transparent 60%), radial-gradient(ellipse 50% 60% at 80% 20%, rgba(13,148,136,.12) 0%, transparent 55%), linear-gradient(180deg, var(--surface) 0%, var(--bg) 100%);
|
|
7
|
-
border-bottom: 1px solid var(--border);
|
|
8
|
-
}
|
|
9
|
-
${R}::before {
|
|
10
|
-
content: ''; position: absolute; inset: 0;
|
|
11
|
-
background-image: linear-gradient(rgba(124,92,252,.04) 1px, transparent 1px), linear-gradient(90deg, rgba(124,92,252,.04) 1px, transparent 1px);
|
|
12
|
-
background-size: 48px 48px; mask-image: radial-gradient(ellipse 60% 50% at 50% 40%, black 0%, transparent 70%); z-index: -1;
|
|
13
|
-
}
|
|
14
|
-
${R} > * { position: relative; }
|
|
15
|
-
${R} > p:first-child { display: inline-flex; margin-bottom: var(--g4); }
|
|
16
|
-
${R} > p:first-child small {
|
|
17
|
-
display: inline-flex; align-items: center; gap: var(--g1);
|
|
18
|
-
background: rgba(255,255,255,.7); backdrop-filter: blur(8px);
|
|
19
|
-
padding: var(--g1) var(--g2); border: 1px solid var(--border); border-radius: var(--pill);
|
|
20
|
-
font-weight: 600; color: var(--teal); box-shadow: var(--shadow);
|
|
21
|
-
}
|
|
22
|
-
${R} > p:first-child small::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #00d24a; box-shadow: 0 0 0 3px rgba(0,210,74,.2); }
|
|
23
|
-
${R} h1 { max-width: 18ch; margin: 0 auto var(--g3); }
|
|
24
|
-
${R} p { font-size: 1.25rem; line-height: 1.55; max-width: 38rem; margin: 0 auto var(--g3); }
|
|
25
|
-
form[role="search"] {
|
|
26
|
-
display: flex; max-width: 495px; height: 48px; margin: var(--g4) auto 0;
|
|
27
|
-
background: var(--surface); border: 1px solid var(--border); border-radius: var(--pill);
|
|
28
|
-
overflow: hidden; box-shadow: var(--shadow-md); transition: all var(--ease);
|
|
29
|
-
}
|
|
30
|
-
form[role="search"]:focus-within { border-color: var(--accent); box-shadow: 0 4px 16px var(--accent-glow); }
|
|
31
|
-
form[role="search"] input { flex: 1; min-width: 0; border: none; background: transparent; color: var(--fg); padding: 0 var(--g3); font-size: 1rem; font-family: var(--font); outline: none; }
|
|
32
|
-
form[role="search"] input::placeholder { color: var(--fg-faint); }
|
|
33
|
-
form[role="search"] button { flex-shrink: 0; height: 100%; border: none; background: var(--accent); color: var(--surface); padding: 0 var(--g4); font-size: 1rem; font-weight: 600; font-family: var(--font); cursor: pointer; border-radius: 0; }
|
|
34
|
-
form[role="search"] button:hover { opacity: .85; }
|
|
35
|
-
@media (max-width: 640px) {
|
|
36
|
-
${R} { padding-block: var(--g5) var(--g4); }
|
|
37
|
-
form[role="search"] { height: 40px; }
|
|
38
|
-
}`;
|
package/src/styles/pagination.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const R = 'main > nav:has(> p)';
|
|
2
|
-
export const css = `${R} > p { display: flex; align-items: center; justify-content: center; gap: var(--g3); font-size: 1rem; color: var(--fg-faint); }
|
|
3
|
-
${R} a {
|
|
4
|
-
height: 32px; display: inline-flex; align-items: center; padding: 0 var(--g2);
|
|
5
|
-
border-radius: var(--pill); background: var(--surface); border: 1px solid var(--border);
|
|
6
|
-
font-size: .8rem; font-weight: 500; transition: all var(--ease); text-decoration: none;
|
|
7
|
-
}
|
|
8
|
-
${R} a:hover { border-color: var(--accent); color: var(--accent); }`;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const R = 'main > article[itemscope]';
|
|
2
|
-
export const css = `${R} { max-width: 667px; }
|
|
3
|
-
${R} > header { padding-bottom: var(--g4); margin-bottom: var(--g4); border-bottom: 2px solid var(--fg); }
|
|
4
|
-
${R} > header h1 { font-size: 2.441rem; font-weight: 800; letter-spacing: -.04em; line-height: 1.05; margin-bottom: var(--g1); }
|
|
5
|
-
${R} > header p { font-size: 1.25rem; color: var(--fg-muted); margin: 0; }
|
|
6
|
-
${R} > header small { display: block; margin-top: var(--g1); font-size: .8rem; color: var(--teal); font-weight: 600; text-transform: uppercase; letter-spacing: .08em; }
|
|
7
|
-
${R} address { font-style: normal; font-size: 1rem; color: var(--fg-muted); padding: var(--g2) 0; margin-bottom: var(--g3); border-bottom: 1px solid var(--border); }
|
|
8
|
-
${R} section { margin: var(--g4) 0; padding-top: var(--g3); border-top: 1px solid var(--border); }
|
|
9
|
-
${R} section h2 { font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; margin: 0 0 var(--g2); }
|
|
10
|
-
${R} ul { list-style: none; display: flex; flex-wrap: wrap; gap: var(--g1); }
|
|
11
|
-
${R} ul li { font-size: .8rem; font-weight: 500; color: var(--fg-muted); }
|
|
12
|
-
${R} ul li:not(:last-child)::after { content: ','; }
|
|
13
|
-
${R} dl { display: grid; grid-template-columns: 8rem 1fr; gap: var(--g1) var(--g3); }
|
|
14
|
-
${R} dt { font-size: .8rem; color: var(--fg-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
|
|
15
|
-
${R} dd { margin: 0; font-size: 1rem; }
|
|
16
|
-
@media (max-width: 640px) {
|
|
17
|
-
${R} dl { grid-template-columns: 1fr; gap: 2px 0; }
|
|
18
|
-
${R} dt { margin-top: var(--g2); }
|
|
19
|
-
}`;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const R = 'main > section:has(> article[itemscope]) > article[itemscope]';
|
|
2
|
-
export const css = `${R} {
|
|
3
|
-
background: var(--card); border: 1px solid var(--border); border-radius: var(--r-lg);
|
|
4
|
-
padding: var(--g3); display: flex; flex-direction: column;
|
|
5
|
-
box-shadow: var(--shadow); transition: all var(--ease);
|
|
6
|
-
}
|
|
7
|
-
${R}:hover { transform: translateY(-2px); box-shadow: var(--shadow-up); border-color: var(--border-strong); }
|
|
8
|
-
${R} > div[aria-hidden] {
|
|
9
|
-
width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
|
10
|
-
font-size: .8rem; font-weight: 700; color: var(--surface); background: var(--accent); margin-bottom: var(--g2);
|
|
11
|
-
}
|
|
12
|
-
${R} header { margin-bottom: var(--g1); }
|
|
13
|
-
${R} h2 { font-size: 1rem; font-weight: 600; line-height: 1.3; margin: 0 0 2px; }
|
|
14
|
-
${R} h2 a { color: var(--fg); text-decoration: none; }
|
|
15
|
-
${R} h2 a:hover { color: var(--accent); }
|
|
16
|
-
${R} header p { font-size: .8rem; color: var(--fg-faint); margin: 0; }
|
|
17
|
-
${R} header p:has([itemprop="address"]) { display: inline-flex; align-items: center; gap: 4px; margin-top: 2px; }
|
|
18
|
-
${R} header p:has([itemprop="address"])::before { content: ''; width: 3px; height: 3px; border-radius: 50%; background: var(--fg-faint); }
|
|
19
|
-
${R} ul { list-style: none; display: flex; flex-wrap: wrap; gap: 4px; margin-top: var(--g1); }
|
|
20
|
-
${R} ul li { background: var(--accent-soft); color: var(--accent); padding: 1px var(--g1); border-radius: var(--pill); font-size: .7rem; font-weight: 500; }
|
|
21
|
-
${R} > a:last-child { display: inline-flex; margin-top: auto; padding-top: var(--g2); font-size: .8rem; font-weight: 600; color: var(--accent); }
|
|
22
|
-
${R} > a:last-child:hover { color: var(--fg); }`;
|
package/src/styles/stat-bar.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const R = 'main > section:has(> dl)';
|
|
2
|
-
export const css = `${R} > dl { display: flex; justify-content: center; gap: var(--g5); }
|
|
3
|
-
${R} > dl div { text-align: center; }
|
|
4
|
-
${R} > dl div > span:has(> svg) {
|
|
5
|
-
display: flex; align-items: center; justify-content: center; width: 36px; height: 36px;
|
|
6
|
-
margin: 0 auto var(--g1); background: var(--accent-soft); border-radius: 50%; color: var(--accent);
|
|
7
|
-
}
|
|
8
|
-
${R} > dl div > span:has(> svg) > svg { width: 18px; height: 18px; }
|
|
9
|
-
${R} > dl dt { font-size: 1.563rem; font-weight: 800; line-height: 1.2; color: var(--accent); }
|
|
10
|
-
${R} > dl dd { margin: 2px 0 0; font-size: .8rem; color: var(--fg-faint); font-weight: 500; text-transform: uppercase; letter-spacing: .06em; }
|
|
11
|
-
@media (max-width: 640px) { ${R} > dl { gap: var(--g3); } }`;
|