From c4f6cf3f1d863c373e7816afe5b5146cbefbab93 Mon Sep 17 00:00:00 2001 From: oskar Date: Fri, 31 Jul 2026 18:46:08 +0200 Subject: [PATCH] =?UTF-8?q?Karty=20HTML=20koncept=C3=B3w:=20gen=5Fpages.py?= =?UTF-8?q?=20+=20katalog=20pages/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Graf pokazywał strukturę, ale treść (ceny, dostępność, plusy/minusy) siedziała tylko w panelu szczegółów viewera. gen_pages.py renderuje każdy koncept do pages/.html: metadane frontmattera jako tabelka (wszystkie pola, też polskie rozszerzenia), body poniżej, plus pages/index.html z listą kart. Renderer: python-markdown, jeśli jest w systemie; tutaj go nie ma, więc działa fallback na samej bibliotece standardowej (nagłówki, akapity, tabele GFM, listy z - [ ], cytaty, kod, hr, inline bold/kursywa/kod/linki). Linki miedzy konceptami przepisane na względne .html, linki zewnętrzne bez zmian. Pliki zarezerwowane (index.md, log.md) nie są konceptami (§3.1) i nie mają kart — linki do nich prowadzą do listy kart. Pliki .md nietknięte. Co-Authored-By: Claude Opus 5 --- gen_pages.py | 549 ++++++++++++++++++++ pages/index.html | 73 +++ pages/oferty/alpenparks-rehrenberg.html | 83 +++ pages/oferty/alpenparks-sonnleiten.html | 83 +++ pages/oferty/dom-dla-grupy.html | 85 +++ pages/oferty/glemm-lodge.html | 88 ++++ pages/oferty/kristall-schattbergxpress.html | 83 +++ pages/oferty/sportchalet-viehhofen.html | 84 +++ pages/region.html | 92 ++++ pages/szablon-maila.html | 86 +++ pages/wyjazd.html | 81 +++ 11 files changed, 1387 insertions(+) create mode 100644 gen_pages.py create mode 100644 pages/index.html create mode 100644 pages/oferty/alpenparks-rehrenberg.html create mode 100644 pages/oferty/alpenparks-sonnleiten.html create mode 100644 pages/oferty/dom-dla-grupy.html create mode 100644 pages/oferty/glemm-lodge.html create mode 100644 pages/oferty/kristall-schattbergxpress.html create mode 100644 pages/oferty/sportchalet-viehhofen.html create mode 100644 pages/region.html create mode 100644 pages/szablon-maila.html create mode 100644 pages/wyjazd.html diff --git a/gen_pages.py b/gen_pages.py new file mode 100644 index 0000000..0224b38 --- /dev/null +++ b/gen_pages.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +"""Renderuje karty HTML konceptów bundle'a do katalogu pages/. + +Graf (viz.html) pokazuje strukturę; karty pokazują treść — ceny, dostępność, +plusy/minusy, listy do zrobienia. Węzeł w grafie linkuje do karty przez pole +`resource` (patrz gen_viz.py). + +Renderer markdown: używa `python-markdown`, jeśli jest zainstalowany +(rozszerzenia tables + fenced_code + sane_lists); w przeciwnym razie własnego +renderera na samej bibliotece standardowej — obsługuje nagłówki, akapity, +tabele GFM, listy (w tym `- [ ]`), cytaty, bloki kodu, linie poziome oraz +inline: **bold**, *kursywa*, `kod`, [linki](...). Który tor zadziałał, skrypt +wypisuje przy uruchomieniu. + +Pliki .md NIE są modyfikowane — to czysty generator, tak samo jak gen_viz.py. +Pliki zarezerwowane (index.md, log.md) nie są konceptami (§3.1) i nie +dostają kart; linki do nich prowadzą do pages/index.html. + +Uruchomienie: python3 gen_pages.py +""" + +from __future__ import annotations + +import html +import posixpath +import re +import shutil +import sys +from dataclasses import dataclass +from pathlib import Path + +import yaml + +BUNDLE_ROOT = Path(__file__).resolve().parent +PAGES_DIRNAME = "pages" +PAGES_DIR = BUNDLE_ROOT / PAGES_DIRNAME +BUNDLE_NAME = "Ski 2027 — Skicircus Saalbach" +RESERVED = {"index.md", "log.md"} + +try: # renderer preferowany, jeśli dostępny w systemie + import markdown as _markdown_lib +except ModuleNotFoundError: + _markdown_lib = None + +RENDERER = "python-markdown" if _markdown_lib else "fallback (biblioteka standardowa)" + + +# --- model ------------------------------------------------------------ + + +@dataclass +class Concept: + id: str # np. "oferty/glemm-lodge" + path: Path + frontmatter: dict + body: str + + @property + def page(self) -> str: + """Ścieżka karty względem katalogu pages/.""" + return f"{self.id}.html" + + @property + def title(self) -> str: + return str(self.frontmatter.get("title") or self.id) + + +def load_concepts() -> list[Concept]: + concepts: list[Concept] = [] + for path in sorted(BUNDLE_ROOT.rglob("*.md")): + if ".git" in path.parts or path.name in RESERVED: + continue + text = path.read_text(encoding="utf-8") + frontmatter: dict = {} + body = text + if text.startswith("---\n"): + end = text.find("\n---", 3) + if end != -1: + frontmatter = yaml.safe_load(text[4:end]) or {} + body = text[end + 4 :] + concepts.append( + Concept( + id=path.relative_to(BUNDLE_ROOT).with_suffix("").as_posix(), + path=path, + frontmatter=frontmatter, + body=body.lstrip("\n"), + ) + ) + return concepts + + +# --- linki ------------------------------------------------------------ + +_LINK_TARGET_RE = re.compile(r"\]\(([^)\s]+\.md)((?:#[^)\s]*)?)\)") + + +def rewrite_links(body: str, source: Concept, pages: dict[str, str]) -> str: + """Linki do plików .md → względne ścieżki do kart. External bez zmian.""" + here = posixpath.dirname(source.page) + + def repl(match: re.Match) -> str: + target, fragment = match.group(1), match.group(2) + if "://" in target: + return match.group(0) + if target.startswith("/"): + rel = target[1:] + else: + rel = posixpath.normpath(posixpath.join(posixpath.dirname(source.id), target)) + concept_id = rel[:-3] if rel.endswith(".md") else rel + if concept_id in pages: + dest = pages[concept_id] + elif posixpath.basename(rel) in RESERVED: + dest = "index.html" # index.md / log.md nie mają kart + else: + return match.group(0) + return "](" + posixpath.relpath(dest, here or ".") + fragment + ")" + + return _LINK_TARGET_RE.sub(repl, body) + + +# --- renderer markdown: fallback na stdlib ----------------------------- + +_CODE_RE = re.compile(r"`([^`]+)`") +_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)\s]+)\)") +_BOLD_RE = re.compile(r"\*\*(.+?)\*\*", re.S) +_ITALIC_STAR_RE = re.compile(r"(? str: + codes: list[str] = [] + + def stash(match: re.Match) -> str: + codes.append(match.group(1)) + return f"\x00c{len(codes) - 1}\x00" + + text = _CODE_RE.sub(stash, text) + text = html.escape(text, quote=False) + + def link(match: re.Match) -> str: + label, href = match.group(1), match.group(2) + attrs = "" + if "://" in href or href.startswith("mailto:"): + attrs = ' target="_blank" rel="noopener" class="ext"' + return f'{label}' + + text = _LINK_RE.sub(link, text) + text = _BOLD_RE.sub(r"\1", text) + text = _ITALIC_STAR_RE.sub(r"\1", text) + text = _ITALIC_UNDER_RE.sub(r"\1", text) + + for i, code in enumerate(codes): + text = text.replace(f"\x00c{i}\x00", f"{html.escape(code, quote=False)}") + return text + + +def _alignments(sep: str) -> list[str]: + out = [] + for cell in _cells(sep): + left, right = cell.startswith(":"), cell.endswith(":") + out.append("center" if left and right else "right" if right else "left") + return out + + +def _cells(row: str) -> list[str]: + return [c.strip() for c in row.strip().strip("|").split("|")] + + +def _starts_block(line: str) -> bool: + s = line.strip() + return ( + not s + or bool(_HEADING_RE.match(s)) + or bool(_HR_RE.match(s)) + or s.startswith(("```", ">")) + or bool(_LIST_RE.match(line)) + ) + + +def render_blocks(text: str) -> str: + lines = text.split("\n") + out: list[str] = [] + i = 0 + while i < len(lines): + line = lines[i] + stripped = line.strip() + + if not stripped: + i += 1 + continue + + if stripped.startswith("```"): + i += 1 + code: list[str] = [] + while i < len(lines) and not lines[i].strip().startswith("```"): + code.append(lines[i]) + i += 1 + i += 1 + out.append(f"
{html.escape(chr(10).join(code), quote=False)}
") + continue + + heading = _HEADING_RE.match(stripped) + if heading: + level = len(heading.group(1)) + out.append(f"{render_inline(heading.group(2))}") + i += 1 + continue + + if _HR_RE.match(stripped): + out.append("
") + i += 1 + continue + + if "|" in stripped and i + 1 < len(lines) and _TABLE_SEP_RE.match(lines[i + 1]): + header = _cells(stripped) + align = _alignments(lines[i + 1]) + i += 2 + rows = [] + while i < len(lines) and "|" in lines[i] and lines[i].strip(): + rows.append(_cells(lines[i])) + i += 1 + head = "".join( + f'' + f"{render_inline(c)}" + for n, c in enumerate(header) + ) + body = "".join( + "" + + "".join( + f'' + f"{render_inline(c)}" + for n, c in enumerate(row) + ) + + "" + for row in rows + ) + out.append( + f'
{head}' + f"{body}
" + ) + continue + + if stripped.startswith(">"): + quoted = [] + while i < len(lines) and lines[i].strip().startswith(">"): + quoted.append(lines[i].strip()[1:].lstrip()) + i += 1 + out.append(f"
{render_blocks(chr(10).join(quoted))}
") + continue + + item = _LIST_RE.match(line) + if item: + ordered = item.group(1)[0].isdigit() + items: list[str] = [] + while i < len(lines): + current = lines[i] + match = _LIST_RE.match(current) + if match: + items.append(match.group(2)) + i += 1 + elif current.startswith((" ", "\t")) and current.strip() and items: + items[-1] += " " + current.strip() + i += 1 + else: + break + rendered = [] + for raw in items: + task = _TASK_RE.match(raw) + if task: + checked = " checked" if task.group(1).lower() == "x" else "" + rendered.append( + f'
  • ' + f"{render_inline(task.group(2))}
  • " + ) + else: + rendered.append(f"
  • {render_inline(raw)}
  • ") + tag = "ol" if ordered else "ul" + out.append(f"<{tag}>{''.join(rendered)}") + continue + + paragraph = [stripped] + i += 1 + while i < len(lines) and not _starts_block(lines[i]): + if "|" in lines[i] and i + 1 < len(lines) and _TABLE_SEP_RE.match(lines[i + 1]): + break + paragraph.append(lines[i].strip()) + i += 1 + out.append(f"

    {render_inline(' '.join(paragraph))}

    ") + + return "\n".join(out) + + +def md_to_html(text: str) -> str: + if _markdown_lib: + return _markdown_lib.markdown( + text, extensions=["tables", "fenced_code", "sane_lists"] + ) + return render_blocks(text) + + +# --- szablon ---------------------------------------------------------- + +CSS = """ +:root { color-scheme: light dark; --fg:#0f172a; --muted:#64748b; --bg:#ffffff; + --line:#e2e8f0; --accent:#0d9488; --chip:#f1f5f9; --quote:#f8fafc; } +@media (prefers-color-scheme: dark) { + :root { --fg:#e2e8f0; --muted:#94a3b8; --bg:#0f172a; --line:#334155; + --accent:#2dd4bf; --chip:#1e293b; --quote:#1e293b; } +} +* { box-sizing: border-box; } +body { margin: 0 auto; padding: 1.25rem 1rem 4rem; max-width: 46rem; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + font-size: 17px; line-height: 1.55; color: var(--fg); background: var(--bg); + -webkit-text-size-adjust: 100%; } +nav { font-size: .85rem; margin-bottom: 1.25rem; } +nav a { color: var(--muted); } +h1 { font-size: 1.55rem; line-height: 1.25; margin: 0 0 .35rem; } +h2 { font-size: 1.2rem; margin: 2rem 0 .5rem; padding-top: .5rem; + border-top: 1px solid var(--line); } +h3 { font-size: 1.02rem; margin: 1.4rem 0 .4rem; } +p, ul, ol { margin: .6rem 0; } +ul, ol { padding-left: 1.3rem; } +li { margin: .25rem 0; } +li.task { list-style: none; margin-left: -1.3rem; } +li.task input { margin-right: .45rem; } +a { color: var(--accent); } +a.ext::after { content: " ↗"; font-size: .8em; color: var(--muted); } +code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em; + background: var(--chip); padding: .1em .35em; border-radius: 4px; } +pre { background: var(--chip); padding: .75rem; border-radius: 6px; overflow-x: auto; } +pre code { background: none; padding: 0; } +blockquote { margin: .9rem 0; padding: .6rem .9rem; background: var(--quote); + border-left: 3px solid var(--accent); border-radius: 0 6px 6px 0; } +blockquote p { margin: .2rem 0; } +hr { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; } +.table-wrap { overflow-x: auto; margin: .8rem 0; -webkit-overflow-scrolling: touch; } +table { border-collapse: collapse; width: 100%; font-size: .92rem; } +th, td { border: 1px solid var(--line); padding: .4rem .55rem; vertical-align: top; } +th { background: var(--chip); font-weight: 600; } +.chip { display: inline-block; background: var(--chip); color: var(--muted); + font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; + padding: .15rem .5rem; border-radius: 999px; } +.lead { color: var(--muted); margin: .35rem 0 1.25rem; } +.meta { margin: 0 0 1.5rem; } +.meta summary { cursor: pointer; font-size: .85rem; color: var(--muted); + padding: .3rem 0; } +.meta table { font-size: .85rem; } +.meta th { width: 38%; text-align: left; font-weight: 500; word-break: break-word; } +.cards { list-style: none; padding: 0; margin: 0; } +.cards li { border: 1px solid var(--line); border-radius: 8px; padding: .8rem .9rem; + margin: .6rem 0; } +.cards a { font-weight: 600; text-decoration: none; } +.cards p { margin: .3rem 0 0; font-size: .9rem; color: var(--muted); } +footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--line); + font-size: .78rem; color: var(--muted); } +""" + + +def _page_shell(title: str, depth: int, content: str) -> str: + up = "../" * depth + return f""" + + + + +{html.escape(title)} — {html.escape(BUNDLE_NAME)} + + + + +{content} +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + +""" + + +def _meta_table(frontmatter: dict) -> str: + rows = [] + for key, value in frontmatter.items(): + if isinstance(value, list): + shown = ", ".join(_link_value(str(v)) for v in value) + else: + shown = _link_value(str(value)) + rows.append( + f"{html.escape(str(key))}{shown}" + ) + return ( + '
    Metadane (frontmatter)' + f'
    {"".join(rows)}
    ' + "
    " + ) + + +_URLISH_RE = re.compile( + r"^(https?://\S+|(?:[a-z0-9][a-z0-9-]*\.)+(?:at|com|pl|de|eu|net|org|io|info))$" +) + + +def _link_value(value: str) -> str: + """Wartość frontmattera; adresy robi klikalnymi, resztę tylko escape'uje.""" + escaped = html.escape(value, quote=False) + if _URLISH_RE.match(value.strip()): + href = value.strip() + if "://" not in href: + href = "https://" + href + return f'{escaped}' + if value.strip().startswith("mailto:") or "@" in value and " " not in value.strip(): + return f'{escaped}' + return escaped + + +def render_page(concept: Concept, pages: dict[str, str]) -> str: + body = rewrite_links(concept.body, concept, pages) + rendered = md_to_html(body) + + # Pierwszy nagłówek H1 body dubluje tytuł karty — awansujemy go na

    + # strony zamiast dokładać drugi. Sam `title` i tak jest w tabelce metadanych. + match = re.match(r"\s*

    (.*?)

    ", rendered, re.S) + if match: + headline = match.group(1) + rendered = rendered[match.end() :] + else: + headline = html.escape(concept.title) + + frontmatter = concept.frontmatter + chip = html.escape(str(frontmatter.get("type") or "—")) + description = str(frontmatter.get("description") or "") + lead = f'

    {html.escape(description)}

    ' if description else "" + + content = ( + f'{chip}\n' + f"

    {headline}

    \n{lead}\n" + f"{_meta_table(frontmatter)}\n{rendered}" + ) + return _page_shell(concept.title, concept.id.count("/"), content) + + +def render_index(concepts: list[Concept]) -> str: + groups: list[tuple[str, list[Concept]]] = [ + ("Wyjazd i wiedza ogólna", [c for c in concepts if "/" not in c.id]), + ("Oferty noclegowe", [c for c in concepts if c.id.startswith("oferty/")]), + ] + sections = [] + for heading, items in groups: + if not items: + continue + cards = [] + for concept in items: + description = str(concept.frontmatter.get("description") or "") + status = concept.frontmatter.get("status_ceny") + badge = ( + f'{html.escape(str(status).split("—")[0].strip())} ' + if status + else "" + ) + cards.append( + f'
  • ' + f"{html.escape(concept.title)} " + f'{badge}{html.escape(str(concept.frontmatter.get("type") or "—"))}' + f"

    {html.escape(description)}

  • " + ) + sections.append(f"

    {heading}

    \n
      {''.join(cards)}
    ") + + content = ( + f"

    {html.escape(BUNDLE_NAME)}

    \n" + '

    Karty konceptów bundle\'a — pełna treść każdego dokumentu ' + "(ceny, dostępność, plusy/minusy). Struktura powiązań: " + 'graf.

    \n' + "\n".join(sections) + ) + return _page_shell("Karty", 0, content) + + +# --- raport braków ---------------------------------------------------- + + +def missing_data(concepts: list[Concept]) -> list[tuple[str, list[str]]]: + """Czego brakuje w ofertach — bez zgadywania, tylko z tego, co jest.""" + report = [] + for concept in concepts: + if not str(concept.frontmatter.get("type", "")).startswith("oferta"): + continue + gaps = [] + status = str(concept.frontmatter.get("status_ceny") or "") + if not status: + gaps.append("brak pola `status_ceny`") + elif "SZACUNEK" in status.upper(): + gaps.append(f"cena niepotwierdzona ({status})") + # Danymi o dostępności jest osobna sekcja (jak w glemm-lodge), a nie + # samo słowo w tekście — "duża dostępność" w opisie się nie liczy. + if not re.search(r"^#+\s*.*dostępnoś", concept.body, re.IGNORECASE | re.M): + gaps.append("brak danych o dostępności terminu 16–23.01.2027") + open_todos = len(re.findall(r"^\s*[-*]\s+\[ \]", concept.body, re.M)) + if open_todos: + gaps.append(f"otwartych pozycji „Do zrobienia”: {open_todos}") + if gaps: + report.append((concept.id, gaps)) + return report + + +# --- main ------------------------------------------------------------- + + +def main() -> int: + concepts = load_concepts() + pages = {c.id: c.page for c in concepts} + + if PAGES_DIR.exists(): + shutil.rmtree(PAGES_DIR) + PAGES_DIR.mkdir(parents=True) + + written = [] + for concept in concepts: + out = PAGES_DIR / concept.page + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(render_page(concept, pages), encoding="utf-8") + written.append(out) + + index = PAGES_DIR / "index.html" + index.write_text(render_index(concepts), encoding="utf-8") + written.append(index) + + print(f"Renderer markdown: {RENDERER}") + print(f"Zapisano {len(written)} plików w {PAGES_DIR}:") + for path in written: + size = path.stat().st_size / 1024 + print(f" {path.relative_to(BUNDLE_ROOT)} ({size:.1f} KiB)") + + gaps = missing_data(concepts) + print() + if gaps: + print(f"Oferty z brakami w danych ({len(gaps)}) — do uzupełnienia ręcznie:") + for concept_id, items in gaps: + print(f" ! {concept_id}") + for item in items: + print(f" – {item}") + else: + print("Braków w danych ofert nie wykryto.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pages/index.html b/pages/index.html new file mode 100644 index 0000000..091255e --- /dev/null +++ b/pages/index.html @@ -0,0 +1,73 @@ + + + + + +Karty — Ski 2027 — Skicircus Saalbach + + + + +

    Ski 2027 — Skicircus Saalbach

    +

    Karty konceptów bundle'a — pełna treść każdego dokumentu (ceny, dostępność, plusy/minusy). Struktura powiązań: graf.

    +

    Wyjazd i wiedza ogólna

    + +

    Oferty noclegowe

    +
    • AlpenParks Apartments Rehrenberg SZACUNEK oferta

      Najtańszy nowy obiekt na liście — przestronne apartamenty z prywatnymi saunami w Viehhofen, wariant rezerwowy o mieszanych opiniach, PLAN C.

    • AlpenParks Hotel & Apartment Sonnleiten SZACUNEK oferta

      Duży aparthotel ~2 min pieszo od stoku w Saalbach, z basenem i sauną w cenie — największa szansa na 4 wolne apartamenty w jednym tygodniu.

    • Duży dom wakacyjny 12–14 os. (do wyszukania) SZACUNEK oferta-koncept

      Koncept wynajęcia jednego domu wakacyjnego dla całej grupy zamiast czterech osobnych apartamentów — klimat chaletowy w połowie ceny Glemm Lodge.

    • Glemm Lodge Apartments ⭐⭐⭐⭐ ZWERYFIKOWANE (cennik + kalendarz obiektu, stan 31.07.2026) oferta

      Jedyna oferta z w pełni zweryfikowanym cennikiem (~4 370 €/rodzinę) — odrzucona budżetowo, służy jako punkt odniesienia cenowego.

    • Appartementhaus Kristall at SchattbergXpress SZACUNEK oferta

      Apartamenty 50 m od gondoli Schattberg X-press w centrum Saalbach — najlepsza lokalizacja w rozważanym budżecie, PLAN A.

    • Sportchalet Viehhofen SZACUNEK oferta

      Kameralny obiekt holenderskich gospodarzy w Viehhofen z oceną 5.0 — najlepszy stosunek ceny do jakości, PLAN B.

    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/alpenparks-rehrenberg.html b/pages/oferty/alpenparks-rehrenberg.html new file mode 100644 index 0000000..3560d35 --- /dev/null +++ b/pages/oferty/alpenparks-rehrenberg.html @@ -0,0 +1,83 @@ + + + + + +AlpenParks Apartments Rehrenberg — Ski 2027 — Skicircus Saalbach + + + + +oferta +

    AlpenParks Rehrenberg Viehhofen — PLAN C

    +

    Najtańszy nowy obiekt na liście — przestronne apartamenty z prywatnymi saunami w Viehhofen, wariant rezerwowy o mieszanych opiniach, PLAN C.

    +
    Metadane (frontmatter)
    typeoferta
    titleAlpenParks Apartments Rehrenberg
    descriptionNajtańszy nowy obiekt na liście — przestronne apartamenty z prywatnymi saunami w Viehhofen, wariant rezerwowy o mieszanych opiniach, PLAN C.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, viehhofen, plan-c
    idalpenparks-rehrenberg
    nazwaAlpenParks Apartments Rehrenberg
    operatorAlpenParks
    miejscowoscViehhofen
    gps47.3662, 12.7317
    odleglosc_od_wyciagu5–10 min pieszo do gondoli zellamseeXpress
    ocena4.2/5 (Google) — opinie mieszane
    uklad_dla_grupy4 osobne apartamenty
    cena_za_apartament_tydzien~1 100–1 500 € (szacunek, styczeń poza feriami)
    koszt_rodzina_2p2~1 100–1 500 €
    koszt_rodzina_2p1~950–1 250 €
    status_cenySZACUNEK — silnik online na alpenparks.at
    priorytetPLAN C (rezerwowy)
    wwwhttps://www.alpenparks.at
    ostatnia_aktualizacja2026-07-31
    + +

    W skrócie

    +

    Najtańszy "nowy" obiekt na liście: przestronne, nowoczesne apartamenty z prywatnymi saunami w Viehhofen. Traktowany jako rezerwa — cena kusi, ale opinie są mieszane i logistyka na stok słabsza niż u konkurencji.

    +

    Fakty

    +
    • Nowe budownictwo, duże apartamenty, część z własną sauną.
    • 5–10 min pieszo do gondoli zellamseeXpress (strona Zell am See); do Skicircusu skibusem.
    • Rezerwacja online przez alpenparks.at.
    +

    Układ dla naszej grupy (14 os.)

    +
    RodzinaSzacunkowy koszt/tydzień
    2× rodzina 2+2~1 100–1 500 €
    2× rodzina 2+1~950–1 250 €
    +

    Plusy / minusy

    +

    + najniższa cena za nowy standard; prywatne sauny; duża dostępność (spory obiekt); pełna rezerwacja online.

    +

    powtarzające się w recenzjach zastrzeżenia do skibusa (mały, zatłoczony rano) i obsługi/organizacji — przed rezerwacją przeczytać świeże opinie z zimy 25/26; Viehhofen bez wieczornego życia; sumarycznie gorszy pakiet niż Sportchalet za podobne pieniądze.

    +

    Do zrobienia

    +
    • Tylko jeśli plany A i B nie wypalą: sprawdzić ceny datami 16–23.01.2027.
    • Zweryfikować najnowsze opinie (Booking/Google, filtr: zima 2026).
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/alpenparks-sonnleiten.html b/pages/oferty/alpenparks-sonnleiten.html new file mode 100644 index 0000000..18f798f --- /dev/null +++ b/pages/oferty/alpenparks-sonnleiten.html @@ -0,0 +1,83 @@ + + + + + +AlpenParks Hotel & Apartment Sonnleiten — Ski 2027 — Skicircus Saalbach + + + + +oferta +

    AlpenParks Sonnleiten Saalbach

    +

    Duży aparthotel ~2 min pieszo od stoku w Saalbach, z basenem i sauną w cenie — największa szansa na 4 wolne apartamenty w jednym tygodniu.

    +
    Metadane (frontmatter)
    typeoferta
    titleAlpenParks Hotel & Apartment Sonnleiten
    descriptionDuży aparthotel ~2 min pieszo od stoku w Saalbach, z basenem i sauną w cenie — największa szansa na 4 wolne apartamenty w jednym tygodniu.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, saalbach, aparthotel
    idalpenparks-sonnleiten
    nazwaAlpenParks Hotel & Apartment Sonnleiten
    operatorAlpenParks
    miejscowoscSaalbach
    gps47.3953, 12.6378
    odleglosc_od_wyciagu~2 min pieszo do stoku
    ocena4.6/5 (Google)
    uklad_dla_grupy4 osobne apartamenty (po 1 na rodzinę)
    cena_za_apartament_tydzien~1 400–1 900 € (szacunek, styczeń poza feriami)
    koszt_rodzina_2p2~1 400–1 900 €
    koszt_rodzina_2p1~1 200–1 500 €
    status_cenySZACUNEK — silnik rezerwacyjny online na alpenparks.at
    priorytetPLAN A-bis (największa szansa na 4 wolne apartamenty)
    wwwhttps://www.alpenparks.at
    ostatnia_aktualizacja2026-07-31
    + +

    W skrócie

    +

    Duży, nowoczesny aparthotel ~2 minuty pieszo od stoku w Saalbach. Kluczowa przewaga: skala obiektu — kilkadziesiąt apartamentów oznacza największe prawdopodobieństwo, że 4 jednostki będą wolne w jednym tygodniu, i pełną rezerwację online bez korespondencji.

    +

    Fakty

    +
    • Apartamenty rodzinne z aneksami kuchennymi, różne rozmiary (2–8 os.).
    • Basen + sauna w cenie — argument nie do przecenienia przy 5 dzieciakach i tygodniu intensywnej jazdy.
    • Restauracja w obiekcie (opcja "nie gotujemy dziś").
    • Rezerwacja przez silnik online (EN), przejrzyste ceny od ręki.
    +

    Układ dla naszej grupy (14 os.)

    +
    RodzinaApartamentSzacunkowy koszt/tydzień
    2× rodzina 2+24-os.~1 400–1 900 €
    2× rodzina 2+13–4-os.~1 200–1 500 €
    +

    Plusy / minusy

    +

    + basen dla dzieci, restauracja, wysoka dostępność, przewidywalna jakość sieciówki, rezerwacja i anulacja na warunkach online.

    +

    mniej "kameralnie" niż u prywatnego gospodarza; ceny sieciówki sztywniejsze w negocjacji (choć mail grupowy i tak warto wysłać); lokalizacja dobra, ale nie tak spektakularna jak Kristall przy gondoli.

    +

    Do zrobienia

    +
    • Wpisać daty 16–23.01.2027 w silnik na alpenparks.at → zrzut cen rzeczywistych dla 4 jednostek.
    • Równolegle mail grupowy z pytaniem o rabat przy 4 apartamentach.
    • Sprawdzić politykę anulacji (sieciówki często mają elastyczne taryfy za dopłatą — przy grupie 14 os. warto).
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/dom-dla-grupy.html b/pages/oferty/dom-dla-grupy.html new file mode 100644 index 0000000..204e1a1 --- /dev/null +++ b/pages/oferty/dom-dla-grupy.html @@ -0,0 +1,85 @@ + + + + + +Duży dom wakacyjny 12–14 os. (do wyszukania) — Ski 2027 — Skicircus Saalbach + + + + +oferta-koncept +

    Dom dla całej grupy (12–14 os.) — koncept

    +

    Koncept wynajęcia jednego domu wakacyjnego dla całej grupy zamiast czterech osobnych apartamentów — klimat chaletowy w połowie ceny Glemm Lodge.

    +
    Metadane (frontmatter)
    typeoferta-koncept
    titleDuży dom wakacyjny 12–14 os. (do wyszukania)
    descriptionKoncept wynajęcia jednego domu wakacyjnego dla całej grupy zamiast czterech osobnych apartamentów — klimat chaletowy w połowie ceny Glemm Lodge.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, dom-wakacyjny, koncept
    iddom-dla-grupy
    nazwaDuży dom wakacyjny 12–14 os. (do wyszukania)
    miejscowoscViehhofen / Maishofen / strona Fieberbrunn
    uklad_dla_grupyjeden dom dla całej grupy
    cena_za_calosc_tydzien4 500–6 500 € (szacunek, styczeń poza feriami)
    koszt_rodzina_2p2~1 400–1 900 € (podział od osoby)
    koszt_rodzina_2p1~1 100–1 500 € (podział od osoby)
    status_cenySZACUNEK — konkretny dom do znalezienia
    priorytetWARIANT RÓWNOLEGŁY (klimat 'wszyscy razem')
    gdzie_szukaccasamundo.pl, interhome.pl, e-domizil.de, vrbo.com, belvilla.com, allchalets.com
    ostatnia_aktualizacja2026-07-31
    + +

    W skrócie

    +

    Alternatywa dla "4 osobnych apartamentów": jeden duży dom wakacyjny dla całej czternastki. Klimat chaletowy jak w Glemm Lodge (wspólne kolacje, wieczory, dzieciaki razem), ale w budżecie ~połowy jego ceny — pod warunkiem odpuszczenia adresu w samym Saalbach.

    +

    Gdzie geograficznie

    +
    • Viehhofen / Maishofen — 3–8 km od Saalbach, skibus lub 10 min autem.
    • Strona Fieberbrunn / St. Jakob in Haus / Hochfilzen — wejście do Skicircusu od tyłu przez gondolę Fieberbrunn; okolica tańsza, spokojna.
    +

    Parametry do filtrowania przy szukaniu

    +
    • ≥ 5 sypialni (4 rodzicielskie + konfiguracje dla 6 dzieci), ≥ 3 łazienki.
    • Duży salon + jadalnia na 14 os. (wspólne kolacje to sens tego wariantu).
    • Parking na 4 auta, narciarnia/suszarnia.
    • Cena docelowa: ≤ 6 500 €/tydzień w terminie 16–23.01.2027.
    • Sauna = miły bonus, częsty w tym segmencie.
    +

    Rozliczenie (podział od osoby, 14 os.)

    +
    RodzinaUdziałPrzy 5 500 €/tydzień
    2+24/14~1 570 €
    2+13/14~1 180 €
    +

    Plusy / minusy

    +

    + najlepszy klimat grupowy; wspólne gotowanie tnie koszty jedzenia; zwykle najlepszy metraż za euro; jedna rezerwacja.

    +

    logistyka poranna (14 os. na skibus/auta); jedna kuchnia i salon = potrzebna zgodność grupy; pojedyncza rezerwacja = pojedyncze ryzyko (anulacja jednej rodziny obciąża resztę — spisać zasady z góry!); konkretny dom trzeba dopiero znaleźć.

    +

    Do zrobienia

    +
    • Przeszukać portale (lista w nagłówku) filtrem: 14 os., 16–23.01.2027, Viehhofen/Maishofen/Fieberbrunn.
    • Shortlista 3 domów → weryfikacja opinii → zapytania.
    • Umowa wewnętrzna grupy: zaliczki, zasady rezygnacji, podział od osoby.
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/glemm-lodge.html b/pages/oferty/glemm-lodge.html new file mode 100644 index 0000000..15d4c44 --- /dev/null +++ b/pages/oferty/glemm-lodge.html @@ -0,0 +1,88 @@ + + + + + +Glemm Lodge Apartments ⭐⭐⭐⭐ — Ski 2027 — Skicircus Saalbach + + + + +oferta +

    Glemm Lodge Apartments — poza budżetem (dane referencyjne)

    +

    Jedyna oferta z w pełni zweryfikowanym cennikiem (~4 370 €/rodzinę) — odrzucona budżetowo, służy jako punkt odniesienia cenowego.

    +
    Metadane (frontmatter)
    typeoferta
    titleGlemm Lodge Apartments ⭐⭐⭐⭐
    descriptionJedyna oferta z w pełni zweryfikowanym cennikiem (~4 370 €/rodzinę) — odrzucona budżetowo, służy jako punkt odniesienia cenowego.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, saalbach, referencja
    idglemm-lodge
    nazwaGlemm Lodge Apartments ⭐⭐⭐⭐
    operatorDieter Weschenfelder (prywatny)
    kontaktinfo@glemmlodge-apartments.com
    miejscowoscmiędzy Saalbach a Hinterglemm
    adresGlemmtaler Landesstr. 263, 5753 Saalbach
    gps47.3879, 12.6178
    odleglosc_od_wyciaguzjazd trasą 86 pod dom; skibus pod drzwiami (5 min do gondoli)
    ocena9.1/10 (Booking), 4.8/5 (Google)
    uklad_dla_grupy2 apartamenty po 7 os. (Top 2 + Top 3)
    cena_za_apartament_tydzien7 000 € + 500 € sprzątanie + taksy (CENNIK OFICJALNY 26/27)
    koszt_rodzina_2p2~4 370 €
    koszt_rodzina_2p1~3 277 €
    status_cenyZWERYFIKOWANE (cennik + kalendarz obiektu, stan 31.07.2026)
    priorytetODRZUCONA BUDŻETOWO (wraca do gry przy rabacie do ~5 000 €/apartament)
    wwwhttps://glemmlodge-apartments.com
    ostatnia_aktualizacja2026-07-31
    + +

    Dlaczego w bazie mimo odrzucenia

    +

    Jedyny obiekt z pełnymi zweryfikowanymi danymi (publiczny cennik i kalendarz obłożenia) — służy jako punkt odniesienia cenowego. Wraca do gry, jeśli właściciel zejdzie do ~5 000 €/apartament przy 7 osobach (precedens: cennik letni skaluje ceny wg liczby osób).

    +

    Fakty (zweryfikowane 31.07.2026)

    +
    • 3 luksusowe apartamenty 10–12 os. (117–128 m², po 3 łazienki + WC).
    • Zjazd trasą 86 bezpośrednio pod dom; powrót na stok skibusem (przystanek pod drzwiami, 5 min); supermarket MPREIS naprzeciwko.
    • Wallbox dla aut elektrycznych; zakaz imprez wieczorów kawalerskich itp.
    +

    Cennik zima 2026/27 (oficjalny)

    +
    OkresSezonZa nocZa tydzień
    27.11–11.12.2026przedsezon800 €5 600 €
    12–18.12.2026niski900 €6 300 €
    19–25.12.2026wysoki1 000 €7 000 €
    26.12.2026–01.01.2027top1 200 €8 400 €
    02.01–05.03.2027wysoki1 000 €7 000 €
    06–19.03.2027niski900 €6 300 €
    20.03–04.04.2027późny800 €5 600 €
    +

    Dodatkowo: sprzątanie końcowe 500 €/apartament, taksa 3 €/os./noc, kaucja zwrotna 900 €/apartament.

    +

    Dostępność w naszym oknie (stan 31.07.2026)

    +
    TerminTop 1Top 2Top 32 apartamenty naraz?
    2–9.01.2027zajętyWOLNYzajęty
    9–16.01.2027zajętyzajętyWOLNY
    16–23.01.2027WOLNYWOLNYWOLNY
    +

    Kosztorys wariantu 2× apartament, 16–23.01 (14 os.)

    +
    PozycjaNa apartament (7 os.)
    Czynsz 7 nocy7 000 €
    Sprzątanie500 €
    Taksa (3 € × 7 os. × 7 nocy)147 €
    Razem (bez kaucji 900 €)7 647 €
    +

    Podział od osoby: rodzina 2+2 → ~4 370 €, rodzina 2+1 → ~3 277 €. Wniosek: 2× powyżej celu budżetowego (~2 000 €/rodzinę).

    +

    Warunki rezerwacji

    +
    • Zaliczka 30% od razu; reszta + kaucja 90 dni przed przyjazdem.
    • Darmowa anulacja tylko do 91 dni przed (dla 16.01 → deadline ~16.10.2026); później 100% kosztów.
    +

    Do zrobienia (opcjonalnie)

    +
    • Mail do Dietera: cena za 7 os./apartament zamiast 10–12 — jeśli ≤ 5 000 €, wariant wraca do rozważań (~2 900/2 200 € na rodzinę).
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/kristall-schattbergxpress.html b/pages/oferty/kristall-schattbergxpress.html new file mode 100644 index 0000000..aa49d1a --- /dev/null +++ b/pages/oferty/kristall-schattbergxpress.html @@ -0,0 +1,83 @@ + + + + + +Appartementhaus Kristall at SchattbergXpress — Ski 2027 — Skicircus Saalbach + + + + +oferta +

    Appartementhaus Kristall at SchattbergXpress — PLAN A

    +

    Apartamenty 50 m od gondoli Schattberg X-press w centrum Saalbach — najlepsza lokalizacja w rozważanym budżecie, PLAN A.

    +
    Metadane (frontmatter)
    typeoferta
    titleAppartementhaus Kristall at SchattbergXpress
    descriptionApartamenty 50 m od gondoli Schattberg X-press w centrum Saalbach — najlepsza lokalizacja w rozważanym budżecie, PLAN A.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, saalbach, plan-a
    idkristall-schattbergxpress
    nazwaAppartementhaus Kristall at SchattbergXpress
    operatorAll in One Apartments
    miejscowoscSaalbach (centrum)
    adresSchulstraße 341, 5753 Saalbach
    gps47.3903, 12.6355
    odleglosc_od_wyciagu50 m — gondola Schattberg X-press
    ocena8.5/10 (Booking)
    uklad_dla_grupy4 osobne apartamenty (po 1 na rodzinę)
    cena_za_apartament_tydzien~1 500–2 000 € (szacunek, styczeń poza feriami)
    koszt_rodzina_2p2~1 500–2 000 €
    koszt_rodzina_2p1~1 200–1 600 €
    status_cenySZACUNEK — do potwierdzenia zapytaniem
    priorytetPLAN A
    wwwhttps://www.booking.com/hotel/at/appartementhaus-kristall.html
    ostatnia_aktualizacja2026-07-31
    + +

    W skrócie

    +

    Najlepsza lokalizacja w rozważanym budżecie: 50 m od gondoli Schattberg X-press — głównej stacji startowej całego Skicircusu, w ścisłym centrum Saalbach. Nastolatki wychodzą z narciarni prosto na gondolę, zero skibusa, zero odwożenia. Rodzice mają restauracje, bary i sklep w promieniu 50 m.

    +

    Fakty

    +
    • Eleganckie apartamenty w stylu alpejskim, w pełni wyposażone aneksy kuchenne, balkony z widokiem na góry.
    • Apartamenty typowo 2–6 os.; opinie gości potwierdzają wygodne jednostki 4-osobowe z dwiema łazienkami.
    • 1 bezpłatne miejsce parkingowe na apartament (ważne: 4 rodziny = 4 auta ✓).
    • Wspólna strefa spa w budynku; narciarnia z suszarką do butów.
    • Restauracja 20 m, sklep spożywczy 50 m, przystanek skibusa 10 m.
    +

    Układ dla naszej grupy (14 os.)

    +
    RodzinaApartamentSzacunkowy koszt/tydzień
    Rodzina A (2+2)4-os. z 2 łazienkami~1 500–2 000 €
    Rodzina B (2+2)4-os. z 2 łazienkami~1 500–2 000 €
    Rodzina C (2+1)3–4-os. (mniejszy)~1 200–1 600 €
    Rodzina D (2+1)3–4-os. (mniejszy)~1 200–1 600 €
    +

    Plusy / minusy

    +

    + lokalizacja bezkonkurencyjna (za taki adres w Saalbach zwykle się dopłaca), każda rodzina ma prywatność i własną łazienkę, spa w budynku, parking w cenie, centrum = wieczorne życie dla nastolatków pod nosem.

    +

    brak wspólnego dużego salonu na wieczory całej ekipy (wieczory "na apartamentach" albo w knajpie); dostępność 4 jednostek naraz do potwierdzenia; centrum Saalbach bywa głośne w weekendy.

    +

    Do zrobienia

    +
    • Sprawdzić dostępność 4 apartamentów: 16–23.01.2027 (opcja B: 9–16.01) — Booking + strona All in One Apartments.
    • Wysłać zapytanie grupowe mailem (4 jednostki, 14 os., prośba o cenę grupową — przy 4 apartamentach rabat direct jest normą).
    • Dopytać: wysokość kaucji, sprzątanie końcowe, taksa, polityka anulacji.
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/oferty/sportchalet-viehhofen.html b/pages/oferty/sportchalet-viehhofen.html new file mode 100644 index 0000000..e05d1c7 --- /dev/null +++ b/pages/oferty/sportchalet-viehhofen.html @@ -0,0 +1,84 @@ + + + + + +Sportchalet Viehhofen — Ski 2027 — Skicircus Saalbach + + + + +oferta +

    Sportchalet Viehhofen — PLAN B (budżetowy faworyt)

    +

    Kameralny obiekt holenderskich gospodarzy w Viehhofen z oceną 5.0 — najlepszy stosunek ceny do jakości, PLAN B.

    +
    Metadane (frontmatter)
    typeoferta
    titleSportchalet Viehhofen
    descriptionKameralny obiekt holenderskich gospodarzy w Viehhofen z oceną 5.0 — najlepszy stosunek ceny do jakości, PLAN B.
    timestamp2026-07-31T00:00:00Z
    tagsnocleg, viehhofen, plan-b
    idsportchalet-viehhofen
    nazwaSportchalet Viehhofen
    operatorprywatni gospodarze (NL, komunikacja EN)
    miejscowoscViehhofen (3 km od Saalbach)
    gps47.3678, 12.7351
    odleglosc_od_wyciagugondola zellamseeXpress za rogiem; skibus do Skicircusu pod domem
    ocena5.0/5 (Google)
    uklad_dla_grupy3–4 apartamenty w jednym budynku
    cena_za_apartament_tydzien~1 200–1 600 € (szacunek, styczeń poza feriami)
    koszt_rodzina_2p2~1 200–1 600 €
    koszt_rodzina_2p1~1 000–1 300 €
    status_cenySZACUNEK — wycena tylko mailowo
    priorytetPLAN B (najlepszy stosunek ceny do jakości)
    wwwhttps://sportchaletviehhofen.com
    ostatnia_aktualizacja2026-07-31
    + +

    W skrócie

    +

    Kameralny obiekt w Viehhofen — wsi 3 km przed Saalbach — prowadzony przez holenderskich gospodarzy z oceną 5.0. Opinie gości wprost opisują scenariusz "kilka rodzin bookuje kilka apartamentów razem" — czyli dokładnie nasz przypadek. Najniższe ceny na liście przy bardzo dobrej jakości.

    +

    Fakty

    +
    • Kilka apartamentów w jednym budynku — możliwość zajęcia obiektu niemal na wyłączność przez naszą grupę.
    • Gondola zellamseeXpress za rogiem (strona Zell am See — na tym samym karnecie ALPIN CARD!), skibus do gondoli Schönleiten w Saalbach staje pod domem (bezpłatny ze skipassem, kursuje często).
    • Gospodarze mówią po angielsku, nastawieni rodzinnie, kontakt bezpośredni.
    +

    Układ dla naszej grupy (14 os.)

    +

    Zależnie od konfiguracji apartamentów (do potwierdzenia mailem): wariant 4×(3–4 os.) albo 2×(6–7 os.) + 1×(3–4 os.). Przy zajęciu całego budynku wspólne wieczory de facto "jak w chalecie".

    +
    RodzinaSzacunkowy koszt/tydzień
    2+2~1 200–1 600 €
    2+1~1 000–1 300 €
    +

    Plusy / minusy

    +

    + najniższa cena przy oceny 5.0; klimat "cały dom dla nas"; elastyczni prywatni gospodarze = realna negocjacja grupowa; parking bez problemu; dwie opcje startu na stok (Zell am See gondolą / Skicircus skibusem) — fajna różnorodność w trakcie tygodnia.

    +

    to nie ski-in/ski-out: do Skicircusu 5–10 min skibusem (z 5 dzieci i sprzętem — logistyka poranna do ogarnięcia); Viehhofen jest cichy — zero wieczornego życia dla nastolatków (trzeba dojechać do Saalbach); brak silnika online — wszystko mailowo.

    +

    Do zrobienia

    +
    • Mail z zapytaniem o dostępność wszystkich apartamentów 16–23.01.2027 (opcja B: 9–16.01) + konfigurację łóżek dla 4+4+3+3.
    • Zapytać o cenę za zajęcie całego obiektu (package deal).
    • Potwierdzić rozkład skibusa w styczniu (częstotliwość rano 8:30–9:30).
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/region.html b/pages/region.html new file mode 100644 index 0000000..afda316 --- /dev/null +++ b/pages/region.html @@ -0,0 +1,92 @@ + + + + + +Skicircus Saalbach-Hinterglemm-Leogang-Fieberbrunn — Ski 2027 — Skicircus Saalbach + + + + +region +

    Skicircus Saalbach — informacje o regionie

    +

    Wszystko poza noclegiem: teren narciarski, skipassy ALPIN CARD, dojazd z Warszawy, analiza terminów, koszty życia i budżet całkowity grupy.

    +
    Metadane (frontmatter)
    typeregion
    titleSkicircus Saalbach-Hinterglemm-Leogang-Fieberbrunn
    descriptionWszystko poza noclegiem: teren narciarski, skipassy ALPIN CARD, dojazd z Warszawy, analiza terminów, koszty życia i budżet całkowity grupy.
    timestamp2026-07-31T00:00:00Z
    tagsregion, saalbach, skipass, logistyka
    idskicircus-saalbach
    nazwaSkicircus Saalbach-Hinterglemm-Leogang-Fieberbrunn
    krajAustria
    regionSalzburg (Pinzgau)
    sezon2026/2027
    grupa4 rodziny: 2×(2+2 nastolatki) + 2×(2+1 dziecko) = 14 osób
    wyjazd_zWarszawa
    rekomendowany_termin16–23.01.2027 (alternatywa: 9–16.01.2027)
    ostatnia_aktualizacja2026-07-31
    + +

    Baza wiedzy wyjazdu narciarskiego zimą 2026/27. Ten dokument zbiera wszystko, co nie dotyczy konkretnego noclegu: teren narciarski, skipassy, dojazd, terminy, koszty życia i logistykę grupy. Oferty noclegowe → katalog oferty/.

    +

    Dlaczego ten region

    +
    • 270 km połączonych tras — największy w pełni połączony teren w Salzburgu, klimat "wielkiej karuzeli" najbliższy Sella Rondzie (którą grupa zna i lubi, ale była tam już 2×).
    • Objazdowa pętla "Skicircus challenge" — całodzienna runda po całym terenie, atrakcyjna dla nastolatków (odpowiednik pętli Sella Ronda).
    • Snowparki (Nightpark Hinterglemm, Learn-to-Ride Leogang), junior race'y.
    • Najkrótszy dojazd z Warszawy spośród rozważanych dużych ośrodków (~950 km; dla porównania: Sella Ronda ~1 100 km, Livigno ~1 250 km).
    +

    Skipass — ALPIN CARD

    +

    Karnet Ski ALPIN CARD obejmuje: Skicircus Saalbach-Hinterglemm-Leogang- Fieberbrunn + Schmittenhöhe (Zell am See) + lodowiec Kitzsteinhorn (Kaprun). Istotne: gondola zellamseeXpress w Viehhofen też jest na tym karnecie.

    +
    Taryfa 6 dniCena 2025/26 (orient.)Prognoza 2026/27 (+3–5%)
    Dorosły~430–450 €~445–470 €
    Młodzież (do 18 lat)~ -25% od ceny dorosłego~335–355 €
    Dziecko (do ~15 lat)~ -50%~225–235 €
    +

    Szacunkowy koszt skipassów dla grupy 14 os. (8 dorosłych + 4 nastolatków

    +
    • 2 dzieci, 6 dni): ~4 900–5 400 € łącznie, czyli:
    +
    RodzinaSkładSkipassy 6 dni
    2+2 (nastolatki)2 dorosłych + 2 młodzież~1 570–1 650 €
    2+1 (dziecko)2 dorosłych + 1 dziecko~1 120–1 175 €
    +

    ⚠️ Ceny 26/27 do potwierdzenia jesienią 2026 na alpincard.at. Dzieci/młodzież: decyduje rocznik, nie wiek w dniu wyjazdu — sprawdzić dokładne progi rocznikowe przy zakupie.

    +

    Dojazd z Warszawy

    +
    • Trasa: Warszawa → Katowice → Ostrawa → Brno → Wiedeń → Salzburg → Zell am See → dolina Glemmtal. ~950 km, 9–10 h jazdy (realnie 11–12 h z przerwami). Da się w jeden dzień.
    • Opłaty: winiety Czechy (~14 €/10 dni) + Austria (~13 €/10 dni, 2026); odcinek specjalny A10 (tunele Tauern) ~14 € w jedną stronę jeśli trasa przez Salzburg–Villach (uwaga: do Zell am See zwykle zjazd wcześniej — bez opłaty specjalnej).
    • Paliwo: ~1 900 km w obie strony → ~150–170 l → ~950–1 100 zł/auto (diesel, spalanie 8 l/100 km).
    • Łączny koszt dojazdu: ~300–350 €/auto w obie strony. Grupa jedzie 4 autami (po 1 na rodzinę) lub 3 (oszczędność ~300 €, ale mniej elastycznie).
    • Łańcuchy/opony zimowe obowiązkowe; ostatnie 15 km doliną Glemmtal — droga lokalna, zimą odśnieżana na bieżąco.
    +

    Terminy — analiza okna 1–23.01.2027

    +
    TydzieńOcenaUzasadnienie
    2–9.01⚠️ średnioogon szczytu noworocznego: tłumy do ~6.01, najwyższe ceny noclegów
    9–16.01✅ dobrypierwszy "pusty" tydzień zimy, niższe ceny, świetny śnieg
    16–23.01✅✅ rekomendowanynajluźniejsze stoki, pełna dostępność noclegów (m.in. jedyne okno na 2× Glemm Lodge), te same ceny co 9–16.01
    +

    Koszty życia na miejscu

    +
    • Obiad na stoku: 15–22 €/os. (spaghetti/schnitzel + napój).
    • Zakupy spożywcze: MPREIS/Billa w Saalbach i Hinterglemm; ceny ~20–30% wyższe niż w PL. Patent grupowy: duże zakupy w Zell am See (Lidl, Spar) po drodze.
    • Wieczorne wyjście na pizzę/burgery: 18–25 €/os.
    • Taksa klimatyczna: ~2–3 €/os./noc (dorośli; wg gminy), płatna na miejscu.
    • Skibus w dolinie: bezpłatny ze skipassem, kursuje Saalbach ↔ Hinterglemm ↔ Viehhofen co 10–20 min.
    +

    Budżet całkowity grupy (orientacyjnie, tydzień, 6 dni jazdy)

    +
    Pozycja14 osób łącznie
    Noclegi (cel: ~1 200–2 000 €/apartament)5 500–7 500 €
    Skipassy4 900–5 400 €
    Dojazd (4 auta)1 200–1 400 €
    Jedzenie + taksy2 500–3 500 €
    RAZEM~14 000–18 000 € (≈ 1 000–1 300 €/os.)
    +

    Kalendarz decyzji

    +
    1. Sierpień 2026 — wysłanie zapytań grupowych do obiektów (szablon maila), zebranie ofert, wybór.
    2. Wrzesień 2026 — rezerwacja + zaliczki (typowo 30%).
    3. ~16.10.2026 — ostateczny deadline darmowej anulacji dla Glemm Lodge (91 dni przed 16.01); inne obiekty: sprawdzić indywidualnie.
    4. Jesień 2026 — potwierdzenie cen skipassów 26/27, decyzja o zakupie online (ewentualne przedsprzedaże).
    5. Grudzień 2026 — dopłaty (typowo 90 dni przed), organizacja aut, sprzętu, ubezpieczeń (rekomendowane: koszty rezygnacji + NNW/OC narciarskie).
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/szablon-maila.html b/pages/szablon-maila.html new file mode 100644 index 0000000..8febdde --- /dev/null +++ b/pages/szablon-maila.html @@ -0,0 +1,86 @@ + + + + + +Szablon zapytania grupowego (EN) — Ski 2027 — Skicircus Saalbach + + + + +narzedzie +

    Szablon maila — zapytanie grupowe (EN)

    +

    Gotowy angielski mail z zapytaniem o 4 apartamenty dla 14 osób w terminie 16–23.01.2027 wraz z notatkami negocjacyjnymi.

    +
    Metadane (frontmatter)
    typenarzedzie
    titleSzablon zapytania grupowego (EN)
    descriptionGotowy angielski mail z zapytaniem o 4 apartamenty dla 14 osób w terminie 16–23.01.2027 wraz z notatkami negocjacyjnymi.
    timestamp2026-07-31T00:00:00Z
    tagsszablon, komunikacja, rezerwacja
    idszablon-maila
    nazwaSzablon zapytania grupowego (EN)
    ostatnia_aktualizacja2026-07-31
    + +

    Wysłać ten sam mail do 2–3 obiektów równolegle, podmieniając [pola]. Wersja uniwersalna pod 4 apartamenty; przy Sportchalet/Glemm Lodge dopisać pytanie o zajęcie całego obiektu.

    +
    +

    Subject: Group inquiry — 4 apartments, week 16–23 Jan 2027 (14 persons)

    +

    Dear [Sir or Madam / imię gospodarza],

    +

    We are a group of four families from Poland (14 persons in total: 8 adults, 4 teenagers and 2 children) planning a ski week in Saalbach in January 2027.

    +

    We would like to ask about availability and a group offer for:

    +
    • 4 apartments (2× for 4 persons: 2 adults + 2 teenagers; 2× for 3 persons: 2 adults + 1 child)
    • Arrival Sat 16 January – departure Sat 23 January 2027 (7 nights)
    • Alternative dates: 9–16 January 2027
    +

    Could you please send us:

    +
    1. Availability for these dates and apartment types,
    2. Your best total price for the group (incl. final cleaning and taxes),
    3. Deposit and cancellation terms.
    +

    Since we are booking four apartments for a full week directly with you, we would appreciate a group rate if possible.

    +

    Best regards, [Imię i nazwisko] [Telefon / e-mail]

    +
    +

    Notatki taktyczne

    +
    • Wysyłać z jednego "kontaktowego" adresu — jedna osoba negocjuje za grupę.
    • Nie podawać budżetu w pierwszym mailu; najpierw ich oferta.
    • Po otrzymaniu 2 ofert można uczciwie napisać "we received a comparable offer at X €" — w Alpach to normalna praktyka.
    • Prosić o ofertę mailem (pisemnie), nie telefonicznie.
    +
    Wygenerowane z bundle'a OKF przez gen_pages.py — nie edytuj +tych plików, edytuj .md i przegeneruj.
    + + diff --git a/pages/wyjazd.html b/pages/wyjazd.html new file mode 100644 index 0000000..c5619f9 --- /dev/null +++ b/pages/wyjazd.html @@ -0,0 +1,81 @@ + + + + + +Ski 2027 — Skicircus Saalbach — Ski 2027 — Skicircus Saalbach + + + + +Trip +

    Ski 2027 — Skicircus Saalbach

    +

    Wyjazd narciarski 4 rodzin (14 osób) z Warszawy do Skicircus Saalbach w styczniu 2027, z celem budżetowym ~2 000 € za nocleg na rodzinę.

    +
    Metadane (frontmatter)
    typeTrip
    titleSki 2027 — Skicircus Saalbach
    descriptionWyjazd narciarski 4 rodzin (14 osób) z Warszawy do Skicircus Saalbach w styczniu 2027, z celem budżetowym ~2 000 € za nocleg na rodzinę.
    timestamp2026-07-31T00:00:00Z
    tagswyjazd, saalbach, 2027, planowanie
    nazwaSki 2027 — Skicircus Saalbach: baza wiedzy wyjazdu
    grupa4 rodziny (2×2+2, 2×2+1) = 14 osób, wyjazd z Warszawy
    termin16–23.01.2027 (alt. 9–16.01)
    ostatnia_aktualizacja2026-07-31
    + +

    Wyjazd narciarski 4 rodzin: 2×(2+2 nastolatki) + 2×(2+1 dziecko) = 14 osób, wyjazd samochodami z Warszawy.

    +
    • Termin: 16–23.01.2027 (alternatywa: 9–16.01.2027).
    • Cel budżetowy noclegu: ~2 000 €/rodzinę za tydzień (nie 3–4 tys.).
    +

    Kontekst regionu, skipassów, dojazdu i budżetu całkowitego → region. Karty noclegów → oferty.

    +

    Oferty — ranking

    +
    #OfertaMiejscowość€/rodzinę 2+2StatusPriorytet
    1Kristall @ SchattbergXpressSaalbach centrum, 50 m od gondoli~1 500–2 000szacunekPLAN A
    2AlpenParks SonnleitenSaalbach, 2 min od stoku~1 400–1 900szacunekPLAN A-bis
    3Sportchalet ViehhofenViehhofen, skibus~1 200–1 600szacunekPLAN B
    4Dom 12–14 os.Viehhofen/Maishofen/Fieberbrunn~1 400–1 900konceptrównoległy
    5AlpenParks RehrenbergViehhofen~1 100–1 500szacunekPLAN C
    6Glemm LodgeSaalbach/Hinterglemm~4 370zweryfikowaneodrzucona (referencja)
    +

    Najbliższe kroki

    +
    1. Mail grupowy (szablon) → Kristall/All in One + Sportchalet Viehhofen.
    2. Ceny online datami 16–23.01.2027 → AlpenParks (oba obiekty).
    3. Zebranie ofert → decyzja grupy → zaliczki (wrzesień 2026).
    +

    Konwencja danych

    +

    Każda karta oferty ma frontmatter YAML z polami OKF (type, title, description, timestamp, tags) oraz z polami rozszerzeń: id, nazwa, miejscowosc, gps, odleglosc_od_wyciagu, ocena, cena_za_apartament_tydzien, koszt_rodzina_2p2, koszt_rodzina_2p1, status_ceny (SZACUNEK / ZWERYFIKOWANE), priorytet, www, ostatnia_aktualizacja — stronę można generować statycznie prosto z tych pól (Astro/Hugo/Eleventy czytają frontmatter natywnie).

    + + +