From 116a915cac9f885fd5cab3f4379bc4f88c2d7c90 Mon Sep 17 00:00:00 2001 From: dm Date: Tue, 16 Dec 2025 07:16:47 +0100 Subject: [PATCH] Stylizacja hero, zmiany w InternetAddonsModal --- src/content/home/hero.yaml | 2 +- .../internet-swiatlowodowy/addons.yaml | 12 +- .../internet-swiatlowodowy/section.yaml | 2 +- src/content/internet-swiatlowodowy/seo.yaml | 2 +- src/islands/Internet/InternetAddonsModal.jsx | 577 ++++++++++-------- .../Internet/InternetAddonsModalCompact.jsx | 537 ---------------- src/islands/Internet/InternetCards.jsx | 2 +- src/styles/hero.css | 254 ++++---- src/styles/modal.css | 4 +- src/styles/offers-table.css | 6 +- src/styles/theme.css | 34 +- 11 files changed, 488 insertions(+), 944 deletions(-) delete mode 100644 src/islands/Internet/InternetAddonsModalCompact.jsx diff --git a/src/content/home/hero.yaml b/src/content/home/hero.yaml index 8205f08..7175551 100644 --- a/src/content/home/hero.yaml +++ b/src/content/home/hero.yaml @@ -1,7 +1,7 @@ title: - "Internet i Telewizja FUZ" subtitle: - - W Wyszkowie i okolicach + - Doskanały internet światłowodowy w Wyszkowie i okolicach, - "Lokalny operator, znamy Twoją okolicę," - "Realny serwis, szybkie wsparcie," - "Stabilna infrastruktura światłowodowa," diff --git a/src/content/internet-swiatlowodowy/addons.yaml b/src/content/internet-swiatlowodowy/addons.yaml index 4ffa8d0..154adc5 100644 --- a/src/content/internet-swiatlowodowy/addons.yaml +++ b/src/content/internet-swiatlowodowy/addons.yaml @@ -1,7 +1,7 @@ tytul: "Dodatkowe usługi" opis: "Wybierz usługi dodatkowe do internetu." -cena_opis: "zł/mies." +cena_opis: "zł / mies." dodatki: - id: "public_ip" @@ -10,13 +10,3 @@ dodatki: ilosc: false opis: "Otrzymujesz unikalny, publiczny adres IP przypisany na stałe do Twojego łącza." cena: 18.45 - - # - id: "ip_v4_extra" - # nazwa: "Dodatkowy publiczny adres IP" - # typ: "quantity" - # ilosc: true - # min: 0 - # max: 4 - # krok: 1 - # opis: "Możesz dodać kilka dodatkowych adresów IP. Cena za sztukę." - # cena: 18.45 diff --git a/src/content/internet-swiatlowodowy/section.yaml b/src/content/internet-swiatlowodowy/section.yaml index 3a84bd4..15162ad 100644 --- a/src/content/internet-swiatlowodowy/section.yaml +++ b/src/content/internet-swiatlowodowy/section.yaml @@ -2,7 +2,7 @@ sections: - title: Router WiFi HL-4BX3V-F image: "HL-4BX3V-F.webp" content: | - W ramach instalacji otrzymujesz nowoczesny router marki HALNy - urządzenie stworzone z myślą o wymagających użytkownikach.. + W ramach instalacji otrzymujesz nowoczesny router marki HALNy - urządzenie stworzone z myślą o wymagających użytkownikach. Znajdziesz w nim nowoczesny standard WiFi 6, porty 2,5 Gb/s oraz 1 Gb/s, wsparcie dla sieci Mesh i VoIP. Stabilność, niezawodność i pełne wykorzystanie łącza – w całym Twoim domu. diff --git a/src/content/internet-swiatlowodowy/seo.yaml b/src/content/internet-swiatlowodowy/seo.yaml index a2a1dea..d8e8878 100644 --- a/src/content/internet-swiatlowodowy/seo.yaml +++ b/src/content/internet-swiatlowodowy/seo.yaml @@ -14,7 +14,7 @@ company: country: "PL" lat: 52.597385 lon: 21.456797 - logo: "/images/logo-fuz.webp" + logo: "/logo.webp" page: title: "FUZ – Internet światłowodowy w Wyszkowie" diff --git a/src/islands/Internet/InternetAddonsModal.jsx b/src/islands/Internet/InternetAddonsModal.jsx index 133918e..7eb60c7 100644 --- a/src/islands/Internet/InternetAddonsModal.jsx +++ b/src/islands/Internet/InternetAddonsModal.jsx @@ -13,10 +13,6 @@ function money(amount) { return n.toFixed(2).replace(".", ","); } -/** - * Mapuje YAML telefonu (cards.yaml) na format używany w modalu: - * { id, name, price_monthly, features: [{label, value}] } - */ function mapPhoneYamlToPlans(phoneCards) { const list = Array.isArray(phoneCards) ? phoneCards : []; return list @@ -32,10 +28,6 @@ function mapPhoneYamlToPlans(phoneCards) { })); } -/** - * Dodatki z YAML: - * { id, nazwa, typ, ilosc, min, max, krok, opis, cena } - */ function normalizeAddons(addons) { const list = Array.isArray(addons) ? addons : []; return list @@ -43,7 +35,7 @@ function normalizeAddons(addons) { .map((a) => ({ id: String(a.id), nazwa: String(a.nazwa), - typ: String(a.typ || "checkbox"), + typ: String(a.typ ?? a.type ?? "checkbox"), ilosc: !!a.ilosc, min: a.min != null ? Number(a.min) : 0, max: a.max != null ? Number(a.max) : 10, @@ -53,15 +45,33 @@ function normalizeAddons(addons) { })); } +function SectionAccordion({ title, right, open, onToggle, children }) { + return ( +
+ + + {open &&
{children}
} +
+ ); +} + export default function InternetAddonsModal({ isOpen, onClose, plan, - - // ✅ nowe: z YAML - phoneCards = [], // telefon/cards.yaml -> cards[] - addons = [], // internet-swiatlowodowy/addons.yaml -> dodatki[] - cenaOpis = "zł/mies.", + phoneCards = [], + addons = [], + cenaOpis = "zł / mies.", }) { const phonePlans = useMemo(() => mapPhoneYamlToPlans(phoneCards), [phoneCards]); const addonsList = useMemo(() => normalizeAddons(addons), [addons]); @@ -69,51 +79,61 @@ export default function InternetAddonsModal({ const [error, setError] = useState(""); const [selectedPhoneId, setSelectedPhoneId] = useState(null); - - // zamiast selectedAddons (DB) -> mapka ilości - // { public_ip: 1, ip_v4_extra: 3 } const [selectedQty, setSelectedQty] = useState({}); - // akordeony - const [openPhoneId, setOpenPhoneId] = useState(null); - const [baseOpen, setBaseOpen] = useState(true); + const [openSections, setOpenSections] = useState({ + internet: true, + phone: false, + addons: false, + summary: false, + }); + + const toggleSection = (key) => { + setOpenSections((prev) => { + const nextOpen = !prev[key]; + return { + internet: false, + phone: false, + addons: false, + summary: false, + [key]: nextOpen, + }; + }); + }; - // reset wyborów po otwarciu / zmianie planu useEffect(() => { if (!isOpen) return; setError(""); setSelectedPhoneId(null); setSelectedQty({}); - setOpenPhoneId(null); - setBaseOpen(true); - }, [isOpen, plan]); + setOpenSections({ internet: true, phone: false, addons: false, summary: false }); + }, [isOpen, plan?.id]); if (!isOpen || !plan) return null; const basePrice = Number(plan.price_monthly || 0); - const phonePrice = (() => { + const phonePrice = useMemo(() => { if (!selectedPhoneId) return 0; - const p = phonePlans.find((p) => String(p.id) === String(selectedPhoneId)); + const p = phonePlans.find((x) => String(x.id) === String(selectedPhoneId)); return Number(p?.price_monthly || 0); - })(); + }, [selectedPhoneId, phonePlans]); - const addonsPrice = addonsList.reduce((sum, a) => { - const qty = Number(selectedQty[a.id] || 0); - return sum + qty * Number(a.cena || 0); - }, 0); + const addonsPrice = useMemo(() => { + return addonsList.reduce((sum, a) => { + const qty = Number(selectedQty[a.id] || 0); + return sum + qty * Number(a.cena || 0); + }, 0); + }, [selectedQty, addonsList]); const totalMonthly = basePrice + phonePrice + addonsPrice; const handlePhoneSelect = (id) => { if (id === null) { setSelectedPhoneId(null); - setOpenPhoneId(null); return; } - setSelectedPhoneId(id); - setOpenPhoneId((prev) => (String(prev) === String(id) ? null : id)); }; const toggleCheckboxAddon = (id) => { @@ -129,6 +149,77 @@ export default function InternetAddonsModal({ setSelectedQty((prev) => ({ ...prev, [id]: safe })); }; + // Zapis do localStorage wyborów + const LS_KEY = "fuz_offer_config_v1"; + + function buildOfferPayload() { + const phone = selectedPhoneId + ? phonePlans.find((p) => String(p.id) === String(selectedPhoneId)) + : null; + + const addonsChosen = addonsList + .map((a) => { + const qty = Number(selectedQty[a.id] || 0); + if (qty <= 0) return null; + return { id: a.id, nazwa: a.nazwa, qty, unit: Number(a.cena || 0) }; + }) + .filter(Boolean); + + return { + createdAt: new Date().toISOString(), + pkg: { id: plan?.id ?? null, name: plan?.name ?? "", price: basePrice }, + + phone: phone ? { id: phone.id, name: phone.name, price: phone.price_monthly } : null, + + addons: addonsChosen, + + totals: { + base: basePrice, + phone: phonePrice, + addons: addonsPrice, + total: totalMonthly, + currencyLabel: cenaOpis, + }, + }; + } + + function moneyWithLabel(v) { + return `${money(v)} ${cenaOpis}`; + } + + function buildOfferMessage(payload) { + const lines = []; + + lines.push(`Internet światłowodowy ${payload?.pkg?.name}: ${moneyWithLabel(payload?.totals?.base ?? 0)}`); + lines.push(`Usługa Telefon: ${payload?.phone ? moneyWithLabel(payload.totals.phone) : "—"}`); + lines.push(`Dodatkowe usługi: ${payload?.addons?.length ? moneyWithLabel(payload.totals.addons) : "—"}`); + lines.push(`Łącznie: ${moneyWithLabel(payload?.totals?.total ?? 0)}`); + + if (payload?.phone) { + lines.push(""); + lines.push(`Telefon: ${payload.phone.name} (${moneyWithLabel(payload.phone.price)})`); + } + + if (Array.isArray(payload?.addons) && payload.addons.length) { + lines.push(""); + lines.push("Dodatkowe usługi:"); + for (const it of payload.addons) { + lines.push(`- ${it.nazwa} x${it.qty} @ ${moneyWithLabel(it.unit)}`); + } + } + + return lines.join("\n"); + } + + function saveOfferToLocalStorage() { + try { + const payload = buildOfferPayload(); + payload.message = buildOfferMessage(payload); + localStorage.setItem(LS_KEY, JSON.stringify(payload)); + } catch { } + } + + return (
- - {baseOpen && plan.features && plan.features.length > 0 && ( -
-
    - {plan.features.map((f, idx) => ( -
  • - {f.label} - - {formatFeatureValue(f.value)} - -
  • - ))} -
-
- )} -
- - {error &&

{error}

} - {/* Telefon */} + {/* INTERNET */}
-

Usługa telefoniczna

+ {money(basePrice)} {cenaOpis}} + open={openSections.internet} + onToggle={() => toggleSection("internet")} + > + {plan.features?.length ? ( +
    + {plan.features.map((f, idx) => ( +
  • + {f.label} + {formatFeatureValue(f.value)} +
  • + ))} +
+ ) : ( +

Brak szczegółów.

+ )} +
+
- {phonePlans.length === 0 ? ( -

Brak dostępnych pakietów telefonicznych.

- ) : ( -
- {/* brak telefonu */} -
- -
+
- {phonePlans.map((p) => { - const isSelected = String(selectedPhoneId) === String(p.id); - const isOpen = String(openPhoneId) === String(p.id); +
+
Nie potrzebuję telefonu
+
- return ( -
- + {phonePlans.map((p) => { + const isSelected = String(selectedPhoneId) === String(p.id); - {isOpen && ( -
- {p.features && p.features.length > 0 && ( + return ( +
+ + + {/* Szczegóły telefony rozwinięte */} + {p.features?.length > 0 && ( +
    {p.features .filter( - (f) => - !String(f.label || "") - .toLowerCase() - .includes("aktyw"), + (f) => !String(f.label || "").toLowerCase().includes("aktyw"), ) .map((f, idx) => (
  • {f.label} - - {formatFeatureValue(f.value)} - + {formatFeatureValue(f.value)}
  • ))}
- )} -
- )} -
- ); - })} -
- )} +
+ )} + + ); + })} + + )} + - {/* Dodatki internetowe */} + {/* USLUGI DODATKOWE */}
-

Dodatkowe usługi

+ + {addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"} + + } + open={openSections.addons} + onToggle={() => toggleSection("addons")} + > + {addonsList.length === 0 ? ( +

Brak dodatkowych usług.

+ ) : ( +
+ {addonsList.map((a) => { + const qty = Number(selectedQty[a.id] || 0); + const isQty = a.typ === "quantity" || a.ilosc === true; - {addonsList.length === 0 ? ( -

Brak dodatkowych usług.

- ) : ( -
- {addonsList.map((a) => { - const qty = Number(selectedQty[a.id] || 0); - const isQty = a.typ === "quantity" || a.ilosc === true; + if (!isQty) { + const checked = qty > 0; + return ( + + ); + } + + // Usługa z ilośćią + const min = Number.isFinite(a.min) ? a.min : 0; + const max = Number.isFinite(a.max) ? a.max : 10; + const step = Number.isFinite(a.krok) ? a.krok : 1; + const lineTotal = qty * Number(a.cena || 0); - if (!isQty) { - const checked = qty > 0; return ( -
+ )} +
- {/* Podsumowanie */} -
-

Podsumowanie miesięczne

+ {/* PODSUMOWANIE */} +
+ {money(totalMonthly)} {cenaOpis}} + open={openSections.summary} + onToggle={() => toggleSection("summary")} + > +
+
+
+ Pakiet + {money(basePrice)} {cenaOpis} +
-
-
- Internet - {money(basePrice)} {cenaOpis} -
+
+ Telefon + {phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"} +
-
- Telefon - {phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"} -
+
+ Dodatkowe usługi + {addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"} +
-
- Dodatki - {addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"} -
+
+ Łącznie + {money(totalMonthly)} {cenaOpis} +
-
- Łącznie - {money(totalMonthly)} {cenaOpis} + saveOfferToLocalStorage()} + > + Wyślij zapytanie z tym wyborem + +
-
+
e.stopPropagation()}> -
- Suma - - {money(totalMonthly)} {cenaOpis} - +
+ Razem + {money(totalMonthly)} + {cenaOpis}
diff --git a/src/islands/Internet/InternetAddonsModalCompact.jsx b/src/islands/Internet/InternetAddonsModalCompact.jsx deleted file mode 100644 index 62ad598..0000000 --- a/src/islands/Internet/InternetAddonsModalCompact.jsx +++ /dev/null @@ -1,537 +0,0 @@ -import { useEffect, useMemo, useState } from "preact/hooks"; -import "../../styles/modal.css"; -import "../../styles/offers-table.css"; - -function formatFeatureValue(val) { - if (val === true || val === "true") return "✓"; - if (val === false || val === "false" || val == null) return "✕"; - return val; -} - -function money(amount) { - const n = Number(amount || 0); - return n.toFixed(2).replace(".", ","); -} - -/** - * Mapuje YAML telefonu (cards.yaml) na format używany w modalu: - * { id, name, price_monthly, features: [{label, value}] } - */ -function mapPhoneYamlToPlans(phoneCards) { - const list = Array.isArray(phoneCards) ? phoneCards : []; - return list - .filter((c) => c?.widoczny !== false) - .map((c, idx) => ({ - id: String(c?.id ?? c?.nazwa ?? idx), - name: c?.nazwa ?? "—", - price_monthly: Number(c?.cena?.wartosc ?? 0), - features: (Array.isArray(c?.parametry) ? c.parametry : []).map((p) => ({ - label: p.label, - value: p.value, - })), - })); -} - -/** - * Dodatki z YAML: - * { id, nazwa, typ, ilosc, min, max, krok, opis, cena } - */ -function normalizeAddons(addons) { - const list = Array.isArray(addons) ? addons : []; - return list - .filter((a) => a?.id && a?.nazwa) - .map((a) => ({ - id: String(a.id), - nazwa: String(a.nazwa), - typ: String(a.typ ?? a.type ?? "checkbox"), - ilosc: !!a.ilosc, - min: a.min != null ? Number(a.min) : 0, - max: a.max != null ? Number(a.max) : 10, - krok: a.krok != null ? Number(a.krok) : 1, - opis: a.opis ? String(a.opis) : "", - cena: Number(a.cena ?? 0), - })); -} - -/** ✅ Sekcja-akordeon (jak w internet modal) */ -function SectionAccordion({ title, right, open, onToggle, children }) { - return ( -
- - - {open &&
{children}
} -
- ); -} - -export default function InternetAddonsModal({ - isOpen, - onClose, - plan, - - phoneCards = [], - addons = [], - cenaOpis = "zł/mies.", -}) { - const phonePlans = useMemo(() => mapPhoneYamlToPlans(phoneCards), [phoneCards]); - const addonsList = useMemo(() => normalizeAddons(addons), [addons]); - - const [error, setError] = useState(""); - - const [selectedPhoneId, setSelectedPhoneId] = useState(null); - const [selectedQty, setSelectedQty] = useState({}); - - // ✅ sekcje jako akordeony (jedna otwarta naraz) - const [openSections, setOpenSections] = useState({ - internet: true, - phone: false, - addons: false, - summary: false, - }); - - const toggleSection = (key) => { - setOpenSections((prev) => { - const nextOpen = !prev[key]; - return { - internet: false, - phone: false, - addons: false, - summary: false, - [key]: nextOpen, - }; - }); - }; - - // reset wyborów po otwarciu / zmianie planu - useEffect(() => { - if (!isOpen) return; - setError(""); - setSelectedPhoneId(null); - setSelectedQty({}); - setOpenSections({ internet: true, phone: false, addons: false, summary: false }); - }, [isOpen, plan?.id]); - - if (!isOpen || !plan) return null; - - const basePrice = Number(plan.price_monthly || 0); - - const phonePrice = useMemo(() => { - if (!selectedPhoneId) return 0; - const p = phonePlans.find((x) => String(x.id) === String(selectedPhoneId)); - return Number(p?.price_monthly || 0); - }, [selectedPhoneId, phonePlans]); - - const addonsPrice = useMemo(() => { - return addonsList.reduce((sum, a) => { - const qty = Number(selectedQty[a.id] || 0); - return sum + qty * Number(a.cena || 0); - }, 0); - }, [selectedQty, addonsList]); - - const totalMonthly = basePrice + phonePrice + addonsPrice; - - const handlePhoneSelect = (id) => { - if (id === null) { - setSelectedPhoneId(null); - return; - } - setSelectedPhoneId(id); - }; - - const toggleCheckboxAddon = (id) => { - setSelectedQty((prev) => { - const next = { ...prev }; - next[id] = (next[id] || 0) > 0 ? 0 : 1; - return next; - }); - }; - - const setQtyAddon = (id, qty, min, max) => { - const safe = Math.max(min, Math.min(max, qty)); - setSelectedQty((prev) => ({ ...prev, [id]: safe })); - }; - - // ====================== - // ✅ zapis do localStorage (jak w Jambox) - // ====================== - const LS_KEY = "fuz_offer_config_v1"; - - function buildOfferPayload() { - const phone = selectedPhoneId - ? phonePlans.find((p) => String(p.id) === String(selectedPhoneId)) - : null; - - const addonsChosen = addonsList - .map((a) => { - const qty = Number(selectedQty[a.id] || 0); - if (qty <= 0) return null; - return { id: a.id, nazwa: a.nazwa, qty, unit: Number(a.cena || 0) }; - }) - .filter(Boolean); - - return { - createdAt: new Date().toISOString(), - // ważne: plan internetowy jako "pkg" żeby kontakt miał wspólny format - pkg: { id: plan?.id ?? null, name: plan?.name ?? "", price: basePrice }, - - phone: phone ? { id: phone.id, name: phone.name, price: phone.price_monthly } : null, - - // tu nie ma dekodera i tv-addons - decoder: null, - tvAddons: [], - addons: addonsChosen, - - totals: { - base: basePrice, - phone: phonePrice, - decoder: 0, - tv: 0, - addons: addonsPrice, - total: totalMonthly, - currencyLabel: cenaOpis, - }, - }; - } - - function moneyWithLabel(v) { - return `${money(v)} ${cenaOpis}`; - } - - function buildOfferMessage(payload) { - const lines = []; - - lines.push(`Wybrana oferta: ${payload?.pkg?.name || "—"}`); - lines.push(""); - - // ✅ WSZYSTKIE linie jak w podsumowaniu (wspólny standard) - lines.push(`Pakiet: ${moneyWithLabel(payload?.totals?.base ?? 0)}`); - lines.push(`Telefon: ${payload?.phone ? moneyWithLabel(payload.totals.phone) : "—"}`); - lines.push(`Dekoder: —`); - lines.push(`Dodatki TV: —`); - lines.push(`Dodatkowe usługi: ${payload?.addons?.length ? moneyWithLabel(payload.totals.addons) : "—"}`); - lines.push(`Łącznie: ${moneyWithLabel(payload?.totals?.total ?? 0)}`); - - if (payload?.phone) { - lines.push(""); - lines.push(`Telefon: ${payload.phone.name} (${moneyWithLabel(payload.phone.price)})`); - } - - if (Array.isArray(payload?.addons) && payload.addons.length) { - lines.push(""); - lines.push("Dodatkowe usługi:"); - for (const it of payload.addons) { - lines.push(`- ${it.nazwa} x${it.qty} @ ${moneyWithLabel(it.unit)}`); - } - } - - return lines.join("\n"); - } - - function saveOfferToLocalStorage() { - try { - const payload = buildOfferPayload(); - payload.message = buildOfferMessage(payload); - localStorage.setItem(LS_KEY, JSON.stringify(payload)); - } catch {} - } - // ====================== - - return ( -
- - -
e.stopPropagation()}> -
-

{plan.name} — konfiguracja usług

- - {error &&

{error}

} - - {/* ✅ INTERNET */} -
- {money(basePrice)} {cenaOpis}} - open={openSections.internet} - onToggle={() => toggleSection("internet")} - > - {plan.features?.length ? ( -
    - {plan.features.map((f, idx) => ( -
  • - {f.label} - {formatFeatureValue(f.value)} -
  • - ))} -
- ) : ( -

Brak szczegółów.

- )} -
-
- - {/* ✅ TELEFON — identyczny wygląd jak w TV (f-radio-*) */} -
- - {phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"} - - } - open={openSections.phone} - onToggle={() => toggleSection("phone")} - > - {phonePlans.length === 0 ? ( -

Brak dostępnych pakietów telefonicznych.

- ) : ( -
- {/* brak telefonu */} - - - {/* pakiety */} - {phonePlans.map((p) => { - const isSelected = String(selectedPhoneId) === String(p.id); - - return ( -
- - - {/* ✅ detale ZAWSZE widoczne (jak w TV) */} - {p.features?.length > 0 && ( -
-
    - {p.features - .filter( - (f) => !String(f.label || "").toLowerCase().includes("aktyw"), - ) - .map((f, idx) => ( -
  • - {f.label} - {formatFeatureValue(f.value)} -
  • - ))} -
-
- )} -
- ); - })} -
- )} -
-
- - {/* ✅ DODATKI */} -
- - {addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"} - - } - open={openSections.addons} - onToggle={() => toggleSection("addons")} - > - {addonsList.length === 0 ? ( -

Brak dodatkowych usług.

- ) : ( -
- {addonsList.map((a) => { - const qty = Number(selectedQty[a.id] || 0); - const isQty = a.typ === "quantity" || a.ilosc === true; - - if (!isQty) { - const checked = qty > 0; - return ( - - ); - } - - // quantity - const min = Number.isFinite(a.min) ? a.min : 0; - const max = Number.isFinite(a.max) ? a.max : 10; - const step = Number.isFinite(a.krok) ? a.krok : 1; - const lineTotal = qty * Number(a.cena || 0); - - return ( -
- - -
-
{a.nazwa}
- {a.opis &&
{a.opis}
} -
- -
e.stopPropagation()}> - - - {qty} - - -
- -
-
- {money(a.cena)} {cenaOpis} -
-
- {qty > 0 ? `${money(lineTotal)} ${cenaOpis}` : "—"} -
-
-
- ); - })} -
- )} -
-
- - {/* ✅ PODSUMOWANIE */} -
- {money(totalMonthly)} {cenaOpis}} - open={openSections.summary} - onToggle={() => toggleSection("summary")} - > -
-
- {/* ✅ WSZYSTKIE linie jak w Jambox (standard) */} -
- Pakiet - {money(basePrice)} {cenaOpis} -
- -
- Telefon - {phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"} -
- -
- Dekoder - -
- -
- Dodatki TV - -
- -
- Dodatkowe usługi - {addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"} -
- -
- Łącznie - {money(totalMonthly)} {cenaOpis} -
- - saveOfferToLocalStorage()} - > - Wyślij zapytanie z tym wyborem - -
-
-
-
- -
e.stopPropagation()}> -
- Razem - {money(totalMonthly)} - {cenaOpis} -
-
-
-
-
- ); -} diff --git a/src/islands/Internet/InternetCards.jsx b/src/islands/Internet/InternetCards.jsx index 00c1989..d7d8f23 100644 --- a/src/islands/Internet/InternetCards.jsx +++ b/src/islands/Internet/InternetCards.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "preact/hooks"; import Markdown from "../Markdown.jsx"; import OffersSwitches from "../OffersSwitches.jsx"; -import InternetAddonsModal from "./InternetAddonsModalCompact.jsx"; +import InternetAddonsModal from "./InternetAddonsModal.jsx"; import "../../styles/offers-table.css"; function formatMoney(amount, currency = "PLN") { diff --git a/src/styles/hero.css b/src/styles/hero.css index f7d80b4..84e9c1d 100644 --- a/src/styles/hero.css +++ b/src/styles/hero.css @@ -1,125 +1,135 @@ -.f-hero { - @apply relative min-h-[600px] md:min-h-[700px] lg:min-h-[700px]; - @apply flex items-center overflow-hidden; -} - -.f-hero-background { - @apply absolute inset-0 w-full h-full object-cover z-0; - object-position: center center; -} - -.f-hero-overlay { - @apply absolute inset-0 z-[1] bg-black/5; -} - -.f-hero--right .f-hero-overlay { - @apply md:bg-gradient-to-r; - background-image: linear-gradient( - to right, - rgba(0, 0, 0, 0) 0%, - rgba(0, 0, 0, 0.1) 15%, - rgba(0, 0, 0, 0.1) 30%, - rgba(0, 0, 0, 0.5) 45%, - rgba(0, 0, 0, 0.5) 55%, - rgba(0, 0, 0, 0.5) 65%, - rgba(0, 0, 0, 0.5) 75%, - rgba(0, 0, 0, 0.75) 100% - ); -} - -.f-hero--left .f-hero-overlay { - @apply md:bg-gradient-to-l; - background-image: linear-gradient( - to left, - rgba(0, 0, 0, 0) 0%, - rgba(0, 0, 0, 0.1) 50%, - rgba(0, 0, 0, 0.5) 60%, - rgba(0, 0, 0, 0.75) 100% - ); -} - -.f-hero--center .f-hero-overlay { - @apply bg-black/50; -} - -.f-hero-container { - @apply relative z-[2] w-full max-w-[1400px] mx-auto; - @apply px-6 md:px-8 py-8 md:py-1; - @apply grid grid-cols-1 gap-4; -} - -.f-hero--right .f-hero-container { - @apply md:grid-cols-2; -} - -.f-hero--right .f-hero-content { - @apply md:col-start-2; -} - -.f-hero--left .f-hero-container { - @apply md:grid-cols-2; -} - -.f-hero--left .f-hero-content { - @apply md:col-start-1; -} - -.f-hero--center .f-hero-container { - @apply md:grid-cols-1 md:max-w-4xl md:text-center; -} - -.f-hero-content { - @apply flex flex-col gap-6 text-[--f-hero-text] mx-1; -} - -.f-hero-titles { - @apply space-y-2; -} - -.f-hero-title { - @apply text-4xl md:text-5xl lg:text-6xl text-[--f-hero-header]; - @apply font-bold leading-tight m-0; - @apply drop-shadow-[0_2px_8px_rgba(0,0,0,0.8)]; -} - -.f-hero-subtitles { - @apply space-y-4; -} - -.f-hero-subtitle { - @apply text-lg md:text-xl lg:text-2xl pl-9; - @apply leading-relaxed m-0; - @apply opacity-0 animate-fade-in-up; - @apply drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]; - animation-delay: calc(var(--delay, 0) * 150ms); -} - -.f-hero-description { - @apply text-base md:text-lg leading-relaxed; - @apply opacity-90 max-w-2xl m-0; - @apply drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]; -} - -.f-hero-ctas { - @apply flex flex-wrap gap-4 mt-2 justify-center; -} - -.f-hero-ctas a { - @apply w-full md:w-auto md:min-w-[280px] md:flex-1 md:max-w-[400px] justify-center; -} - - -@keyframes fade-in-up { - from { - opacity: 0; - transform: translateY(20px); +@layer components { + .f-hero { + @apply relative flex items-center overflow-hidden; + @apply min-h-[600px] md:min-h-[700px] lg:min-h-[700px]; } - to { - opacity: 1; - transform: translateY(0); + + .f-hero-background { + @apply absolute inset-0 z-0 w-full h-full object-cover; + object-position: center top; + } + + .f-hero-overlay { + @apply absolute inset-0 z-[1] bg-black/5; + } + + .f-hero--right .f-hero-overlay { + background-image: linear-gradient( + to right, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 0.1) 15%, + rgba(0, 0, 0, 0.1) 30%, + rgba(0, 0, 0, 0.5) 45%, + rgba(0, 0, 0, 0.5) 55%, + rgba(0, 0, 0, 0.5) 65%, + rgba(0, 0, 0, 0.5) 75%, + rgba(0, 0, 0, 0.75) 100% + ); + } + + .f-hero--left .f-hero-overlay { + background-image: linear-gradient( + to left, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 0.1) 50%, + rgba(0, 0, 0, 0.5) 60%, + rgba(0, 0, 0, 0.75) 100% + ); + } + + .f-hero--center .f-hero-overlay { + @apply bg-black/50; + background-image: none; + } + + .f-hero-container { + @apply relative z-[2] w-full max-w-[1400px] mx-auto; + @apply px-6 md:px-8 py-8 md:py-1; + @apply grid grid-cols-1 gap-4; + } + + .f-hero--right .f-hero-container, + .f-hero--left .f-hero-container { + @apply md:grid-cols-2; + } + + .f-hero--right .f-hero-content { + @apply md:col-start-2; + } + + .f-hero--left .f-hero-content { + @apply md:col-start-1; + } + + .f-hero--center .f-hero-container { + @apply md:grid-cols-1 md:max-w-4xl md:text-center; + } + + + .f-hero-content { + @apply mx-1 flex flex-col gap-6; + @apply text-[--f-hero-text]; + } + + .f-hero-title { + @apply m-0 font-bold leading-tight; + @apply text-4xl md:text-6xl lg:text-7xl text-center; + @apply text-[--f-hero-header]; + @apply drop-shadow-[0_2px_8px_rgba(0,0,0,0.8)]; + } + + .f-hero-subtitles { + @apply space-y-4; + } + + .f-hero-subtitle { + @apply m-0 leading-relaxed; + @apply text-xl md:text-2xl lg:text-3xl; + @apply pl-9 opacity-0; + @apply drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]; + animation: fade-in-up 0.6s ease-out forwards; + animation-delay: calc(var(--delay, 0) * 150ms); + } + + .f-hero--center .f-hero-subtitle { + @apply pl-0; + } + + .f-hero-description { + @apply m-0 max-w-2xl opacity-90; + @apply text-base md:text-lg leading-relaxed; + @apply drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]; + } + + .f-hero-ctas { + @apply mt-2 flex flex-wrap gap-4; + } + + .f-hero--left .f-hero-ctas { + @apply justify-start; + } + + .f-hero--right .f-hero-ctas { + @apply justify-end; + } + + .f-hero--center .f-hero-ctas { + @apply justify-center; + } + + .f-hero-ctas a { + @apply w-full justify-center; + @apply md:w-auto md:min-w-[280px] md:flex-1 md:max-w-[400px]; + } + + @keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } } - -.animate-fade-in-up { - animation: fade-in-up 0.6s ease-out forwards; -} \ No newline at end of file diff --git a/src/styles/modal.css b/src/styles/modal.css index 8fa3f12..7ec070a 100644 --- a/src/styles/modal.css +++ b/src/styles/modal.css @@ -102,7 +102,7 @@ @apply flex flex-col gap-2; } -/* BAZA: checkbox | main | price */ +/* BAZA: checkbox | main | price */ .f-addon-item { @apply grid items-start gap-3 px-3 py-2; border-bottom: 1px solid rgba(148, 163, 184, 0.4); @@ -110,7 +110,9 @@ grid-template-columns: auto 1fr auto; /* border-color: rgba(148, 163, 184, 0.5); */ background: var(--f-background); + @apply cursor-pointer; } +.f-addon-item * { @apply cursor-pointer; } .f-addon-item:last-child { border-bottom: none; diff --git a/src/styles/offers-table.css b/src/styles/offers-table.css index d76c27d..c0e8c46 100644 --- a/src/styles/offers-table.css +++ b/src/styles/offers-table.css @@ -36,7 +36,7 @@ } .f-card-name { - @apply text-xl font-semibold; + @apply text-3xl font-semibold; } .f-card-price { @@ -57,11 +57,11 @@ } .f-card-label { - @apply text-sm font-medium opacity-80; + @apply text-base font-medium opacity-80; } .f-card-value { - @apply text-sm font-semibold text-right; + @apply text-base font-semibold text-right; } .f-card-value.yes { diff --git a/src/styles/theme.css b/src/styles/theme.css index bb4a64d..b368d23 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -10,22 +10,16 @@ --link-color-light: hsla(210, 100%, 40%, 1); --link-hover-light: hsla(165, 80%, 25%, 1); - --text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%); - --text2-light: hsl(var(--brand-hue) 30% 30%); - --text3-light: hsl(var(--brand-hue) 15% 85%); - /* - --surface1-light: hsl(var(--brand-hue) 25% 90%); - --surface2-light: hsl(var(--brand-hue) 20% 99%); - --surface3-light: hsl(var(--brand-hue) 20% 92%); - --surface4-light: hsl(var(--brand-hue) 20% 85%); - --surface5-light: hsla(217, 70%, 26%, 1); - */ + --text1-light: hsla(200, 100%, 10%, 1); + --text2-light: hsla(200, 30%, 30%, 1); + --text3-light: hsla(200, 15%, 85%, 1); --surface1-light: hsla(200, 25%, 90%, 1); --surface2-light: hsla(200, 20%, 99%, 1); --surface3-light: hsla(200, 20%, 92%, 1); --surface4-light: hsla(200, 20%, 85%, 1); --surface5-light: hsla(217, 70%, 26%, 1); + --surface6-light: hsla(200, 5%, 75%, 1); --surface-shadow-light: var(--brand-hue) 10% 20%; @@ -34,8 +28,10 @@ --brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 0.2) calc(var(--brand-lightness) / 1.5)); - --text1-dark: hsl(var(--brand-hue) 15% 85%); - --text2-dark: hsl(var(--brand-hue) 5% 65%); + + --text1-dark: hsla(200, 15%, 85%, 1); + --text2-dark: hsla(200, 5%, 65%, 1); + --text3-dark: hsla(200, 5%, 100%, 1); --surface1-dark: hsla(200, 10%, 10%, 1); --surface2-dark: hsla(200, 10%, 15%, 1); @@ -60,16 +56,16 @@ } :root { - /* --- Hero --- */ - --f-hero-text: var(--text1-dark); - --f-hero-header: var(--text1-dark); + --f-hero-text: var(--text3-dark); + --f-hero-header: var(--text3-dark); + /* --- Background and Text --- */ --f-background: var(--surface3-light); --f-text: var(--text1-light); --f-header: var(--text1-light); --f-header-items: (var(--text1-light)); /*--- Navbar --- */ - --f-navbar-background: var(--surface4-light); + --f-navbar-background: var(--surface6-light); --f-navbar-link: var(--link-color-light); --f-navbar-link-hover: var(--link-hover-light); /*--- Footer --- */ @@ -121,9 +117,9 @@ } :root.dark { - /* --- Hero --- */ - --f-hero-text: var(--text1-dark); - --f-hero-header: var(--text1-dark); + --f-hero-text: var(--text3-dark); + --f-hero-header: var(--text3-dark); + /* --- Background and Text --- */ --f-background: var(--surface1-dark); --f-text: var(--text1-dark);