feat: dodanie kalkulatora doboru klimatyzacji i zaktualizowanie 996 produktów
This commit is contained in:
+7
-45
@@ -1,51 +1,13 @@
|
|||||||
import { CtaBanner } from "@/components/sections/CtaBanner";
|
import type { Metadata } from 'next';
|
||||||
import { Wrench, Phone, ArrowRight } from "lucide-react";
|
import { KalkulatorClient } from '@/components/kalkulator/KalkulatorClient';
|
||||||
import Link from "next/link";
|
import { getProducts } from '@/content/products';
|
||||||
import { companyDetails } from "@/content/company";
|
|
||||||
import type { Metadata } from "next";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Kalkulator doboru | ThermCool',
|
title: 'Kalkulator doboru klimatyzacji | ThermCool',
|
||||||
description: "Kalkulator doboru klimatyzacji i pomp ciepła w przygotowaniu.",
|
description: 'Dobierz klimatyzator do swojego pomieszczenia w 4 krokach. Kalkulator mocy, dobór kategorii i szacunkowy koszt montażu.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function KalkulatorPage() {
|
export default function KalkulatorPage() {
|
||||||
return (
|
const products = getProducts();
|
||||||
<main className="flex-1">
|
return <KalkulatorClient products={products} />;
|
||||||
<section className="py-24 md:py-32 bg-muted">
|
|
||||||
<div className="container mx-auto px-4 max-w-3xl text-center">
|
|
||||||
<div className="w-24 h-24 bg-primary rounded-full flex items-center justify-center mx-auto mb-8 text-white shadow-xl">
|
|
||||||
<Wrench className="w-12 h-12" />
|
|
||||||
</div>
|
|
||||||
<h1 className="text-4xl md:text-5xl font-heading font-bold text-primary mb-6 leading-tight">
|
|
||||||
Narzędzie w przygotowaniu
|
|
||||||
</h1>
|
|
||||||
<p className="text-lg md:text-xl text-muted-foreground mb-10 leading-relaxed">
|
|
||||||
Pracujemy nad zaawansowanym konfiguratorem, który krok po kroku pomoże Ci dobrać sprzęt i natychmiast oszacuje koszty montażu. Narzędzie zostanie udostępnione już wkrótce!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
||||||
<a href={`tel:${companyDetails.phoneRaw}`} className="w-full sm:w-auto">
|
|
||||||
<button className="btn btn-primary font-bold w-full sm:w-auto text-white flex items-center justify-center gap-2">
|
|
||||||
<Phone className="size-5" />
|
|
||||||
<span>Zadzwoń: {companyDetails.phone}</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
<Link href="/kontakt" className="w-full sm:w-auto">
|
|
||||||
<button className="btn btn-outline btn-primary bg-white font-bold w-full sm:w-auto flex items-center justify-center gap-2">
|
|
||||||
<span>Napisz do nas</span>
|
|
||||||
<ArrowRight className="size-5" />
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<CtaBanner
|
|
||||||
title="Skorzystaj z naszej darmowej wyceny od ręki"
|
|
||||||
subtitle="Zadzwoń do nas lub wypełnij krótki formularz na stronie kontaktu. Przygotujemy dla Ciebie indywidualną, niezobowiązującą i darmową ofertę szybciej, niż myślisz."
|
|
||||||
ctaText="Przejdź do kontaktu"
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-15
@@ -9,15 +9,7 @@ export const metadata: Metadata = {
|
|||||||
alternates: { canonical: '/sklep' },
|
alternates: { canonical: '/sklep' },
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
export default function ShopPage() {
|
||||||
searchParams: Promise<{ search?: string; brand?: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ShopPage({ searchParams }: Props) {
|
|
||||||
const params = await searchParams;
|
|
||||||
const initialSearch = params.search || '';
|
|
||||||
const initialBrand = params.brand || '';
|
|
||||||
|
|
||||||
const products = getProducts();
|
const products = getProducts();
|
||||||
const brands = getBrands();
|
const brands = getBrands();
|
||||||
const splitCount = products.filter(p => p.category === 'scienne').length;
|
const splitCount = products.filter(p => p.category === 'scienne').length;
|
||||||
@@ -35,12 +27,7 @@ export default async function ShopPage({ searchParams }: Props) {
|
|||||||
secondaryCtaLink={undefined}
|
secondaryCtaLink={undefined}
|
||||||
backgroundImage="https://images.unsplash.com/photo-1621905251189-08b45d6a269e?w=1600&q=80"
|
backgroundImage="https://images.unsplash.com/photo-1621905251189-08b45d6a269e?w=1600&q=80"
|
||||||
/>
|
/>
|
||||||
<ProductsGrid
|
<ProductsGrid products={products} brands={brands} />
|
||||||
products={products}
|
|
||||||
brands={brands}
|
|
||||||
initialSearch={initialSearch}
|
|
||||||
initialBrand={initialBrand}
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { ArrowRight, ArrowLeft, Check, Thermometer, Home, Zap, ShoppingCart, Phone, RotateCcw } from 'lucide-react';
|
||||||
|
import { Product } from '@/content/products';
|
||||||
|
import { companyDetails } from '@/content/company';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
products: Product[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oblicz wymaganą moc chłodniczą w kW
|
||||||
|
function calculatePower(m2: number, floor: string, sun: string, building: string): number {
|
||||||
|
let power = m2 * 0.1; // baza: 1 kW na 10 m²
|
||||||
|
|
||||||
|
// Korekta typu budynku i piętra
|
||||||
|
if (building === 'apartment') {
|
||||||
|
if (floor === 'top') power *= 1.2; // ostatnie piętro - więcej ciepła z dachu
|
||||||
|
if (floor === 'ground') power *= 0.95;
|
||||||
|
} else if (building === 'house') {
|
||||||
|
if (floor === 'top') power *= 1.1; // poddasze - dodatkowe nagrzewanie
|
||||||
|
}
|
||||||
|
|
||||||
|
// Korekta nasłonecznienia
|
||||||
|
if (sun === 'south') power *= 1.15; // południe - duże nasłonecznienie
|
||||||
|
if (sun === 'west') power *= 1.1;
|
||||||
|
if (sun === 'north') power *= 0.9;
|
||||||
|
|
||||||
|
return Math.round(power * 10) / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Szacunkowy koszt montażu
|
||||||
|
function estimateInstall(pipes: string, rooms: number): { min: number; max: number } {
|
||||||
|
const base = rooms === 1 ? 1200 : rooms === 2 ? 2800 : rooms === 3 ? 4200 : 5500;
|
||||||
|
const pipeExtra = pipes === 'short' ? 0 : pipes === 'medium' ? 300 : pipes === 'long' ? 700 : 1200;
|
||||||
|
return { min: base + pipeExtra, max: base + pipeExtra + 600 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const STEPS = ['Pomieszczenie', 'Konfiguracja', 'Instalacja', 'Budżet'] as const;
|
||||||
|
|
||||||
|
export function KalkulatorClient({ products }: Props) {
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const [building, setBuilding] = useState('apartment');
|
||||||
|
const [m2, setM2] = useState(25);
|
||||||
|
const [floor, setFloor] = useState('middle'); // ground | middle | top
|
||||||
|
const [sun, setSun] = useState('south'); // north | east | south | west
|
||||||
|
const [rooms, setRooms] = useState(1);
|
||||||
|
const [pipes, setPipes] = useState('medium'); // short(<3m) | medium(3-6m) | long(6-10m) | verylong(>10m)
|
||||||
|
const [budget, setBudget] = useState(8000);
|
||||||
|
const [showResults, setShowResults] = useState(false);
|
||||||
|
|
||||||
|
const requiredPower = useMemo(() => calculatePower(m2, floor, sun, building), [m2, floor, sun, building]);
|
||||||
|
const installCost = useMemo(() => estimateInstall(pipes, rooms), [pipes, rooms]);
|
||||||
|
const category = rooms === 1 ? 'scienne' : 'multisplit';
|
||||||
|
|
||||||
|
const results = useMemo(() => {
|
||||||
|
if (!showResults) return [];
|
||||||
|
const powerMin = requiredPower * 0.8;
|
||||||
|
const powerMax = requiredPower * 1.4;
|
||||||
|
const maxPrice = budget - installCost.min;
|
||||||
|
|
||||||
|
return products
|
||||||
|
.filter(p => {
|
||||||
|
if (p.category !== category) return false;
|
||||||
|
if (p.price === null || p.price > maxPrice) return false;
|
||||||
|
const moc = parseFloat(String(p.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||||||
|
return moc >= powerMin && moc <= powerMax;
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sortuj po tym jak blisko jest moc do wymaganej
|
||||||
|
const mocA = parseFloat(String(a.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||||||
|
const mocB = parseFloat(String(b.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||||||
|
return Math.abs(mocA - requiredPower) - Math.abs(mocB - requiredPower);
|
||||||
|
})
|
||||||
|
.slice(0, 6);
|
||||||
|
}, [showResults, products, requiredPower, category, budget, installCost.min]);
|
||||||
|
|
||||||
|
const handleFinish = () => setShowResults(true);
|
||||||
|
const handleReset = () => { setShowResults(false); setStep(0); };
|
||||||
|
|
||||||
|
if (showResults) {
|
||||||
|
return (
|
||||||
|
<main className="bg-slate-50 min-h-screen py-12">
|
||||||
|
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Podsumowanie */}
|
||||||
|
<div className="bg-white rounded-2xl border border-border p-6 mb-8">
|
||||||
|
<h1 className="text-2xl font-extrabold text-primary font-heading mb-4">
|
||||||
|
Dopasowane klimatyzatory
|
||||||
|
</h1>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: 'Wymagana moc', value: `${requiredPower} kW` },
|
||||||
|
{ label: 'Powierzchnia', value: `${m2} m²` },
|
||||||
|
{ label: 'Typ instalacji', value: rooms === 1 ? 'Split (1 pokój)' : `Multisplit (${rooms} pokoje)` },
|
||||||
|
{ label: 'Szac. montaż', value: `${installCost.min.toLocaleString('pl-PL')}–${installCost.max.toLocaleString('pl-PL')} zł` },
|
||||||
|
].map((s, i) => (
|
||||||
|
<div key={i} className="bg-slate-50 rounded-xl p-3">
|
||||||
|
<p className="text-xs text-text-muted mb-1">{s.label}</p>
|
||||||
|
<p className="text-base font-bold text-primary font-heading">{s.value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{results.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
|
{results.map(product => (
|
||||||
|
<div key={product.slug} className="bg-white rounded-2xl border border-border overflow-hidden hover:border-accent hover:shadow-md transition-all">
|
||||||
|
<div className="relative aspect-[4/3] bg-slate-50">
|
||||||
|
<Image
|
||||||
|
src={product.image}
|
||||||
|
alt={product.name}
|
||||||
|
fill
|
||||||
|
className="object-contain p-6"
|
||||||
|
unoptimized={product.image.startsWith('http')}
|
||||||
|
/>
|
||||||
|
{product.specs?.['Klasa energetyczna'] && (
|
||||||
|
<span className="absolute top-2 left-2 px-2 py-0.5 bg-green-500 text-white text-xs font-bold rounded">
|
||||||
|
{product.specs['Klasa energetyczna'].split('/')[0]}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
<p className="text-xs text-accent font-bold uppercase mb-1">{product.brand}</p>
|
||||||
|
<h3 className="text-sm font-bold text-primary font-heading leading-snug mb-3 line-clamp-2">
|
||||||
|
{product.name}
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
||||||
|
{product.specs?.['Moc chłodnicza'] && (
|
||||||
|
<span className="px-2 py-0.5 bg-blue-50 text-blue-700 text-xs rounded border border-blue-100">
|
||||||
|
{product.specs['Moc chłodnicza']} kW
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{product.specs?.['Hałas wewnątrz (dB)'] && (
|
||||||
|
<span className="px-2 py-0.5 bg-slate-50 text-text-muted text-xs rounded border border-border">
|
||||||
|
{product.specs['Hałas wewnątrz (dB)']} dB
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-extrabold text-primary font-heading leading-none">
|
||||||
|
{product.price?.toLocaleString('pl-PL')} zł
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-text-muted mt-0.5">+ montaż od {installCost.min.toLocaleString('pl-PL')} zł</p>
|
||||||
|
</div>
|
||||||
|
<Link href={`/sklep/${product.slug}`}>
|
||||||
|
<button className="flex items-center gap-1.5 px-3 py-2 bg-accent text-white rounded-xl text-xs font-bold hover:bg-accent/90 transition-colors">
|
||||||
|
Zobacz <ArrowRight className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white rounded-2xl border border-border p-12 text-center">
|
||||||
|
<p className="text-text-muted mb-4">
|
||||||
|
Nie znaleziono produktów dla podanych kryteriów. Spróbuj zwiększyć budżet lub zadzwoń do nas — dobierzemy ofertę indywidualnie.
|
||||||
|
</p>
|
||||||
|
<a href={`tel:${companyDetails.phoneRaw}`}>
|
||||||
|
<button className="flex items-center gap-2 px-6 py-3 bg-accent text-white rounded-xl font-bold mx-auto hover:bg-accent/90 transition-colors">
|
||||||
|
<Phone className="size-4" /> {companyDetails.phone}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-8 flex gap-4">
|
||||||
|
<button onClick={handleReset} className="flex items-center gap-2 px-5 py-3 bg-white border border-border rounded-xl text-sm font-semibold text-primary hover:border-accent transition-colors">
|
||||||
|
<RotateCcw className="size-4" /> Zacznij od nowa
|
||||||
|
</button>
|
||||||
|
<Link href="/sklep">
|
||||||
|
<button className="flex items-center gap-2 px-5 py-3 bg-white border border-border rounded-xl text-sm font-semibold text-primary hover:border-accent transition-colors">
|
||||||
|
Przeglądaj cały sklep <ArrowRight className="size-4" />
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="bg-slate-50 min-h-screen py-12">
|
||||||
|
<div className="max-w-2xl mx-auto px-4 sm:px-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-3xl font-extrabold text-primary font-heading mb-2">
|
||||||
|
Dobierz klimatyzator
|
||||||
|
</h1>
|
||||||
|
<p className="text-text-muted">Odpowiedz na 4 pytania — pokażemy Ci najlepsze opcje z naszej oferty</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pasek postępu */}
|
||||||
|
<div className="flex items-center gap-2 mb-8">
|
||||||
|
{STEPS.map((s, i) => (
|
||||||
|
<div key={s} className="flex-1 flex items-center gap-2">
|
||||||
|
<div className={`size-7 rounded-full flex items-center justify-center text-xs font-bold shrink-0 transition-all ${
|
||||||
|
i < step ? 'bg-green-500 text-white' : i === step ? 'bg-accent text-white' : 'bg-slate-200 text-text-muted'
|
||||||
|
}`}>
|
||||||
|
{i < step ? <Check className="size-3.5" /> : i + 1}
|
||||||
|
</div>
|
||||||
|
<span className={`text-xs font-medium hidden sm:block ${i === step ? 'text-accent' : 'text-text-muted'}`}>{s}</span>
|
||||||
|
{i < STEPS.length - 1 && <div className={`flex-1 h-0.5 ${i < step ? 'bg-green-500' : 'bg-slate-200'}`} />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Karty kroków */}
|
||||||
|
<div className="bg-white rounded-2xl border border-border p-6 md:p-8">
|
||||||
|
|
||||||
|
{/* Krok 0: Pomieszczenie */}
|
||||||
|
{step === 0 && (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="size-10 rounded-xl bg-blue-50 flex items-center justify-center">
|
||||||
|
<Home className="size-5 text-blue-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-primary font-heading">Pomieszczenie</h2>
|
||||||
|
<p className="text-xs text-text-muted">Metraż i warunki</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-semibold text-primary mb-3 block">
|
||||||
|
Powierzchnia pomieszczenia: <span className="text-accent">{m2} m²</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range" min={10} max={100} step={5} value={m2}
|
||||||
|
onChange={e => setM2(Number(e.target.value))}
|
||||||
|
className="w-full accent-accent"
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between text-xs text-text-muted mt-1">
|
||||||
|
<span>10 m²</span><span>100 m²</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 p-3 bg-blue-50 rounded-xl text-sm text-blue-700 font-medium">
|
||||||
|
Wymagana moc: ok. <strong>{requiredPower} kW</strong>
|
||||||
|
<span className="text-xs font-normal ml-2">(dla {m2} m²)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-semibold text-primary mb-3 block">Typ budynku</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{[
|
||||||
|
{ value: 'apartment', label: '🏢 Mieszkanie w bloku', sub: 'Kamienica, wieżowiec, blok' },
|
||||||
|
{ value: 'house', label: '🏠 Dom jednorodzinny', sub: 'Wolnostojący, szeregowiec, bliźniak' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setBuilding(o.value)}
|
||||||
|
className={`p-4 rounded-xl border-2 text-left transition-all ${building === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||||||
|
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||||||
|
<p className="text-xs text-text-muted">{o.sub}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{building === 'house' && (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-semibold text-primary mb-3 block">Typ przestrzeni</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{[
|
||||||
|
{ value: 'ground', label: '🏠 Parter' },
|
||||||
|
{ value: 'top', label: '🏠 Piętro / poddasze' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setFloor(o.value)}
|
||||||
|
className={`py-2.5 px-3 rounded-xl text-sm font-semibold border-2 transition-all ${floor === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||||||
|
{o.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{building === 'apartment' && (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-semibold text-primary mb-3 block">Piętro</label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{[
|
||||||
|
{ value: 'ground', label: 'Parter' },
|
||||||
|
{ value: 'low', label: '1-3 piętro' },
|
||||||
|
{ value: 'middle', label: '4-8 piętro' },
|
||||||
|
{ value: 'high', label: '9+ piętro' },
|
||||||
|
{ value: 'top', label: 'Ostatnie' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setFloor(o.value)}
|
||||||
|
className={`py-2 px-4 rounded-xl text-sm font-semibold border-2 transition-all ${floor === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||||||
|
{o.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-semibold text-primary mb-3 block">Ekspozycja okien</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{[
|
||||||
|
{ value: 'north', label: '🧭 Północ (chłodna)' },
|
||||||
|
{ value: 'east', label: '🧭 Wschód' },
|
||||||
|
{ value: 'west', label: '🧭 Zachód' },
|
||||||
|
{ value: 'south', label: '🧭 Południe (mocne słońce)' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setSun(o.value)}
|
||||||
|
className={`py-2.5 px-3 rounded-xl text-sm font-semibold border-2 transition-all ${sun === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||||||
|
{o.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Krok 1: Konfiguracja */}
|
||||||
|
{step === 1 && (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="size-10 rounded-xl bg-purple-50 flex items-center justify-center">
|
||||||
|
<Thermometer className="size-5 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-primary font-heading">Ile pomieszczeń?</h2>
|
||||||
|
<p className="text-xs text-text-muted">1 pomieszczenie = split, więcej = multisplit</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{[
|
||||||
|
{ value: 1, label: '1 pomieszczenie', sub: 'Klimatyzator split', icon: '🏠' },
|
||||||
|
{ value: 2, label: '2 pomieszczenia', sub: 'System multisplit 2+1', icon: '🏡' },
|
||||||
|
{ value: 3, label: '3 pomieszczenia', sub: 'System multisplit 3+1', icon: '🏘️' },
|
||||||
|
{ value: 4, label: '4-5 pomieszczeń', sub: 'System multisplit 4+1', icon: '🏗️' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setRooms(o.value)}
|
||||||
|
className={`p-4 rounded-xl border-2 text-left transition-all ${rooms === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||||||
|
<span className="text-2xl mb-2 block">{o.icon}</span>
|
||||||
|
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||||||
|
<p className="text-xs text-text-muted">{o.sub}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl text-xs text-text-muted">
|
||||||
|
{rooms === 1
|
||||||
|
? '✅ Klimatyzator split — jedna jednostka wewnętrzna + jedna zewnętrzna. Idealne do pojedynczego pomieszczenia.'
|
||||||
|
: `✅ System multisplit — jedna jednostka zewnętrzna obsługuje ${rooms} jednostki wewnętrzne. Ekonomiczne i estetyczne rozwiązanie dla całego domu.`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Krok 2: Instalacja */}
|
||||||
|
{step === 2 && (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="size-10 rounded-xl bg-orange-50 flex items-center justify-center">
|
||||||
|
<Zap className="size-5 text-orange-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-primary font-heading">Długość instalacji</h2>
|
||||||
|
<p className="text-xs text-text-muted">Rury i kable między jednostkami</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{[
|
||||||
|
{ value: 'short', label: 'Do 3 metrów', sub: 'Jednostka zewnętrzna tuż obok (balkon, taras)' },
|
||||||
|
{ value: 'medium', label: '3–6 metrów', sub: 'Typowa instalacja w bloku lub domu' },
|
||||||
|
{ value: 'long', label: '6–10 metrów', sub: 'Dłuższa trasa, np. przez kilka pomieszczeń' },
|
||||||
|
{ value: 'verylong', label: 'Powyżej 10 metrów', sub: 'Bardzo długa trasa, wymaga dodatkowych materiałów' },
|
||||||
|
].map(o => (
|
||||||
|
<button key={o.value} onClick={() => setPipes(o.value)}
|
||||||
|
className={`p-4 rounded-xl border-2 text-left transition-all ${pipes === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||||||
|
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||||||
|
<p className="text-xs text-text-muted">{o.sub}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-orange-50 rounded-xl text-xs text-orange-700">
|
||||||
|
Szacowany koszt montażu dla Twojej konfiguracji:
|
||||||
|
<strong className="ml-1">{installCost.min.toLocaleString('pl-PL')}–{installCost.max.toLocaleString('pl-PL')} zł</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Krok 3: Budżet */}
|
||||||
|
{step === 3 && (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="size-10 rounded-xl bg-green-50 flex items-center justify-center">
|
||||||
|
<ShoppingCart className="size-5 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-primary font-heading">Całkowity budżet</h2>
|
||||||
|
<p className="text-xs text-text-muted">Urządzenie + montaż łącznie</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-3">
|
||||||
|
<label className="text-sm font-semibold text-primary">Budżet całkowity</label>
|
||||||
|
<span className="text-lg font-extrabold text-accent font-heading">{budget.toLocaleString('pl-PL')} zł</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min={3000} max={25000} step={500} value={budget}
|
||||||
|
onChange={e => setBudget(Number(e.target.value))}
|
||||||
|
className="w-full accent-accent"
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between text-xs text-text-muted mt-1">
|
||||||
|
<span>3 000 zł</span><span>25 000 zł</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div className="bg-slate-50 rounded-xl p-3">
|
||||||
|
<p className="text-xs text-text-muted mb-1">Na urządzenie zostaje</p>
|
||||||
|
<p className="text-base font-bold text-primary font-heading">
|
||||||
|
ok. {Math.max(0, budget - installCost.min).toLocaleString('pl-PL')} zł
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-orange-50 rounded-xl p-3">
|
||||||
|
<p className="text-xs text-orange-700 mb-1">Szac. koszt montażu</p>
|
||||||
|
<p className="text-base font-bold text-orange-700 font-heading">
|
||||||
|
{installCost.min.toLocaleString('pl-PL')}–{installCost.max.toLocaleString('pl-PL')} zł
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-blue-50 rounded-xl text-xs text-blue-700">
|
||||||
|
💡 Podsumowanie: szukamy klimatyzatora <strong>{category === 'scienne' ? 'split ściennego' : 'multisplit'}</strong> o mocy ok. <strong>{requiredPower} kW</strong> w cenie do <strong>{Math.max(0, budget - installCost.min).toLocaleString('pl-PL')} zł</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Nawigacja */}
|
||||||
|
<div className="flex justify-between mt-8 pt-6 border-t border-border">
|
||||||
|
{step > 0 ? (
|
||||||
|
<button onClick={() => setStep(s => s - 1)}
|
||||||
|
className="flex items-center gap-2 px-4 py-2.5 border border-border rounded-xl text-sm font-semibold text-primary hover:border-accent transition-colors">
|
||||||
|
<ArrowLeft className="size-4" /> Wstecz
|
||||||
|
</button>
|
||||||
|
) : <div />}
|
||||||
|
|
||||||
|
{step < 3 ? (
|
||||||
|
<button onClick={() => setStep(s => s + 1)}
|
||||||
|
className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
||||||
|
Dalej <ArrowRight className="size-4" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button onClick={handleFinish}
|
||||||
|
className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
||||||
|
Pokaż dopasowane produkty <ArrowRight className="size-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -49,11 +49,6 @@ export function Header() {
|
|||||||
href: "/sklep?category=multisplit",
|
href: "/sklep?category=multisplit",
|
||||||
description: "Systemy wielopokojowe (2-5 pomieszczeń)",
|
description: "Systemy wielopokojowe (2-5 pomieszczeń)",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Przenośne",
|
|
||||||
href: "/sklep?category=przenosne",
|
|
||||||
description: "Mobilne chłodzenie bez stałej instalacji",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Kasetonowe i konsolowe",
|
label: "Kasetonowe i konsolowe",
|
||||||
href: "/sklep?category=kasetonowe",
|
href: "/sklep?category=kasetonowe",
|
||||||
|
|||||||
@@ -10,21 +10,21 @@ const acTypes = [
|
|||||||
description: 'Najpopularniejsze rozwiązanie do jednego pomieszczenia. Składa się z jednej jednostki wewnętrznej i jednej zewnętrznej.',
|
description: 'Najpopularniejsze rozwiązanie do jednego pomieszczenia. Składa się z jednej jednostki wewnętrznej i jednej zewnętrznej.',
|
||||||
icon: Wind,
|
icon: Wind,
|
||||||
subtypes: ['Ścienne', 'Konsolowe', 'Kasetonowe', 'Kanałowe'],
|
subtypes: ['Ścienne', 'Konsolowe', 'Kasetonowe', 'Kanałowe'],
|
||||||
link: '/sklep/klimatyzatory-split',
|
link: '/sklep?category=scienne',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Systemy MULTISPLIT',
|
title: 'Systemy MULTISPLIT',
|
||||||
description: 'Idealne do chłodzenia całego domu. Do jednej jednostki zewnętrznej można podłączyć od 2 do 5 jednostek wewnętrznych.',
|
description: 'Idealne do chłodzenia całego domu. Do jednej jednostki zewnętrznej można podłączyć od 2 do 5 jednostek wewnętrznych.',
|
||||||
icon: LayoutGrid,
|
icon: LayoutGrid,
|
||||||
subtypes: ['Dla 2 pomieszczeń', 'Dla 3 pomieszczeń', 'Dla 4-5 pomieszczeń', 'Mieszane typy jednostek'],
|
subtypes: ['Dla 2 pomieszczeń', 'Dla 3 pomieszczeń', 'Dla 4-5 pomieszczeń', 'Mieszane typy jednostek'],
|
||||||
link: '/sklep/klimatyzatory-multisplit',
|
link: '/sklep?category=multisplit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pozostałe rozwiązania',
|
title: 'Pozostałe rozwiązania',
|
||||||
description: 'Specjalistyczne urządzenia łączące funkcje chłodzenia z zaawansowanym grzaniem lub oczyszczaniem powietrza.',
|
description: 'Specjalistyczne urządzenia łączące funkcje chłodzenia z zaawansowanym grzaniem lub oczyszczaniem powietrza.',
|
||||||
icon: Sparkles,
|
icon: Sparkles,
|
||||||
subtypes: ['Z funkcją ogrzewania (do -25°C)', 'Z zaawansowanym oczyszczaczem', 'Klimatyzatory przenośne', 'Klimatyzatory bez jednostki zewn.'],
|
subtypes: ['Z funkcją ogrzewania (do -25°C)', 'Z zaawansowanym oczyszczaczem', 'Klimatyzatory przenośne', 'Klimatyzatory bez jednostki zewn.'],
|
||||||
link: '/sklep/inne',
|
link: '/sklep',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
price: product.price,
|
price: product.price,
|
||||||
});
|
});
|
||||||
setAdded(true);
|
setAdded(true);
|
||||||
setTimeout(() => setAdded(false), 1500);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCompare = (e: React.MouseEvent) => {
|
const handleCompare = (e: React.MouseEvent) => {
|
||||||
@@ -161,20 +160,22 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{product.price !== null ? (
|
{product.price !== null ? (
|
||||||
|
added ? (
|
||||||
|
<Link
|
||||||
|
href="/koszyk"
|
||||||
|
className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-green-500 text-white hover:bg-green-600 transition-colors shrink-0"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<ShoppingCart className="size-3.5" /> Zobacz koszyk
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={handleAddToCart}
|
onClick={handleAddToCart}
|
||||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold transition-all shrink-0 ${
|
className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-accent text-white hover:bg-accent/90 transition-colors shrink-0"
|
||||||
added
|
|
||||||
? 'bg-green-500 text-white'
|
|
||||||
: 'bg-accent text-white hover:bg-accent/90'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{added ? (
|
<ShoppingCart className="size-3.5" /> Do koszyka
|
||||||
<><Check className="size-3.5" /> Dodano</>
|
|
||||||
) : (
|
|
||||||
<><ShoppingCart className="size-3.5" /> Do koszyka</>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<Link href="/kontakt">
|
<Link href="/kontakt">
|
||||||
<button className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-primary text-white hover:bg-primary/90 transition-colors shrink-0">
|
<button className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-primary text-white hover:bg-primary/90 transition-colors shrink-0">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { ChevronDown, ChevronUp, Phone, X } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Phone, X } from 'lucide-react';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Product } from '@/content/products';
|
import { Product } from '@/content/products';
|
||||||
import { ProductCard } from './ProductCard';
|
import { ProductCard } from './ProductCard';
|
||||||
@@ -10,8 +11,6 @@ import { companyDetails } from '@/content/company';
|
|||||||
interface ProductsGridProps {
|
interface ProductsGridProps {
|
||||||
products: Product[];
|
products: Product[];
|
||||||
brands: string[];
|
brands: string[];
|
||||||
initialSearch?: string;
|
|
||||||
initialBrand?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PER_PAGE = 24;
|
const PER_PAGE = 24;
|
||||||
@@ -76,10 +75,17 @@ function Accordion({ title, children, defaultOpen = true }: AccordionProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProductsGrid({ products, brands, initialSearch, initialBrand }: ProductsGridProps) {
|
export function ProductsGrid({ products, brands }: ProductsGridProps) {
|
||||||
const [category, setCategory] = useState<'all' | 'scienne' | 'multisplit'>('all');
|
const searchParams = useSearchParams();
|
||||||
const [selectedBrands, setSelectedBrands] = useState<string[]>(initialBrand ? [initialBrand] : []);
|
const urlSearch = searchParams.get('search') || '';
|
||||||
const [searchQuery, setSearchQuery] = useState(initialSearch || '');
|
const urlBrand = searchParams.get('brand') || '';
|
||||||
|
const urlCategory = searchParams.get('category') || 'all';
|
||||||
|
|
||||||
|
const [category, setCategory] = useState<'all' | 'scienne' | 'multisplit' | 'kasetonowe' | 'kanalowe' | 'konsolowe' | 'podlogowe' | 'jednostka-scienna'>(
|
||||||
|
urlCategory as 'all' | 'scienne' | 'multisplit' | 'kasetonowe' | 'kanalowe' | 'konsolowe' | 'podlogowe' | 'jednostka-scienna'
|
||||||
|
);
|
||||||
|
const [selectedBrands, setSelectedBrands] = useState<string[]>(urlBrand ? [urlBrand] : []);
|
||||||
|
const [searchQuery, setSearchQuery] = useState(urlSearch);
|
||||||
const [priceMin, setPriceMin] = useState('');
|
const [priceMin, setPriceMin] = useState('');
|
||||||
const [priceMax, setPriceMax] = useState('');
|
const [priceMax, setPriceMax] = useState('');
|
||||||
const [selectedRooms, setSelectedRooms] = useState<string[]>([]);
|
const [selectedRooms, setSelectedRooms] = useState<string[]>([]);
|
||||||
@@ -89,6 +95,17 @@ export function ProductsGrid({ products, brands, initialSearch, initialBrand }:
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchQuery(urlSearch);
|
||||||
|
setSelectedBrands(urlBrand ? [urlBrand] : []);
|
||||||
|
if (urlCategory !== 'all') {
|
||||||
|
setCategory(urlCategory as 'scienne' | 'multisplit' | 'kasetonowe' | 'kanalowe' | 'konsolowe' | 'podlogowe' | 'jednostka-scienna');
|
||||||
|
} else {
|
||||||
|
setCategory('all');
|
||||||
|
}
|
||||||
|
setPage(1);
|
||||||
|
}, [urlSearch, urlBrand, urlCategory]);
|
||||||
|
|
||||||
const toggleBrand = (b: string) => {
|
const toggleBrand = (b: string) => {
|
||||||
setSelectedBrands(prev => prev.includes(b) ? prev.filter(x => x !== b) : [...prev, b]);
|
setSelectedBrands(prev => prev.includes(b) ? prev.filter(x => x !== b) : [...prev, b]);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
@@ -207,8 +224,11 @@ export function ProductsGrid({ products, brands, initialSearch, initialBrand }:
|
|||||||
<ul className="flex flex-col gap-2">
|
<ul className="flex flex-col gap-2">
|
||||||
{[
|
{[
|
||||||
{ val: 'all', label: 'Wszystkie' },
|
{ val: 'all', label: 'Wszystkie' },
|
||||||
{ val: 'scienne', label: 'Klimatyzacja ścienna' },
|
{ val: 'scienne', label: 'Ścienne split' },
|
||||||
{ val: 'multisplit', label: 'Klimatyzacja Multisplit' },
|
{ val: 'multisplit', label: 'Multisplit' },
|
||||||
|
{ val: 'kasetonowe', label: 'Kasetonowe' },
|
||||||
|
{ val: 'konsolowe', label: 'Konsolowe' },
|
||||||
|
{ val: 'kanalowe', label: 'Kanałowe' },
|
||||||
].map(opt => (
|
].map(opt => (
|
||||||
<li key={opt.val}>
|
<li key={opt.val}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
+5184
-1359
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
transcript_path = '/Users/michalcukrowski/.gemini/antigravity-ide/brain/1d7cf0f7-087b-4c82-94a5-07c51084a559/.system_generated/logs/transcript_full.jsonl'
|
||||||
|
|
||||||
|
with open(transcript_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
data = json.loads(line)
|
||||||
|
if data.get('type') == 'USER_INPUT':
|
||||||
|
content = data.get('content', '')
|
||||||
|
if 'export interface Product {' in content and len(content) > 100000:
|
||||||
|
print(f"Found large user input of length {len(content)}")
|
||||||
|
# Extract the file content. It probably starts with "export interface Product {"
|
||||||
|
# or there is some text before it.
|
||||||
|
idx = content.find('export interface Product {')
|
||||||
|
if idx != -1:
|
||||||
|
file_content = content[idx:]
|
||||||
|
with open('content/products.ts', 'w', encoding='utf-8') as out:
|
||||||
|
out.write(file_content)
|
||||||
|
print("Wrote to content/products.ts successfully!")
|
||||||
|
break
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
transcript_path = '/Users/michalcukrowski/.gemini/antigravity-ide/brain/1d7cf0f7-087b-4c82-94a5-07c51084a559/.system_generated/logs/transcript_full.jsonl'
|
||||||
|
|
||||||
|
with open(transcript_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
data = json.loads(line)
|
||||||
|
if data.get('type') == 'USER_INPUT':
|
||||||
|
content = data.get('content', '')
|
||||||
|
print(f"USER_INPUT len: {len(content)}")
|
||||||
|
if len(content) > 50000:
|
||||||
|
with open('large_input.txt', 'w', encoding='utf-8') as out:
|
||||||
|
out.write(content)
|
||||||
|
print("Saved large input to large_input.txt")
|
||||||
Reference in New Issue
Block a user