Kolejne przeróbki,

This commit is contained in:
dm
2025-12-13 10:13:20 +01:00
parent 5822237745
commit 4655554ff2
17 changed files with 197 additions and 218 deletions

View File

@@ -2,7 +2,7 @@
import { Image } from "astro:assets";
import type { ImageMetadata } from "astro";
import Markdown from "../../islands/Markdown.jsx";
import TvChannelsSearch from "../../islands/jambox/JamboxChannelsSearch.jsx";
import JamboxChannelsSearch from "../../islands/jambox/JamboxChannelsSearch.jsx";
const props = Astro.props ?? {};
const section = props.section ?? {};
@@ -44,7 +44,7 @@ if (section.image) {
{section.title && <h2 class="f-section-title">{section.title}</h2>}
{section.content && <Markdown text={section.content} />}
<TvChannelsSearch client:load />
<JamboxChannelsSearch client:load />
{section.button && (
<div class="f-section-nav">

View File

@@ -1,4 +1,4 @@
title: SKONTAKTUJ SIĘ Z NAMI
title: Skontaktuj się z nami
description: |
<h3>FUZ ADAM ROJEK</h3>
<h4>ul. Świętojańska 46</h4>

View File

@@ -1,31 +1,75 @@
import { useState } from "preact/hooks";
import { useEffect, useRef, useState } from "preact/hooks";
/**
* @typedef {{ name: string; href: string }} MenuLink
* @param {{ links: MenuLink[] }} props
*/
export default function MobileMenu({ links = [] }) {
const [open, setOpen] = useState(false);
const menuRef = useRef(null);
const btnRef = useRef(null);
const toggle = () => setOpen(o => !o);
const close = () => setOpen(false);
const toggle = (e) => {
e?.stopPropagation?.();
setOpen((o) => !o);
};
useEffect(() => {
const onKey = (e) => e.key === "Escape" && close();
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, []);
useEffect(() => {
if (!open) return;
const onDocClick = (e) => {
const menuEl = menuRef.current;
const btnEl = btnRef.current;
if (menuEl && menuEl.contains(e.target)) return;
if (btnEl && btnEl.contains(e.target)) return;
close();
};
document.addEventListener("mousedown", onDocClick);
document.addEventListener("touchstart", onDocClick, { passive: true });
return () => {
document.removeEventListener("mousedown", onDocClick);
document.removeEventListener("touchstart", onDocClick);
};
}, [open]);
return (
<>
<button
ref={btnRef}
onClick={toggle}
class="f-mobile-toggle md:hidden"
aria-label="Menu mobilne"
aria-expanded={open}
aria-controls="mobile-menu"
>
{open ? "✖" : "☰"}
</button>
<div class={`f-mobile-menu ${open ? "open" : ""}`}>
{links.map(link => (
<a
href={link.href}
class="f-mobile-link"
onClick={() => setOpen(false)}
>
<div
class={`f-mobile-backdrop ${open ? "open" : ""}`}
onClick={close}
aria-hidden="true"
/>
<div
id="mobile-menu"
ref={menuRef}
class={`f-mobile-menu ${open ? "open" : ""}`}
onClick={(e) => e.stopPropagation()}
>
{links.map((link) => (
<a href={link.href} class="f-mobile-link" onClick={close}>
{link.name}
</a>
))}

View File

@@ -23,7 +23,6 @@ export default function OffersSwitches(props) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// 🔹 AUTO mode sam pobieram /api/switches + wysyłam event
useEffect(() => {
if (isControlled) return;
@@ -52,7 +51,6 @@ export default function OffersSwitches(props) {
setAutoSwitches(sws);
setAutoSelected(initial);
// 🔥 zapisz globalny stan
window.fuzSwitchState = {
selected: initial,
labels,
@@ -69,7 +67,7 @@ export default function OffersSwitches(props) {
}),
);
} catch (err) {
console.error("Błąd pobierania switchy:", err);
console.error("Błąd pobierania przełączników:", err);
if (!cancelled) setError("Nie udało się załadować przełączników.");
} finally {
if (!cancelled) setLoading(false);
@@ -93,7 +91,6 @@ export default function OffersSwitches(props) {
const next = { ...prev, [id]: value };
const labels = buildLabels(autoSwitches, next);
// 🔥 aktualizuj globalny stan
window.fuzSwitchState = {
selected: next,
labels,
@@ -115,7 +112,6 @@ export default function OffersSwitches(props) {
}
};
// 🔥 CONTROLLED: zsynchronizuj globalny stan + event
useEffect(() => {
if (!isControlled) return;
if (!Array.isArray(switches) || !switches.length) return;
@@ -123,7 +119,6 @@ export default function OffersSwitches(props) {
const safeSelected = selected || {};
const labels = buildLabels(switches, safeSelected);
// 🔥 globalny stan
window.fuzSwitchState = {
selected: safeSelected,
labels,
@@ -162,23 +157,20 @@ export default function OffersSwitches(props) {
return (
<div class="f-switches-wrapper">
{effectiveSwitches.map((sw) => (
<div class="f-switch-box">
<div class="f-switch-group">
{sw.opcje.map((op) => (
<button
type="button"
class={`f-switch ${
String(effectiveSelected[sw.id]) === String(op.id)
? "active"
: ""
<div class="f-switch-group">
{sw.opcje.map((op) => (
<button
type="button"
class={`f-switch ${String(effectiveSelected[sw.id]) === String(op.id)
? "active"
: ""
}`}
onClick={() => handleClick(sw.id, op.id)}
title={sw.title}
>
{op.nazwa}
</button>
))}
</div>
onClick={() => handleClick(sw.id, op.id)}
title={sw.title}
>
{op.nazwa}
</button>
))}
</div>
))}
</div>

View File

@@ -79,20 +79,15 @@ export default function JamboxChannelsModal({ isOpen, onClose, pkg }) {
<div class="max-w-8xl mx-auto px-3 md:px-6">
<h2 class="fuz-modal-title">Kanały w pakiecie {pkg.name}</h2>
<div class="jmb-search">
<div class="fuz-chsearch__top">
<input
class="jmb-search-input"
class="fuz-chsearch__input"
type="search"
value={query}
onInput={(e) => setQuery(e.currentTarget.value)}
placeholder="Szukaj kanału po nazwie…"
aria-label="Szukaj kanału po nazwie"
/>
{query && (
<button class="jmb-search-clear" type="button" onClick={() => setQuery("")}>
Wyczyść
</button>
)}
</div>
{!loading && !error && (
@@ -112,35 +107,32 @@ export default function JamboxChannelsModal({ isOpen, onClose, pkg }) {
<div class="">
<div class="f-section-channel">
{filtered.map((ch) => (
<div class="jmb-channel-card" key={ch.number}>
<div
class="jmb-channel-card"
key={ch.number}
onClick={(e) => {
if (e.target.closest("a, button")) return;
e.currentTarget.classList.toggle("is-flipped");
}}
>
<div class="jmb-channel-inner">
{/* FRONT */}
<div class="jmb-channel-face jmb-channel-front">
{ch.logo_url && (
<img
src={ch.logo_url}
alt={ch.name}
class="jmb-channel-logo"
loading="lazy"
/>
<img src={ch.logo_url} alt={ch.name} class="jmb-channel-logo" loading="lazy" />
)}
<div class="jmb-channel-name">{ch.name}</div>
<div class="jmb-channel-number">kanał {ch.number}</div>
</div>
{/* BACK */}
<div class="jmb-channel-face jmb-channel-back">
<div class="jmb-channel-back-title">{ch.name}</div>
<div
class="jmb-channel-desc"
dangerouslySetInnerHTML={{
__html: ch.description || "<em>Brak opisu kanału.</em>",
}}
dangerouslySetInnerHTML={{ __html: ch.description || "<em>Brak opisu kanału.</em>" }}
/>
</div>
</div>
</div>
))}
</div>
</div>

View File

@@ -97,9 +97,9 @@ export default function JamboxChannelsSearch() {
{c.name}
</div>
<div class="fuz-chsearch__channel-number">
{/* <div class="fuz-chsearch__channel-number">
kanał {c.min_number || "—"}
</div>
</div> */}
</div>
@@ -116,7 +116,7 @@ export default function JamboxChannelsSearch() {
{c.packages.map((p, i) => (
<span class="fuz-chsearch__pkg" key={p.id}>
{p.name}{" "}
<span class="fuz-chsearch__pkgnum">({p.number})</span>
<span class="fuz-chsearch__pkgnum">(kanał {p.number})</span>
{i < c.packages.length - 1 ? ", " : ""}
</span>
))}

View File

@@ -3,7 +3,7 @@ import DefaultLayout from "../../layouts/DefaultLayout.astro";
import OffersSwitches from "../../islands/OffersSwitches.jsx";
import JamboxCards from "../../islands/jambox/JamboxCards.jsx";
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
import SectionChannelsSearch from "../../components/sections/SectionChannelsSearch.astro"
import SectionChannelsSearch from "../../components/sections/SectionChannelsSearch.astro";
import yaml from "js-yaml";
import fs from "fs";
@@ -24,7 +24,6 @@ const seo = yaml.load(
<JamboxCards client:load />
</div>
</section>
<SectionChannelsSearch />
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
<SectionChannelsSearch/>
</DefaultLayout>

View File

@@ -18,83 +18,85 @@ const form = data.form;
---
<DefaultLayout seo={seo}>
<section id="kontakt" class="f-section">
<div class="f-contact-grid">
<!-- Kolumna lewa -->
<div class="f-contact-col-1">
<h2>{data.title}</h2>
<div class="f-contact-item" set:html={data.description} />
</div>
<div class="f-contact-col-2">
<h2>{data.contactFormTitle}</h2>
<form id="contactForm" class="f-contact-form">
<div class="f-contact-form-inner">
<input
type="text"
name="firstName"
placeholder={form.firstName.placeholder}
class="f-input"
required
/>
<input
type="text"
name="lastName"
placeholder={form.lastName.placeholder}
class="f-input"
required
/>
</div>
<div class="grid grid-cols-2 gap-4">
<input
type="email"
name="email"
placeholder={form.email.placeholder}
class="f-input"
required
autocomplete="email"
/>
<input
type="tel"
name="phone"
placeholder={form.phone.placeholder}
class="f-input"
required
autocomplete="tel"
/>
</div>
<section class="f-section">
<div class="f-section-grid md:grid-cols-2 gap-10 items-start">
<div>
<h2 class="f-section-title">{data.title}</h2>
<div class="f-contact-item" set:html={data.description} />
</div>
<div>
<h2 class="f-section-title">{data.contactFormTitle}</h2>
<form id="contactForm" class="f-contact-form">
<div class="f-contact-form-inner">
<input
type="text"
name="subject"
placeholder={form.subject.placeholder}
name="firstName"
placeholder={form.firstName.placeholder}
class="f-input"
required
/>
<textarea
name="message"
rows={form.message.rows}
placeholder={form.message.placeholder}
<input
type="text"
name="lastName"
placeholder={form.lastName.placeholder}
class="f-input"
required></textarea>
required
/>
</div>
<label class="f-rodo">
<input type="checkbox" name="rodo" required />
<span>
{form.rodo.label}
<a href={form.rodo.policyLink} title={form.rodo.policyTitle}
>{form.rodo.policyText}</a
>.
</span>
</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input
type="email"
name="email"
placeholder={form.email.placeholder}
class="f-input"
required
autocomplete="email"
/>
<input
type="tel"
name="phone"
placeholder={form.phone.placeholder}
class="f-input"
required
autocomplete="tel"
/>
</div>
<button title={form.submit.title}>{form.submit.label}</button>
</form>
</div>
<input
type="text"
name="subject"
placeholder={form.subject.placeholder}
class="f-input"
required
/>
<textarea
name="message"
rows={form.message.rows}
placeholder={form.message.placeholder}
class="f-input"
required></textarea>
<label class="f-rodo">
<input type="checkbox" name="rodo" required />
<span>
{form.rodo.label}
<a href={form.rodo.policyLink} title={form.rodo.policyTitle}>
{form.rodo.policyText}
</a>.
</span>
</label>
<button type="submit" class="btn btn-primary w-full py-3" title={form.submit.title}>
{form.submit.label}
</button>
</form>
</div>
</div>
<div class="mt-10">
<div class="f-contact-map">
<MapGoogle
apiKey={apiKey}
@@ -108,9 +110,10 @@ const form = data.form;
mapStyleId={data.maps.mapId}
/>
</div>
</div>
<div id="toast" class="f-toast"></div>
</section>
<div id="toast" class="f-toast"></div>
</section>
<!-- ReCaptcha v3 -->
<script

View File

@@ -12,7 +12,6 @@
@import "./footer.css";
@import "./cookie.css";
@import "./contact.css";
@import "./offers/offers-main.css";
@import "./offers/offers-switches.css";
html {

View File

@@ -1,56 +1,49 @@
.btn {
@apply inline-flex items-center justify-center font-semibold rounded-lg px-6 py-3 text-base transition-all duration-200 cursor-pointer select-none;
@apply inline-flex items-center justify-center gap-2 font-semibold rounded-lg px-6 py-3 text-base transition-all duration-200 cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-[--f-header] focus-visible:ring-offset-2 focus-visible:ring-offset-[--f-background];
}
.btn-primary {
@apply border-none bg-[--btn-background] text-[--btn-text];
@apply border border-transparent bg-[--btn-background] text-[--btn-text];
}
.btn-primary:hover {
@apply bg-[--btn-background-hover] text-[--btn-text-hover];
}
.btn-primary:disabled {
@apply opacity-60 cursor-not-allowed;
}
.f-input {
@apply w-full py-3 px-4 rounded-xl border border-[--f-input-border] bg-[--f-background] text-[--f-text] transition-all duration-200;
}
.f-input:hover {
@apply border-[--f-text] opacity-[0.9];
@apply border-[--f-text] opacity-[0.4];
}
.f-input:focus {
@apply outline-none border-[--f-header];
box-shadow: 0 0 0 3px color-mix(in srgb, var(--f-header) 40%, transparent);
}
.fuz-input::placeholder {
.f-input::placeholder {
color: color-mix(in srgb, var(--f-text) 40%, transparent);
}
.fuz-input-error {
border-color: #ff4d4f !important;
box-shadow: 0 0 0 3px rgba(255, 77, 79, 0.3) !important;
}
.fuz-input:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: color-mix(in srgb, var(--f-background) 80%, #888);
.f-input-error {
@apply border-red-500 ring-2 ring-red-500/30;
}
.autocomplete-wrapper {
position: relative;
@apply relative;
}
.autocomplete-open {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-bottom-width: 0 !important;
@apply rounded-b-none border-b-0;
}
.autocomplete-list {
@apply absolute left-0 right-0 z-50 bg-[--f-background] text-[--f-text] border border-gray-300 dark:border-slate-700 rounded-b-xl shadow-xl max-h-56 overflow-auto;
@apply absolute left-0 right-0 z-50 bg-[--f-background] text-[--f-text] border border-[--f-input-border] rounded-b-xl shadow-xl max-h-56 overflow-auto;
border-top: none;
animation: fadeIn 0.12s ease-out;

View File

@@ -1,30 +1,5 @@
@tailwind base;
.f-contact-grid {
@apply grid md:grid-cols-2 gap-10 items-start max-w-7xl mx-auto;
}
.f-contact-col-1 {
h1,
h2,
h3,
h4 {
@apply f-section-header;
}
}
.f-contact-col-2 {
@apply bg-[--f-background] text-[--f-text];
h1,
h2,
h3,
h4 {
@apply f-section-header;
}
}
.f-contact-item {
@apply space-y-1;
@@ -55,10 +30,6 @@
@apply mt-2 h-4 w-4;
}
}
button {
@apply btn btn-primary w-full py-3 text-lg;
}
}
.f-contact-map {

View File

@@ -44,6 +44,7 @@
.fuz-markdown button.modal-link {
@apply no-underline hover:no-underline mt-2 bg-[--f-background] text-[--f-link-text];
}
.fuz-markdown blockquote {
@apply border-l-4 border-gray-300 dark:border-gray-700 pl-4 italic mb-4;
}

View File

@@ -15,21 +15,39 @@
}
.f-mobile-toggle {
@apply text-3xl p-2 text-[--f-text];
@apply text-3xl p-2 text-[--f-text] relative z-[70];
}
.f-mobile-menu {
@apply fixed top-0 right-0 h-full w-64 bg-[--f-background] shadow-lg transform translate-x-full transition-transform duration-300 flex flex-col gap-4 p-6;
@apply fixed right-4 bg-[--f-background] border border-[--f-border-color] p-4 flex flex-col gap-2 opacity-0 scale-95 -translate-y-2 pointer-events-none transition duration-200 divide-y divide-[--f-border-color];
top: calc(var(--f-navbar-height, 64px) + 0.75rem);
width: 18rem;
max-width: calc(100vw - 2rem);
z-index: 70;
max-height: calc(100vh - var(--f-navbar-height, 64px) - 2rem);
overflow-y: auto;
}
.f-mobile-menu.open {
@apply translate-x-0;
@apply opacity-100 scale-100 translate-y-0 pointer-events-auto;
}
.f-mobile-link {
@apply text-lg py-2 border-b text-[--f-navbar-link] hover:text-[--f-navbar-link-hover] border-[--f-border-color];
@apply text-base py-2 px-2
text-[--f-navbar-link]
hover:text-[--f-navbar-link-hover]
hover:bg-black/5 dark:hover:bg-white/5
transition;
}
.f-navbar-logo {
@apply w-[70] h-[36];
}
.f-mobile-backdrop {
@apply fixed inset-0 bg-black/30 opacity-0 pointer-events-none transition-opacity duration-200 z-[60];
}
.f-mobile-backdrop.open {
@apply opacity-100 pointer-events-auto;
}

View File

@@ -1,22 +0,0 @@
.f-extra-services {
@apply mt-6 max-w-6xl mx-auto;
}
.f-services-title {
@apply text-2xl font-semibold mb-4 ml-10;
}
.f-services-body {
@apply text-lg font-semibold mb-4 ml-10;
}
.f-expand-details {
@apply px-4 py-4;
}
.f-feature-link {
@apply cursor-pointer w-full h-full text-[--f-link-text] text-lg;
}
.f-feature-link:hover {
@apply text-[--f-link-text-hover];
}

View File

@@ -1,11 +0,0 @@
.fuz-offers-section {
@apply py-6;
}
.f-offers-container {
@apply max-w-7xl mx-auto px-6;
}
.f-offers-description {
@apply mb-10 text-base leading-relaxed;
}

View File

@@ -7,7 +7,7 @@
}
.f-switch {
@apply px-6 py-2 text-sm font-semibold cursor-pointer select-none transition-all;
@apply px-6 py-3 text-sm font-semibold cursor-pointer select-none transition-all;
}
.f-switch.active {

View File

@@ -78,7 +78,7 @@
/* var(--link-color-light); */
--btn-background-hover: var(--surface4-light);
/* var(--link-color-light); */
--btn-text-hover: var(--link-color-light);
--btn-text-hover: var(--link-color-light);
/* var(--surface4-light); */
--f-background-toast: var(--surface2-dark);