Seo - generowanie schema, api lista miejscowości , sekcja z listą miejscowości
This commit is contained in:
98
src/components/sections/SectionAreaServed.astro
Normal file
98
src/components/sections/SectionAreaServed.astro
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import type { ImageMetadata } from "astro";
|
||||
import Markdown from "../../islands/Markdown.jsx";
|
||||
import { findSectionImage, loadGlobalSeo } from "../../lib/astro-helpers";
|
||||
|
||||
const { section, index } = Astro.props;
|
||||
|
||||
// ✅ Załaduj global-seo.yaml
|
||||
const globalSeo = loadGlobalSeo();
|
||||
const cities = globalSeo?.schema?.areaServed || [];
|
||||
|
||||
const hasImage = !!section.image;
|
||||
const reverse = index % 2 === 1;
|
||||
|
||||
const sectionImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||
"/src/assets/sections/**/*.{png,jpg,jpeg,webp,avif}",
|
||||
{ eager: true },
|
||||
);
|
||||
|
||||
const sectionImage = section.image ? findSectionImage(sectionImages, section.image) : null;
|
||||
const isAboveFold = index === 0;
|
||||
|
||||
// ✅ Konfiguracja wyświetlania
|
||||
const showTitle = section.showTitle !== false; // domyślnie true
|
||||
|
||||
// ✅ Formatowanie listy miejscowości jako string
|
||||
const citiesText = cities.join(", ");
|
||||
---
|
||||
|
||||
<section class="f-section">
|
||||
<div
|
||||
class={`f-section-grid ${hasImage ? "md:grid-cols-2" : "md:grid-cols-1"}`}
|
||||
>
|
||||
{
|
||||
sectionImage && (
|
||||
<Image
|
||||
src={sectionImage}
|
||||
alt={section.title || "Obszar działania"}
|
||||
class={`f-section-image ${reverse ? "md:order-1" : "md:order-2"} ${section.dimmed ? "f-image-dimmed" : ""}`}
|
||||
loading={isAboveFold ? "eager" : "lazy"}
|
||||
fetchpriority={isAboveFold ? "high" : "auto"}
|
||||
decoding="async"
|
||||
format="webp"
|
||||
widths={[480, 768, 1024, 1440]}
|
||||
sizes="(min-width: 768px) 50vw, 100vw"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div class={`${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}>
|
||||
{showTitle && section.title && (
|
||||
<h2 class="f-section-title">{section.title}</h2>
|
||||
)}
|
||||
|
||||
{section.content && <Markdown text={section.content} />}
|
||||
|
||||
{/* ✅ Lista miejscowości jako paragraf */}
|
||||
{cities.length > 0 && (
|
||||
<p class="f-cities-paragraph">
|
||||
{citiesText}. <a href={section.button.url}
|
||||
class=""
|
||||
title={section.button.title}
|
||||
>
|
||||
{section.button.text}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<!-- {
|
||||
section.button && (
|
||||
<div class="f-section-nav mt-6">
|
||||
|
||||
<a href={section.button.url}
|
||||
class="btn btn-primary"
|
||||
title={section.button.title}
|
||||
>
|
||||
{section.button.text}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
} -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- <style>
|
||||
.f-cities-paragraph {
|
||||
margin-top: 1.5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.8;
|
||||
color: var(--f-text, #212529);
|
||||
}
|
||||
|
||||
:global(.dark) .f-cities-paragraph {
|
||||
color: var(--f-text-dark, #f7fafc);
|
||||
}
|
||||
</style> -->
|
||||
@@ -1,13 +1,28 @@
|
||||
---
|
||||
import { loadYaml, processMarkdownSections } from "../../lib/astro-helpers";
|
||||
import yaml from "js-yaml";
|
||||
import fs from "fs";
|
||||
import { marked } from "marked";
|
||||
|
||||
import SectionDefault from "./SectionDefault.astro";
|
||||
import SectionAreaServed from "./SectionAreaServed.astro"; // ✅ Import nowego komponentu
|
||||
|
||||
const { src } = Astro.props;
|
||||
|
||||
const data = loadYaml(src) ?? { sections: [] };
|
||||
const sections = processMarkdownSections(data.sections as any[]);
|
||||
const data = yaml.load(fs.readFileSync(src, "utf8")) ?? { sections: [] };
|
||||
|
||||
const sections = (data.sections as any[]).map((s: any) => ({
|
||||
...s,
|
||||
html: marked(s.content || "")
|
||||
}));
|
||||
---
|
||||
|
||||
{sections.map((section: any, index: number) => {
|
||||
const type = section.type || "default";
|
||||
|
||||
// ✅ Routing do właściwego komponentu
|
||||
if (type === "area-served") {
|
||||
return <SectionAreaServed section={section} index={index} />;
|
||||
}
|
||||
|
||||
return <SectionDefault section={section} index={index} />;
|
||||
})}
|
||||
@@ -1,6 +1,6 @@
|
||||
page:
|
||||
title: "Internet Światłowodowy Wyszków - Szybkie Łącze bez Limitów | FUZ"
|
||||
description: "Internet światłowodowy do 1 Gb/s w Wyszkowie. Bez limitów danych, stabilne połączenie, montaż w 48h. Lokalny operator z profesjonalnym serwisem. Sprawdź ceny!"
|
||||
title: "Internet Światłowodowy Wyszków - Szybkie Łącze | FUZ"
|
||||
description: "Internet światłowodowy w Wyszkowie. Bez limitów danych, stabilne połączenie, szybki montaż. Lokalny operator z profesjonalnym serwisem. Sprawdź ceny!"
|
||||
image: "/og/internet-og.png"
|
||||
url: "/internet-swiatlowodowy"
|
||||
keywords:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
page:
|
||||
title: "Internet i Telewizja Wyszków - Pakiety Światłowodowe 2w1 | FUZ"
|
||||
description: "Pakiety Internet + TV w Wyszkowie. Światłowód do 1 Gb/s + ponad 200 kanałów. Jeden rachunek, niższa cena, lokalny serwis. Sprawdź ofertę pakietów 2w1!"
|
||||
title: "Internet i Telewizja Wyszków - Pakiety Telewizyjne | FUZ"
|
||||
description: "Internet i telewizja w Wyszkowie. Jeden rachunek, niższa cena, lokalny serwis. Sprawdź ofertę pakietów internetu i telewizji!"
|
||||
image: "/og/telewizja-og.png"
|
||||
url: "/internet-telewizja"
|
||||
keywords:
|
||||
|
||||
12
src/content/site/area-section.yaml
Normal file
12
src/content/site/area-section.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
sections:
|
||||
- type: area-served
|
||||
title:
|
||||
content: |
|
||||
Świadczymy usługi internetu światłowodowego oraz telewizji w następujących miejscowościach
|
||||
image:
|
||||
showTitle: true
|
||||
columns: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
|
||||
button:
|
||||
text: "Sprawdź dostępność usługi pod Twoim adresem"
|
||||
url: "/mapa-zasiegu"
|
||||
title: "Sprawdź czy jesteś w zasięgu"
|
||||
@@ -1,25 +1,63 @@
|
||||
site:
|
||||
name: "FUZ Adam Rojek"
|
||||
description: "Lokalny operator internetu światłowodowego w Wyszkowie i okolicach. Stabilne łącze, szybki serwis, konkurencyjne ceny."
|
||||
url: "https://www.fuz.pl"
|
||||
lang: "pl"
|
||||
|
||||
name: FUZ Adam Rojek
|
||||
description: Lokalny operator internetu światłowodowego w Wyszkowie i okolicach. Stabilne łącze, szybki serwis, konkurencyjne ceny.
|
||||
url: https://www.fuz.pl
|
||||
lang: pl
|
||||
company:
|
||||
name: "FUZ Adam Rojek"
|
||||
phone: "+48 29 643 80 55"
|
||||
email: "biuro@fuz.pl"
|
||||
street: "ul. Świętojańska 46"
|
||||
city: "Wyszków"
|
||||
postal: "07-200"
|
||||
country: "PL"
|
||||
name: FUZ Adam Rojek
|
||||
phone: +48 29 643 80 55
|
||||
email: biuro@fuz.pl
|
||||
street: ul. Świętojańska 46
|
||||
city: Wyszków
|
||||
postal: 07-200
|
||||
country: PL
|
||||
lat: 52.597385
|
||||
lon: 21.456797
|
||||
logo: "/logo.webp"
|
||||
|
||||
logo: /logo.webp
|
||||
schema:
|
||||
openingHours: "Mo-Fr 09:00-17:00"
|
||||
priceRange: "$"
|
||||
openingHours: Mo-Fr 09:00-17:00
|
||||
priceRange: $
|
||||
areaServed:
|
||||
- Wyszków
|
||||
- Rząśnik
|
||||
- Dąbrowa
|
||||
- Gulczewo
|
||||
- Kamieńczyk
|
||||
- Komorowo
|
||||
- Kręgi
|
||||
- Kręgi Nowe
|
||||
- Leszczydół Stary
|
||||
- Leszczydół-działki
|
||||
- Leszczydół-nowiny
|
||||
- Leszczydół-podwielątki
|
||||
- Leszczydół-pustki
|
||||
- Lucynów
|
||||
- Lucynów Duży
|
||||
- Łosinno
|
||||
- Natalin
|
||||
- Nowe Kozłowo
|
||||
- Nowe Wielątki
|
||||
- Nowe Wypychy
|
||||
- Ochudno
|
||||
- Olszanka
|
||||
- Ostrowy
|
||||
- Porządzie
|
||||
- Pułtusk
|
||||
- Puste Łąki
|
||||
- Rybienko Nowe
|
||||
- Rybienko Stare
|
||||
- Rybno
|
||||
- Rząśnik
|
||||
- Sitno
|
||||
- Skuszew
|
||||
- Stare Kozłowo
|
||||
- Stary Mystkówiec
|
||||
- Strachów
|
||||
- Świniotop
|
||||
- Tulewo
|
||||
- Tulewo Górne
|
||||
- Tumanek
|
||||
- Wielątki
|
||||
- Wielątki Rosochate
|
||||
- Wielątki-folwark
|
||||
- Wólka-folwark
|
||||
- Wólka-przekory
|
||||
- Wyszków
|
||||
|
||||
@@ -111,6 +111,12 @@ export type GlobalSeo = {
|
||||
lat: number;
|
||||
lon: number;
|
||||
};
|
||||
schema?: {
|
||||
openingHours?: string;
|
||||
priceRange?: string;
|
||||
areaServed?: string[];
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
// ✅ DODANY BRAKUJĄCY TYP
|
||||
@@ -158,6 +164,11 @@ export function buildSeoMeta(
|
||||
},
|
||||
};
|
||||
|
||||
const mergedSchema = {
|
||||
...globalSeo.schema, // ← Globalna schema (openingHours, priceRange, areaServed)
|
||||
...page.schema, // ← Page-specific schema (override jeśli istnieje)
|
||||
};
|
||||
|
||||
const schemaLocalBusiness = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "LocalBusiness",
|
||||
@@ -178,6 +189,14 @@ export function buildSeoMeta(
|
||||
longitude: company.lon,
|
||||
},
|
||||
url: baseUrl,
|
||||
...(mergedSchema.openingHours && { openingHours: mergedSchema.openingHours }),
|
||||
...(mergedSchema.priceRange && { priceRange: mergedSchema.priceRange }),
|
||||
...(mergedSchema.areaServed && { areaServed: mergedSchema.areaServed }),
|
||||
...Object.fromEntries(
|
||||
Object.entries(mergedSchema).filter(
|
||||
([key]) => !['openingHours', 'priceRange', 'areaServed'].includes(key)
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -295,3 +314,85 @@ export const DEFAULT_FAVICON_CONFIG: FaviconConfig = {
|
||||
export function mergeFaviconConfig(custom?: Partial<FaviconConfig>): FaviconConfig {
|
||||
return { ...DEFAULT_FAVICON_CONFIG, ...custom };
|
||||
}
|
||||
|
||||
// ==================== LOCALE HELPERS ====================
|
||||
|
||||
/**
|
||||
* Title Case dla tekstu (każde słowo z wielkiej)
|
||||
* UWAGA: Może mieć problemy z polskimi znakami z SQLite
|
||||
*/
|
||||
export function titleCaseWords(input: string): string {
|
||||
return String(input ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/\b\p{L}/gu, (c) => c.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizacja nazw miast dla polskiego locale
|
||||
* Bezpieczne dla danych z SQLite
|
||||
*
|
||||
* @example
|
||||
* normalizeCityName("dąbrowa") => "Dąbrowa"
|
||||
* normalizeCityName("DĄBROWA") => "Dąbrowa"
|
||||
* normalizeCityName("DĄBrowa") => "Dąbrowa"
|
||||
*/
|
||||
export function normalizeCityName(input: string): string {
|
||||
const text = String(input ?? "").trim();
|
||||
|
||||
if (!text) return "";
|
||||
|
||||
// 1. Normalizuj do NFD (dekompozycja znaków diakrytycznych)
|
||||
const normalized = text.normalize("NFD");
|
||||
|
||||
// 2. Lowercase całość
|
||||
const lower = normalized.toLowerCase();
|
||||
|
||||
// 3. Title Case słowo po słowie
|
||||
const words = lower.split(/\s+/);
|
||||
|
||||
const titleCased = words.map(word => {
|
||||
if (!word) return "";
|
||||
|
||||
// Pierwsza litera wielka, reszta mała
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}).join(" ");
|
||||
|
||||
// 4. Normalizuj z powrotem do NFC (kompozycja)
|
||||
return titleCased.normalize("NFC");
|
||||
}
|
||||
|
||||
// ==================== ENV HELPERS ====================
|
||||
|
||||
/**
|
||||
* Pobierz zmienną środowiskową (działa zarówno w Astro jak i Node)
|
||||
* @param key - Nazwa zmiennej
|
||||
* @param fallback - Wartość domyślna jeśli brak
|
||||
*/
|
||||
export function getEnv(key: string, fallback?: string): string | undefined {
|
||||
// Próbuj import.meta.env (Astro)
|
||||
if (typeof import.meta !== 'undefined' && import.meta.env?.[key]) {
|
||||
return import.meta.env[key];
|
||||
}
|
||||
|
||||
// Próbuj process.env (Node.js)
|
||||
if (typeof process !== 'undefined' && process.env?.[key]) {
|
||||
return process.env[key];
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobierz wymaganą zmienną środowiskową (rzuca błąd jeśli brak)
|
||||
*/
|
||||
export function getRequiredEnv(key: string): string {
|
||||
const value = getEnv(key);
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Brak wymaganej zmiennej środowiskowej: ${key}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
188
src/pages/api/update-area-served.ts
Normal file
188
src/pages/api/update-area-served.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import { getDb } from "./db.js";
|
||||
import { loadYaml, sortByLocale, normalizeCityName, getEnv } from "../../lib/astro-helpers";
|
||||
|
||||
/**
|
||||
* API do automatycznej aktualizacji schema.areaServed w global-seo.yaml
|
||||
*
|
||||
* Wymaga: .env z AREA_ADMIN_TOKEN
|
||||
* Opcjonalnie: SEO_YAML_PATH
|
||||
*/
|
||||
|
||||
// ==================== AUTHORIZATION ====================
|
||||
|
||||
function isAuthorized(request: Request): boolean {
|
||||
const expected = getEnv('JAMBOX_ADMIN_TOKEN'); // ✅ Zamiast import.meta.env
|
||||
|
||||
if (!expected) {
|
||||
console.warn("⚠️ AREA_ADMIN_TOKEN nie jest ustawiony w .env");
|
||||
return false;
|
||||
}
|
||||
|
||||
const token = request.headers.get("x-admin-token");
|
||||
|
||||
if (!token) {
|
||||
console.warn("⚠️ Brak nagłówka x-admin-token w request");
|
||||
return false;
|
||||
}
|
||||
|
||||
const isValid = token === expected;
|
||||
|
||||
if (!isValid) {
|
||||
console.warn("⚠️ Nieprawidłowy token:", {
|
||||
received: token.substring(0, 10) + '...',
|
||||
expected: expected.substring(0, 10) + '...'
|
||||
});
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// ==================== YAML PATH ====================
|
||||
|
||||
function getYamlPath(): string {
|
||||
return (
|
||||
getEnv('SEO_YAML_PATH') || // ✅ Zamiast import.meta.env
|
||||
path.join(process.cwd(), "src", "content", "site", "global-seo.yaml")
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== DATABASE QUERIES ====================
|
||||
|
||||
/**
|
||||
* Pobierz unikalne miasta z bazy danych
|
||||
* @returns Array<string> - Posortowane alfabetycznie (locale: pl)
|
||||
*/
|
||||
function getCitiesFromDatabase(): string[] {
|
||||
const db = getDb();
|
||||
|
||||
const rows = db
|
||||
.prepare("SELECT DISTINCT city FROM ranges WHERE city IS NOT NULL ORDER BY city")
|
||||
.all() as Array<{ city: string }>;
|
||||
|
||||
// ✅ Używamy normalizeCityName zamiast titleCaseWords
|
||||
const cities = rows
|
||||
.map((r) => normalizeCityName(r.city))
|
||||
.filter((s) => s.length > 0);
|
||||
|
||||
const uniqueCities = [...new Set(cities)];
|
||||
|
||||
return sortByLocale(uniqueCities, "pl");
|
||||
}
|
||||
|
||||
// ==================== YAML OPERATIONS ====================
|
||||
|
||||
async function updateAreaServedInYaml(
|
||||
yamlPath: string,
|
||||
cities: string[],
|
||||
dryRun: boolean
|
||||
): Promise<{ changed: boolean; before: string[]; after: string[] }> {
|
||||
const doc = loadYaml<any>(yamlPath) || {};
|
||||
|
||||
if (!doc.schema) {
|
||||
doc.schema = {};
|
||||
}
|
||||
|
||||
const before = Array.isArray(doc.schema.areaServed) ? doc.schema.areaServed : [];
|
||||
const after = cities;
|
||||
|
||||
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
||||
|
||||
if (!dryRun && changed) {
|
||||
doc.schema.areaServed = after;
|
||||
|
||||
const newYaml = yaml.dump(doc, {
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false,
|
||||
});
|
||||
|
||||
await fs.writeFile(yamlPath, newYaml, "utf8");
|
||||
}
|
||||
|
||||
return { changed, before, after };
|
||||
}
|
||||
|
||||
// ==================== API HANDLER ====================
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
// Debug logging
|
||||
console.log("📥 Request received at /api/area/update-area-served");
|
||||
console.log(" Headers:", {
|
||||
'content-type': request.headers.get('content-type'),
|
||||
'x-admin-token': request.headers.get('x-admin-token') ? '***' : 'missing'
|
||||
});
|
||||
console.log(" ENV AREA_ADMIN_TOKEN:", getEnv('AREA_ADMIN_TOKEN') ? '***' : 'MISSING!');
|
||||
|
||||
// Authorization
|
||||
if (!isAuthorized(request)) {
|
||||
console.error("❌ Unauthorized request");
|
||||
return new Response(
|
||||
JSON.stringify({ ok: false, error: "Unauthorized" }),
|
||||
{
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log("✅ Authorization successful");
|
||||
|
||||
// Parse body
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const dryRun = body?.dryRun === true;
|
||||
|
||||
console.log(" DryRun:", dryRun);
|
||||
|
||||
// Get cities from DB
|
||||
const cities = getCitiesFromDatabase();
|
||||
console.log(" Cities from DB:", cities.length);
|
||||
|
||||
// Update YAML
|
||||
const yamlPath = getYamlPath();
|
||||
console.log(" YAML path:", yamlPath);
|
||||
|
||||
const { changed, before, after } = await updateAreaServedInYaml(
|
||||
yamlPath,
|
||||
cities,
|
||||
dryRun
|
||||
);
|
||||
|
||||
console.log(" Changed:", changed);
|
||||
console.log("✅ Success");
|
||||
|
||||
// Response
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
dryRun,
|
||||
yamlPath,
|
||||
changed,
|
||||
count: after.length,
|
||||
before,
|
||||
after,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("❌ update-area-served error:", err);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
error: String((err as Error)?.message ?? err),
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,5 @@ const hero = yaml.load(fs.readFileSync("./src/content/home/hero.yaml", "utf8"));
|
||||
<DefaultLayout seo={seo}>
|
||||
<Hero {...hero} />
|
||||
<SectionRenderer src="./src/content/site/site.section.yaml" />
|
||||
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -110,4 +110,5 @@ const addonsCenaOpis = addonsData?.cena_opis ?? cenaOpis;
|
||||
</section>
|
||||
|
||||
<SectionRenderer src="./src/content/internet-swiatlowodowy/section.yaml" />
|
||||
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -129,4 +129,5 @@ const addonsCenaOpis = addonsYaml?.cena_opis ?? cenaOpis;
|
||||
</section>
|
||||
<SectionChannelsSearch />
|
||||
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
|
||||
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||||
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
||||
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
|
||||
import RangeForm from "../../islands/RangeForm.jsx";
|
||||
import { loadYaml, safeArray } from "../../lib/astro-helpers";
|
||||
import "../../styles/map-google.css";
|
||||
@@ -48,7 +49,7 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||
<script is:inline>
|
||||
let fiberLayer = null;
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@
|
||||
|
||||
|
||||
.f-note-acc {
|
||||
@apply mx-20
|
||||
@apply md:mx-20
|
||||
}
|
||||
|
||||
.f-note-acc-summary {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
@apply items-start;
|
||||
}
|
||||
|
||||
|
||||
.f-contact-item {
|
||||
h3,
|
||||
h4 {
|
||||
@@ -32,12 +31,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.f-contact-map {
|
||||
@apply mx-auto mt-6 w-full max-w-7xl;
|
||||
}
|
||||
|
||||
|
||||
.f-toast {
|
||||
@apply fixed left-1/2 z-[999999] pointer-events-none;
|
||||
top: calc(var(--nav-height, 80px) + 20px);
|
||||
|
||||
Reference in New Issue
Block a user